diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 0c08bac47..7e079dac5 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -8,65 +8,68 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
- - name: Set up JDK 1.11
- uses: actions/setup-java@v1
+ - uses: actions/checkout@v3
+ - name: Set up JDK 1.8
+ uses: actions/setup-java@v3
with:
- java-version: 11
+ distribution: temurin
+ java-version: 8
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew build
- - name: Build fat jars
- run: ./gradlew shadowJar
- - name: Move jars
-# run: ls -la
- run: mkdir shadowJars && mv build/libs/imgui*.jar shadowJars
- - uses: actions/upload-artifact@v1
- with:
- name: ImGui linux artifacts
- path: 'shadowJars'
+# - name: Build fat jars
+# run: ./gradlew shadowJar
+# - name: Move jars
+## run: ls -la
+# run: mkdir shadowJars && mv build/libs/imgui*.jar shadowJars
+# - uses: actions/upload-artifact@v1
+# with:
+# name: ImGui linux artifacts
+# path: 'shadowJars'
windows:
name: 'Windows'
runs-on: windows-latest
steps:
- - uses: actions/checkout@v2
- - name: Set up JDK 1.11
- uses: actions/setup-java@v1
+ - uses: actions/checkout@v3
+ - name: Set up JDK 1.8
+ uses: actions/setup-java@v3
with:
- java-version: 11
+ distribution: temurin
+ java-version: 8
- name: Build with Gradle
run: .\gradlew.bat build
- - name: Build fat jars
- run: ./gradlew shadowJar
- - name: Move jars
- run: mkdir shadowJars; mv build/libs/imgui*.jar shadowJars
- - uses: actions/upload-artifact@v1
- with:
- name: ImGui windows artifacts
- path: 'shadowJars'
+# - name: Build fat jars
+# run: ./gradlew shadowJar
+# - name: Move jars
+# run: mkdir shadowJars; mv build/libs/imgui*.jar shadowJars
+# - uses: actions/upload-artifact@v1
+# with:
+# name: ImGui windows artifacts
+# path: 'shadowJars'
mac:
name: 'Mac OS'
runs-on: macos-latest
steps:
- - uses: actions/checkout@v2
- - name: Set up JDK 1.11
- uses: actions/setup-java@v1
+ - uses: actions/checkout@v3
+ - name: Set up JDK 1.8
+ uses: actions/setup-java@v3
with:
- java-version: 11
+ distribution: temurin
+ java-version: 8
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew build
- - name: Build fat jars
- run: ./gradlew shadowJar
- - name: Move jars
- run: mkdir shadowJars && mv build/libs/imgui*.jar shadowJars
- - uses: actions/upload-artifact@v1
- with:
- name: ImGui macOS artifacts
- path: 'shadowJars'
+# - name: Build fat jars
+# run: ./gradlew shadowJar
+# - name: Move jars
+# run: mkdir shadowJars && mv build/libs/imgui*.jar shadowJars
+# - uses: actions/upload-artifact@v1
+# with:
+# name: ImGui macOS artifacts
+# path: 'shadowJars'
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 000000000..0bbb8c92f
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
index a55e7a179..79ee123c2 100644
--- a/.idea/codeStyles/codeStyleConfig.xml
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -1,5 +1,5 @@
-
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 000000000..356d39b17
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
index b1077fbd0..69e86158b 100644
--- a/.idea/kotlinc.xml
+++ b/.idea/kotlinc.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index c3df5ec6d..8141a215a 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -5,4 +5,7 @@
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index b1e2901e6..0f3c47977 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,6 @@
[](https://github.com/kotlin-graphics/imgui/actions?workflow=build)
[](https://github.com/kotlin-graphics/imgui/blob/master/LICENSE)
-[](https://jitpack.io/#kotlin-graphics/imgui)

[]()
@@ -213,7 +212,7 @@ import org.gradle.internal.os.OperatingSystem
repositories {
...
- maven { url 'https://jitpack.io' }
+ maven("https://raw.githubusercontent.com/kotlin-graphics/mary/master")
}
dependencies {
@@ -229,11 +228,11 @@ dependencies {
Any number of renderers can be added to the project like this however, you could all all of them with the array ["gl", "glfw", "core", "vk", "jogl", "openjfx"]
This example gets the OpenGL needed modules.
*/
- ["gl", "glfw", "core"].each {
- implementation "com.github.kotlin-graphics.imgui:$it:-SNAPSHOT"
- }
+ implementation("kotlin.graphics:imgui-core:1.79+05")
+ implementation("kotlin.graphics:imgui-gl:1.79+05")
+ implementation("kotlin.graphics:imgui-glfw:1.79+05")
- switch ( OperatingSystem.current() ) {
+ /*switch ( OperatingSystem.current() ) {
case OperatingSystem.WINDOWS:
ext.lwjglNatives = "natives-windows"
break
@@ -250,7 +249,7 @@ dependencies {
if (it.moduleVersion.id.group == "org.lwjgl") {
runtime "org.lwjgl:${it.moduleVersion.id.name}:${it.moduleVersion.id.version}:${lwjglNatives}"
}
- }
+ }*/
}
```
diff --git a/build.gradle.kts b/build.gradle.kts
index 5655f5640..81a66da8e 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,58 +1,40 @@
+import magik.createGithubPublication
import magik.github
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask
plugins {
- kotlin("jvm") version embeddedKotlinVersion
- id("org.lwjgl.plugin") version "0.0.30"
- id("elect86.magik") version "0.3.1"
+ kotlin("jvm") version "1.8.20"
+ id("org.lwjgl.plugin") version "0.0.34"
+ id("elect86.magik") version "0.3.2"
`maven-publish`
- id("com.github.johnrengelman.shadow") version "7.1.2"
+// id("com.github.johnrengelman.shadow") version "8.1.1"
+ id("com.google.devtools.ksp") version "1.8.20-1.0.10" apply false
}
-//projects.core.dependencyProject.apply {
-// repositories { github("kotlin-graphics/mary") }
-// dependencies {
-// implementation(kotlin("reflect"))
-// implementation(unsigned, kool, glm, gli/*, uno.core*/)
-// // implementation("kotlin.graphics:uno-core:0.7.10")
-// Lwjgl { implementation(jemalloc, stb) }
-// }
-//}
+dependencies {
+ api(projects.core)
+ api(projects.gl)
+ api(projects.glfw)
+ // vk
+}
+
+kotlin.jvmToolchain { languageVersion.set(JavaLanguageVersion.of(8)) }
+
+tasks {
+ withType>().configureEach {
+ compilerOptions.freeCompilerArgs.addAll("-opt-in=kotlin.RequiresOptIn", "-Xallow-kotlin-package")
+ }
+ test { useJUnitPlatform() }
+}
+
+publishing {
+ publications {
+ createGithubPublication {
+ from(components["java"])
+ suppressAllPomMetadataWarnings()
+ }
+ }
+ repositories.github { domain = "kotlin-graphics/mary" }
+}
-//projects.gl.dependencyProject.dependencies {
-// implementation(projects.core)
-// implementation(projects.glfw)
-// implementation(kotlin("reflect"))
-// implementation(unsigned, kool, glm, gli, gln, uno)
-// Lwjgl { implementation(jemalloc, glfw, opengl, remotery, stb) }
-// testImplementation("com.github.ajalt:mordant:1.2.1")
-//}
-//
-//projects.glfw.dependencyProject.dependencies {
-// implementation(projects.core)
-// implementation(kotlin("reflect"))
-// implementation(kool, glm, uno)
-// Lwjgl { implementation(glfw, opengl, remotery) }
-//}
-//
-//projects.openjfx.dependencyProject.dependencies {
-// implementation(projects.core)
-// val os = current()
-// val platform = when {
-// os.isLinux -> "linux"
-// os.isWindows -> "win"
-// else -> "mac"
-// }
-// listOf("base", "graphics").forEach {
-// implementation("org.openjfx:javafx-$it:11:$platform")
-// }
-// implementation(glm)
-// Lwjgl { implementation(core) }
-//}
-//
-//projects.vk.dependencyProject.dependencies {
-// implementation(projects.core)
-// implementation(projects.glfw)
-// implementation(kotlin("reflect"))
-// implementation(kool, glm, gli, vkk, uno)
-// Lwjgl { implementation(glfw, opengl, remotery, vulkan) }
-//}
\ No newline at end of file
+java.withSourcesJar()
\ No newline at end of file
diff --git a/core/core.gradle.kts b/core/core.gradle.kts
index c5a26a5c8..65bf4dc3e 100644
--- a/core/core.gradle.kts
+++ b/core/core.gradle.kts
@@ -1,46 +1,55 @@
import magik.createGithubPublication
import magik.github
-import org.lwjgl.lwjgl
+import org.gradle.nativeplatform.platform.internal.Architectures
+import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask
import org.lwjgl.Lwjgl.Module.jemalloc
import org.lwjgl.Lwjgl.Module.stb
+import org.lwjgl.lwjgl
plugins {
+ kotlin("jvm")
id("org.lwjgl.plugin")
id("elect86.magik")
`maven-publish`
- kotlin("jvm")
- id("com.github.johnrengelman.shadow")
+ id("com.google.devtools.ksp")
}
group = rootProject.group
version = rootProject.version
dependencies {
- implementation(kotlin("stdlib-jdk8"))
implementation(kotlin("reflect"))
+ testImplementation(kotlin("test"))
- implementation("kotlin.graphics:uno-core:0.7.17")
- implementation("kotlin.graphics:gln:0.5.31")
- implementation("kotlin.graphics:glm:0.9.9.1-5")
- implementation("kotlin.graphics:gli:0.8.3.0-18")
- implementation("kotlin.graphics:unsigned:3.3.31")
- implementation("kotlin.graphics:kool:0.9.68")
- // implementation(unsigned, kool, glm, gli/*, uno.core*/)
+ api("kotlin.graphics:uno-core:0.7.21")
lwjgl { implementation(jemalloc, stb) }
-}
-kotlin.jvmToolchain {
- this as JavaToolchainSpec
- languageVersion.set(JavaLanguageVersion.of(8))
+ implementation("com.github.livefront.sealed-enum:runtime:0.7.0")
+ ksp("com.github.livefront.sealed-enum:ksp:0.7.0")
+
+ val brotliVersion = "1.11.0"
+ val operatingSystem: OperatingSystem = DefaultNativePlatform.getCurrentOperatingSystem()
+ implementation("com.aayushatharva.brotli4j:brotli4j:$brotliVersion")
+ runtimeOnly("com.aayushatharva.brotli4j:native-${
+ if (operatingSystem.isWindows) "windows-x86_64"
+ else if (operatingSystem.isMacOsX)
+ if (DefaultNativePlatform.getCurrentArchitecture().isArm()) "osx-aarch64"
+ else "osx-x86_64"
+ else if (operatingSystem.isLinux)
+ if (Architectures.ARM_V7.isAlias(DefaultNativePlatform.getCurrentArchitecture().name)) "linux-armv7"
+ else if (Architectures.AARCH64.isAlias(DefaultNativePlatform.getCurrentArchitecture().name)) "linux-aarch64"
+ else if (Architectures.X86_64.isAlias(DefaultNativePlatform.getCurrentArchitecture().name)) "linux-x86_64"
+ else throw IllegalStateException("Unsupported architecture: ${DefaultNativePlatform.getCurrentArchitecture().name}")
+ else throw IllegalStateException("Unsupported operating system: $operatingSystem")
+ }:$brotliVersion")
}
+kotlin.jvmToolchain { languageVersion.set(JavaLanguageVersion.of(8)) }
+
tasks {
- withType>().all {
- kotlinOptions {
- freeCompilerArgs += listOf("-opt-in=kotlin.RequiresOptIn")
- }
- }
- withType().configureEach { useJUnitPlatform() }
+ withType>().configureEach { compilerOptions.freeCompilerArgs.addAll("-opt-in=kotlin.RequiresOptIn", "-Xallow-kotlin-package") }
+ test { useJUnitPlatform() }
}
publishing {
@@ -51,11 +60,7 @@ publishing {
suppressAllPomMetadataWarnings()
}
}
- repositories {
- github {
- domain = "kotlin-graphics/mary"
- }
- }
+ repositories.github { domain = "kotlin-graphics/mary" }
}
-java { withSourcesJar() }
\ No newline at end of file
+java.withSourcesJar()
\ No newline at end of file
diff --git a/core/src/main/java/imgui/Jdsl.java b/core/src/main/java/imgui/Jdsl.java
index 32b61d386..f3ef98c9f 100644
--- a/core/src/main/java/imgui/Jdsl.java
+++ b/core/src/main/java/imgui/Jdsl.java
@@ -3,6 +3,8 @@
import glm_.vec2.Vec2;
import glm_.vec4.Vec4;
import imgui.font.Font;
+import imgui.internal.sections.ButtonFlag;
+import imgui.internal.sections.OldColumnsFlag;
/**
* twin brother of dsl, manual overloads
@@ -15,17 +17,18 @@ public class Jdsl {
// Tables
public static void table(String strId, int columnsCount, Runnable block) {
- tables(strId, columnsCount, 0, new Vec2(), 0f, block);
+ tables(strId, columnsCount, Flag.none(), new Vec2(), 0f, block);
}
- public static void tables(String strId, int columnsCount, int flags, Runnable block) {
+ public static void tables(String strId, int columnsCount, Flag flags, Runnable block) {
tables(strId, columnsCount, flags, new Vec2(), 0f, block);
}
- public static void tables(String strId, int columnsCount, int flags, Vec2 outerSize, Runnable block) {
+ public static void tables(String strId, int columnsCount, Flag flags, Vec2 outerSize, Runnable block) {
tables(strId, columnsCount, flags, outerSize, 0f, block);
}
- public static void tables(String strId, int columnsCount, int flags, Vec2 outerSize,
+
+ public static void tables(String strId, int columnsCount, Flag flags, Vec2 outerSize,
float innerWidth, Runnable block) {
if (imgui.beginTable(strId, columnsCount, flags, outerSize, innerWidth)) { // ~open
block.run();
@@ -36,14 +39,14 @@ public static void tables(String strId, int columnsCount, int flags, Vec2 outerS
// Windows
public static void window(String name, Runnable block) {
- window(name, null, 0, block);
+ window(name, null, Flag.none(), block);
}
- public static void window(String name, MutableProperty0 open, Runnable block) {
- window(name, open, 0, block);
+ public static void window(String name, MutableReference open, Runnable block) {
+ window(name, open, Flag.none(), block);
}
- public static void window(String name, MutableProperty0 open, int 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();
@@ -52,18 +55,18 @@ public static void window(String name, MutableProperty0 open, int windo
// Child Windows
public static void child(String strId, Runnable block) {
- child(strId, new Vec2(), false, 0, block);
+ child(strId, new Vec2(), false, Flag.none(), block);
}
public static void child(String strId, Vec2 size, Runnable block) {
- child(strId, size, false, 0, block);
+ child(strId, size, false, Flag.none(), block);
}
public static void child(String strId, Vec2 size, boolean border, Runnable block) {
- child(strId, size, border, 0, block);
+ child(strId, size, border, Flag.none(), block);
}
- public static void child(String strId, Vec2 size, boolean border, int windowFlags, Runnable block) {
+ public static void child(String strId, Vec2 size, boolean border, Flag windowFlags, Runnable block) {
if (imgui.beginChild(strId, size, border, windowFlags)) // ~open
block.run();
imgui.endChild();
@@ -167,9 +170,9 @@ public static void withTextWrapPos(float wrapPosX, Runnable block) {
}
public static void withAllowKeyboardFocus(boolean allowKeyboardFocus, Runnable block) {
- imgui.pushAllowKeyboardFocus(allowKeyboardFocus);
+ imgui.pushTabStop(allowKeyboardFocus);
block.run();
- imgui.popAllowKeyboardFocus();
+ imgui.popTabStop();
}
public static void withButtonRepeat(boolean repeat, Runnable block) {
@@ -229,7 +232,7 @@ public static void smallButton(String label, Runnable block) {
block.run();
}
- public static void invisibleButton(String strId, Vec2 sizeArg, int flags, Runnable block) {
+ public static void invisibleButton(String strId, Vec2 sizeArg, Flag flags, Runnable block) {
if (imgui.invisibleButton(strId, sizeArg, flags))
block.run();
}
@@ -252,22 +255,27 @@ 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, int flagsValue, Runnable block) {
+ public static > void checkboxFlags(String label, MutableReference> vPtr, Flag flagsValue, Runnable block) {
if (imgui.checkboxFlags(label, vPtr, flagsValue))
block.run();
}
+ public static > void checkboxFlags(String label, int[] flags, Flag flagsValue, Runnable block) {
+ if (imgui.checkboxFlags(label, flags, flagsValue))
+ block.run();
+ }
+
public static void radioButton(String label, boolean active, Runnable block) {
if (imgui.radioButton(label, active))
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();
}
@@ -277,21 +285,21 @@ public static void radioButton(String label, MutableProperty0 v, int vB
public static void useCombo(String label, String previewValue, Runnable block) {
- useCombo(label, previewValue, 0, block);
+ useCombo(label, previewValue, Flag.none(), block);
}
- public static void useCombo(String label, String previewValue, int comboFlags, Runnable block) {
+ public static void useCombo(String label, String previewValue, Flag comboFlags, Runnable block) {
if (imgui.beginCombo(label, previewValue, comboFlags)) {
block.run();
imgui.endCombo();
}
}
- 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();
}
@@ -354,19 +362,19 @@ public static void treeNode(Object ptrId, String fmt, Runnable block) {
// }
public static void collapsingHeader(String label, Runnable block) {
- collapsingHeader(label, 0, block);
+ collapsingHeader(label, Flag.none(), block);
}
- public static void collapsingHeader(String label, int treeNodeFlags, Runnable block) {
+ public static void collapsingHeader(String label, Flag treeNodeFlags, Runnable block) {
if (imgui.collapsingHeader(label, treeNodeFlags))
block.run();
}
- public static void collapsingHeader(String label, MutableProperty0 open, Runnable block) {
- collapsingHeader(label, open, 0, block);
+ public static void collapsingHeader(String label, MutableReference open, Runnable block) {
+ collapsingHeader(label, open, Flag.none(), block);
}
- public static void collapsingHeader(String label, MutableProperty0 open, int treeNodeFlags, Runnable block) {
+ public static void collapsingHeader(String label, MutableReference open, Flag treeNodeFlags, Runnable block) {
if (imgui.collapsingHeader(label, open, treeNodeFlags))
block.run();
}
@@ -375,18 +383,18 @@ public static void collapsingHeader(String label, MutableProperty0 open
// Widgets: Selectables
public static void selectable(String label, Runnable block) {
- selectable(label, false, 0, new Vec2(), block);
+ selectable(label, false, Flag.none(), new Vec2(), block);
}
public static void selectable(String label, boolean selected, Runnable block) {
- selectable(label, selected, 0, new Vec2(), block);
+ selectable(label, selected, Flag.none(), new Vec2(), block);
}
- public static void selectable(String label, boolean selected, int flags, Runnable block) {
+ public static void selectable(String label, boolean selected, Flag flags, Runnable block) {
selectable(label, selected, flags, new Vec2(), block);
}
- public static void selectable(String label, boolean selected, int flags, Vec2 sizeArg, Runnable block) {
+ public static void selectable(String label, boolean selected, Flag flags, Vec2 sizeArg, Runnable block) {
if (imgui.selectable(label, selected, flags, sizeArg))
block.run();
}
@@ -440,19 +448,20 @@ public static void menuItem(String label, String shortcut, boolean selected, boo
// Tooltips
public static void tooltip(Runnable block) {
- imgui.beginTooltip();
- block.run();
- imgui.endTooltip();
+ if (imgui.beginTooltip()) {
+ block.run();
+ imgui.endTooltip();
+ }
}
// Popups, Modals
public static void popup(String strId, Runnable block) {
- popup(strId, 0, block);
+ popup(strId, Flag.none(), block);
}
- public static void popup(String strId, int windowFlags, Runnable block) {
+ public static void popup(String strId, Flag windowFlags, Runnable block) {
if (imgui.beginPopup(strId, windowFlags)) {
block.run();
imgui.endPopup();
@@ -460,10 +469,10 @@ public static void popup(String strId, int windowFlags, Runnable block) {
}
public static void popupContextItem(String strId, Runnable block) {
- popupContextItem(strId, 0, block);
+ popupContextItem(strId, Flag.none(), block);
}
- public static void popupContextItem(String strId, int popupFlags, Runnable block) {
+ public static void popupContextItem(String strId, Flag popupFlags, Runnable block) {
if (imgui.beginPopupContextItem(strId, popupFlags)) {
block.run();
imgui.endPopup();
@@ -471,10 +480,10 @@ public static void popupContextItem(String strId, int popupFlags, Runnable block
}
public static void popupContextWindow(String strId, Runnable block) {
- popupContextWindow(strId, 0, block);
+ popupContextWindow(strId, Flag.none(), block);
}
- public static void popupContextWindow(String strId, int popupFlags, Runnable block) {
+ public static void popupContextWindow(String strId, Flag popupFlags, Runnable block) {
if (imgui.beginPopupContextWindow(strId, popupFlags)) {
block.run();
imgui.endPopup();
@@ -482,10 +491,10 @@ public static void popupContextWindow(String strId, int popupFlags, Runnable blo
}
public static void popupContextVoid(String strId, Runnable block) {
- popupContextVoid(strId, 0, block);
+ popupContextVoid(strId, Flag.none(), block);
}
- public static void popupContextVoid(String strId, int popupFlags, Runnable block) {
+ public static void popupContextVoid(String strId, Flag popupFlags, Runnable block) {
if (imgui.beginPopupContextVoid(strId, popupFlags)) {
block.run();
imgui.endPopup();
@@ -493,14 +502,14 @@ public static void popupContextVoid(String strId, int popupFlags, Runnable block
}
public static void popupModal(String name, Runnable block) {
- popupModal(name, null, 0, block);
+ popupModal(name, null, Flag.none(), block);
}
- public static void popupModal(String name, MutableProperty0 pOpen, Runnable block) {
- popupModal(name, pOpen, 0, block);
+ public static void popupModal(String name, MutableReference pOpen, Runnable block) {
+ popupModal(name, pOpen, Flag.none(), block);
}
- public static void popupModal(String name, MutableProperty0 pOpen, int 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();
@@ -511,24 +520,24 @@ public static void popupModal(String name, MutableProperty0 pOpen, int
// Tab Bars, Tabs
public static void tabBar(String strId, Runnable block) {
- tabBar(strId, 0, block);
+ tabBar(strId, Flag.none(), block);
}
- public static void tabBar(String strId, int tabBarFlags, Runnable block) {
+ public static void tabBar(String strId, Flag tabBarFlags, Runnable block) {
if (imgui.beginTabBar(strId, tabBarFlags))
block.run();
imgui.endTabBar();
}
public static void tabItem(String label, Runnable block) {
- tabItem(label, null, 0, block);
+ tabItem(label, null, Flag.none(), block);
}
- public static void tabItem(String label, MutableProperty0 pOpen, Runnable block) {
- tabItem(label, pOpen, 0, block);
+ public static void tabItem(String label, MutableReference pOpen, Runnable block) {
+ tabItem(label, pOpen, Flag.none(), block);
}
- public static void tabItem(String label, MutableProperty0 pOpen, int 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();
@@ -538,10 +547,10 @@ public static void tabItem(String label, MutableProperty0 pOpen, int ta
// Drag and Drop
public static void dragDropSource(Runnable block) {
- dragDropSource(0, block);
+ dragDropSource(Flag.none(), block);
}
- public static void dragDropSource(int dragDropFlags, Runnable block) {
+ public static void dragDropSource(Flag dragDropFlags, Runnable block) {
if (imgui.beginDragDropSource(dragDropFlags)) {
block.run();
imgui.endDragDropSource();
@@ -568,18 +577,18 @@ public static void withClipRect(Vec2 clipRectMin, Vec2 clipRectMax, boolean inte
// Miscellaneous Utilities
public static void childFrame(int id, Vec2 size, Runnable block) {
- childFrame(id, size, 0, block);
+ childFrame(id, size, Flag.none(), block);
}
- public static void childFrame(int id, Vec2 size, int windowFlags, Runnable block) {
- imgui.beginChildFrame(id, size, windowFlags);
- block.run();
+ public static void childFrame(int id, Vec2 size, Flag windowFlags, Runnable block) {
+ if (imgui.beginChildFrame(id, size, windowFlags))
+ block.run();
imgui.endChildFrame();
}
// Columns
- public static void columns(String strId, int columnsCount, int flags, Runnable block) {
+ public static void columns(String strId, int columnsCount, Flag flags, Runnable block) {
imgui.beginColumns(strId, columnsCount, flags);
try {
block.run();
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/childWindows.kt b/core/src/main/kotlin/imgui/api/childWindows.kt
index d2408da45..9d841c84d 100644
--- a/core/src/main/kotlin/imgui/api/childWindows.kt
+++ b/core/src/main/kotlin/imgui/api/childWindows.kt
@@ -1,7 +1,7 @@
package imgui.api
-import gli_.has
import glm_.glm
+import glm_.has
import glm_.vec2.Vec2
import imgui.*
import imgui.ImGui.beginChildEx
@@ -11,7 +11,10 @@ import imgui.ImGui.itemAdd
import imgui.ImGui.itemSize
import imgui.ImGui.renderNavHighlight
import imgui.internal.classes.Rect
-import imgui.internal.sections.*
+import imgui.internal.sections.Axis
+import imgui.internal.sections.ItemStatusFlag
+import imgui.internal.sections.NavHighlightFlag
+import imgui.internal.sections.shl
import imgui.WindowFlag as Wf
@@ -27,14 +30,14 @@ interface childWindows {
* [Important: due to legacy reason, this is inconsistent with most other functions such as BeginMenu/EndMenu,
* BeginPopup/EndPopup, etc. where the EndXXX call should only be called if the corresponding BeginXXX function
* returned true. Begin and BeginChild are the only odd ones out. Will be fixed in a future update.] */
- fun beginChild(strId: String, size: Vec2 = Vec2(), border: Boolean = false, flags: WindowFlags = 0): Boolean =
- beginChildEx(strId, currentWindow.getID(strId), size, border, flags)
+ fun beginChild(strId: String, size: Vec2 = Vec2(), border: Boolean = false, flags: WindowFlags = none): Boolean =
+ beginChildEx(strId, currentWindow.getID(strId), size, border, flags)
/** begin a scrolling region.
* size == 0f: use remaining window size
* size < 0f: use remaining window size minus abs(size)
* size > 0f: fixed size. each axis can use a different mode, e.g. Vec2(0, 400). */
- fun beginChild(id: ID, sizeArg: Vec2 = Vec2(), border: Boolean = false, flags: WindowFlags = Wf.None.i): Boolean {
+ fun beginChild(id: ID, sizeArg: Vec2 = Vec2(), border: Boolean = false, flags: WindowFlags = none): Boolean {
assert(id != 0)
return beginChildEx("", id, sizeArg, border, flags)
}
@@ -61,15 +64,21 @@ interface childWindows {
val parentWindow = currentWindow
val bb = Rect(parentWindow.dc.cursorPos, parentWindow.dc.cursorPos + sz)
itemSize(sz)
- if ((window.dc.navLayersActiveMask != 0 || window.dc.navHasScroll) && window.flags hasnt Wf._NavFlattened) {
+ if ((window.dc.navLayersActiveMask != 0 || window.dc.navWindowHasScrollY) && window.flags hasnt Wf._NavFlattened) {
itemAdd(bb, window.childId)
renderNavHighlight(bb, window.childId)
// When browsing a window that has no activable items (scroll only) we keep a highlight on the child (pass g.NavId to trick into always displaying)
if (window.dc.navLayersActiveMask == 0 && window === g.navWindow)
- renderNavHighlight(Rect(bb.min - 2, bb.max + 2), g.navId, NavHighlightFlag.TypeThin.i)
- } else // Not navigable into
+ renderNavHighlight(Rect(bb.min - 2, bb.max + 2), g.navId, NavHighlightFlag.TypeThin)
+ } else {
+ // Not navigable into
itemAdd(bb, 0)
+
+ // But when flattened we directly reach items, adjust active layer mask accordingly
+ if (window.flags has Wf._NavFlattened)
+ parentWindow.dc.navLayersActiveMaskNext = parentWindow.dc.navLayersActiveMaskNext or window.dc.navLayersActiveMaskNext
+ }
if (g.hoveredWindow === window)
g.lastItemData.statusFlags = g.lastItemData.statusFlags or ItemStatusFlag.HoveredWindow
}
diff --git a/core/src/main/kotlin/imgui/api/clipboardUtilities.kt b/core/src/main/kotlin/imgui/api/clipboardUtilities.kt
index 687b06579..4327da841 100644
--- a/core/src/main/kotlin/imgui/api/clipboardUtilities.kt
+++ b/core/src/main/kotlin/imgui/api/clipboardUtilities.kt
@@ -1,6 +1,7 @@
package imgui.api
import imgui.ImGui.io
+import imgui.classes.Context
/** Clipboard Utilities
* - Also see the LogToClipboard() function to capture GUI into clipboard, or easily output text data to the clipboard. */
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/columns.kt b/core/src/main/kotlin/imgui/api/columns.kt
index fbd0a7f1c..c62b69c28 100644
--- a/core/src/main/kotlin/imgui/api/columns.kt
+++ b/core/src/main/kotlin/imgui/api/columns.kt
@@ -15,12 +15,13 @@ import imgui.ImGui.popItemWidth
import imgui.ImGui.pushItemWidth
import imgui.ImGui.setWindowClipRectBeforeSetChannel
import imgui.ImGui.style
+import imgui.none
import imgui.has
import imgui.hasnt
import imgui.internal.floor
import imgui.internal.lerp
import imgui.internal.sections.OldColumns
-import imgui.internal.sections.OldColumnsFlags
+import imgui.internal.sections.OldColumnFlags
import kotlin.math.max
import kotlin.math.min
import imgui.internal.sections.OldColumnsFlag as Cf
@@ -34,7 +35,7 @@ interface columns {
val window = currentWindow
assert(columnsCount >= 1)
- val flags: OldColumnsFlags = if (border) Cf.None.i else Cf.NoBorder.i
+ val flags: OldColumnFlags = if (border) none else Cf.NoBorder
//flags |= ImGuiOldColumnFlags_NoPreserveWidths; // NB: Legacy behavior
window.dc.currentColumns?.let {
if (it.count == columnsCount && it.flags == flags)
diff --git a/core/src/main/kotlin/imgui/api/context.kt b/core/src/main/kotlin/imgui/api/context.kt
index babcf2ab8..edebbaf9e 100644
--- a/core/src/main/kotlin/imgui/api/context.kt
+++ b/core/src/main/kotlin/imgui/api/context.kt
@@ -25,7 +25,11 @@ val g: Context
get() = gImGui
/** ~GetCurrentContext/SetCurrentContext */
-lateinit var gImGui: Context
-val gImGuiNullable: Context? get() = if(::gImGui.isInitialized) gImGui else null
+var gImGui: Context
+ get() = gImGuiNullable!!
+ set(value) {
+ gImGuiNullable = value
+ }
+var gImGuiNullable: Context? = null
diff --git a/core/src/main/kotlin/imgui/api/cursorLayout.kt b/core/src/main/kotlin/imgui/api/cursorLayout.kt
index 985db0e70..544068f34 100644
--- a/core/src/main/kotlin/imgui/api/cursorLayout.kt
+++ b/core/src/main/kotlin/imgui/api/cursorLayout.kt
@@ -17,6 +17,7 @@ import imgui.internal.classes.Rect
import imgui.internal.sections.ItemFlag
import imgui.internal.sections.ItemStatusFlag
import imgui.internal.sections.SeparatorFlag
+import imgui.internal.sections.SeparatorFlags
import imgui.internal.sections.LayoutType as Lt
@@ -36,10 +37,11 @@ interface cursorLayout {
if (window.skipItems)
return
- // Those flags should eventually be overridable by the user
- var flags = if (window.dc.layoutType == Lt.Horizontal) SeparatorFlag.Vertical.i else SeparatorFlag.Horizontal.i
+ // Those flags should eventually be configurable by the user
+ // FIXME: We cannot g.Style.SeparatorTextBorderSize for thickness as it relates to SeparatorText() which is a decorated separator, not defaulting to 1.0f.
+ var flags: SeparatorFlags = if (window.dc.layoutType == Lt.Horizontal) SeparatorFlag.Vertical else SeparatorFlag.Horizontal
flags /= SeparatorFlag.SpanAllColumns // NB: this only applies to legacy Columns() api as they relied on Separator() a lot.
- separatorEx(flags)
+ separatorEx(flags, 1f)
}
fun sameLine(offsetFromStartX: Int, spacing: Int = -1) = sameLine(offsetFromStartX.f, spacing.f)
@@ -178,7 +180,7 @@ interface cursorLayout {
window.dc.currLineTextBaseOffset = window.dc.prevLineTextBaseOffset max groupData.backupCurrLineTextBaseOffset // FIXME: Incorrect, we should grab the base offset from the *first line* of the group but it is hard to obtain now.
itemSize(groupBb.size)
- itemAdd(groupBb, 0, null, ItemFlag.NoTabStop.i)
+ itemAdd(groupBb, 0, null, ItemFlag.NoTabStop)
// If the current ActiveId was declared within the boundary of our group, we copy it to LastItemId so IsItemActive(), IsItemDeactivated() etc. will be functional on the entire group.
// It would be neater if we replaced window.DC.LastItemId by e.g. 'bool LastItemIsActive', but would put a little more burden on individual widgets.
diff --git a/core/src/main/kotlin/imgui/api/debugUtilities.kt b/core/src/main/kotlin/imgui/api/debugUtilities.kt
index 8bd369e7a..1aae3436a 100644
--- a/core/src/main/kotlin/imgui/api/debugUtilities.kt
+++ b/core/src/main/kotlin/imgui/api/debugUtilities.kt
@@ -1,5 +1,6 @@
package imgui.api
+import glm_.b
import imgui.ImGui
import imgui.ImGui.sameLine
import imgui.ImGui.tableHeadersRow
@@ -8,6 +9,7 @@ import imgui.ImGui.tableSetupColumn
import imgui.ImGui.text
import imgui.ImGui.textUnformatted
import imgui.TableFlag
+import imgui.div
import imgui.dsl.table
import imgui.internal.textCharFromUtf8
import imgui.or
@@ -17,28 +19,28 @@ import uno.kotlin.NUL
interface debugUtilities {
/** Helper tool to diagnose between text encoding issues and font loading issues. Pass your UTF-8 string and verify that there are correct. */
- fun debugTextEncoding(str: String) {
+ fun debugTextEncoding(str: ByteArray) {
text("Text: \"$str\"")
- table("list", 4, TableFlag.Borders or TableFlag.RowBg or TableFlag.SizingFixedFit) {
+ table("##DebugTextEncoding", 4, TableFlag.Borders / TableFlag.RowBg / TableFlag.SizingFixedFit / TableFlag.Resizable) {
tableSetupColumn("Offset")
tableSetupColumn("UTF-8")
tableSetupColumn("Glyph")
tableSetupColumn("Codepoint")
tableHeadersRow()
var p = 0
- while (str[p] != NUL) {
- val (c, cUtf8Len) = textCharFromUtf8(str.encodeToByteArray(), p, -1)
+ while (p < str.size && str[p] != 0.b) {
+ val (c, cUtf8Len) = textCharFromUtf8(str, p, -1)
tableNextColumn()
text("$p")
tableNextColumn()
for (byteIndex in 0 until cUtf8Len) {
if (byteIndex > 0)
sameLine()
- text("0x%02X", str[p + byteIndex].code)
+ text("0x%02X", str[p + byteIndex])
}
tableNextColumn()
if (ImGui.font.findGlyphNoFallback(Char(c)) != null)
- textUnformatted(str.drop(p), p + cUtf8Len)
+ textUnformatted(str.copyOfRange(p, p + cUtf8Len), cUtf8Len)
else
textUnformatted("[missing]")
tableNextColumn()
diff --git a/core/src/main/kotlin/imgui/api/demoDebugInformations.kt b/core/src/main/kotlin/imgui/api/demoDebugInformations.kt
index 2053cf998..aca51e145 100644
--- a/core/src/main/kotlin/imgui/api/demoDebugInformations.kt
+++ b/core/src/main/kotlin/imgui/api/demoDebugInformations.kt
@@ -1,16 +1,17 @@
+@file:OptIn(ExperimentalStdlibApi::class)
+
package imgui.api
import glm_.*
import glm_.vec2.Vec2
import glm_.vec4.Vec4
import imgui.*
-import imgui.ImGui.alignTextToFramePadding
import imgui.ImGui.begin
import imgui.ImGui.beginChild
import imgui.ImGui.beginChildFrame
import imgui.ImGui.beginCombo
+import imgui.ImGui.beginItemTooltip
import imgui.ImGui.beginTable
-import imgui.ImGui.beginTooltip
import imgui.ImGui.bulletText
import imgui.ImGui.button
import imgui.ImGui.calcTextSize
@@ -47,10 +48,12 @@ import imgui.ImGui.getForegroundDrawList
import imgui.ImGui.getID
import imgui.ImGui.getInstanceData
import imgui.ImGui.getKeyChordName
+import imgui.ImGui.getOwnerData
import imgui.ImGui.indent
import imgui.ImGui.inputText
import imgui.ImGui.inputTextMultiline
import imgui.ImGui.io
+import imgui.ImGui.isClicked
import imgui.ImGui.isDown
import imgui.ImGui.isItemHovered
import imgui.ImGui.isMouseHoveringRect
@@ -59,7 +62,6 @@ import imgui.ImGui.isReleased
import imgui.ImGui.logFinish
import imgui.ImGui.logText
import imgui.ImGui.logToClipboard
-import imgui.ImGui.ownerData
import imgui.ImGui.popID
import imgui.ImGui.popTextWrapPos
import imgui.ImGui.pushID
@@ -92,14 +94,14 @@ import imgui.ImGui.treeNode
import imgui.ImGui.treeNodeToLabelSpacing
import imgui.ImGui.treePop
import imgui.ImGui.unindent
-import imgui.classes.ListClipper
import imgui.classes.Style
-import imgui.demo.ExampleApp
+import imgui.classes.listClipper
+import imgui.demo.DemoWindow
import imgui.demo.showExampleApp.StyleEditor
import imgui.dsl.indent
import imgui.dsl.listBox
import imgui.dsl.treeNode
-import imgui.hasnt
+import imgui.dsl.withID
import imgui.internal.DrawIdx
import imgui.internal.DrawVert
import imgui.internal.api.debugTools.Companion.metricsHelpMarker
@@ -153,7 +155,7 @@ interface demoDebugInformations {
fun showDemoWindow(open: KMutableProperty0) {
// Exceptionally add an extra assert here for people confused about initial Dear ImGui setup
// Most functions would normally just crash if the context is missing.
- ExampleApp(open)
+ DemoWindow(open)
}
/** create Metrics/Debugger window. display Dear ImGui internals: windows, draw commands, various internal state, etc. */
@@ -171,7 +173,7 @@ interface demoDebugInformations {
}
// Basic info
- text("Dear ImGui $version")
+ text("Dear ImGui $IMGUI_VERSION ($IMGUI_VERSION_NUM)")
text("Application average %.3f ms/frame (%.1f FPS)", 1000f / io.framerate, io.framerate)
text("${io.metricsRenderVertices} vertices, ${io.metricsRenderIndices} indices (${io.metricsRenderIndices / 3} triangles)")
text("${io.metricsRenderWindows} visible windows, ${io.metricsActiveAllocations} active allocations")
@@ -203,7 +205,7 @@ interface demoDebugInformations {
if (showEncodingViewer) {
setNextItemWidth(-Float.MIN_VALUE)
inputText("##Text", buf)
- if (buf.isNotEmpty())
+ if (buf[0] != 0.b)
debugTextEncoding(buf)
treePop()
}
@@ -228,16 +230,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)
}
}
}
@@ -255,7 +257,7 @@ interface demoDebugInformations {
bulletText("Table 0x%08X (${table.columnsCount} columns, in '${table.outerWindow!!.name}')", table.id)
if (isItemHovered())
- foregroundDrawList.addRect(table.outerRect.min - 1, table.outerRect.max + 1, COL32(255, 255, 0, 255), 0f, 0, 2f)
+ foregroundDrawList.addRect(table.outerRect.min - 1, table.outerRect.max + 1, COL32(255, 255, 0, 255), thickness = 2f)
indent()
for (rectN in TRT.values()) {
if (rectN >= TRT.ColumnsRect) {
@@ -263,23 +265,25 @@ interface demoDebugInformations {
continue
for (columnN in 0 until table.columnsCount) {
val r = Funcs.getTableRect(table, rectN, columnN)
- val buf = "(%6.1f,%6.1f) (%6.1f,%6.1f) Size (%6.1f,%6.1f) Col $columnN ${rectN.name}"
- .format(r.min.x, r.min.y, r.max.x, r.max.y, r.width, r.height)
+ val buf = "(%6.1f,%6.1f) (%6.1f,%6.1f) Size (%6.1f,%6.1f) Col $columnN ${rectN.name}".format(r.min.x, r.min.y, r.max.x, r.max.y, r.width, r.height)
selectable(buf)
if (isItemHovered())
- foregroundDrawList.addRect(r.min - 1, r.max + 1, COL32(255, 255, 0, 255), 0f, 0, 2f)
+ foregroundDrawList.addRect(r.min - 1, r.max + 1, COL32(255, 255, 0, 255), thickness = 2f)
}
} else {
val r = Funcs.getTableRect(table, rectN, -1)
- val buf = "(%6.1f,%6.1f) (%6.1f,%6.1f) Size (%6.1f,%6.1f) ${rectN.name}".format(
- r.min.x, r.min.y, r.max.x, r.max.y, r.width, r.height)
+ val buf = "(%6.1f,%6.1f) (%6.1f,%6.1f) Size (%6.1f,%6.1f) ${rectN.name}".format(r.min.x, r.min.y, r.max.x, r.max.y, r.width, r.height)
selectable(buf)
if (isItemHovered())
- foregroundDrawList.addRect(r.min - 1, r.max + 1, COL32(255, 255, 0, 255), 0f, 0, 2f)
+ foregroundDrawList.addRect(r.min - 1, r.max + 1, COL32(255, 255, 0, 255), thickness = 2f)
}
}
unindent()
}
+
+ checkbox("Debug Begin/BeginChild return value", io::configDebugBeginReturnValueLoop)
+ sameLine()
+ metricsHelpMarker("Some calls to Begin()/BeginChild() will return false.\n\nWill cycle through window depths then repeat. Windows should be flickering while running.")
}
// Windows
@@ -335,15 +339,16 @@ interface demoDebugInformations {
}
// Details for TabBars
- treeNode("TabBars", "Tab Bars (${g.tabBars.size})") {
- for (n in 0 until g.tabBars.size)
- debugNodeTabBar(g.tabBars[n]!!, "TabBar")
-
+ treeNode("TabBars", "Tab Bars (${g.tabBars.aliveCount.i})") {
+ for (tabBar in g.tabBars)
+ withID(tabBar) {
+ debugNodeTabBar(tabBar, "TabBar")
+ }
}
- treeNode("Tables", "Tables (${g.tables.size})") {
- for (n in 0 until g.tables.size)
- debugNodeTable(g.tables.getByIndex(n))
+ treeNode("Tables", "Tables (${g.tables.aliveCount.i})") {
+ for (table in g.tables)
+ debugNodeTable(table)
}
// Details for Fonts
@@ -359,10 +364,11 @@ interface demoDebugInformations {
// Details for Docking
- // #ifdef IMGUI_HAS_DOCK
- treeNode("Dock nodes") {
+ if (IMGUI_HAS_DOCK)
+ treeNode("Dock nodes") {
- } // #endif // #define IMGUI_HAS_DOCK
+ }
+ // #endif // #define IMGUI_HAS_DOCK
// Settings
treeNode("Settings") {
@@ -374,9 +380,10 @@ interface demoDebugInformations {
sameLine()
if (io.iniFilename != null) text("\"${io.iniFilename}\"")
else textUnformatted("")
+ checkbox("io.ConfigDebugIniSettings", io::configDebugIniSettings)
text("SettingsDirtyTimer %.2f", g.settingsDirtyTimer)
treeNode("SettingsHandlers", "Settings handlers: (${g.settingsHandlers.size})") {
- g.settingsHandlers.forEach { bulletText(it.typeName) }
+ g.settingsHandlers.forEach { bulletText("\"${it.typeName}\"") }
}
treeNode("SettingsWindows", "Settings packed data: Windows: ${g.settingsWindows.size} bytes") {
g.settingsWindows.forEach(::debugNodeWindowSettings)
@@ -391,14 +398,14 @@ interface demoDebugInformations {
treeNode("SettingsIniData", "Settings unpacked data (.ini): ${g.settingsIniData.toByteArray().size} bytes") {
val size = Vec2(-Float.MIN_VALUE, ImGui.textLineHeight * 20)
- inputTextMultiline("##Ini", g.settingsIniData, size, InputTextFlag.ReadOnly.i)
+ inputTextMultiline("##Ini", g.settingsIniData, size, InputTextFlag.ReadOnly)
}
}
treeNode("Inputs") {
text("KEYBOARD/GAMEPAD/MOUSE KEYS")
// We iterate both legacy native range and named ImGuiKey ranges, which is a little odd but this allows displaying the data for old/new backends.
- // User code should never have to go through such hoops: old code may use native keycodes, new code may use ImGuiKey codes.
+ // User code should never have to go through such hoops! You can generally iterate between ImGuiKey_NamedKey_BEGIN and ImGuiKey_NamedKey_END.
indent {
// #ifdef IMGUI_DISABLE_OBSOLETE_KEYIO
// struct funcs { static bool IsLegacyNativeDupe(ImGuiKey) { return false; } };
@@ -437,10 +444,12 @@ interface demoDebugInformations {
text("Mouse pos: ")
text("Mouse delta: (%g, %g)", io.mouseDelta.x, io.mouseDelta.y)
val count = io.mouseDown.size
- text("Mouse down:"); for (i in 0 until count) if (ImGui.isMouseDown(MouseButton of i)) {; sameLine(); text("b$i (%.02f secs)", io.mouseDownDuration[i]); }
- text("Mouse clicked:"); for (i in 0 until count) if (ImGui.isMouseClicked(MouseButton of i)) {; sameLine(); text("b$i (${io.mouseClickedCount[i]})"); }
- text("Mouse released:"); for (i in 0 until count) if (ImGui.isMouseReleased(MouseButton of i)) {; sameLine(); text("b$i"); }
+ text("Mouse down:"); for (i in 0 until count) if (MouseButton.of(i).isDown) {; sameLine(); text("b$i (%.02f secs)", io.mouseDownDuration[i]); }
+ text("Mouse clicked:"); for (i in 0 until count) if (MouseButton.of(i).isClicked) {; sameLine(); text("b$i (${io.mouseClickedCount[i]})"); }
+ text("Mouse released:"); for (i in 0 until count) if (MouseButton.of(i).isReleased) {; sameLine(); text("b$i"); }
text("Mouse wheel: %.1f", io.mouseWheel)
+ text("mouseStationaryTimer: %.2f", g.mouseStationaryTimer)
+ text("Mouse source: " + io.mouseSource)
text("Pen Pressure: %.1f", io.penPressure) // Note: currently unused
}
@@ -449,14 +458,14 @@ interface demoDebugInformations {
text("WheelingWindow: '${g.wheelingWindow?.name ?: "NULL"}'")
text("WheelingWindowReleaseTimer: %.2f", g.wheelingWindowReleaseTimer)
val axis = if (g.wheelingAxisAvg.x > g.wheelingAxisAvg.y) "X" else if (g.wheelingAxisAvg.x < g.wheelingAxisAvg.y) "Y" else ""
- text("WheelingAxisAvg[] = { %.3f, %.3f }, Main Axis: %s", g.wheelingAxisAvg.x, g.wheelingAxisAvg.y)
+ text("WheelingAxisAvg[] = { %.3f, %.3f }, Main Axis: $axis", g.wheelingAxisAvg.x, g.wheelingAxisAvg.y)
}
text("KEY OWNERS")
indent {
listBox("##owners", Vec2(-Float.MIN_VALUE, textLineHeightWithSpacing * 6)) {
for (key in Key.Named) {
- val ownerData = key.ownerData
+ val ownerData = key.getOwnerData(g)
if (ownerData.ownerCurr == KeyOwner_None)
continue
text("$key: 0x%08X${if (ownerData.lockUntilRelease) " LockUntilRelease" else if (ownerData.lockThisFrame) " LockThisFrame" else ""}", ownerData.ownerCurr)
@@ -470,7 +479,7 @@ interface demoDebugInformations {
listBox("##routes", Vec2(-Float.MIN_VALUE, textLineHeightWithSpacing * 8)) {
for (key in Key.Named) {
val rt = g.keysRoutingTable
- var idx = rt.index[key.i]
+ var idx = rt.index[key]
while (idx != -1) {
val routingData = rt.entries[idx]
val keyChordName = getKeyChordName(key or routingData.mods)
@@ -480,14 +489,14 @@ interface demoDebugInformations {
}
}
}
- text("(ActiveIdUsing: AllKeyboardKeys: %d, NavDirMask: 0x%X)", g.activeIdUsingAllKeyboardKeys, g.activeIdUsingNavDirMask)
+ text("(ActiveIdUsing: AllKeyboardKeys: ${g.activeIdUsingAllKeyboardKeys.i}, NavDirMask: 0x%X)", g.activeIdUsingNavDirMask)
}
}
if (treeNode("Internal state")) {
// [JVM] redundant
- // const char* input_source_names[] = { "None", "Mouse", "Keyboard", "Gamepad", "Nav", "Clipboard" }; IM_ASSERT(IM_ARRAYSIZE(input_source_names) == ImGuiInputSource_COUNT);
+ // const char* input_source_names[] = { "None", "Mouse", "Keyboard", "Gamepad", "Clipboard" }; IM_ASSERT(IM_ARRAYSIZE(input_source_names) == ImGuiInputSource_COUNT);
text("WINDOWING")
indent {
@@ -506,7 +515,7 @@ interface demoDebugInformations {
text("ActiveIdUsing: AllKeyboardKeys: ${g.activeIdUsingAllKeyboardKeys} NavDirMask: %X", g.activeIdUsingNavDirMask)
text("HoveredId: 0x%08X (%.2f sec), AllowOverlap: ${g.hoveredIdAllowOverlap.i}", g.hoveredIdPreviousFrame, g.hoveredIdTimer) // Not displaying g.HoveredId as it is update mid-frame
- text("HoverDelayId: 0x%08X, Timer: %.2f, ClearTimer: %.2f", g.hoverDelayId, g.hoverDelayTimer, g.hoverDelayClearTimer)
+ text("HoverItemDelayId: 0x%08X, Timer: %.2f, ClearTimer: %.2f", g.hoverItemDelayId, g.hoverItemDelayTimer, g.hoverItemDelayClearTimer)
text("DragDrop: ${g.dragDropActive.i}, SourceId = 0x%08X, Payload \"${g.dragDropPayload.dataType}\" (${g.dragDropPayload.dataSize} bytes)", g.dragDropPayload.sourceId)
debugLocateItemOnHover(g.dragDropPayload.sourceId)
}
@@ -518,7 +527,7 @@ interface demoDebugInformations {
debugLocateItemOnHover(g.navId)
text("NavInputSource: ${g.navInputSource}")
text("NavActive: ${io.navActive}, NavVisible: ${io.navVisible}")
- text("NavActivateId/DownId/PressedId/InputId: %08X/%08X/%08X/%08X", g.navActivateId, g.navActivateDownId, g.navActivatePressedId, g.navActivateInputId)
+ text("NavActivateId/DownId/PressedId: %08X/%08X/%08X", g.navActivateId, g.navActivateDownId, g.navActivatePressedId)
text("NavActivateFlags: %04X", g.navActivateFlags)
text("NavDisableHighlight: ${g.navDisableHighlight}, NavDisableMouseHover: ${g.navDisableMouseHover}")
text("NavFocusScopeId = 0x%08X", g.navFocusScopeId)
@@ -555,7 +564,7 @@ interface demoDebugInformations {
val r = Funcs.getTableRect(table, TRT.values()[cfg.showTablesRectsType], columnN)
val col = if (table.hoveredColumnBody == columnN) COL32(255, 255, 128, 255) else COL32(255, 0, 128, 255)
val thickness = if (table.hoveredColumnBody == columnN) 3f else 1f
- drawList.addRect(r.min, r.max, col, 0f, 0, thickness)
+ drawList.addRect(r.min, r.max, col, thickness = thickness)
}
} else {
val r = Funcs.getTableRect(table, TRT.values()[cfg.showTablesRectsType], -1)
@@ -583,15 +592,14 @@ interface demoDebugInformations {
return
}
- alignTextToFramePadding()
- text("Log events:")
- sameLine(); checkboxFlags("All", g::debugLogFlags, DebugLogFlag.EventMask_.i)
- sameLine(); checkboxFlags("ActiveId", g::debugLogFlags, DebugLogFlag.EventActiveId.i)
- sameLine(); checkboxFlags("Focus", g::debugLogFlags, DebugLogFlag.EventFocus.i)
- sameLine(); checkboxFlags("Popup", g::debugLogFlags, DebugLogFlag.EventPopup.i)
- sameLine(); checkboxFlags("Nav", g::debugLogFlags, DebugLogFlag.EventNav.i)
- sameLine(); checkboxFlags("Clipper", g::debugLogFlags, DebugLogFlag.EventClipper.i)
- sameLine(); checkboxFlags("IO", g::debugLogFlags, DebugLogFlag.EventIO.i)
+ checkboxFlags("All", g::debugLogFlags, DebugLogFlag.EventMask)
+ sameLine(); checkboxFlags("ActiveId", g::debugLogFlags, DebugLogFlag.EventActiveId)
+ sameLine(); checkboxFlags("Focus", g::debugLogFlags, DebugLogFlag.EventFocus)
+ sameLine(); checkboxFlags("Popup", g::debugLogFlags, DebugLogFlag.EventPopup)
+ sameLine(); checkboxFlags("Nav", g::debugLogFlags, DebugLogFlag.EventNav)
+ sameLine(); checkboxFlags("Clipper", g::debugLogFlags, DebugLogFlag.EventClipper)
+ //SameLine(); CheckboxFlags("Selection", &g.DebugLogFlags, ImGuiDebugLogFlags_EventSelection);
+ sameLine(); checkboxFlags("IO", g::debugLogFlags, DebugLogFlag.EventIO)
if (smallButton("Clear")) {
g.debugLogBuf.clear()
@@ -602,17 +610,15 @@ interface demoDebugInformations {
clipboardText = g.debugLogBuf.toString()
beginChild("##log", Vec2(), true, Wf.AlwaysVerticalScrollbar or Wf.AlwaysHorizontalScrollbar)
- val clipper = ListClipper()
- clipper.begin(g.debugLogIndex.size)
- while (clipper.step())
- for (lineNo in clipper.displayStart until clipper.displayEnd) {
+ listClipper(g.debugLogIndex.size) {
+ for (lineNo in it.display) {
val lineBegin = g.debugLogIndex getLineBegin lineNo
val lineEnd = g.debugLogIndex getLineEnd lineNo
textUnformatted(g.debugLogBuf.toString(), lineEnd)
val textRect = g.lastItemData.rect
if (isItemHovered()) {
var p = lineBegin
- while (p < lineEnd - 10) {
+ while (p <= lineEnd - 10) {
val buf = g.debugLogBuf.toString()
val id: ID = buf.drop(2).parseInt()
if (buf[p] != '0' || (buf[p + 1] != 'x' && buf[p + 1] != 'X')/* || sscanf(p + 2, "%X", & id) != 1*/)
@@ -626,12 +632,12 @@ interface demoDebugInformations {
}
}
}
+ }
if (ImGui.scrollY >= ImGui.scrollMaxY)
setScrollHereY(1f)
endChild()
end()
- clipper.end()
}
/** create Stack Tool window. hover items with mouse to query information about the source of their unique ID.
@@ -687,12 +693,12 @@ interface demoDebugInformations {
// Display decorated stack
tool.lastActiveFrame = g.frameCount
- if (tool.results.isNotEmpty() && beginTable("##table", 3, TableFlag.Borders.i)) {
+ if (tool.results.isNotEmpty() && beginTable("##table", 3, TableFlag.Borders)) {
val idWidth = calcTextSize("0xDDDDDDDD").x
- tableSetupColumn("Seed", TableColumnFlag.WidthFixed.i, idWidth)
- tableSetupColumn("PushID", TableColumnFlag.WidthStretch.i)
- tableSetupColumn("Result", TableColumnFlag.WidthFixed.i, idWidth)
+ tableSetupColumn("Seed", TableColumnFlag.WidthFixed, idWidth)
+ tableSetupColumn("PushID", TableColumnFlag.WidthStretch)
+ tableSetupColumn("Result", TableColumnFlag.WidthFixed, idWidth)
tableHeadersRow()
for (n in tool.results.indices) {
val info = tool.results[n]
@@ -717,7 +723,7 @@ interface demoDebugInformations {
var showConfigInfo = false
operator fun invoke(open: KMutableProperty0) {
- if (!begin("About Dear ImGui", open, Wf.AlwaysAutoResize.i)) {
+ if (!begin("About Dear ImGui", open, Wf.AlwaysAutoResize)) {
end()
return
}
@@ -727,13 +733,14 @@ interface demoDebugInformations {
separator()
text("By Omar Cornut and all Dear Imgui contributors.")
text("Dear ImGui is licensed under the MIT License, see LICENSE for more information.")
+ text("If your company uses this, please consider sponsoring the project!")
checkbox("Config/Build Information", ::showConfigInfo)
if (showConfigInfo) {
val copyToClipboard = button("Copy to clipboard")
val childSize = Vec2(0f, textLineHeightWithSpacing * 18)
- beginChildFrame(getID("cfginfos"), childSize, Wf.NoMove.i)
+ beginChildFrame(getID("cfginfos"), childSize, Wf.NoMove)
if (copyToClipboard) {
logToClipboard()
logText("```\n") // Back quotes will make text appears without formatting when pasting on GitHub
@@ -758,19 +765,16 @@ interface demoDebugInformations {
if (io.configInputTextCursorBlink) text("io.configInputTextCursorBlink")
if (io.configWindowsResizeFromEdges) text("io.configWindowsResizeFromEdges")
if (io.configWindowsMoveFromTitleBarOnly) text("io.configWindowsMoveFromTitleBarOnly")
- if (io.configMemoryCompactTimer >= 0f) text("io.ConfigMemoryCompactTimer = %.1f",
- io.configMemoryCompactTimer)
+ if (io.configMemoryCompactTimer >= 0f) text("io.ConfigMemoryCompactTimer = %.1f", io.configMemoryCompactTimer)
text("io.backendFlags: 0x%08X", io.backendFlags)
if (io.backendFlags has BackendFlag.HasGamepad) text(" HasGamepad")
if (io.backendFlags has BackendFlag.HasMouseCursors) text(" HasMouseCursors")
if (io.backendFlags has BackendFlag.HasSetMousePos) text(" HasSetMousePos")
if (io.backendFlags has BackendFlag.RendererHasVtxOffset) text(" RendererHasVtxOffset") // @formatter:on
separator()
- text("io.fonts: ${io.fonts.fonts.size} fonts, Flags: 0x%08X, TexSize: ${io.fonts.texSize.x},${io.fonts.texSize.y}",
- io.fonts.flags)
+ text("io.fonts: ${io.fonts.fonts.size} fonts, Flags: 0x%08X, TexSize: ${io.fonts.texSize.x},${io.fonts.texSize.y}", io.fonts.flags)
text("io.displaySize: ${io.displaySize.x},${io.displaySize.y}")
- text("io.displayFramebufferScale: %.2f,%.2f".format(io.displayFramebufferScale.x,
- io.displayFramebufferScale.y))
+ text("io.displayFramebufferScale: %.2f,%.2f".format(io.displayFramebufferScale.x, io.displayFramebufferScale.y))
separator()
text("style.windowPadding: %.2f,%.2f", style.windowPadding.x, style.windowPadding.y)
text("style.windowBorderSize: %.2f", style.windowBorderSize)
@@ -943,10 +947,12 @@ interface demoDebugInformations {
val min = window.innerRect.min - window.scroll + window.windowPadding
Rect(min, min + window.contentSize)
}
+
WRT.ContentIdeal -> {
val min = window.innerRect.min - window.scroll + window.windowPadding
Rect(min, min + window.contentSize)
}
+
WRT.ContentRegionRect -> window.contentRegionRect
}
}
@@ -958,8 +964,7 @@ interface demoDebugInformations {
* In your own code you may want to display an actual icon if you are using a merged icon fonts (see docs/FONTS.txt) */
fun helpMarker(desc: String) {
textDisabled("(?)")
- if (isItemHovered(HoveredFlag.DelayShort)) {
- beginTooltip()
+ if (beginItemTooltip()) {
pushTextWrapPos(fontSize * 35f)
textEx(desc)
popTextWrapPos()
@@ -987,7 +992,7 @@ interface demoDebugInformations {
fun stackToolFormatLevelInfo(tool: StackTool, n: Int, formatForUi: Boolean, buf: ByteArray): Int {
val info = tool.results[n]
val desc = info.desc.toByteArray()
- val window = if (desc[0] == 0.b && n == 0) findWindowByID(info.id) else null
+ val window = if (desc.isNotEmpty() && desc[0] == 0.b && n == 0) findWindowByID(info.id) else null
if (window != null) // Source: window name (because the root ID don't call GetID() and so doesn't get hooked)
return formatString(buf, if (formatForUi) "\"%s\" [window]" else "%s", window.name)
if (info.querySuccess) // Source: GetID() hooks (prioritize over ItemInfo() because we frequently use patterns like: PushID(str), Button("") where they both have same id)
@@ -1001,6 +1006,6 @@ interface demoDebugInformations {
return formatString(buf, "???")
}
- val buf = ""
+ val buf = ByteArray(100)
}
}
\ No newline at end of file
diff --git a/core/src/main/kotlin/imgui/api/dragAndDrop.kt b/core/src/main/kotlin/imgui/api/dragAndDrop.kt
index 192c69d2b..874d0baf9 100644
--- a/core/src/main/kotlin/imgui/api/dragAndDrop.kt
+++ b/core/src/main/kotlin/imgui/api/dragAndDrop.kt
@@ -1,14 +1,13 @@
package imgui.api
import imgui.*
-import imgui.DragDropFlag
import imgui.ImGui.beginTooltip
import imgui.ImGui.clearDragDrop
import imgui.ImGui.endTooltip
import imgui.ImGui.focusWindow
import imgui.ImGui.io
-import imgui.ImGui.isMouseDown
-import imgui.ImGui.isMouseDragging
+import imgui.ImGui.isDown
+import imgui.ImGui.isDragging
import imgui.ImGui.itemHoverable
import imgui.ImGui.keepAliveID
import imgui.ImGui.setActiveID
@@ -16,8 +15,8 @@ import imgui.ImGui.setActiveIdUsingAllKeyboardKeys
import imgui.classes.Payload
import imgui.internal.classes.Rect
import imgui.internal.classes.Window
-import imgui.internal.*
-import imgui.internal.sections.*
+import imgui.internal.hashStr
+import imgui.internal.sections.ItemStatusFlag
import imgui.DragDropFlag as Ddf
// Drag and Drop
@@ -26,7 +25,6 @@ import imgui.DragDropFlag as Ddf
// - If you stop calling BeginDragDropSource() the payload is preserved however it won't have a preview tooltip (we currently display a fallback "..." tooltip, see #1725)
// - An item can be both drag source and drop target.
interface dragAndDrop {
-
/** call after submitting an item which may be dragged. when this return true, you can call SetDragDropPayload() + EndDragDropSource()
*
* When this returns true you need to: a) call SetDragDropPayload() exactly once, b) you may render the payload visual/description, c) call EndDragDropSource()
@@ -36,18 +34,7 @@ interface dragAndDrop {
* - We then pull and use the mouse button that was used to activate the item and use it to carry on the drag.
* If the item has no identifier:
* - Currently always assume left mouse button. */
- fun beginDragDropSource(flag: Ddf): Boolean = beginDragDropSource(flag.i)
-
- /** call after submitting an item which may be dragged. when this return true, you can call SetDragDropPayload() + EndDragDropSource()
- *
- * When this returns true you need to: a) call SetDragDropPayload() exactly once, b) you may render the payload visual/description, c) call EndDragDropSource()
- * If the item has an identifier:
- * - This assume/require the item to be activated (typically via ButtonBehavior).
- * - Therefore if you want to use this with a mouse button other than left mouse button, it is up to the item itself to activate with another button.
- * - We then pull and use the mouse button that was used to activate the item and use it to carry on the drag.
- * If the item has no identifier:
- * - Currently always assume left mouse button. */
- fun beginDragDropSource(flags: DragDropFlags = 0): Boolean {
+ fun beginDragDropSource(flags: DragDropFlags = none): Boolean {
var window: Window? = g.currentWindow!!
@@ -64,8 +51,8 @@ interface dragAndDrop {
// Common path: items with ID
if (g.activeId != sourceId)
return false
- if (g.activeIdMouseButton != -1)
- mouseButton = MouseButton.of(g.activeIdMouseButton)
+ if (g.activeIdMouseButton != MouseButton.None)
+ mouseButton = g.activeIdMouseButton
if (!g.io.mouseDown[mouseButton.i] || window!!.skipItems)
return false
g.activeIdAllowOverlap = false
@@ -88,7 +75,7 @@ interface dragAndDrop {
// Rely on keeping other window->LastItemXXX fields intact.
sourceId = window.getIDFromRectangle(g.lastItemData.rect); g.lastItemData.id = sourceId
keepAliveID(sourceId)
- val isHovered = itemHoverable(g.lastItemData.rect, sourceId)
+ val isHovered = itemHoverable(g.lastItemData.rect, sourceId, g.lastItemData.inFlags)
if (isHovered && io.mouseClicked[mouseButton.i]) {
setActiveID(sourceId, window)
focusWindow(window)
@@ -98,8 +85,8 @@ interface dragAndDrop {
}
if (g.activeId != sourceId)
return false
- sourceParentId = window!!.idStack.last()
- sourceDragActive = isMouseDragging(mouseButton)
+ sourceParentId = window.idStack.last()
+ sourceDragActive = mouseButton.isDragging()
// Disable navigation and key inputs while dragging + cancel existing request if any
setActiveIdUsingAllKeyboardKeys()
@@ -228,7 +215,7 @@ interface dragAndDrop {
/** Accept contents of a given type. If DragDropFlag.AcceptBeforeDelivery is set you can peek into the payload
* before the mouse button is released. */
- fun acceptDragDropPayload(type: String, flags_: DrawListFlags = 0): Payload? {
+ fun acceptDragDropPayload(type: String, flags_: DragDropFlags = none): Payload? {
var flags = flags_
val window = g.currentWindow!!
val payload = g.dragDropPayload
@@ -236,40 +223,47 @@ interface dragAndDrop {
assert(payload.dataFrameCount != -1) { "Forgot to call EndDragDropTarget() ?" }
if (type.isNotEmpty() && !payload.isDataType(type)) return null
- /* Accept smallest drag target bounding box, this allows us to nest drag targets conveniently without ordering constraints.
- NB: We currently accept NULL id as target. However, overlapping targets requires a unique ID to function! */
+ // Accept smallest drag target bounding box, this allows us to nest drag targets conveniently without ordering constraints.
+ // NB: We currently accept NULL id as target. However, overlapping targets requires a unique ID to function!
val wasAcceptedPreviously = g.dragDropAcceptIdPrev == g.dragDropTargetId
val r = Rect(g.dragDropTargetRect)
val rSurface = r.width * r.height
- if (rSurface <= g.dragDropAcceptIdCurrRectSurface) {
- g.dragDropAcceptFlags = flags
- g.dragDropAcceptIdCurr = g.dragDropTargetId
- g.dragDropAcceptIdCurrRectSurface = rSurface
- }
+ if (rSurface > g.dragDropAcceptIdCurrRectSurface)
+ return null
+
+ g.dragDropAcceptFlags = flags
+ g.dragDropAcceptIdCurr = g.dragDropTargetId
+ g.dragDropAcceptIdCurrRectSurface = rSurface
+ //IMGUI_DEBUG_LOG("AcceptDragDropPayload(): %08X: accept\n", g.DragDropTargetId);
// Render default drop visuals
payload.preview = wasAcceptedPreviously
- flags = flags or (g.dragDropSourceFlags and DragDropFlag.AcceptNoDrawDefaultRect) // Source can also inhibit the preview (useful for external sources that live for 1 frame)
+ flags /= g.dragDropSourceFlags and Ddf.AcceptNoDrawDefaultRect // Source can also inhibit the preview (useful for external sources that live for 1 frame)
if (flags hasnt Ddf.AcceptNoDrawDefaultRect && payload.preview)
- window.drawList.addRect(r.min - 3.5f, r.max + 3.5f, Col.DragDropTarget.u32, 0f, 0, 2f)
+ window.drawList.addRect(r.min - 3.5f, r.max + 3.5f, Col.DragDropTarget.u32, thickness = 2f)
g.dragDropAcceptFrameCount = g.frameCount
- // For extern drag sources affecting os window focus, it's easier to just test !isMouseDown() instead of isMouseReleased()
- payload.delivery = wasAcceptedPreviously && !isMouseDown(g.dragDropMouseButton)
- if (!payload.delivery && flags hasnt Ddf.AcceptBeforeDelivery) return null
+ // For extern drag sources affecting OS window focus, it's easier to just test !isMouseDown() instead of isMouseReleased()
+ payload.delivery = wasAcceptedPreviously && !g.dragDropMouseButton.isDown
+ if (!payload.delivery && flags hasnt Ddf.AcceptBeforeDelivery)
+ return null
+ //IMGUI_DEBUG_LOG("AcceptDragDropPayload(): %08X: return payload\n", g.DragDropTargetId);
return payload
}
- /** We don't really use/need this now, but added it for the sake of consistency and because we might need it later.
- * Only call EndDragDropTarget() if BeginDragDropTarget() returns true! */
+ /** Only call EndDragDropTarget() if BeginDragDropTarget() returns true! */
fun endDragDropTarget() {
assert(g.dragDropActive)
assert(g.dragDropWithinTarget)
g.dragDropWithinTarget = false
+
+ // Clear drag and drop state payload right after delivery
+ if (g.dragDropPayload.delivery)
+ clearDragDrop()
}
/** ~GetDragDropPayload */
val dragDropPayload: Payload?
- get() = if(g.dragDropActive && g.dragDropPayload.dataFrameCount != -1) g.dragDropPayload else null
+ get() = if (g.dragDropActive && g.dragDropPayload.dataFrameCount != -1) g.dragDropPayload else null
}
\ No newline at end of file
diff --git a/core/src/main/kotlin/imgui/api/focusActivation.kt b/core/src/main/kotlin/imgui/api/focusActivation.kt
index 8fc4960d5..f570ffa50 100644
--- a/core/src/main/kotlin/imgui/api/focusActivation.kt
+++ b/core/src/main/kotlin/imgui/api/focusActivation.kt
@@ -1,17 +1,16 @@
package imgui.api
import imgui.Dir
-import imgui.ImGui.currentWindow
-import imgui.ImGui.isItemVisible
import imgui.ImGui.navMoveRequestResolveWithLastItem
import imgui.ImGui.navMoveRequestSubmit
-import imgui.ImGui.rectAbsToRel
import imgui.ImGui.scrollToRectEx
import imgui.ImGui.setNavWindow
-import imgui.internal.sections.IMGUI_DEBUG_LOG_ACTIVEID
+import imgui.div
+import imgui.internal.sections.IMGUI_DEBUG_LOG_FOCUS
import imgui.internal.sections.NavMoveFlag
import imgui.internal.sections.ScrollFlag
-import imgui.static.navUpdateAnyRequestFlag
+import imgui.statics.navApplyItemToResult
+import imgui.statics.navUpdateAnyRequestFlag
/** Focus, Activation
@@ -25,41 +24,42 @@ interface focusActivation {
val window = g.currentWindow!!
if (!window.appearing)
return
- if (g.navWindow !== window.rootWindowForNav || (!g.navInitRequest && g.navInitResultId == 0) || g.navLayer != window.dc.navLayerCurrent)
+ if (g.navWindow !== window.rootWindowForNav || (!g.navInitRequest && g.navInitResult.id == 0) || g.navLayer != window.dc.navLayerCurrent)
return
g.navInitRequest = false
- g.navInitResultId = g.lastItemData.id
- g.navInitResultRectRel = window rectAbsToRel g.lastItemData.rect
+ navApplyItemToResult(g.navInitResult)
navUpdateAnyRequestFlag()
// Scroll could be done in NavInitRequestApplyResult() via an opt-in flag (we however don't want regular init requests to scroll)
- if (!isItemVisible)
- scrollToRectEx(window, g.lastItemData.rect, ScrollFlag.None.i)
+ if (g.lastItemData.rect !in window.clipRect)
+ scrollToRectEx(window, g.lastItemData.rect)
}
/** focus keyboard on the next widget. Use positive 'offset' to access sub components of a multiple component widget.
* Use -1 to access previous widget.
*
- * Note: this will likely be called ActivateItem() once we rework our Focus/Activation system! */
- fun setKeyboardFocusHere(offset: Int = 0) = with(currentWindow) {
+ * Note: this will likely be called ActivateItem() once we rework our Focus/Activation system!
+ * But ActivateItem() should function without altering scroll/focus? */
+ fun setKeyboardFocusHere(offset: Int = 0) {
val window = g.currentWindow!!
assert(offset >= -1) { "-1 is allowed but not below" }
- IMGUI_DEBUG_LOG_ACTIVEID("SetKeyboardFocusHere($offset) in window \"${window.name}\"")
+ IMGUI_DEBUG_LOG_FOCUS("SetKeyboardFocusHere($offset) in window \"${window.name}\"")
// It makes sense in the vast majority of cases to never interrupt a drag and drop.
// When we refactor this function into ActivateItem() we may want to make this an option.
// MovingWindow is protected from most user inputs using SetActiveIdUsingNavAndKeys(), but
// is also automatically dropped in the event g.ActiveId is stolen.
if (g.dragDropActive || g.movingWindow != null) {
- IMGUI_DEBUG_LOG_ACTIVEID("SetKeyboardFocusHere() ignored while DragDropActive!")
+ IMGUI_DEBUG_LOG_FOCUS("SetKeyboardFocusHere() ignored while DragDropActive!")
return
}
setNavWindow(window)
- val scrollFlags = if (window.appearing) ScrollFlag.KeepVisibleEdgeX or ScrollFlag.AlwaysCenterY else ScrollFlag.KeepVisibleEdgeX or ScrollFlag.KeepVisibleEdgeY
- navMoveRequestSubmit(Dir.None, if (offset < 0) Dir.Up else Dir.Down, NavMoveFlag.Tabbing or NavMoveFlag.FocusApi, scrollFlags) // FIXME-NAV: Once we refactor tabbing, add LegacyApi flag to not activate non-inputable.
+ val moveFlags = NavMoveFlag.Tabbing / NavMoveFlag.Activate / NavMoveFlag.FocusApi
+ val scrollFlags = if (window.appearing) ScrollFlag.KeepVisibleEdgeX / ScrollFlag.AlwaysCenterY else ScrollFlag.KeepVisibleEdgeX / ScrollFlag.KeepVisibleEdgeY
+ navMoveRequestSubmit(Dir.None, if (offset < 0) Dir.Up else Dir.Down, moveFlags, scrollFlags) // FIXME-NAV: Once we refactor tabbing, add LegacyApi flag to not activate non-inputable.
if (offset == -1)
navMoveRequestResolveWithLastItem(g.navMoveResultLocal)
else {
diff --git a/core/src/main/kotlin/imgui/api/inputUtilitiesMouse.kt b/core/src/main/kotlin/imgui/api/inputUtilitiesMouse.kt
index 5e65f65ce..41d1edb7d 100644
--- a/core/src/main/kotlin/imgui/api/inputUtilitiesMouse.kt
+++ b/core/src/main/kotlin/imgui/api/inputUtilitiesMouse.kt
@@ -2,15 +2,12 @@ package imgui.api
import glm_.i
import glm_.vec2.Vec2
+import imgui.*
import imgui.ImGui.io
import imgui.ImGui.isClicked
-import imgui.ImGui.isMouseDragPastThreshold
+import imgui.ImGui.isDragPastThreshold
import imgui.ImGui.style
import imgui.ImGui.testOwner
-import imgui.ImGui.toKey
-import imgui.MOUSE_INVALID
-import imgui.MouseButton
-import imgui.MouseCursor
import imgui.internal.classes.InputFlag
import imgui.internal.classes.Rect
import imgui.internal.sections.KeyOwner_Any
@@ -21,33 +18,38 @@ import imgui.internal.sections.KeyOwner_Any
* - Dragging operations are only reported after mouse has moved a certain distance away from the initial clicking position (see 'lock_threshold' and 'io.MouseDraggingThreshold') */
interface inputUtilitiesMouse {
- /** is mouse button held? */
- fun isMouseDown(button: MouseButton): Boolean {
- assert(button.i in io.mouseDown.indices)
- return io.mouseDown[button.i] && button.toKey() testOwner KeyOwner_Any // should be same as IsKeyDown(MouseButtonToKey(button), ImGuiKeyOwner_Any), but this allows legacy code hijacking the io.Mousedown[] array.
- }
-
- /** did mouse button clicked? (went from !Down to Down). Same as GetMouseClickedCount() == 1. */
- fun isMouseClicked(button: MouseButton, repeat: Boolean = false): Boolean = button.isClicked(KeyOwner_Any, if (repeat) InputFlag.Repeat.i else InputFlag.None.i)
-
- fun isMouseReleased(button: Int): Boolean = isMouseReleased(MouseButton of button)
+ /** is mouse button held?
+ * ~isMouseDown */
+ val MouseButton.isDown: Boolean
+ get() {
+ assert(i in io.mouseDown.indices)
+ return io.mouseDown[i] && key testOwner KeyOwner_Any // should be same as IsKeyDown(MouseButtonToKey(button), ImGuiKeyOwner_Any), but this allows legacy code hijacking the io.Mousedown[] array.
+ }
- /** did mouse button released? (went from Down to !Down) */
- fun isMouseReleased(button: MouseButton): Boolean {
- if (button == MouseButton.None)
- return false // The None button is never clicked.
+ /** did mouse button clicked? (went from !Down to Down). Same as GetMouseClickedCount() == 1.
+ * ~isMouseClicked */
+ val MouseButton.isClicked
+ get() = isClicked(false)
- return io.mouseReleased[button.i] && button.toKey() testOwner KeyOwner_Any // Should be same as IsKeyReleased(MouseButtonToKey(button), ImGuiKeyOwner_Any)
- }
+ /** ~isMouseClicked */
+ infix fun MouseButton.isClicked(repeat: Boolean): Boolean = isClicked(KeyOwner_Any, if (repeat) InputFlag.Repeat else none)
+ /** did mouse button released? (went from Down to !Down)
+ * ~isMouseReleased */
+ val MouseButton.isReleased: Boolean
+ get() = when (this) {
+ MouseButton.None -> false // The None button is never clicked.
+ else -> io.mouseReleased[i] && key testOwner KeyOwner_Any // Should be same as IsKeyReleased(MouseButtonToKey(button), ImGuiKeyOwner_Any)
+ }
- /** did mouse button double-clicked? Same as GetMouseClickedCount() == 2. (note that a double-click will also report IsMouseClicked() == true) */
- fun isMouseDoubleClicked(button: MouseButton): Boolean {
- if (button == MouseButton.None)
- return false // The None button is never clicked.
- return io.mouseClickedCount[button.i] == 2 && button.toKey() testOwner KeyOwner_Any
- }
+ /** did mouse button double-clicked? Same as GetMouseClickedCount() == 2. (note that a double-click will also report IsMouseClicked() == true)
+ * ~isMouseDoubleClicked */
+ val MouseButton.isDoubleClicked: Boolean
+ get() = when (this) {
+ MouseButton.None -> false // The None button is never clicked.
+ else -> io.mouseClickedCount[i] == 2 && key testOwner KeyOwner_Any
+ }
/** return the number of successive mouse-clicks at the time where a click happen (otherwise 0). */
fun getMouseClickedCount(button: MouseButton): Int {
@@ -64,7 +66,7 @@ interface inputUtilitiesMouse {
*
* is mouse hovering given bounding rect (in screen space). clipped by current clipping settings, but disregarding of other consideration of focus/window ordering/popup-block. */
fun isMouseHoveringRect(r: Rect, clip: Boolean = true): Boolean =
- isMouseHoveringRect(r.min, r.max, clip)
+ isMouseHoveringRect(r.min, r.max, clip)
fun isMouseHoveringRect(rMin: Vec2, rMax: Vec2, clip: Boolean = true): Boolean {
@@ -92,11 +94,11 @@ interface inputUtilitiesMouse {
get() = Vec2(g.beginPopupStack.lastOrNull()?.openMousePos ?: io.mousePos)
/** is mouse dragging? (if lock_threshold < -1.0f, uses io.MouseDraggingThreshold) */
- fun isMouseDragging(button: MouseButton, lockThreshold: Float = -1f): Boolean {
- assert(button.i in io.mouseDown.indices)
- if (!io.mouseDown[button.i])
+ fun MouseButton.isDragging(lockThreshold: Float = -1f): Boolean {
+ assert(i in io.mouseDown.indices)
+ if (!io.mouseDown[i])
return false
- return isMouseDragPastThreshold(button, lockThreshold)
+ return isDragPastThreshold(lockThreshold)
}
/** return the delta from the initial clicking position while the mouse button is clicked or was just released.
diff --git a/core/src/main/kotlin/imgui/api/inputsUtilitiesKeyboardMouseGamepad.kt b/core/src/main/kotlin/imgui/api/inputsUtilitiesKeyboardMouseGamepad.kt
index ecad67852..a34e0128e 100644
--- a/core/src/main/kotlin/imgui/api/inputsUtilitiesKeyboardMouseGamepad.kt
+++ b/core/src/main/kotlin/imgui/api/inputsUtilitiesKeyboardMouseGamepad.kt
@@ -7,6 +7,7 @@ import imgui.ImGui.isDown
import imgui.ImGui.isPressed
import imgui.ImGui.isReleased
import imgui.Key
+import imgui.none
import imgui.internal.classes.InputFlag
import imgui.internal.sections.KeyOwner_Any
@@ -33,7 +34,7 @@ interface inputsUtilitiesKeyboardMouseGamepad {
* uses io.KeyRepeatDelay / KeyRepeatRate
*
* was key pressed (went from !Down to Down)? if repeat=true, uses io.KeyRepeatDelay / KeyRepeatRate */
- infix fun Key.isPressed(repeat: Boolean): Boolean = isPressed(KeyOwner_Any, if (repeat) InputFlag.Repeat.i else InputFlag.None.i)
+ infix fun Key.isPressed(repeat: Boolean): Boolean = isPressed(KeyOwner_Any, if (repeat) InputFlag.Repeat else none)
/** ~IsKeyPressed() */
val Key.isPressed: Boolean
diff --git a/core/src/main/kotlin/imgui/api/inputsUtilitiesShortcutRouting.kt b/core/src/main/kotlin/imgui/api/inputsUtilitiesShortcutRouting.kt
index ffdfb22b8..1d3faa998 100644
--- a/core/src/main/kotlin/imgui/api/inputsUtilitiesShortcutRouting.kt
+++ b/core/src/main/kotlin/imgui/api/inputsUtilitiesShortcutRouting.kt
@@ -6,8 +6,8 @@ import imgui.ImGui.convertShortcutMod
import imgui.ImGui.convertSingleModFlagToKey
import imgui.ImGui.getRoutingIdFromOwnerId
import imgui.ImGui.isPressed
-import imgui.internal.classes.*
-import imgui.internal.isPowerOfTwo
+import imgui.internal.classes.InputFlag
+import imgui.internal.classes.InputFlags
import imgui.internal.sections.KeyRoutingData
// Inputs Utilities: Shortcut testing (with Routing Resolution)
@@ -36,29 +36,29 @@ interface inputsUtilitiesShortcutRouting {
// - Route is granted to a single owner. When multiple requests are made we have policies to select the winning route.
// - Multiple read sites may use the same owner id and will all get the granted route.
// - For routing: when owner_id is 0 we use the current Focus Scope ID as a default owner in order to identify our location.
- fun shortcut(keyChord_: KeyChord, ownerId: ID = 0, flags_: InputFlags = 0): Boolean {
+ fun shortcut(keyChord_: KeyChord, ownerId: ID = 0, flags_: InputFlags = none): Boolean {
var flags = flags_
// When using (owner_id == 0/Any): SetShortcutRouting() will use CurrentFocusScopeId and filter with this, so IsKeyPressed() is fine with he 0/Any.
- if (flags hasnt InputFlag.RouteMask_)
+ if (flags hasnt InputFlag.RouteMask)
flags /= InputFlag.RouteFocused
if (!setShortcutRouting(keyChord_, ownerId, flags))
return false
val keyChord = if (keyChord_ has Key.Mod_Shortcut) convertShortcutMod(keyChord_) else keyChord_
// [JVM] don't attempt finding a `Key` with mods, it might not exist and crash, keep it as an `Int`
- val mods = keyChord and Key.Mod_Mask_
+ val mods = keyChord and Key.Mod_Mask
if (g.io.keyMods != mods)
return false
// Special storage location for mods
- var key = Key of (keyChord wo Key.Mod_Mask_)
+ var key = Key of (keyChord wo Key.Mod_Mask)
if (key == Key.None)
- key = (Key of mods).convertSingleModFlagToKey()
+ key = (Key of mods).convertSingleModFlagToKey(g)
- if (!key.isPressed(ownerId, flags and (InputFlag.Repeat or InputFlag.RepeatRateMask_)))
+ if (!key.isPressed(ownerId, flags and (InputFlag.Repeat or InputFlag.RepeatRateMask)))
return false
- assert((flags wo InputFlag.SupportedByShortcut) == 0) { "Passing flags not supported by this function !" }
+ assert((flags wo InputFlag.SupportedByShortcut).isEmpty) { "Passing flags not supported by this function !" }
return true
}
@@ -70,13 +70,13 @@ interface inputsUtilitiesShortcutRouting {
// As such, it could be called TrySetXXX or SubmitXXX, or the Submit and Test operations should be separate.)
// - Using 'owner_id == ImGuiKeyOwner_Any/0': auto-assign an owner based on current focus scope (each window has its focus scope by default)
// - Using 'owner_id == ImGuiKeyOwner_None': allows disabling/locking a shortcut.
- fun setShortcutRouting(keyChord: KeyChord, ownerId: ID, flags_: InputFlags): Boolean {
+ fun setShortcutRouting(keyChord: KeyChord, ownerId: ID = 0, flags_: InputFlags = none): Boolean {
var flags = flags_
- if (flags hasnt InputFlag.RouteMask_)
+ if (flags hasnt InputFlag.RouteMask)
flags /= InputFlag.RouteGlobalHigh // IMPORTANT: This is the default for SetShortcutRouting() but NOT Shortcut()
else
- assert((flags and InputFlag.RouteMask_).isPowerOfTwo) { "Check that only 1 routing flag is used" }
+ assert((flags and InputFlag.RouteMask).isPowerOfTwo) { "Check that only 1 routing flag is used" }
if (flags has InputFlag.RouteUnlessBgFocused)
if (g.navWindow == null)
@@ -119,11 +119,11 @@ interface inputsUtilitiesShortcutRouting {
// - Shortcut(ImGuiMod_Ctrl | ImGuiMod_Shift); // Not legal
val rt = g.keysRoutingTable
val keyChord = if (keyChord_ has Key.Mod_Shortcut) convertShortcutMod(keyChord_) else keyChord_
- var key = Key of (keyChord wo Key.Mod_Mask_)
+ var key = Key of (keyChord wo Key.Mod_Mask)
// [JVM] don't attempt finding a `Key` with mods, it might not exist and crash, keep it as an `Int`
- val mods = keyChord and Key.Mod_Mask_
+ val mods = keyChord and Key.Mod_Mask
if (key == Key.None)
- key = key.convertSingleModFlagToKey()
+ key = (Key of mods).convertSingleModFlagToKey(g)
// IM_ASSERT(IsNamedKey(key))
// Get (in the majority of case, the linked list will have one element so this should be 2 reads.
diff --git a/core/src/main/kotlin/imgui/api/itemWidgetsUtilities.kt b/core/src/main/kotlin/imgui/api/itemWidgetsUtilities.kt
index 5746b4e3b..8a8c41b9e 100644
--- a/core/src/main/kotlin/imgui/api/itemWidgetsUtilities.kt
+++ b/core/src/main/kotlin/imgui/api/itemWidgetsUtilities.kt
@@ -1,15 +1,11 @@
package imgui.api
-import glm_.hasnt
import glm_.vec2.Vec2
-import imgui.ID
-import imgui.ImGui.isMouseClicked
-import imgui.MouseButton
-import imgui.has
-import imgui.hasnt
+import imgui.*
+import imgui.HoveredFlag
+import imgui.ImGui.isClicked
import imgui.internal.sections.ItemFlag
import imgui.internal.sections.ItemStatusFlag
-import imgui.or
import imgui.HoveredFlag as Hf
// Item/Widgets Utilities and Query Functions
@@ -17,27 +13,34 @@ import imgui.HoveredFlag as Hf
// - See Demo Window under "Widgets->Querying Status" for an interactive visualization of most of those functions.
interface itemWidgetsUtilities {
- /** This is roughly matching the behavior of internal-facing ItemHoverable()
- * - we allow hovering to be true when activeId==window.moveID, so that clicking on non-interactive items
- * such as a text() item still returns true with isItemHovered()
- * - this should work even for non-interactive items that have no ID, so we cannot use LastItemId */
- fun isItemHovered(flags: Hf) = isItemHovered(flags.i)
-
- fun isItemHovered(flags: Int = Hf.None.i): Boolean {
+ // This is roughly matching the behavior of internal-facing ItemHoverable()
+ // - we allow hovering to be true when ActiveId==window->MoveID, so that clicking on non-interactive items such as a Text() item still returns true with IsItemHovered()
+ // - this should work even for non-interactive items that have no ID, so we cannot use LastItemId
+ fun isItemHovered(flags_: HoveredFlags = none): Boolean {
+ var flags = flags_
val window = g.currentWindow!!
+ assert((flags wo HoveredFlag.AllowedMaskForIsItemHovered) == none) { "Invalid flags for IsItemHovered()!" }
+
if (g.navDisableMouseHover && !g.navDisableHighlight && flags hasnt Hf.NoNavOverride) {
if (g.lastItemData.inFlags has ItemFlag.Disabled && flags hasnt Hf.AllowWhenDisabled)
return false
if (!isItemFocused)
return false
+
+ if (flags has HoveredFlag.ForTooltip)
+ flags /= g.style.hoverFlagsForTooltipNav
} else {
// Test for bounding box overlap, as updated as ItemAdd()
val statusFlags = g.lastItemData.statusFlags
if (statusFlags hasnt ItemStatusFlag.HoveredRect)
return false
- assert(flags hasnt (Hf.AnyWindow or Hf.RootWindow or Hf.ChildWindows or Hf.NoPopupHierarchy)) { "Flags not supported by this function" }
+
+ if (flags has HoveredFlag.ForTooltip)
+ flags /= g.style.hoverFlagsForTooltipMouse
+
+ assert(flags hasnt (HoveredFlag.AnyWindow / HoveredFlag.RootWindow / HoveredFlag.ChildWindows / HoveredFlag.NoPopupHierarchy)) { "Flags not supported by this function" }
// Done with rectangle culling so we can perform heavier checks now
// Test if we are hovering the right window (our window could be behind another window)
@@ -46,12 +49,13 @@ interface itemWidgetsUtilities {
// to use IsItemHovered() after EndChild() itself. Until a solution is found I believe reverting to the test from 2017/09/27 is safe since this was
// the test that has been running for a long while.
if (g.hoveredWindow !== window && statusFlags hasnt ItemStatusFlag.HoveredWindow)
- if (flags hasnt Hf.AllowWhenOverlapped)
+ if (flags hasnt Hf.AllowWhenOverlappedByWindow)
return false
// Test if another item is active (e.g. being dragged)
+ val id = g.lastItemData.id
if (flags hasnt Hf.AllowWhenBlockedByActiveItem)
- if (g.activeId != 0 && g.activeId != g.lastItemData.id && !g.activeIdAllowOverlap && g.activeId != window.moveId)
+ if (g.activeId != 0 && g.activeId != id && !g.activeIdAllowOverlap && g.activeId != window.moveId)
return false
// Test if interactions on this window are blocked by an active popup or modal.
@@ -64,29 +68,46 @@ interface itemWidgetsUtilities {
return false
// Special handling for calling after Begin() which represent the title bar or tab.
- // When the window is collapsed (SkipItems==true) that last item will never be overwritten so we need to detect the case.
- if (g.lastItemData.id == window.moveId && window.writeAccessed)
+ // When the window is skipped/collapsed (SkipItems==true) that last item will never be overwritten so we need to detect the case.
+ if (id == window.moveId && window.writeAccessed)
return false
+
+ // Test if using AllowOverlap and overlapped
+ if (g.lastItemData.inFlags has ItemFlag.AllowOverlap && id != 0)
+ if (flags hasnt HoveredFlag.AllowWhenOverlappedByItem)
+ if (g.hoveredIdPreviousFrame != g.lastItemData.id)
+ return false
}
// Handle hover delay
// (some ideas: https://www.nngroup.com/articles/timing-exposing-content)
val delay = when {
- flags has Hf.DelayNormal -> g.io.hoverDelayNormal
- flags has Hf.DelayShort -> g.io.hoverDelayShort
+ flags has Hf.DelayShort -> g.style.hoverDelayShort
+ flags has Hf.DelayNormal -> g.style.hoverDelayNormal
else -> 0f
}
- if (delay > 0f) {
+ if (delay > 0f || flags has HoveredFlag.Stationary) {
val hoverDelayId = if (g.lastItemData.id != 0) g.lastItemData.id else window.getIDFromRectangle(g.lastItemData.rect)
- if (flags has Hf.NoSharedDelay && g.hoverDelayIdPreviousFrame != hoverDelayId)
- g.hoverDelayTimer = 0f
- g.hoverDelayId = hoverDelayId
- return g.hoverDelayTimer >= delay
+ if (flags has Hf.NoSharedDelay && g.hoverItemDelayIdPreviousFrame != hoverDelayId)
+ g.hoverItemDelayTimer = 0f
+ g.hoverItemDelayId = hoverDelayId
+
+ // When changing hovered item we requires a bit of stationary delay before activating hover timer,
+ // but once unlocked on a given item we also moving.
+ //if (g.HoverDelayTimer >= delay && (g.HoverDelayTimer - g.IO.DeltaTime < delay || g.MouseStationaryTimer - g.IO.DeltaTime < g.Style.HoverStationaryDelay)) { IMGUI_DEBUG_LOG("HoverDelayTimer = %f/%f, MouseStationaryTimer = %f\n", g.HoverDelayTimer, delay, g.MouseStationaryTimer); }
+ if (flags has HoveredFlag.Stationary && g.hoverItemUnlockedStationaryId != hoverDelayId)
+ return false
+
+ if (g.hoverItemDelayTimer < delay)
+ return false
}
return true
}
+ val isItemHovered: Boolean
+ get() = isItemHovered()
+
/** Is the last item active? (e.g. button being held, text field being edited.
* This will continuously return true while holding mouse button on an item. Items that don't interact will always return false) */
val isItemActive: Boolean
@@ -102,8 +123,7 @@ interface itemWidgetsUtilities {
*
* Important: this can be useful but it is NOT equivalent to the behavior of e.g.Button()!
* Most widgets have specific reactions based on mouse-up/down state, mouse position etc. */
- fun isItemClicked(mouseButton: MouseButton = MouseButton.Left): Boolean =
- isMouseClicked(mouseButton) && isItemHovered(Hf.None)
+ fun isItemClicked(mouseButton: MouseButton = MouseButton.Left): Boolean = mouseButton.isClicked && isItemHovered()
/** Is the last item visible? (items may be out of sight because of clipping/scrolling) */
val isItemVisible: Boolean
@@ -162,14 +182,4 @@ interface itemWidgetsUtilities {
* ~GetItemRectSize */
val itemRectSize: Vec2
get() = g.lastItemData.rect.size
-
- /** Allow last item to be overlapped by a subsequent item. Both may be activated during the same frame before the later one takes priority.
- * FIXME: Although this is exposed, its interaction and ideal idiom with using ImGuiButtonFlags_AllowItemOverlap flag are extremely confusing, need rework. */
- fun setItemAllowOverlap() {
- val id = g.lastItemData.id
- if (g.hoveredId == id)
- g.hoveredIdAllowOverlap = true
- if (g.activeId == id)
- g.activeIdAllowOverlap = true
- }
}
\ No newline at end of file
diff --git a/core/src/main/kotlin/imgui/api/loggingCapture.kt b/core/src/main/kotlin/imgui/api/loggingCapture.kt
index 33b2ccd86..4e0a5f24f 100644
--- a/core/src/main/kotlin/imgui/api/loggingCapture.kt
+++ b/core/src/main/kotlin/imgui/api/loggingCapture.kt
@@ -3,13 +3,12 @@ package imgui.api
import imgui.ImGui.button
import imgui.ImGui.clipboardText
import imgui.ImGui.logBegin
-import imgui.ImGui.popAllowKeyboardFocus
+import imgui.ImGui.popTabStop
import imgui.ImGui.popID
-import imgui.ImGui.pushAllowKeyboardFocus
+import imgui.ImGui.pushTabStop
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
@@ -48,7 +47,8 @@ interface loggingCapture {
/** stop logging (close file, etc.) */
fun logFinish() {
- if (!g.logEnabled) return
+ if (!g.logEnabled)
+ return
logText("\n")
@@ -59,7 +59,7 @@ interface loggingCapture {
LogType.Clipboard -> {
if (g.logBuffer.length > 1) { // TODO 1? maybe 0?
clipboardText = g.logBuffer.toString()
- g.logBuffer = StringBuilder()
+ g.logBuffer.clear()
}
}
else -> error("Invalid log type")
@@ -78,10 +78,10 @@ interface loggingCapture {
val logToTty = button("Log To TTY"); sameLine()
val logToFile = button("Log To File"); sameLine()
val logToClipboard = button("Log To Clipboard"); sameLine()
- pushAllowKeyboardFocus(false)
+ pushTabStop(false)
setNextItemWidth(80f)
- sliderInt("Default Depth", g::logDepthToExpandDefault, 0, 9)
- popAllowKeyboardFocus()
+ slider("Default Depth", g::logDepthToExpandDefault, 0, 9)
+ popTabStop()
popID()
// Start logging at the end of the function so that the buttons don't appear in the log
@@ -92,25 +92,14 @@ interface loggingCapture {
/** pass text data straight to log (without being displayed) */
fun logTextV(g: Context, fmt: String, vararg args: Any) {
- if (g.logFile != null) {
- val writer = FileWriter(g.logFile!!, true)
- writer.write(String.format(fmt, *args))
- } else
- g.logBuffer.append(fmt.format(*args))
+ val text = if (args.isEmpty()) fmt else fmt.format(*args)
+ g.logFile?.appendText(text) ?: g.logBuffer.append(text)
}
fun logText(fmt: String, vararg args: Any) {
if (!g.logEnabled)
return
- logTextV(g, fmt, args)
- }
-
- fun logTextV(fmt: String, vararg args: Any) {
-
- if (!g.logEnabled)
- return
-
- logTextV(g, fmt, args)
+ logTextV(g, fmt, *args)
}
}
\ No newline at end of file
diff --git a/core/src/main/kotlin/imgui/api/main.kt b/core/src/main/kotlin/imgui/api/main.kt
index 127410f3f..91c8431f1 100644
--- a/core/src/main/kotlin/imgui/api/main.kt
+++ b/core/src/main/kotlin/imgui/api/main.kt
@@ -1,7 +1,7 @@
package imgui.api
import glm_.f
-import glm_.hasnt
+import glm_.i
import glm_.max
import glm_.min
import glm_.vec2.Vec2
@@ -10,12 +10,13 @@ import imgui.ImGui.begin
import imgui.ImGui.callHooks
import imgui.ImGui.clearActiveID
import imgui.ImGui.clearDragDrop
+import imgui.ImGui.debugLog
import imgui.ImGui.defaultFont
import imgui.ImGui.end
import imgui.ImGui.focusTopMostWindowUnderOne
import imgui.ImGui.gcCompactTransientBuffers
import imgui.ImGui.gcCompactTransientMiscBuffers
-import imgui.ImGui.isMouseDown
+import imgui.ImGui.isDown
import imgui.ImGui.keepAliveID
import imgui.ImGui.mainViewport
import imgui.ImGui.renderMouseCursor
@@ -32,17 +33,19 @@ import imgui.classes.ContextHookType
import imgui.classes.IO
import imgui.classes.Style
import imgui.font.FontAtlas
-import imgui.internal.*
+import imgui.internal.DrawData
+import imgui.internal.classes.DebugLogFlag
+import imgui.internal.classes.FocusRequestFlag
import imgui.internal.classes.Rect
+import imgui.internal.sections.DrawListFlags
import imgui.internal.sections.IMGUI_DEBUG_LOG_ACTIVEID
-import imgui.internal.sections.ItemFlag
+import imgui.internal.sections.IMGUI_DEBUG_LOG_IO
import imgui.static.*
+import imgui.statics.*
import org.lwjgl.system.Platform
import imgui.WindowFlag as Wf
import imgui.internal.sections.DrawListFlag as Dlf
-@Suppress("UNCHECKED_CAST")
-
/** Main */
interface main {
@@ -77,7 +80,8 @@ interface main {
g.time += io.deltaTime
g.withinFrameScope = true
g.frameCount += 1
- println(" [%04d]".format(ImGui.frameCount))
+ if (g.debugLogFlags != none)
+ println(" [%04d]".format(ImGui.frameCount))
g.tooltipOverrideCount = 0
g.windowsActiveCount = 0
g.menusIdSubmittedThisFrame.clear()
@@ -89,6 +93,11 @@ interface main {
g.framerateSecPerFrameCount = (g.framerateSecPerFrameCount + 1) min g.framerateSecPerFrame.size
io.framerate = if (g.framerateSecPerFrameAccum > 0f) 1f / (g.framerateSecPerFrameAccum / g.framerateSecPerFrameCount.f) else Float.MAX_VALUE
+ // Process input queue (trickle as many events as possible), turn events into writes to IO structure
+ g.inputEventsTrail.clear()
+ updateInputEvents(g.io.configInputTrickleEventQueue)
+
+ // Update viewports (after processing input queue, so io.MouseHoveredViewport is set)
updateViewportsNewFrame()
// Setup current font and draw list shared data
@@ -101,15 +110,15 @@ interface main {
g.drawListSharedData.clipRectFullscreen = virtualSpace.toVec4()
g.drawListSharedData.curveTessellationTol = style.curveTessellationTol
g.drawListSharedData.setCircleTessellationMaxError_(style.circleTessellationMaxError)
- var flags = Dlf.None.i
+ var flags: DrawListFlags = none
if (style.antiAliasedLines)
- flags = flags or Dlf.AntiAliasedLines
- if (style.antiAliasedLinesUseTex && g.font.containerAtlas.flags hasnt FontAtlas.Flag.NoBakedLines.i)
- flags = flags or Dlf.AntiAliasedLinesUseTex
+ flags /= Dlf.AntiAliasedLines
+ if (style.antiAliasedLinesUseTex && g.font.containerAtlas.flags hasnt FontAtlas.Flag.NoBakedLines)
+ flags /= Dlf.AntiAliasedLinesUseTex
if (style.antiAliasedFill)
- flags = flags or Dlf.AntiAliasedFill
+ flags /= Dlf.AntiAliasedFill
if (io.backendFlags has BackendFlag.RendererHasVtxOffset)
- flags = flags or Dlf.AllowVtxOffset
+ flags /= Dlf.AllowVtxOffset
g.drawListSharedData.initialFlags = flags
// Mark rendering data as invalid to prevent user who may have a handle on it to use it.
@@ -160,18 +169,26 @@ interface main {
g.activeIdUsingAllKeyboardKeys = false
}
+ // Record when we have been stationary as this state is preserved while over same item.
+ // FIXME: The way this is expressed means user cannot alter HoverStationaryDelay during the frame to use varying values.
+ // To allow this we should store HoverItemMaxStationaryTime+ID and perform the >= check in IsItemHovered() function.
+ if (g.hoverItemDelayId != 0 && g.mouseStationaryTimer >= g.style.hoverStationaryDelay)
+ g.hoverItemUnlockedStationaryId = g.hoverItemDelayId
+ else if (g.hoverItemDelayId == 0)
+ g.hoverItemUnlockedStationaryId = 0
+
// Update hover delay for IsItemHovered() with delays and tooltips
- g.hoverDelayIdPreviousFrame = g.hoverDelayId
- if (g.hoverDelayId != 0) {
- //if (g.IO.MouseDelta.x == 0.0f && g.IO.MouseDelta.y == 0.0f) // Need design/flags
- g.hoverDelayTimer += g.io.deltaTime
- g.hoverDelayClearTimer = 0f
- g.hoverDelayId = 0
- } else if (g.hoverDelayTimer > 0f) {
+ g.hoverItemDelayIdPreviousFrame = g.hoverItemDelayId
+ if (g.hoverItemDelayId != 0) {
+ g.hoverItemDelayTimer += g.io.deltaTime
+ g.hoverItemDelayClearTimer = 0f
+ g.hoverItemDelayId = 0
+ } else if (g.hoverItemDelayTimer > 0f) {
// This gives a little bit of leeway before clearing the hover timer, allowing mouse to cross gaps
- g.hoverDelayClearTimer += g.io.deltaTime
- if (g.hoverDelayClearTimer >= 0.2f max (g.io.deltaTime * 2f)) { // ~6 frames at 30 Hz + allow for low framerate
- g.hoverDelayTimer = 0f; g.hoverDelayClearTimer = 0f // May want a decaying timer, in which case need to clamp at max first, based on max of caller last requested timer.
+ // We could expose 0.25f as style.HoverClearDelay but I am not sure of the logic yet, this is particularly subtle.
+ g.hoverItemDelayClearTimer += g.io.deltaTime
+ if (g.hoverItemDelayClearTimer >= 0.25f max (g.io.deltaTime * 2f)) { // ~7 frames at 30 Hz + allow for low framerate
+ g.hoverItemDelayTimer = 0f; g.hoverItemDelayClearTimer = 0f // May want a decaying timer, in which case need to clamp at max first, based on max of caller last requested timer.
}
}
@@ -187,10 +204,6 @@ interface main {
//if (g.IO.AppFocusLost)
// ClosePopupsExceptModals();
- // Process input queue (trickle as many events as possible)
- g.inputEventsTrail.clear()
- updateInputEvents(g.io.configInputTrickleEventQueue)
-
// Update keyboard input state
updateKeyboardInputs()
@@ -223,7 +236,7 @@ interface main {
g.wantCaptureMouseNextFrame = -1
// Platform IME data: reset for the frame
- g.platformImeData = PlatformImeData(g.platformImeData) // OS Input Method Editor showing on top-left of our window by default
+ g.platformImeDataPrev = PlatformImeData(g.platformImeData) // OS Input Method Editor showing on top-left of our window by default
g.platformImeData.wantVisible = false
// Mouse wheel scrolling, scale
@@ -254,21 +267,26 @@ interface main {
// Closing the focused window restore focus to the first active root window in descending z-order
if (g.navWindow?.wasActive == false)
- focusTopMostWindowUnderOne()
+ focusTopMostWindowUnderOne(flags = FocusRequestFlag.RestoreFocusedChild) // [JVM] all default args are fine
// No window should be open at the beginning of the frame.
// But in order to allow the user to call NewFrame() multiple times without calling Render(), we are doing an explicit clear.
g.currentWindowStack.clear()
g.beginPopupStack.clear()
g.itemFlagsStack.clear()
- g.itemFlagsStack += ItemFlag.None.i
+ g.itemFlagsStack += none
g.groupStack.clear()
- // // [DEBUG] Update debug features
+ // [DEBUG] Update debug features
updateDebugToolItemPicker()
updateDebugToolStackQueries()
if (g.debugLocateFrames > 0 && --g.debugLocateFrames == 0)
g.debugLocateId = 0
+ if (g.debugLogClipperAutoDisableFrames > 0 && --g.debugLogClipperAutoDisableFrames == 0) {
+ debugLog("(Auto-disabled ImGuiDebugLogFlags_EventClipper to avoid spamming)")
+ g.debugLogFlags -= DebugLogFlag.EventClipper
+ }
+
// Create implicit/fallback window - which we will only render it if the user has added something to it.
// We don't use "Debug" to avoid colliding with user trying to create a "Debug" window with custom flags.
@@ -297,9 +315,11 @@ interface main {
errorCheckEndFrameSanityChecks()
// Notify Platform/OS when our Input Method Editor cursor has moved (e.g. CJK inputs using Microsoft IME)
- if (io.setPlatformImeDataFn != null && g.platformImeData != g.platformImeDataPrev) {
+ val imeData = g.platformImeData
+ if (io.setPlatformImeDataFn != null && imeData != g.platformImeDataPrev) {
// if (DEBUG)
// println("in (${g.platformImePos.x}, ${g.platformImePos.y}) (${g.platformImeLastPos.x}, ${g.platformImeLastPos.y})")
+ IMGUI_DEBUG_LOG_IO("Calling io.SetPlatformImeDataFn(): WantVisible: ${imeData.wantVisible.i}, InputPos (%.2f,%.2f)", imeData.inputPos.x, imeData.inputPos.y)
g.io.setPlatformImeDataFn!!(mainViewport, g.platformImeData)
}
@@ -318,7 +338,7 @@ interface main {
if (g.dragDropActive) {
val isDelivered = g.dragDropPayload.delivery
val isElapsed = g.dragDropPayload.dataFrameCount + 1 < g.frameCount &&
- (g.dragDropSourceFlags has DragDropFlag.SourceAutoExpirePayload || !isMouseDown(g.dragDropMouseButton))
+ (g.dragDropSourceFlags has DragDropFlag.SourceAutoExpirePayload || !g.dragDropMouseButton.isDown)
if (isDelivered || isElapsed)
clearDragDrop()
}
@@ -342,7 +362,7 @@ interface main {
g.windowsTempSortBuffer.clear()
g.windowsTempSortBuffer.ensureCapacity(g.windows.size)
g.windows.filter { !it.active || it.flags hasnt Wf._ChildWindow } // if a child is active its parent will add it
- .forEach { it addToSortBuffer g.windowsTempSortBuffer }
+ .forEach { it addToSortBuffer g.windowsTempSortBuffer }
assert(g.windows.size == g.windowsTempSortBuffer.size) { "This usually assert if there is a mismatch between the ImGuiWindowFlags_ChildWindow / ParentWindow values and DC.ChildWindows[] in parents, aka we've done something wrong." }
g.windows.clear()
g.windows += g.windowsTempSortBuffer
@@ -388,16 +408,15 @@ interface main {
renderDimmedBackgrounds()
// Add ImDrawList to render
- val windowsToRenderTopMost = arrayOf(
- g.navWindowingTarget?.rootWindow?.takeIf { it.flags has Wf.NoBringToFrontOnFocus },
- g.navWindowingTarget?.let { g.navWindowingListWindow })
+ val windowsToRenderTopMost = arrayOf(g.navWindowingTarget?.rootWindow?.takeIf { it.flags has Wf.NoBringToFrontOnFocus },
+ g.navWindowingTarget?.let { g.navWindowingListWindow })
g.windows
- .filter { it.isActiveAndVisible && it.flags hasnt Wf._ChildWindow && it !== windowsToRenderTopMost[0] && it !== windowsToRenderTopMost[1] }
- .forEach { it.addRootToDrawData() }
+ .filter { it.isActiveAndVisible && it.flags hasnt Wf._ChildWindow && it !== windowsToRenderTopMost[0] && it !== windowsToRenderTopMost[1] }
+ .forEach { it.addRootToDrawData() }
windowsToRenderTopMost
- .filterNotNull()
- .filter { it.isActiveAndVisible } // NavWindowingTarget is always temporarily displayed as the top-most window
- .forEach { it.addRootToDrawData() }
+ .filterNotNull()
+ .filter { it.isActiveAndVisible } // NavWindowingTarget is always temporarily displayed as the top-most window
+ .forEach { it.addRootToDrawData() }
// Draw software mouse cursor if requested by io.MouseDrawCursor flag
if (g.io.mouseDrawCursor && firstRenderOfFrame && g.mouseCursor != MouseCursor.None)
diff --git a/core/src/main/kotlin/imgui/api/miscellaneousUtilities.kt b/core/src/main/kotlin/imgui/api/miscellaneousUtilities.kt
index 814a1f70d..94c9c3df7 100644
--- a/core/src/main/kotlin/imgui/api/miscellaneousUtilities.kt
+++ b/core/src/main/kotlin/imgui/api/miscellaneousUtilities.kt
@@ -53,7 +53,7 @@ interface miscellaneousUtilities {
/** helper to create a child window / scrolling region that looks like a normal widget frame */
- fun beginChildFrame(id: ID, size: Vec2, extraFlags: WindowFlags = 0): Boolean {
+ fun beginChildFrame(id: ID, size: Vec2, extraFlags: WindowFlags = none): Boolean {
pushStyleColor(Col.ChildBg, style.colors[Col.FrameBg])
pushStyleVar(StyleVar.ChildRounding, style.frameRounding)
pushStyleVar(StyleVar.ChildBorderSize, style.frameBorderSize)
diff --git a/core/src/main/kotlin/imgui/api/overlappingMode.kt b/core/src/main/kotlin/imgui/api/overlappingMode.kt
new file mode 100644
index 000000000..f27231f4b
--- /dev/null
+++ b/core/src/main/kotlin/imgui/api/overlappingMode.kt
@@ -0,0 +1,16 @@
+package imgui.api
+
+import imgui.div
+import imgui.internal.sections.ItemFlag
+
+// Overlapping mode
+interface overlappingMode {
+
+ // Allow next item to be overlapped by subsequent items.
+ // This works by requiring HoveredId to match for two subsequent frames,
+ // so if a following items overwrite it our interactions will naturally be disabled.
+ fun setNextItemAllowOverlap() {
+ val g = gImGui
+ g.nextItemData.itemFlags /= ItemFlag.AllowOverlap
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/imgui/api/parametersStacks.kt b/core/src/main/kotlin/imgui/api/parametersStacks.kt
index 4b6141ad3..09be96d24 100644
--- a/core/src/main/kotlin/imgui/api/parametersStacks.kt
+++ b/core/src/main/kotlin/imgui/api/parametersStacks.kt
@@ -10,6 +10,7 @@ import imgui.ImGui.currentWindow
import imgui.ImGui.defaultFont
import imgui.ImGui.popItemFlag
import imgui.ImGui.pushItemFlag
+import imgui.ImGui.setCurrentFont
import imgui.ImGui.style
import imgui.font.Font
import imgui.internal.classes.ColorMod
@@ -23,7 +24,7 @@ interface parametersStacks {
/** use NULL as a shortcut to push default font */
fun pushFont(font: Font = defaultFont) {
- font.setCurrent()
+ setCurrentFont(font)
g.fontStack.push(font)
g.currentWindow!!.drawList.pushTextureID(font.containerAtlas.texID)
}
@@ -31,7 +32,7 @@ interface parametersStacks {
fun popFont() {
g.currentWindow!!.drawList.popTextureID()
g.fontStack.pop()
- (g.fontStack.lastOrNull() ?: defaultFont).setCurrent()
+ setCurrentFont(g.fontStack.lastOrNull() ?: defaultFont)
}
/** FIXME: This may incur a round-trip (if the end user got their data from a float4) but eventually we aim to store
@@ -74,102 +75,141 @@ interface parametersStacks {
it.floats[0] = style.alpha
style.alpha = value as Float
}
+
StyleVar.DisabledAlpha -> {
it.floats[0] = style.disabledAlpha
style.disabledAlpha = value as Float
}
+
StyleVar.WindowPadding -> {
style.windowPadding to it.floats
style.windowPadding put value as Vec2
}
+
StyleVar.WindowRounding -> {
it.floats[0] = style.windowRounding
style.windowRounding = value as Float
}
+
StyleVar.WindowBorderSize -> {
it.floats[0] = style.windowBorderSize
style.windowBorderSize = value as Float
}
+
StyleVar.WindowMinSize -> {
style.windowMinSize to it.floats
style.windowMinSize put value as Vec2
}
+
StyleVar.WindowTitleAlign -> {
style.windowTitleAlign to it.floats
style.windowTitleAlign put value as Vec2
}
+
StyleVar.ChildRounding -> {
it.floats[0] = style.childRounding
style.childRounding = value as Float
}
+
StyleVar.ChildBorderSize -> {
it.floats[0] = style.childBorderSize
style.childBorderSize = value as Float
}
+
StyleVar.PopupRounding -> {
it.floats[0] = style.popupRounding
style.popupRounding = value as Float
}
+
StyleVar.PopupBorderSize -> {
it.floats[0] = style.popupBorderSize
style.popupBorderSize = value as Float
}
+
StyleVar.FramePadding -> {
style.framePadding to it.floats
style.framePadding put value as Vec2
}
+
StyleVar.FrameRounding -> {
it.floats[0] = style.frameRounding
style.frameRounding = value as Float
}
+
StyleVar.FrameBorderSize -> {
it.floats[0] = style.frameBorderSize
style.frameBorderSize = value as Float
}
+
StyleVar.ItemSpacing -> {
style.itemSpacing to it.floats
style.itemSpacing put value as Vec2
}
+
StyleVar.ItemInnerSpacing -> {
style.itemInnerSpacing to it.floats
style.itemInnerSpacing put value as Vec2
}
+
StyleVar.IndentSpacing -> {
it.floats[0] = style.indentSpacing
style.indentSpacing = value as Float
}
+
StyleVar.CellPadding -> {
style.cellPadding to it.floats
style.cellPadding put value as Vec2
}
+
StyleVar.ScrollbarSize -> {
it.floats[0] = style.scrollbarSize
style.scrollbarSize = value as Float
}
+
StyleVar.ScrollbarRounding -> {
it.floats[0] = style.scrollbarRounding
style.scrollbarRounding = value as Float
}
+
StyleVar.GrabMinSize -> {
it.floats[0] = style.grabMinSize
style.grabMinSize = value as Float
}
+
StyleVar.GrabRounding -> {
it.floats[0] = style.grabRounding
style.grabRounding = value as Float
}
+
StyleVar.TabRounding -> {
it.floats[0] = style.tabRounding
style.tabRounding = value as Float
}
+
StyleVar.ButtonTextAlign -> {
style.buttonTextAlign to it.floats
style.buttonTextAlign put value as Vec2
}
+
StyleVar.SelectableTextAlign -> {
style.selectableTextAlign to it.floats
style.selectableTextAlign put value as Vec2
}
+
+ StyleVar.SeparatorTextBorderSize -> {
+ it.floats[0] = style.separatorTextBorderSize
+ style.separatorTextBorderSize = value as Float
+ }
+
+ StyleVar.SeparatorTextAlign -> {
+ style.separatorTextAlign to it.floats
+ style.separatorTextAlign put value as Vec2
+ }
+
+ StyleVar.SeparatorTextPadding -> {
+ style.separatorTextPadding to it.floats
+ style.separatorTextPadding put value as Vec2
+ }
}
})
}
@@ -177,7 +217,7 @@ interface parametersStacks {
fun popStyleVar(count_: Int = 1) {
var count = count_
if (g.styleVarStack.size < count) {
- System.err.println( "Calling PopStyleVar() too many times: stack underflow.")
+ System.err.println("Calling PopStyleVar() too many times: stack underflow.")
count = g.styleVarStack.size
}
repeat(count) {
@@ -208,21 +248,22 @@ interface parametersStacks {
StyleVar.TabRounding -> style.tabRounding = backup.floats[0]
StyleVar.ButtonTextAlign -> style.buttonTextAlign put backup.floats
StyleVar.SelectableTextAlign -> style.selectableTextAlign put backup.floats
+ StyleVar.SeparatorTextBorderSize -> style.separatorTextBorderSize = backup.floats[0]
+ StyleVar.SeparatorTextAlign -> style.separatorTextAlign put backup.floats
+ StyleVar.SeparatorTextPadding -> style.separatorTextPadding put backup.floats
}
}
}
- /** FIXME: Look into renaming this once we have settled the new Focus/Activation/TabStop system.
- *
- * == tab stop enable. Allow focusing using TAB/Shift-TAB, enabled by default but you can disable it for certain widgets */
- fun pushAllowKeyboardFocus(allowKeyboardFocus: Boolean) = pushItemFlag(If.NoTabStop.i, !allowKeyboardFocus)
+ /** == tab stop enable. Allow focusing using TAB/Shift-TAB, enabled by default but you can disable it for certain widgets */
+ fun pushTabStop(tabStop: Boolean) = pushItemFlag(If.NoTabStop, !tabStop)
- fun popAllowKeyboardFocus() = popItemFlag()
+ fun popTabStop() = popItemFlag()
/** in 'repeat' mode, Button*() functions return repeated true in a typematic manner
* (using io.KeyRepeatDelay/io.KeyRepeatRate setting). Note that you can call IsItemActive() after any Button() to
* tell if the button is held in the current frame. */
- fun pushButtonRepeat(repeat: Boolean) = pushItemFlag(If.ButtonRepeat.i, repeat)
+ fun pushButtonRepeat(repeat: Boolean) = pushItemFlag(If.ButtonRepeat, repeat)
fun popButtonRepeat() = popItemFlag()
diff --git a/core/src/main/kotlin/imgui/api/popupsModals.kt b/core/src/main/kotlin/imgui/api/popupsModals.kt
index cf05b33cb..b86e0a933 100644
--- a/core/src/main/kotlin/imgui/api/popupsModals.kt
+++ b/core/src/main/kotlin/imgui/api/popupsModals.kt
@@ -8,8 +8,8 @@ import imgui.ImGui.closePopupToLevel
import imgui.ImGui.end
import imgui.ImGui.isAnyItemHovered
import imgui.ImGui.isItemHovered
-import imgui.ImGui.isMouseReleased
import imgui.ImGui.isPopupOpen
+import imgui.ImGui.isReleased
import imgui.ImGui.isWindowHovered
import imgui.ImGui.mainViewport
import imgui.ImGui.navMoveRequestTryWrapping
@@ -38,7 +38,7 @@ import imgui.WindowFlag as Wf
interface popupsModals {
/** return true if the popup is open, and you can start outputting to it. */
- fun beginPopup(strId: String, flags_: WindowFlags = Wf.None.i): Boolean {
+ fun beginPopup(strId: String, flags_: WindowFlags = none): Boolean {
if (g.openPopupStack.size <= g.beginPopupStack.size) { // Early out for performance
g.nextWindowData.clearFlags() // We behave like Begin() and need to consume those values
return false
@@ -52,7 +52,7 @@ interface popupsModals {
*
* If 'p_open' is specified for a modal popup window, the popup will have a regular close button which will close the popup.
* Note that popup visibility status is owned by Dear ImGui (and manipulated with e.g. OpenPopup) so the actual value of *p_open is meaningless here. */
- fun beginPopupModal(name: String, pOpen: KMutableProperty0? = null, flags_: WindowFlags = 0): Boolean {
+ fun beginPopupModal(name: String, pOpen: KMutableProperty0? = null, flags_: WindowFlags = none): Boolean {
val window = g.currentWindow!!
val id = window.getID(name)
@@ -89,7 +89,7 @@ interface popupsModals {
// Make all menus and popups wrap around for now, may need to expose that policy (e.g. focus scope could include wrap/loop policy flags used by new move requests)
if (g.navWindow === window)
- navMoveRequestTryWrapping(window, NavMoveFlag.LoopY.i)
+ navMoveRequestTryWrapping(window, NavMoveFlag.LoopY)
// Child-popups don't need to be laid out
assert(!g.withinEndChild)
@@ -110,29 +110,29 @@ interface popupsModals {
// - IMPORTANT: Notice that for OpenPopupOnItemClick() we exceptionally default flags to 1 (== ImGuiPopupFlags_MouseButtonRight) for backward compatibility with older API taking 'int mouse_button = 1' parameter
/** call to mark popup as open (don't call every frame!). */
- fun openPopup(strId: String, popupFlags: PopupFlags = PopupFlag.None.i) {
+ fun openPopup(strId: String, popupFlags: PopupFlags = none) {
val id = g.currentWindow!!.getID(strId)
IMGUI_DEBUG_LOG_POPUP("[popup] OpenPopup(\"$strId\" -> 0x%08X)", id)
openPopupEx(id, popupFlags)
}
/** id overload to facilitate calling from nested stacks */
- fun openPopup(id: ID, popupFlags: PopupFlags = 0) = openPopupEx(id, popupFlags)
+ fun openPopup(id: ID, popupFlags: PopupFlags = none) = openPopupEx(id, popupFlags)
/** helper to open popup when clicked on last item. Default to ImGuiPopupFlags_MouseButtonRight == 1.
* (note: actually triggers on the mouse _released_ event to be consistent with popup behaviors)
*
* Helper to open a popup if mouse button is released over the item
* - This is essentially the same as BeginPopupContextItem() but without the trailing BeginPopup() */
- fun openPopupOnItemClick(strId: String = "", popupFlags: PopupFlags = PopupFlag.MouseButtonRight.i) =
- with(g.currentWindow!!) {
- val mouseButton = popupFlags and PopupFlag.MouseButtonMask_
- if (isMouseReleased(mouseButton) && isItemHovered(Hf.AllowWhenBlockedByPopup)) {
- val id = if (strId.isNotEmpty()) getID(strId) else g.lastItemData.id // If user hasn't passed an ID, we can use the LastItemID. Using LastItemID as a Popup ID won't conflict!
- assert(id != 0) { "You cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item)" }
- openPopupEx(id, popupFlags)
- }
+ fun openPopupOnItemClick(strId: String = "", popupFlags: PopupFlags = PopupFlag.MouseButtonRight) =
+ with(g.currentWindow!!) {
+ val mouseButton = popupFlags.mouseButton
+ if (mouseButton.isReleased && isItemHovered(Hf.AllowWhenBlockedByPopup)) {
+ val id = if (strId.isNotEmpty()) getID(strId) else g.lastItemData.id // If user hasn't passed an ID, we can use the LastItemID. Using LastItemID as a Popup ID won't conflict!
+ assert(id != 0) { "You cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item)" }
+ openPopupEx(id, popupFlags)
}
+ }
/** cmanually close the popup we have begin-ed into. */
fun closeCurrentPopup() {
@@ -191,14 +191,14 @@ interface popupsModals {
* The main difference being that this is tweaked to avoid computing the ID twice.
*
* open+begin popup when clicked on last item. Use str_id==NULL to associate the popup to previous item. If you want to use that on a non-interactive item such as Text() you need to pass in an explicit ID here. read comments in .cpp! */
- fun beginPopupContextItem(strId: String = "", popupFlags: PopupFlags = PopupFlag.MouseButtonRight.i): Boolean {
+ fun beginPopupContextItem(strId: String = "", popupFlags: PopupFlags = PopupFlag.MouseButtonRight): Boolean {
val window = g.currentWindow!!
if (window.skipItems)
return false
val id = if (strId.isNotEmpty()) window.getID(strId) else g.lastItemData.id // If user hasn't passed an id, we can use the lastItemID. Using lastItemID as a Popup id won't conflict!
assert(id != 0) { "You cannot pass a NULL str_id if the last item has no identifier (e.g. a text() item)" }
- val mouseButton = popupFlags and PopupFlag.MouseButtonMask_
- if (isMouseReleased(mouseButton) && isItemHovered(Hf.AllowWhenBlockedByPopup))
+ val mouseButton = popupFlags.mouseButton
+ if (mouseButton.isReleased && isItemHovered(Hf.AllowWhenBlockedByPopup))
openPopupEx(id, popupFlags)
return beginPopupEx(id, Wf.AlwaysAutoResize or Wf.NoTitleBar or Wf.NoSavedSettings)
}
@@ -206,11 +206,11 @@ interface popupsModals {
/** Helper to open and begin popup when clicked on current window.
*
* open+begin popup when clicked on current window.*/
- fun beginPopupContextWindow(strId: String = "", popupFlags: PopupFlags = PopupFlag.MouseButtonRight.i): Boolean {
+ fun beginPopupContextWindow(strId: String = "", popupFlags: PopupFlags = PopupFlag.MouseButtonRight): Boolean {
val window = g.currentWindow!!
val id = window.getID(if (strId.isEmpty()) "window_context" else strId)
- val mouseButton = popupFlags and PopupFlag.MouseButtonMask_
- if (isMouseReleased(mouseButton) && isWindowHovered(Hf.AllowWhenBlockedByPopup))
+ val mouseButton = popupFlags.mouseButton
+ if (mouseButton.isReleased && isWindowHovered(Hf.AllowWhenBlockedByPopup))
if (popupFlags hasnt PopupFlag.NoOpenOverItems || !isAnyItemHovered)
openPopupEx(id, popupFlags)
return beginPopupEx(id, Wf.AlwaysAutoResize or Wf.NoTitleBar or Wf.NoSavedSettings)
@@ -219,11 +219,11 @@ interface popupsModals {
/** helper to open and begin popup when clicked in void (where there are no imgui windows).
*
* open+begin popup when clicked in void (where there are no windows). */
- fun beginPopupContextVoid(strId: String = "", popupFlags: PopupFlags = PopupFlag.MouseButtonRight.i): Boolean {
+ fun beginPopupContextVoid(strId: String = "", popupFlags: PopupFlags = PopupFlag.MouseButtonRight): Boolean {
val window = g.currentWindow!!
val id = window.getID(if (strId.isEmpty()) "window_context" else strId)
- val mouseButton = popupFlags and PopupFlag.MouseButtonMask_
- if (isMouseReleased(mouseButton) && !isWindowHovered(Hf.AnyWindow))
+ val mouseButton = popupFlags.mouseButton
+ if (mouseButton.isReleased && !isWindowHovered(Hf.AnyWindow))
if (topMostPopupModal == null)
openPopupEx(id, popupFlags)
return beginPopupEx(id, Wf.AlwaysAutoResize or Wf.NoTitleBar or Wf.NoSavedSettings)
@@ -237,7 +237,7 @@ interface popupsModals {
/** return true if the popup is open. */
- fun isPopupOpen(strId: String, popupFlags: PopupFlags = PopupFlag.None.i): Boolean {
+ fun isPopupOpen(strId: String, popupFlags: PopupFlags = none): Boolean {
val id = if (popupFlags has PopupFlag.AnyPopupId) 0 else g.currentWindow!!.getID(strId)
if (popupFlags has PopupFlag.AnyPopupLevel && id != 0)
assert(false) { "Cannot use IsPopupOpen() with a string id and ImGuiPopupFlags_AnyPopupLevel." } // But non-string version is legal and used internally
diff --git a/core/src/main/kotlin/imgui/api/settingsIniUtilities.kt b/core/src/main/kotlin/imgui/api/settingsIniUtilities.kt
index a19270d7b..2ff2353ca 100644
--- a/core/src/main/kotlin/imgui/api/settingsIniUtilities.kt
+++ b/core/src/main/kotlin/imgui/api/settingsIniUtilities.kt
@@ -23,7 +23,8 @@ interface settingsIniUtilities {
/** call after CreateContext() and before the first call to NewFrame() to provide .ini data from your own data source.
*
- * Zero-tolerance, no error reporting, cheap .ini parsing */
+ * Zero-tolerance, no error reporting, cheap .ini parsing
+ * Set ini_size==0 to let us use strlen(ini_data). Do not call this function with a 0 if your buffer is actually empty! */
fun loadIniSettingsFromMemory(lines: List) {
assert(g.initialized)
@@ -52,8 +53,6 @@ interface settingsIniUtilities {
val name = line.substring(firstCloseBracket + 2, line.lastIndex)
entryHandler = findSettingsHandler(type)
entryData = entryHandler?.readOpenFn?.invoke(g, entryHandler, name)
-// val typeHash = hash(type)
-// settings = findWindowSettings(typeHash) ?: createNewWindowSettings(name)
}
} else if (entryHandler != null && entryData != null)
// Let type handler parse the line
diff --git a/core/src/main/kotlin/imgui/api/tabBarsTabs.kt b/core/src/main/kotlin/imgui/api/tabBarsTabs.kt
index 4c2622ba5..e764f8052 100644
--- a/core/src/main/kotlin/imgui/api/tabBarsTabs.kt
+++ b/core/src/main/kotlin/imgui/api/tabBarsTabs.kt
@@ -20,7 +20,7 @@ import kotlin.reflect.KMutableProperty0
interface tabBarsTabs {
/** create and append into a TabBar */
- fun beginTabBar(strId: String, flags: TabBarFlags = 0): Boolean {
+ fun beginTabBar(strId: String, flags: TabBarFlags = none): Boolean {
val window = g.currentWindow!!
if (window.skipItems) return false
@@ -54,6 +54,7 @@ interface tabBarsTabs {
if (tabBar.beginCount > 1)
window.dc.cursorPos put tabBar.backupCursorPos
+ tabBar.lastTabItemIdx = -1
if (tabBar.flags hasnt TabBarFlag._DockNode)
popID()
@@ -62,18 +63,13 @@ interface tabBarsTabs {
}
/** create a Tab. Returns true if the Tab is selected. */
- fun beginTabItem(label: String, pOpen: BooleanArray, index: Int, flags: TabItemFlags = 0): Boolean =
- beginTabItem(label, pOpen mutablePropertyAt index, flags)
-
- /** create a Tab. Returns true if the Tab is selected. */
- fun beginTabItem(label: String, pOpen: KMutableProperty0? = null, flags: TabItemFlags = 0): Boolean {
+ fun beginTabItem(label: String, pOpen: KMutableProperty0? = null, flags: TabItemFlags = none): Boolean {
val window = g.currentWindow!!
if (window.skipItems)
return false
val tabBar = g.currentTabBar ?: error("Needs to be called between BeginTabBar() and EndTabBar()!")
- assert(flags hasnt TabItemFlag._Button) { "BeginTabItem() Can't be used with button flags, use TabItemButton() instead!" }
val ret = tabBar.tabItemEx(label, pOpen, flags, null)
if (ret && flags hasnt TabItemFlag.NoPushId) {
@@ -97,7 +93,7 @@ interface tabBarsTabs {
}
/** create a Tab behaving like a button. return true when clicked. cannot be selected in the tab bar. */
- fun tabItemButton(label: String, flags: TabItemFlags = TabItemFlag.None.i): Boolean {
+ fun tabItemButton(label: String, flags: TabItemOnlyFlags = none): Boolean {
val window = g.currentWindow!!
if (window.skipItems)
diff --git a/core/src/main/kotlin/imgui/api/tables.kt b/core/src/main/kotlin/imgui/api/tables.kt
index 1ba7d5e3e..cb521acb9 100644
--- a/core/src/main/kotlin/imgui/api/tables.kt
+++ b/core/src/main/kotlin/imgui/api/tables.kt
@@ -1,7 +1,5 @@
package imgui.api
-import glm_.has
-import glm_.hasnt
import glm_.max
import glm_.min
import glm_.vec2.Vec2
@@ -22,12 +20,13 @@ import imgui.ImGui.getColumnName
import imgui.ImGui.getColumnWidthAuto
import imgui.ImGui.getID
import imgui.ImGui.getInstanceData
+import imgui.ImGui.isDragging
import imgui.ImGui.isItemHovered
-import imgui.ImGui.isMouseDragging
-import imgui.ImGui.isMouseReleased
+import imgui.ImGui.isReleased
import imgui.ImGui.itemAdd
import imgui.ImGui.itemSize
import imgui.ImGui.mergeDrawChannels
+import imgui.ImGui.navUpdateCurrentWindowIsScrollPushableX
import imgui.ImGui.popID
import imgui.ImGui.popStyleColor
import imgui.ImGui.pushID
@@ -37,9 +36,8 @@ import imgui.ImGui.renderNavHighlight
import imgui.ImGui.renderText
import imgui.ImGui.renderTextEllipsis
import imgui.ImGui.saveSettings
-import imgui.ImGui.setItemAllowOverlap
+import imgui.ImGui.setItemTooltip
import imgui.ImGui.setScrollFromPosX
-import imgui.ImGui.setTooltip
import imgui.ImGui.sortSpecsBuild
import imgui.ImGui.tableGetColumnNextSortDirection
import imgui.ImGui.tableGetHeaderRowHeight
@@ -47,6 +45,8 @@ import imgui.ImGui.tableGetHoveredColumn
import imgui.ImGui.tableOpenContextMenu
import imgui.ImGui.tableSetColumnSortDirection
import imgui.ImGui.updateLayout
+import imgui.TableColumnFlag
+import imgui.TableFlag
import imgui.classes.TableSortSpecs
import imgui.internal.classes.*
import imgui.internal.floor
@@ -85,8 +85,7 @@ import imgui.TableRowFlag as Trf
interface tables {
/** Read about "TABLE SIZING" at the top of this file. */
- fun beginTable(strId: String, columns: Int, flags: TableFlags = Tf.None.i,
- outerSize: Vec2 = Vec2(), innerWidth: Float = 0f): Boolean {
+ fun beginTable(strId: String, columns: Int, flags: TableFlags = none, outerSize: Vec2 = Vec2(), innerWidth: Float = 0f): Boolean {
val id = getID(strId)
return beginTableEx(strId, id, columns, flags, outerSize, innerWidth)
}
@@ -118,7 +117,7 @@ interface tables {
// Context menu in columns body
if (flags has Tf.ContextMenuInBody)
- if (table.hoveredColumnBody != -1 && !ImGui.isAnyItemHovered && ImGui.isMouseReleased(MouseButton.Right))
+ if (table.hoveredColumnBody != -1 && !ImGui.isAnyItemHovered && MouseButton.Right.isReleased)
tableOpenContextMenu(table.hoveredColumnBody)
// Finalize table height
@@ -186,18 +185,18 @@ interface tables {
var autoFitWidthForStretched = 0f
var autoFitWidthForStretchedMin = 0f
for (columnN in 0 until table.columnsCount)
- if (table.enabledMaskByIndex has (1L shl columnN)) {
+ if (table.enabledMaskByIndex testBit columnN) {
val column = table.columns[columnN]
val columnWidthRequest = if (column.flags has Tcf.WidthFixed && column.flags hasnt Tcf.NoResize) column.widthRequest else table getColumnWidthAuto column
if (column.flags has Tcf.WidthFixed)
autoFitWidthForFixed += columnWidthRequest
else
autoFitWidthForStretched += columnWidthRequest
- if (column.flags has Tcf.WidthStretch && column.flags hasnt Tcf.NoResize)
+ if (column.flags has Tcf.WidthStretch && column.flags has Tcf.NoResize)
autoFitWidthForStretchedMin = autoFitWidthForStretchedMin max (columnWidthRequest / (column.stretchWeight / table.columnsStretchSumWeights))
}
val widthSpacings = table.outerPaddingX * 2f + (table.cellSpacingX1 + table.cellSpacingX2) * (table.columnsEnabledCount - 1)
- table.columnsAutoFitWidth = widthSpacings + (table.cellPaddingX * 2f) * table.columnsEnabledCount + autoFitWidthForFixed + autoFitWidthForStretched max autoFitWidthForStretchedMin
+ table.columnsAutoFitWidth = widthSpacings + (table.cellPaddingX * 2f) * table.columnsEnabledCount + autoFitWidthForFixed + (autoFitWidthForStretched max autoFitWidthForStretchedMin)
// Update scroll
if (table.flags hasnt Tf.ScrollX && innerWindow !== outerWindow)
@@ -221,8 +220,10 @@ interface tables {
}
// Pop from id stack
- assert(innerWindow.idStack.last() == table.id + table.instanceCurrent) { "Mismatching PushID/PopID!" }
+ assert(innerWindow.idStack.last() == tableInstance.tableInstanceID) { "Mismatching PushID/PopID!" }
assert(outerWindow.dc.itemWidthStack.size >= tempData.hostBackupItemWidthStackSize) { "Too many PopItemWidth!" }
+ if (table.instanceCurrent > 0)
+ popID()
popID()
// Restore window data that we modified
@@ -284,16 +285,16 @@ interface tables {
g.currentTable = tempData?.let { g.tables.getByIndex(it.tableIndex) }
g.currentTable?.let {
it.tempData = tempData
- it.drawSplitter.clearFreeMemory()
it.drawSplitter = tempData!!.drawSplitter
}
outerWindow.dc.currentTableIdx = g.currentTable?.let { g.tables.getIndex(it).i } ?: -1
+ navUpdateCurrentWindowIsScrollPushableX()
}
/** [Public] Starts into the first cell of a new row
*
* append into the first cell of a new row. */
- fun tableNextRow(rowFlags: TableRowFlags = Trf.None.i, rowMinHeight: Float = 0f) {
+ fun tableNextRow(rowFlags: TableRowFlags = none, rowMinHeight: Float = 0f) {
val table = g.currentTable!!
@@ -334,8 +335,7 @@ interface tables {
// Return whether the column is visible. User may choose to skip submitting items based on this return value,
// however they shouldn't skip submitting for columns that may have the tallest contribution to row height.
- val columnN = table.currentColumn
- return table.requestOutputMaskByIndex has (1L shl columnN)
+ return table.columns[table.currentColumn].isRequestOutput
}
/** [Public] Append into a specific column
@@ -354,7 +354,7 @@ interface tables {
// Return whether the column is visible. User may choose to skip submitting items based on this return value,
// however they shouldn't skip submitting for columns that may have the tallest contribution to row height.
- return table.requestOutputMaskByIndex has (1L shl columnN)
+ return table.columns[columnN].isRequestOutput
}
// Tables: Headers & Columns declaration
@@ -368,13 +368,14 @@ interface tables {
/** See "COLUMN SIZING POLICIES" comments at the top of this file
* If (init_width_or_weight <= 0.0f) it is ignored */
- fun tableSetupColumn(label: String?, flags_: TableColumnFlags = Tcf.None.i, initWidthOrWeight: Float = 0f, userId: ID = 0) {
-
+ fun tableSetupColumn(label: String?,
+ flags_: TableColumnFlags = none,
+ initWidthOrWeight: Float = 0f,
+ userId: ID = 0) {
var flags = flags_
val table = g.currentTable
check(table != null) { "Need to call TableSetupColumn() after BeginTable()!" }
assert(!table.isLayoutLocked) { "Need to call call TableSetupColumn() before first row!" }
- assert(flags hasnt Tcf.StatusMask_) { "Illegal to pass StatusMask values to TableSetupColumn()" }
if (table.declColumnsCount >= table.columnsCount) {
assert(table.declColumnsCount < table.columnsCount) { "Called TableSetupColumn() too many times!" }
return
@@ -385,16 +386,14 @@ interface tables {
// Assert when passing a width or weight if policy is entirely left to default, to avoid storing width into weight and vice-versa.
// Give a grace to users of ImGuiTableFlags_ScrollX.
- if (table.isDefaultSizingPolicy && flags hasnt Tcf.WidthMask_ && flags hasnt Tf.ScrollX)
+ if (table.isDefaultSizingPolicy && flags hasnt Tcf.WidthMask && table.flags hasnt Tf.ScrollX)
assert(initWidthOrWeight <= 0f) { "Can only specify width/weight if sizing policy is set explicitly in either Table or Column." }
// When passing a width automatically enforce WidthFixed policy
// (whereas TableSetupColumnFlags would default to WidthAuto if table is not Resizable)
- if (flags hasnt Tcf.WidthMask_ && initWidthOrWeight > 0f)
- (table.flags and Tf._SizingMask).let {
- if (it == Tf.SizingFixedFit.i || it == Tf.SizingFixedSame.i)
- flags = flags or Tcf.WidthFixed
- }
+ if (flags hasnt TableColumnFlag.WidthMask && initWidthOrWeight > 0f)
+ if ((table.flags and TableFlag._SizingMask) == TableFlag.SizingFixedFit || (table.flags and TableFlag._SizingMask) == TableFlag.SizingFixedSame)
+ flags /= TableColumnFlag.WidthFixed
table.setupColumnFlags(column, flags)
column.userID = userId
@@ -430,7 +429,7 @@ interface tables {
// Store name (append with zero-terminator in contiguous buffer)
column.nameOffset = -1
- if (label != null && label.isNotEmpty()) {
+ if (!label.isNullOrEmpty()) {
column.nameOffset = table.columnsNames.size
table.columnsNames += label
}
@@ -461,7 +460,8 @@ interface tables {
table.apply {
// ImSwap(table->Columns[table->DisplayOrderToIndex[order_n]].DisplayOrder, table->Columns[table->DisplayOrderToIndex[column_n]].DisplayOrder)
var order = this.columns[displayOrderToIndex[orderN]].displayOrder
- this.columns[displayOrderToIndex[orderN]].displayOrder = this.columns[displayOrderToIndex[columnN]].displayOrder
+ this.columns[displayOrderToIndex[orderN]].displayOrder =
+ this.columns[displayOrderToIndex[columnN]].displayOrder
this.columns[displayOrderToIndex[columnN]].displayOrder = order
// ImSwap(table->DisplayOrderToIndex[order_n], table->DisplayOrderToIndex[column_n])
order = displayOrderToIndex[orderN]
@@ -491,7 +491,7 @@ interface tables {
// Open row
val rowY1 = ImGui.cursorScreenPos.y
val rowHeight = tableGetHeaderRowHeight()
- tableNextRow(Trf.Headers.i, rowHeight)
+ tableNextRow(Trf.Headers, rowHeight)
if (table.hostSkipItems) // Merely an optimization, you may skip in your own code.
return
@@ -502,17 +502,16 @@ interface tables {
continue
// Push an id to allow unnamed labels (generally accidental, but let's behave nicely with them)
- // - in your own code you may omit the PushID/PopID all-together, provided you know they won't collide
- // - table->InstanceCurrent is only >0 when we use multiple BeginTable/EndTable calls with same identifier.
+ // In your own code you may omit the PushID/PopID all-together, provided you know they won't collide.
val name = if (tableGetColumnFlags(columnN) has Tcf.NoHeaderLabel) "" else tableGetColumnName(columnN)!!
- pushID(table.instanceCurrent * table.columnsCount + columnN)
+ pushID(columnN)
tableHeader(name)
popID()
}
// Allow opening popup from the right-most section after the last column.
val mousePos = ImGui.mousePos
- if (isMouseReleased(1) && tableGetHoveredColumn() == columnsCount)
+ if (MouseButton.Right.isReleased && tableGetHoveredColumn() == columnsCount)
if (mousePos.y >= rowY1 && mousePos.y < rowY1 + rowHeight)
tableOpenContextMenu(-1) // Will open a non-column-specific popup.
}
@@ -575,10 +574,8 @@ interface tables {
//GetForegroundDrawList()->AddRect(cell_r.Min, cell_r.Max, IM_COL32(255, 0, 0, 255)); // [DEBUG]
//GetForegroundDrawList()->AddRect(bb.Min, bb.Max, IM_COL32(255, 0, 0, 255)); // [DEBUG]
- // Using AllowItemOverlap mode because we cover the whole cell, and we want user to be able to submit subsequent items.
- val (pressed, hovered, held) = buttonBehavior(bb, id, ButtonFlag.AllowItemOverlap.i)
- if (g.activeId != id)
- setItemAllowOverlap()
+ // Using AllowOverlap mode because we cover the whole cell, and we want user to be able to submit subsequent items.
+ val (pressed, hovered, held) = buttonBehavior(bb, id, ButtonFlag.AllowOverlap)
if (held || hovered || selected) {
val col = if (held) Col.HeaderActive else if (hovered) Col.HeaderHovered else Col.Header
//RenderFrame(bb.Min, bb.Max, col, false, 0.0f);
@@ -593,7 +590,7 @@ interface tables {
// Drag and drop to re-order columns.
// FIXME-TABLE: Scroll request while reordering a column and it lands out of the scrolling zone.
- if (held && table.flags has Tf.Reorderable && isMouseDragging(MouseButton.Left) && !g.dragDropActive) {
+ if (held && table.flags has Tf.Reorderable && MouseButton.Left.isDragging() && !g.dragDropActive) {
// While moving a column it will jump on the other side of the mouse, so we also test for MouseDelta.x
table.reorderColumn = columnN
table.instanceInteracted = table.instanceCurrent
@@ -601,20 +598,20 @@ interface tables {
// We don't reorder: through the frozen<>unfrozen line, or through a column that is marked with ImGuiTableColumnFlags_NoReorder.
if (g.io.mouseDelta.x < 0.0f && g.io.mousePos.x < cellR.min.x)
table.columns.getOrNull(column.prevEnabledColumn)?.let { prevColumn ->
- if (((column.flags or prevColumn.flags) and Tcf.NoReorder) == 0)
+ if (((column.flags or prevColumn.flags) and Tcf.NoReorder).isEmpty)
if (column.indexWithinEnabledSet < table.freezeColumnsRequest == prevColumn.indexWithinEnabledSet < table.freezeColumnsRequest)
table.reorderColumnDir = -1
}
if (g.io.mouseDelta.x > 0f && g.io.mousePos.x > cellR.max.x)
table.columns.getOrNull(column.nextEnabledColumn)?.let { nextColumn ->
- if (((column.flags or nextColumn.flags) and Tcf.NoReorder) == 0)
+ if (((column.flags or nextColumn.flags) and Tcf.NoReorder).isEmpty)
if (column.indexWithinEnabledSet < table.freezeColumnsRequest == nextColumn.indexWithinEnabledSet < table.freezeColumnsRequest)
table.reorderColumnDir = +1
}
}
// Sort order arrow
- val ellipsisMax = cellR.max.x - wArrow - wSortText
+ val ellipsisMax = (cellR.max.x - wArrow - wSortText) max labelPos.x
if (table.flags has Tf.Sortable && column.flags hasnt Tcf.NoSort) {
if (column.sortOrder != -1) {
var x = cellR.min.x max (cellR.max.x - wArrow - wSortText)
@@ -640,13 +637,12 @@ interface tables {
//window->DrawList->AddCircleFilled(ImVec2(ellipsis_max, label_pos.y), 40, IM_COL32_WHITE);
val posMax = Vec2(ellipsisMax, labelPos.y + labelHeight + g.style.framePadding.y)
renderTextEllipsis(window.drawList, labelPos, posMax, ellipsisMax, ellipsisMax, label.toByteArray(), labelEnd, labelSize)
-
val textClipped = labelSize.x > (ellipsisMax - labelPos.x)
- if (textClipped && hovered && g.activeId == 0 && isItemHovered(HoveredFlag.DelayNormal))
- setTooltip(label.substring(0, labelEnd))
+ if (textClipped && hovered && g.activeId == 0)
+ setItemTooltip(label.substring(0, labelEnd))
// We don't use BeginPopupContextItem() because we want the popup to stay up even after the column is hidden
- if (isMouseReleased(1) && isItemHovered())
+ if (MouseButton.Right.isReleased && isItemHovered())
tableOpenContextMenu(columnN)
}
@@ -676,7 +672,6 @@ interface tables {
table.updateLayout()
table.sortSpecsBuild()
-
return table.sortSpecs
}
@@ -705,10 +700,10 @@ interface tables {
*
* return column flags so you can query their Enabled/Visible/Sorted/Hovered status flags. Pass -1 to use current column. */
fun tableGetColumnFlags(columnN_: Int = -1): TableColumnFlags {
- val table = g.currentTable ?: return Tcf.None.i
+ val table = g.currentTable ?: return none
val columnN = if (columnN_ < 0) table.currentColumn else columnN_
return when (columnN) {
- table.columnsCount -> if (table.hoveredColumnBody == columnN) Tcf.IsHovered.i else Tcf.None.i
+ table.columnsCount -> if (table.hoveredColumnBody == columnN) Tcf.IsHovered else none
else -> table.columns[columnN].flags
}
}
@@ -739,7 +734,6 @@ interface tables {
fun tableSetBgColor(target: TableBgTarget, color_: Int, columnN_: Int = -1) {
val table = g.currentTable!!
- assert(target != TableBgTarget.None)
val color = if (color_ == COL32_DISABLE) 0 else color_
var columnN = columnN_
@@ -751,7 +745,7 @@ interface tables {
return
if (columnN == -1)
columnN = table.currentColumn
- if (table.visibleMaskByIndex hasnt (1L shl columnN))
+ if (!table.visibleMaskByIndex.testBit(columnN))
return
if (table.rowCellDataCurrent < 0 || table.rowCellData[table.rowCellDataCurrent].column != columnN)
table.rowCellDataCurrent++
@@ -759,6 +753,7 @@ interface tables {
cellData.bgColor = color
cellData.column = columnN
}
+
TableBgTarget.RowBg0, TableBgTarget.RowBg1 -> {
if (table.rowPosY1 > table.innerClipRect.max.y) // Discard
return
@@ -766,6 +761,7 @@ interface tables {
val bgIdx = if (target == TableBgTarget.RowBg1) 1 else 0
table.rowBgColor[bgIdx] = color
}
+
else -> assert(false)
}
}
diff --git a/core/src/main/kotlin/imgui/api/tooltips.kt b/core/src/main/kotlin/imgui/api/tooltips.kt
index 0affd1095..fa7dfd61a 100644
--- a/core/src/main/kotlin/imgui/api/tooltips.kt
+++ b/core/src/main/kotlin/imgui/api/tooltips.kt
@@ -1,29 +1,57 @@
package imgui.api
+import imgui.HoveredFlag
import imgui.ImGui.beginTooltipEx
import imgui.ImGui.currentWindowRead
import imgui.ImGui.end
+import imgui.ImGui.isItemHovered
import imgui.ImGui.text
import imgui.has
import imgui.internal.sections.TooltipFlag
+import imgui.none
import imgui.WindowFlag as Wf
-/** Tooltips
- * - Tooltip are windows following the mouse. They do not take focus away. */
+// Tooltips
+// - Tooltips are windows following the mouse. They do not take focus away.
+// - A tooltip window can contain items of any types. SetTooltip() is a shortcut for the 'if (BeginTooltip()) { Text(...); EndTooltip(); }' idiom.
interface tooltips {
- /** begin/append a tooltip window. to create full-featured tooltip (with any kind of items). */
- fun beginTooltip() = beginTooltipEx(TooltipFlag.None.i, Wf.None.i)
+ /** begin/append a tooltip window. */
+ fun beginTooltip(): Boolean = beginTooltipEx()
+ /** only call EndTooltip() if BeginTooltip()/BeginItemTooltip() returns true! */
fun endTooltip() {
assert(currentWindowRead!!.flags has Wf._Tooltip) { "Mismatched BeginTooltip()/EndTooltip() calls" }
end()
}
- /** set a text-only tooltip, typically use with ImGui::IsItemHovered(). override any previous call to SetTooltip(). */
+ /** set a text-only tooltip. Often used after a ImGui::IsItemHovered() check. Override any previous call to SetTooltip(). */
fun setTooltip(fmt: String, vararg args: Any) {
- beginTooltipEx(TooltipFlag.OverridePreviousTooltip.i, Wf.None.i)
+ if (!beginTooltipEx(TooltipFlag.OverridePrevious))
+ return
text(fmt, *args)
endTooltip()
}
+
+ // Tooltips: helpers for showing a tooltip when hovering an item
+ // - BeginItemTooltip() is a shortcut for the 'if (IsItemHovered(ImGuiHoveredFlags_Tooltip) && BeginTooltip())' idiom.
+ // - SetItemTooltip() is a shortcut for the 'if (IsItemHovered(ImGuiHoveredFlags_Tooltip)) { SetTooltip(...); }' idiom.
+ // - Where 'ImGuiHoveredFlags_Tooltip' itself is a shortcut to use 'style.HoverFlagsForTooltipMouse' or 'style.HoverFlagsForTooltipNav' depending on active input type. For mouse it defaults to 'ImGuiHoveredFlags_Stationary | ImGuiHoveredFlags_DelayShort'.
+
+ /** begin/append a tooltip window if preceding item was hovered. */
+ fun beginItemTooltip(): Boolean {
+ if (!isItemHovered(HoveredFlag.ForTooltip))
+ return false
+ return beginTooltipEx(none, none)
+ }
+
+ /** Shortcut to use 'style.HoverFlagsForTooltipMouse' or 'style.HoverFlagsForTooltipNav'.
+ * Defaults to == ImGuiHoveredFlags_Stationary | ImGuiHoveredFlags_DelayShort when using the mouse.
+ *
+ * set a text-only tooltip if preceeding item was hovered. override any previous call to SetTooltip().
+ */
+ fun setItemTooltip(fmt: String, vararg args: String) {
+ if (isItemHovered(HoveredFlag.ForTooltip))
+ setTooltip(fmt, *args)
+ }
}
\ No newline at end of file
diff --git a/core/src/main/kotlin/imgui/api/widgetsColorEditorPicker.kt b/core/src/main/kotlin/imgui/api/widgetsColorEditorPicker.kt
index d96002125..c616d5e61 100644
--- a/core/src/main/kotlin/imgui/api/widgetsColorEditorPicker.kt
+++ b/core/src/main/kotlin/imgui/api/widgetsColorEditorPicker.kt
@@ -1,12 +1,12 @@
package imgui.api
-import gli_.has
import glm_.func.cos
import glm_.func.sin
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.*
@@ -20,13 +20,12 @@ 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.endDragDropSource
import imgui.ImGui.endDragDropTarget
import imgui.ImGui.endGroup
@@ -40,6 +39,7 @@ import imgui.ImGui.inputText
import imgui.ImGui.invisibleButton
import imgui.ImGui.io
import imgui.ImGui.isItemActive
+import imgui.ImGui.isItemHovered
import imgui.ImGui.itemAdd
import imgui.ImGui.itemSize
import imgui.ImGui.markItemEdited
@@ -57,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
@@ -65,8 +66,13 @@ import imgui.ImGui.spacing
import imgui.ImGui.style
import imgui.ImGui.text
import imgui.ImGui.textEx
+import imgui.api.widgetsColorEditorPicker.Companion.colorEditRestoreH
+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.has
import imgui.internal.*
import imgui.internal.classes.Rect
import imgui.internal.classes.Window
@@ -83,769 +89,737 @@ 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 = 0): Boolean =
- colorEdit4(label, col to _fa, flags or Cef.NoAlpha)
- .also { col put _fa }
+ fun colorEdit3(label: String, col: Vec3, flags: ColorEditFlags = none): Boolean = colorEdit4(label, col.x, col.y, col.z, 0f, flags or Cef.NoAlpha, col::put)
- fun colorEdit3(label: String, col: FloatArray, flags: ColorEditFlags = 0): 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 = none): 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 = 0): Boolean =
- colorEdit4(label, col to _fa, flags).also { col put _fa }
- fun colorEdit4(label: String, col: FloatArray, flags_: ColorEditFlags = 0): 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
- 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)
+ 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 i = IntArray(4) { F32_TO_INT8_UNBOUND(f[it]) }
+ fun colorEdit4(label: String, col: Vec3, flags: ColorEditFlags = none): Boolean = colorEdit4(label, col.x, col.y, col.z, 1f, flags, col::put)
- var valueChanged = false
- var valueChangedAsFloat = false
+ fun colorEdit4(label: String, col: Vec4, flags: ColorEditFlags = none): Boolean = colorEdit4(label, col.x, col.y, col.z, col.w, flags, col::put)
- val pos = Vec2(window.dc.cursorPos)
- val inputsOffsetX = if (style.colorButtonPosition == Dir.Left) wButton else 0f
- window.dc.cursorPos.x = pos.x + inputsOffsetX
+ fun colorPicker3(label: String, col: Vec3, flags: ColorEditFlags = none): Boolean = colorPicker3(label, col.x, col.y, col.z, flags, col::put)
- if (flags has (Cef.DisplayRGB or Cef.DisplayHSV) && flags hasnt Cef.NoInputs) {
+ fun colorPicker3(label: String, col: Vec4, flags: ColorEditFlags = none): Boolean = colorPicker3(label, col.x, col.y, col.z, flags, col::put)
- // 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))
+ /** 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 = none, refCol: Vec4?): Boolean =
+ colorPicker4(label, col.x, col.y, col.z, 1f, flags, refCol, col::put)
- 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
+ /** 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 = none, refCol: Vec4? = null): Boolean =
+ colorPicker4(label, col.x, col.y, col.z, col.w, flags, refCol, col::put)
- repeat(components) { n ->
- if (n > 0)
- sameLine(0f, style.itemInnerSpacing.x)
- setNextItemWidth(if (n + 1 < components) wItemOne else wItemLast)
+ fun colorButton(descId: String, col: Vec3, flags: ColorEditFlags = none, sizeArg: Vec2 = Vec2()): Boolean = colorButton(descId, col.x, col.y, col.z, 1f, flags, sizeArg)
- // 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.i)
- }
+ fun colorButton(descId: String, col: Vec4, flags: ColorEditFlags = none, sizeArg: Vec2 = Vec2()): Boolean = colorButton(descId, col.x, col.y, col.z, col.w, flags, sizeArg)
- } 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.i)
- }
+ /** 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 /= Cef.DefaultOptions and Cef._DisplayMask
+ if (flags hasnt Cef._DataTypeMask) flags /= Cef.DefaultOptions and Cef._DataTypeMask
+ if (flags hasnt Cef._PickerMask) flags /= Cef.DefaultOptions and Cef._PickerMask
+ if (flags hasnt Cef._InputMask) flags /= 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
+ }
- 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.i)
-
- 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()
- }
- }
+ 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
- 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)
+ 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))
}
- // 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]
+ fun colorEditRestoreH(col: Vec4, pH: MutableProperty) {
+ assert(g.colorEditCurrentID != 0)
+ if (g.colorEditSavedID != g.colorEditCurrentID || g.colorEditSavedColor != floatsToU32(col[0], col[1], col[2], 0f))
+ return
+ var H by pH
+ H = g.colorEditSavedHue
}
- 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()) {
- 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
- }
+ /** 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)
- // Drag-drop payloads are always RGB
- if (acceptedDragDrop && flags has Cef.InputHSV)
- col.rgbToHSV()
- endDragDropTarget()
- }
+ /** 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)
+
+ /** 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)
- // 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
+ /** 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 (valueChanged && g.lastItemData.id != 0) // In case of ID collision, the second EndGroup() won't catch g.ActiveId
- markItemEdited(g.lastItemData.id)
+ /** 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) {
+ assert(g.colorEditCurrentID != 0)
+ var h = h_
+ var s = s_
+ if (g.colorEditSavedID != g.colorEditCurrentID || g.colorEditSavedColor != floatsToU32(x, y, z, 0f))
+ return
- return valueChanged
- }
+ // When s == 0, h is undefined.
+ // When h == 1 it wraps around to 0.
+ if (s == 0f || (h == 0f && g.colorEditSavedHue == 1f)) h = g.colorEditSavedHue
- 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
+ // When v == 0, s is undefined.
+ if (v == 0f) s = g.colorEditSavedSat
+ hsvSetter(h, s, v)
}
}
-
- fun colorEditVec4(label: String, col: Vec4, flags: ColorEditFlags = 0): 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
+}
+
+inline fun colorEdit4(label: String, x: Float, y: Float, z: Float, w: Float, flags_: ColorEditFlags = none, 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)
+ val setCurrentColorEditId = g.colorEditCurrentID == 0
+ if (setCurrentColorEditId)
+ g.colorEditCurrentID = window.idStack.last()
+
+ 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 /= g.colorEditOptions and Cef._DisplayMask
+ if (flags hasnt Cef._DataTypeMask) flags /= g.colorEditOptions and Cef._DataTypeMask
+ if (flags hasnt Cef._PickerMask) flags /= g.colorEditOptions and Cef._PickerMask
+ if (flags hasnt Cef._InputMask) flags /= g.colorEditOptions and Cef._InputMask
+ flags /= 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 grayscale rgb (saturation=0). Restore it.
+ f.rgbToHSV()
+ colorEditRestoreHS(x, y, z, f)
}
- fun colorPicker3(label: String, col: Vec4, flags: ColorEditFlags = 0): Boolean =
- colorPicker3(label, col to _fa, flags)
- .also { col put _fa }
+ val i = IntArray(4) { F32_TO_INT8_UNBOUND(f[it]) }
- fun colorPicker3(label: String, col: FloatArray, flags: ColorEditFlags = 0): 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
- }
+ var valueChanged = false
+ var valueChangedAsFloat = false
- /** 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 = 0, refCol: Vec4? = null): Boolean =
- colorPicker4(label, col to _fa, flags, refCol?.to(_fa2))
- .also { col put _fa; refCol?.put(_fa2) }
+ val pos = Vec2(window.dc.cursorPos)
+ val inputsOffsetX = if (style.colorButtonPosition == Dir.Left) wButton else 0f
+ window.dc.cursorPos.x = pos.x + inputsOffsetX
- /** 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 = 0, refCol: FloatArray? = null): Boolean {
+ if (flags has (Cef.DisplayRGB or Cef.DisplayHSV) && flags hasnt Cef.NoInputs) {
- val window = currentWindow
- if (window.skipItems)
- return false
+ // 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))
- val drawList = window.drawList
+ 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
- val width = calcItemWidth()
- g.nextItemData.clearFlags()
+ repeat(components) { n ->
+ if (n > 0) sameLine(0f, style.itemInnerSpacing.x)
+ setNextItemWidth(if (n + 1 < components) wItemOne else wItemLast)
- 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.i) and Cef._PickerMask)
- if (flags hasnt Cef._InputMask)
- flags = flags or ((if (g.colorEditOptions has Cef._InputMask) g.colorEditOptions else Cef.DefaultOptions.i) 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.i, 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 (uu, vv, _) = triangleBarycentricCoords(trianglePa, trianglePb, trianglePc, currentOffUnrotated)
- 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.i)
-
- } 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.i)
- // 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
- }
+ // 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 mutablePropertyAt n, 1f, 0, if (hdr) 0 else 255, fmtTableInt[fmtIdx][n])
+ if (flags hasnt Cef.NoOptions) openPopupOnItemClick("context", PopupFlag.MouseButtonRight)
}
- // 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
- }
+ } 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)
}
- popItemFlag() // ItemFlag.NoNav
+ if (flags hasnt Cef.NoOptions) openPopupOnItemClick("context", PopupFlag.MouseButtonRight)
+ }
- if (flags hasnt Cef.NoSidePreview) {
- sameLine(0f, style.itemInnerSpacing.x)
- beginGroup()
+ 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)
- if (flags hasnt Cef.NoLabel) {
- val labelDisplayEnd = findRenderedTextEnd(label)
+ if (beginPopup("picker")) if (g.currentWindow!!.beginCount == 1) {
+ pickerActiveWindow = g.currentWindow
if (0 != labelDisplayEnd) {
- if (flags has Cef.NoSidePreview)
- sameLine(0f, style.itemInnerSpacing.x)
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()
}
- if (flags hasnt Cef.NoSidePreview) {
- pushItemFlag(ItemFlag.NoNavDefaultFocus.i, 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
- }
- }
- 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
- }
+ 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)
+ }
- // 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
+ 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.colorEditSavedHue = f[0]
+ g.colorEditSavedSat = f[1]
+ f.hsvToRGB()
+ g.colorEditSavedID = g.colorEditCurrentID
+ g.colorEditSavedColor = 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)
+ }
- // 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)
- }
+ if (setCurrentColorEditId)
+ g.colorEditCurrentID = 0
+ 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
}
- 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]
+ endDragDropTarget()
+ }
+
+ // 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 = none, 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 = none, 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 = none, refCol: Vec4? = null): Boolean {
+ val window = currentWindow
+ if (window.skipItems) return false
+
+ val drawList = window.drawList
+
+ val width = calcItemWidth()
+ g.nextItemData.clearFlags()
+
+ pushID(label)
+ val setCurrentColorEditId = g.colorEditCurrentID == 0
+ if (setCurrentColorEditId)
+ g.colorEditCurrentID = window.idStack.last()
+ beginGroup()
+
+ var flags = flags_
+ if (flags hasnt Cef.NoSidePreview) flags /= 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 /= (if (g.colorEditOptions has Cef._PickerMask) g.colorEditOptions else Cef.DefaultOptions) and Cef._PickerMask
+ if (flags hasnt Cef._InputMask) flags /= (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 /= 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 grayscale 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)
}
- 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]
+ 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
}
}
-
- 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, 0, 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])
- }
-
- // 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)
+ 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.
+ colorEditRestoreH(col, H.mutableReference) // Greatly reduces hue jitter and reset to 0 when hue == 255 and color is rapidly modified using SV square.
+ if (g.colorEditSavedColor == Vec4(col[0], col[1], col[2], 0f).u32)
+ H = g.colorEditSavedHue
+ valueChangedSv = true; valueChanged = true
}
-
- // 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)
+ 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
}
-
- 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()
-
- return valueChanged
}
- /** 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 = 0, sizeArg: Vec2 = Vec2()): Boolean {
-
- val window = currentWindow
- if (window.skipItems)
- return false
-
- 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
+ // 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
- val (pressed, hovered, _) = buttonBehavior(bb, id)
+ if (flags hasnt Cef.NoSidePreview) {
+ sameLine(0f, style.itemInnerSpacing.x)
+ beginGroup()
+ }
- var flags = flags_
- if (flags has Cef.NoAlpha)
- flags = flags and (Cef.AlphaPreview or Cef.AlphaPreviewHalf).inv()
-
- 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.i)
- window.drawList.addRectFilled(bbInner.min, Vec2(midX, bbInner.max.y), getColorU32(colRgbWithoutAlpha), rounding, DrawFlag.RoundCornersLeft.i)
- } 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()
+ 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)
}
- // 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)
+ }
+ 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()
+ }
- return pressed
+ // Convert back color to RGB
+ if (valueChangedH || valueChangedSv) if (flags has Cef.InputRGB) {
+ colorConvertHSVtoRGB(H, S, V, col)
+ g.colorEditSavedHue = H
+ g.colorEditSavedSat = S
+ g.colorEditSavedID = g.colorEditCurrentID
+ g.colorEditSavedColor = floatsToU32(col.x, col.y, col.z, 0f)
+ } else if (flags has Cef.InputHSV) {
+ col[0] = H
+ col[1] = S
+ col[2] = V
}
- /** 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
+ // 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()
}
- 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
+ // 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)
+ }
+ }
- 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))
+ 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
+ }
+ 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
+ }
}
+ }
- /** 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 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])
+ }
- 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
+ // 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 = drawList._calcCircleAutoSegmentCount(hueCursorRad) // Lock segment count so the +1 one matches others.
+ 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(3, 3)
+ drawList.primVtx(tra, uvWhite, hueColor32)
+ drawList.primVtx(trb, uvWhite, colBlack)
+ drawList.primVtx(trc, uvWhite, colWhite)
+ 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)
+ }
+
+ // 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
+ val svCursorSegments = drawList._calcCircleAutoSegmentCount(svCursorRad) // Lock segment count so the +1 one matches others.
+ drawList.addCircleFilled(svCursorPos, svCursorRad, userCol32StripedOfAlpha, svCursorSegments)
+ drawList.addCircle(svCursorPos, svCursorRad + 1, colMidgrey, svCursorSegments)
+ drawList.addCircle(svCursorPos, svCursorRad, colWhite, svCursorSegments)
+
+ // 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)
+ }
+
+ 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)
+
+ if (setCurrentColorEditId)
+ g.colorEditCurrentID = 0
+ popID()
+
+ return valueChanged
+}
+
+/** 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 = none, sizeArg: Vec2 = Vec2()): Boolean {
+
+ val window = currentWindow
+ if (window.skipItems) return false
+
+ 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
+
+ val (pressed, hovered, _) = buttonBehavior(bb, id)
+
+ var flags = flags_
+ if (flags has Cef.NoAlpha) flags -= Cef.AlphaPreview or Cef.AlphaPreviewHalf
+
+ 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
}
-}
\ No newline at end of file
+ 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, x, y, z, w, flags, sizeArg)
+ sameLine()
+ textEx("Color")
+ endDragDropSource()
+ }
+ // Tooltip
+ if (flags hasnt Cef.NoTooltip && hovered && isItemHovered(HoveredFlag.ForTooltip))
+ 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 7cff3d181..8afc1fe90 100644
--- a/core/src/main/kotlin/imgui/api/widgetsComboBox.kt
+++ b/core/src/main/kotlin/imgui/api/widgetsComboBox.kt
@@ -28,11 +28,10 @@ import imgui.ImGui.setItemDefaultFocus
import imgui.ImGui.setNextWindowSizeConstraints
import imgui.ImGui.style
import imgui.classes.SizeCallbackData
-import imgui.has
-import imgui.hasnt
import imgui.internal.classes.Rect
import imgui.internal.hashStr
-import imgui.internal.sections.*
+import imgui.internal.sections.DrawFlag
+import imgui.internal.sections.NextWindowDataFlag
import kool.getValue
import kool.setValue
import uno.kotlin.NUL
@@ -44,10 +43,9 @@ import imgui.ComboFlag as Cf
// - The old Combo() api are helpers over BeginCombo()/EndCombo() which are kept available for convenience purpose. This is analogous to how ListBox are created.
interface widgetsComboBox {
- fun beginCombo(label: String, previewValue_: String?, flags_: ComboFlags = 0): Boolean {
+ fun beginCombo(label: String, previewValue_: String?, flags: ComboFlags = none): Boolean {
var previewValue = previewValue_
- var flags = flags_
val window = currentWindow
@@ -57,7 +55,7 @@ interface widgetsComboBox {
return false
val id = window.getID(label)
- assert(flags and (Cf.NoArrowButton or Cf.NoPreview) != Cf.NoArrowButton or Cf.NoPreview) { "Can't use both flags together" }
+ assert(Cf.NoArrowButton or Cf.NoPreview !in flags) { "Can't use both flags together" }
val arrowSize = if (flags has Cf.NoArrowButton) 0f else frameHeight
val labelSize = calcTextSize(label, hideTextAfterDoubleHash = true)
@@ -71,9 +69,9 @@ interface widgetsComboBox {
// Open on click
val (pressed, hovered, _) = buttonBehavior(bb, id)
val popupId = hashStr("##ComboPopup", 0, id)
- var popupOpen = isPopupOpen(popupId, PopupFlag.None.i)
+ var popupOpen = isPopupOpen(popupId)
if (pressed && !popupOpen) {
- openPopupEx(popupId, PopupFlag.None.i)
+ openPopupEx(popupId)
popupOpen = true
}
@@ -82,11 +80,22 @@ interface widgetsComboBox {
val valueX2 = bb.min.x max (bb.max.x - arrowSize)
renderNavHighlight(bb, id)
if (flags hasnt Cf.NoPreview)
- window.drawList.addRectFilled(bb.min, Vec2(valueX2, bb.max.y), frameCol.u32,
- style.frameRounding, if (flags has Cf.NoArrowButton) DrawFlag.RoundCornersAll.i else DrawFlag.RoundCornersLeft.i)
+ window.drawList.addRectFilled(
+ bb.min,
+ Vec2(valueX2, bb.max.y),
+ frameCol.u32,
+ style.frameRounding,
+ if (flags has Cf.NoArrowButton) DrawFlag.RoundCornersAll else DrawFlag.RoundCornersLeft
+ )
if (flags hasnt Cf.NoArrowButton) {
val bgCol = if (popupOpen || hovered) Col.ButtonHovered else Col.Button
- window.drawList.addRectFilled(Vec2(valueX2, bb.min.y), bb.max, bgCol.u32, style.frameRounding, if (w <= arrowSize) DrawFlag.RoundCornersAll.i else DrawFlag.RoundCornersRight.i)
+ window.drawList.addRectFilled(
+ Vec2(valueX2, bb.min.y),
+ bb.max,
+ bgCol.u32,
+ style.frameRounding,
+ if (w <= arrowSize) DrawFlag.RoundCornersAll else DrawFlag.RoundCornersRight
+ )
if (valueX2 + arrowSize - style.framePadding.x <= bb.max.x)
window.drawList.renderArrow(Vec2(valueX2 + style.framePadding.y, bb.min.y + style.framePadding.y), Col.Text.u32, Dir.Down, 1f)
}
@@ -123,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() }
@@ -139,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 {
@@ -156,7 +154,7 @@ interface widgetsComboBox {
if (popupMaxHeightInItem != -1 && g.nextWindowData.flags hasnt NextWindowDataFlag.HasSizeConstraint)
setNextWindowSizeConstraints(Vec2(), Vec2(Float.MAX_VALUE, calcMaxPopupHeightFromItemCount(popupMaxHeightInItem)))
- if (!beginCombo(label, previewValue, Cf.None.i)) return false
+ if (!beginCombo(label, previewValue)) return false
// Display items
// FIXME-OPT: Use clipper (but we need to disable it on the appearing frame to make sure our call to setItemDefaultFocus() is processed)
@@ -182,15 +180,16 @@ 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)
setNextWindowSizeConstraints(Vec2(), Vec2(Float.MAX_VALUE, calcMaxPopupHeightFromItemCount(popupMaxHeightInItems)))
- if (!beginCombo(label, previewValue, Cf.None.i))
+ if (!beginCombo(label, previewValue))
return false
// Display items
@@ -199,10 +198,11 @@ 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)) {
+ if (selectable(itemText, itemSelected) && currentItem != i) {
valueChanged = true
currentItem = i
}
diff --git a/core/src/main/kotlin/imgui/api/widgetsDataPlotting.kt b/core/src/main/kotlin/imgui/api/widgetsDataPlotting.kt
index 5261d1455..6387e8766 100644
--- a/core/src/main/kotlin/imgui/api/widgetsDataPlotting.kt
+++ b/core/src/main/kotlin/imgui/api/widgetsDataPlotting.kt
@@ -1,46 +1,51 @@
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, 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
- }
- }
-}
\ No newline at end of file
+ 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): Int =
+ plotEx(PlotType.Lines, label, values.size, valuesOffset, overlayText, scaleMin, scaleMax, graphSize) {
+ values[it * stride]
+ }
+
+ 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): Int =
+ plotEx(PlotType.Histogram, label, values.size, valuesOffset, overlayText, scaleMin, scaleMax, graphSize) {
+ values[it * stride]
+ }
+}
+
+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 95c12eff9..3047e555c 100644
--- a/core/src/main/kotlin/imgui/api/widgetsDrags.kt
+++ b/core/src/main/kotlin/imgui/api/widgetsDrags.kt
@@ -12,13 +12,14 @@ 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
+import imgui.ImGui.isDragPastThreshold
import imgui.ImGui.logSetNextTextDecoration
import imgui.ImGui.popID
import imgui.ImGui.popItemWidth
@@ -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',
@@ -54,51 +54,66 @@ import kotlin.reflect.KMutableProperty0
// If you get a warning converting a float to ImGuiSliderFlags, read https://github.com/ocornut/imgui/issues/3361
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 = SliderFlag.None.i): 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 = SliderFlag.None.i): 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 = SliderFlag.None.i): 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 = SliderFlag.None.i): 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 = SliderFlag.None.i): 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 = SliderFlag.None.i): 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 = SliderFlag.None.i): 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 = SliderFlag.None.i): Boolean =
- dragScalarN(label, DataType.Float, v to _fa, 4, vSpeed, vMin.asMutableProperty, vMax.asMutableProperty, format, flags)
- .also { v put _fa }
+ fun drag2(label: String,
+ v: FloatArray,
+ vSpeed: Float = 1f,
+ vMin: Float = 0f,
+ vMax: Float = 0f,
+ format: String = "%.3f",
+ flags: SliderFlags = none): 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 = none): 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 = none): 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 = none): 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 = none): 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 = none): 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 = SliderFlag.None.i): Boolean {
+ 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 = none): Boolean {
val vCurrentMin by vCurrentMinPtr
val vCurrentMax by vCurrentMaxPtr
@@ -109,20 +124,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 minFlags = flags or if (minMin == minMax) SliderFlag._ReadOnly else SliderFlag.None
- var valueChanged = dragScalar("##min", DataType.Float, vCurrentMinPtr, vSpeed,
- minMin mutableProperty { minMin = it }, minMax mutableProperty { minMax = it }, format, minFlags)
+ 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 none
+ 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 maxFlags = flags or if (maxMin == maxMax) SliderFlag._ReadOnly else SliderFlag.None
+ 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 none
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)
@@ -136,45 +149,64 @@ 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 = SliderFlag.None.i): 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 = SliderFlag.None.i): 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 = SliderFlag.None.i): 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 = SliderFlag.None.i): 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 = SliderFlag.None.i): 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 = SliderFlag.None.i): 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 = SliderFlag.None.i): 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 = SliderFlag.None.i): Boolean =
- dragScalarN(label, DataType.Int, v to _ia, 4, vSpeed, vMin.asMutableProperty, vMax.asMutableProperty, format, flags)
- .also { v put _ia }
+ fun drag2(label: String,
+ v: IntArray,
+ vSpeed: Float = 1f,
+ vMin: Int = 0,
+ vMax: Int = 0,
+ format: String = "%d",
+ flags: SliderFlags = none): 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 = none): 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 = none): 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 = none): 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 = none): 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 = none): 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 = SliderFlag.None.i): Boolean {
+ 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 = none): Boolean {
val vCurrentMin by vCurrentMinPtr
val vCurrentMax by vCurrentMaxPtr
@@ -187,16 +219,16 @@ 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 SliderFlag.None
- var valueChanged = dragInt("##min", vCurrentMinPtr, vSpeed, minMin, minMax, format, minFlags)
+ val minFlags = flags or if (minMin == minMax) SliderFlag._ReadOnly else none
+ 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 SliderFlag.None
- val fmt = if (formatMax.isNotEmpty()) formatMax else format
- valueChanged /= dragInt("##max", vCurrentMaxPtr, vSpeed, maxMin, maxMax, fmt, maxFlags)
+ val maxFlags = flags or if (maxMin == maxMax) SliderFlag._ReadOnly else none
+ val fmt = formatMax.ifEmpty { format }
+ valueChanged /= drag("##max", vCurrentMaxPtr, vSpeed, maxMin, maxMax, fmt, maxFlags)
popItemWidth()
sameLine(0f, style.itemInnerSpacing.x)
@@ -213,24 +245,23 @@ 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 = SliderFlag.None.i): 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 = SliderFlag.None.i): 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 = none): Boolean = drag(label, pData mutablePropertyAt 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 = SliderFlag.None.i): 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 = none): Boolean where N : Number, N : Comparable {
val window = ImGui.currentWindow
if (window.skipItems) return false
@@ -244,13 +275,13 @@ interface widgetsDrags {
val tempInputAllowed = flags hasnt SliderFlag.NoInput
ImGui.itemSize(totalBb, ImGui.style.framePadding.y)
- if (!ImGui.itemAdd(totalBb, id, frameBb, if (tempInputAllowed) ItemFlag.Inputable.i else 0))
+ if (!ImGui.itemAdd(totalBb, id, frameBb, if (tempInputAllowed) ItemFlag.Inputable else none))
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 hovered = ImGui.itemHoverable(frameBb, id, g.lastItemData.inFlags)
var tempInputIsActive = tempInputAllowed && ImGui.tempInputIsActive(id)
if (!tempInputIsActive) {
@@ -258,17 +289,16 @@ interface widgetsDrags {
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)
+ val makeActive = inputRequestedByTabbing || clicked || doubleClicked || g.navActivateId == id
+ if (makeActive && (clicked || doubleClicked)) Key.MouseLeft.setOwner(id)
if (makeActive && tempInputAllowed)
- if (inputRequestedByTabbing || (clicked && ImGui.io.keyCtrl) || doubleClicked || g.navActivateInputId == id)
+ if (inputRequestedByTabbing || (clicked && ImGui.io.keyCtrl) || doubleClicked || (g.navActivateId == id && g.navActivateFlags has ActivateFlag.PreferInput))
tempInputIsActive = true
// (Optional) simple click (without moving) turns Drag into an InputText
if (io.configDragClickToInputText && tempInputAllowed && !tempInputIsActive)
- if (g.activeId == id && hovered && io.mouseReleased[0] && !isMouseDragPastThreshold(MouseButton.Left, io.mouseDragThreshold * DRAG_MOUSE_THRESHOLD_FACTOR)) {
- g.navActivateId = id; g.navActivateInputId = id
+ if (g.activeId == id && hovered && io.mouseReleased[0] && !MouseButton.Left.isDragPastThreshold(io.mouseDragThreshold * DRAG_MOUSE_THRESHOLD_FACTOR)) {
+ g.navActivateId = id
tempInputIsActive = true
}
@@ -282,8 +312,8 @@ 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
@@ -296,66 +326,74 @@ 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)
- 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 = SliderFlag.None.i): 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()
+ IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.lastItemData.statusFlags / if (tempInputAllowed) ItemStatusFlag.Inputable else none)
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 = none): Boolean where N : Number, N : Comparable =
+ ImGui.drag(label, pData, vSpeed, min, max, format_, flags)
+
+inline fun ImGui.drag(label: String,
+ pData: KMutableProperty0,
+ vSpeed: Float = 1f,
+ min: N? = null,
+ max: N? = null,
+ format_: String? = null,
+ flags: SliderFlags = none): Boolean where N : Number, N : Comparable =
+ numberOps().drag(label, pData, vSpeed, min, max, format_, flags)
+
+/** 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 = none,
+ properties: (Int) -> KMutableProperty0): Boolean where N : Number, N : Comparable =
+ ImGui.dragN(label, components, vSpeed, min, max, format, flags, properties)
+
+/** 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 ImGui.dragN(label: String,
+ components: Int,
+ vSpeed: Float = 1f,
+ min: N? = null,
+ max: N? = null,
+ format: String? = null,
+ flags: SliderFlags = none,
+ 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 = none,
+ properties: (Int) -> KMutableProperty0): Boolean where N : Number, N : Comparable =
+ widgetN(label, components) { i -> drag("", properties(i), vSpeed, min, max, format, flags) }
\ No newline at end of file
diff --git a/core/src/main/kotlin/imgui/api/widgetsInputWithKeyboard.kt b/core/src/main/kotlin/imgui/api/widgetsInputWithKeyboard.kt
index 9aa70e2b3..eef76d036 100644
--- a/core/src/main/kotlin/imgui/api/widgetsInputWithKeyboard.kt
+++ b/core/src/main/kotlin/imgui/api/widgetsInputWithKeyboard.kt
@@ -1,6 +1,5 @@
package imgui.api
-import gli_.hasnt
import glm_.func.common.max
import glm_.vec2.Vec2
import glm_.vec2.Vec2i
@@ -14,42 +13,36 @@ 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 imgui.internal.sections.ItemStatusFlag
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. */
interface widgetsInputWithKeyboard {
/** String overload */
- fun inputText(label: String, pString: KMutableProperty0, flags: InputTextFlags = Itf.None.i,
- callback: InputTextCallback? = null, userData: Any? = null): Boolean {
+ fun inputText(label: String, pString: KMutableProperty0, flags: InputTextSingleFlags = none, callback: InputTextCallback? = null, userData: Any? = null): Boolean {
val buf = pString.get().toByteArray()
return inputText(label, buf, flags, callback, userData).also {
pString.set(buf.cStr)
@@ -57,156 +50,112 @@ interface widgetsInputWithKeyboard {
}
/** String overload */
- fun inputText(label: String, buf: String, flags: InputTextFlags = Itf.None.i,
- callback: InputTextCallback? = null, userData: Any? = null): Boolean =
- inputText(label, buf.toByteArray(), flags, callback, userData)
-
- fun inputText(label: String, buf: StringBuilder, flags: InputTextFlags = Itf.None.i,
- callback: InputTextCallback? = null, userData: Any? = null): Boolean {
- val array = buf.toString().toByteArray()
- return inputText(label, array, flags, callback, userData).also {
- buf.clear()
-// buf.append(array.)
- }
- }
+ fun inputText(label: String, buf: String, flags: InputTextSingleFlags = none, callback: InputTextCallback? = null, userData: Any? = null): Boolean =
+ inputText(label, buf.toByteArray(), flags, callback, userData)
- fun inputText(label: String, buf: ByteArray, flags: InputTextFlags = Itf.None.i,
- callback: InputTextCallback? = null, userData: Any? = null): Boolean {
- assert(flags hasnt Itf._Multiline) { "call InputTextMultiline()" }
- return inputTextEx(label, null, buf, Vec2(), flags, callback, userData)
- }
+ fun inputText(label: String, buf: StringBuilder, flags: InputTextSingleFlags = none, callback: InputTextCallback? = null, userData: Any? = null): Boolean =
+ inputText(label, buf.toString(), flags, callback, userData)
+
+ // [JVM] since this is a very particular case, that's why we don't overload
+ fun inputText(label: String, buf: ByteArray, bufEnd: Int, flags: InputTextSingleFlags = none, callback: InputTextCallback? = null, userData: Any? = null): Boolean =
+ inputTextEx(label, null, buf, bufEnd, Vec2(), flags, callback, userData)
+
+ fun inputText(label: String, buf: ByteArray, flags: InputTextSingleFlags = none, callback: InputTextCallback? = null, userData: Any? = null): Boolean =
+ inputTextEx(label, null, buf, Vec2(), flags, callback, userData)
/** String overload */
- fun inputTextMultiline(label: String, buf: String, size: Vec2 = Vec2(), flags: InputTextFlags = Itf.None.i,
- callback: InputTextCallback? = null, userData: Any? = null): Boolean =
- inputTextEx(label, null, buf.toByteArray(), size, flags or Itf._Multiline, callback, userData)
+ fun inputTextMultiline(label: String, buf: String, size: Vec2 = Vec2(), flags: InputTextSingleFlags = none, callback: InputTextCallback? = null, userData: Any? = null): Boolean =
+ inputTextEx(label, null, buf.toByteArray(), size, flags or Itf._Multiline, callback, userData)
- fun inputTextMultiline(label: String, buf: ByteArray, size: Vec2 = Vec2(), flags: InputTextFlags = Itf.None.i,
- callback: InputTextCallback? = null, userData: Any? = null): Boolean =
- inputTextEx(label, null, buf, size, flags or Itf._Multiline, callback, userData)
+ fun inputTextMultiline(label: String, buf: ByteArray, size: Vec2 = Vec2(), flags: InputTextSingleFlags = none, callback: InputTextCallback? = null, userData: Any? = null): Boolean =
+ inputTextEx(label, null, buf, size, flags or Itf._Multiline, callback, userData)
/** String overload */
- fun inputTextWithHint(label: String, hint: String, buf: String, flags: InputTextFlags = Itf.None.i,
- callback: InputTextCallback? = null, userData: Any? = null): Boolean =
- inputTextWithHint(label, hint, buf.toByteArray(), flags)
-
- fun inputTextWithHint(label: String, hint: String, buf: ByteArray, flags: InputTextFlags = 0,
- callback: InputTextCallback? = null, userData: Any? = null): Boolean {
- assert(flags hasnt Itf._Multiline) { "call InputTextMultiline() or InputTextEx() manually if you need multi-line + hint." }
- return inputTextEx(label, hint, buf, Vec2(), flags, callback, userData)
- }
+ fun inputTextWithHint(label: String, hint: String, buf: String, flags: InputTextSingleFlags = none, callback: InputTextCallback? = null, userData: Any? = null): 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 = none, callback: InputTextCallback? = null, userData: Any? = null): Boolean =
+ inputTextEx(label, hint, buf, Vec2(), flags, callback, userData)
- fun inputFloat(label: String, v: FloatArray, step: Float = 0f, stepFast: Float = 0f,
- format: String = "%.3f", flags: InputTextFlags = Itf.None.i): 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 = none): Boolean =
+ input(label, v mutablePropertyAt 0, step, stepFast, format, flags)
- fun inputFloat(label: String, v: FloatArray, ptr: Int = 0, step: Float = 0f, stepFast: Float = 0f,
- format: String = "%.3f", flags: InputTextFlags = Itf.None.i): Boolean =
- withFloat(v, ptr) { inputFloat(label, it, step, stepFast, format, flags) }
+ fun input(label: String, v: KMutableProperty0, step: Float = 0f, stepFast: Float = 0f, format: String = "%.3f", flags: InputTextSingleFlags = none): Boolean =
+ input(label, v, step.takeIf { it > 0f }, stepFast.takeIf { it > 0f }, format, flags / Itf.CharsScientific)
- fun inputFloat(label: String, v: KMutableProperty0, step: Float = 0f, stepFast: Float = 0f,
- format: String = "%.3f", flags_: InputTextFlags = Itf.None.i): Boolean {
- val flags = flags_ or Itf.CharsScientific
- return inputScalar(label, DataType.Float, v, step.takeIf { it > 0f }, stepFast.takeIf { it > 0f }, format, flags)
- }
+ fun input2(label: String, v: FloatArray, format: String = "%.3f", flags: InputTextSingleFlags = none): Boolean = inputN(label, 2, null, null, format, flags, v::mutablePropertyAt)
+ fun input2(label: String, v: Vec2, format: String = "%.3f", flags: InputTextSingleFlags = none): Boolean = inputN(label, 2, null, null, format, flags, v::mutablePropertyAt)
- fun inputFloat2(label: String, v: FloatArray, format: String = "%.3f", flags: InputTextFlags = Itf.None.i): Boolean =
- inputScalarN(label, DataType.Float, v, 2, null, null, format, flags)
+ fun input2(label: String, v: Vec3, format: String = "%.3f", flags: InputTextSingleFlags = none): Boolean = inputN(label, 2, null, null, format, flags, v::mutablePropertyAt)
- fun inputVec2(label: String, v: Vec2, format: String = "%.3f", flags: InputTextFlags = Itf.None.i): Boolean =
- inputScalarN(label, DataType.Float, v to _fa, Vec2.length, null, null, format, flags)
- .also { v put _fa }
+ fun input2(label: String, v: Vec4, format: String = "%.3f", flags: InputTextSingleFlags = none): Boolean = inputN(label, 2, null, null, format, flags, v::mutablePropertyAt)
- fun inputFloat3(label: String, v: FloatArray, format: String = "%.3f", flags: InputTextFlags = Itf.None.i): Boolean =
- inputScalarN(label, DataType.Float, v, 3, null, null, format, flags)
+ fun input3(label: String, v: FloatArray, format: String = "%.3f", flags: InputTextSingleFlags = none): Boolean = inputN(label, 3, null, null, format, flags, v::mutablePropertyAt)
- fun inputVec3(label: String, v: Vec3, format: String = "%.3f", flags: InputTextFlags = Itf.None.i): Boolean =
- inputScalarN(label, DataType.Float, v to _fa, Vec3.length, null, null, format, flags)
- .also { v put _fa }
+ fun input3(label: String, v: Vec3, format: String = "%.3f", flags: InputTextSingleFlags = none): Boolean = inputN(label, 3, null, null, format, flags, v::mutablePropertyAt)
- fun inputFloat4(label: String, v: FloatArray, format: String = "%.3f", flags: InputTextFlags = Itf.None.i): Boolean =
- inputScalarN(label, DataType.Float, v, 4, null, null, format, flags)
+ fun input3(label: String, v: Vec4, format: String = "%.3f", flags: InputTextSingleFlags = none): Boolean = inputN(label, 3, null, null, format, flags, v::mutablePropertyAt)
- fun inputVec4(label: String, v: Vec4, format: String = "%.3f", flags: InputTextFlags = Itf.None.i): Boolean =
- inputScalarN(label, DataType.Float, v to _fa, Vec4.length, null, null, format, flags)
- .also { v put _fa }
+ fun input4(label: String, v: FloatArray, format: String = "%.3f", flags: InputTextSingleFlags = none): Boolean = inputN(label, 4, null, null, format, flags, v::mutablePropertyAt)
- fun inputInt(label: String, v: KMutableProperty0, step: Int = 1, stepFast: Int = 100, flags: InputTextFlags = 0): Boolean {
- /* Hexadecimal input provided as a convenience but the flag name is awkward. Typically you'd use inputText()
+ fun input4(label: String, v: Vec4, format: String = "%.3f", flags: InputTextSingleFlags = none): Boolean = inputN(label, 4, null, null, format, flags, v::mutablePropertyAt)
+
+ fun input(label: String, v: KMutableProperty0, step: Int = 1, stepFast: Int = 100, flags: InputTextSingleFlags = none): 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: InputTextFlags = 0): 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 = none): Boolean = input(label, pData mutablePropertyAt 0, step, stepFast, format, flags)
+
+ fun input2(label: String, v: IntArray, flags: InputTextSingleFlags = none): Boolean = inputN(label, 2, null, null, "%d", flags, v::mutablePropertyAt)
- fun inputVec2i(label: String, v: Vec2i, flags: InputTextFlags = 0): Boolean =
- inputScalarN(label, DataType.Int, v to _ia, Vec2i.length, null, null, "%d", flags)
- .also { v put _ia }
+ fun input2(label: String, v: Vec2i, flags: InputTextSingleFlags = none): Boolean = inputN(label, 2, null, null, "%d", flags, v::mutablePropertyAt)
- fun inputInt3(label: String, v: IntArray, flags: InputTextFlags = 0): Boolean =
- inputScalarN(label, DataType.Int, v, 3, null, null, "%d", flags)
+ fun input3(label: String, v: IntArray, flags: InputTextSingleFlags = none): Boolean = inputN(label, 3, null, null, "%d", flags, v::mutablePropertyAt)
- fun inputVec3i(label: String, v: Vec3i, flags: InputTextFlags = 0): Boolean =
- inputScalarN(label, DataType.Int, v to _ia, Vec3i.length, null, null, "%d", flags)
- .also { v put _ia }
+ fun input3(label: String, v: Vec3i, flags: InputTextSingleFlags = none): Boolean = inputN(label, 3, null, null, "%d", flags, v::mutablePropertyAt)
- fun inputInt4(label: String, v: IntArray, flags: InputTextFlags = 0): Boolean =
- inputScalarN(label, DataType.Int, v, 4, null, null, "%d", flags)
+ fun input4(label: String, v: IntArray, flags: InputTextSingleFlags = none): Boolean = inputN(label, 4, null, null, "%d", flags, v::mutablePropertyAt)
- fun inputVec4i(label: String, v: Vec4i, flags: InputTextFlags = 0): Boolean =
- inputScalarN(label, DataType.Int, v to _ia, Vec4i.length, null, null, "%d", flags)
- .also { v put _ia }
+ fun input4(label: String, v: Vec4i, flags: InputTextSingleFlags = none): 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_: InputTextFlags = Itf.None.i): 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 input(label: String, v: KMutableProperty0, step: Double = 0.0, stepFast: Double = 0.0, format: String? = "%.6f", flags_: InputTextSingleFlags = none): 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: InputTextFlags = Itf.None.i)
- : 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_: InputTextFlags = Itf.None.i): Boolean where N : Number, N : Comparable {
-
+ /** ~InputScalar */
+ fun NumberOps.input(label: String, pData: KMutableProperty0, step: N? = null, stepFast: N? = null, format_: String? = null, flags_: InputTextSingleFlags = none): 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) {
+ if (step == null) {
+ if (inputText(label, buf, flags))
+ valueChanged = pData.applyFromText(buf, format)
+ } else {
val buttonSize = frameHeight
beginGroup() // The only purpose of the group here is to allow the caller to query item data e.g. IsItemActive()
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)
- IMGUI_TEST_ENGINE_ITEM_INFO(g.lastItemData.id, label, g.lastItemData.statusFlags)
+ valueChanged = pData.applyFromText(buf, format)
+ IMGUI_TEST_ENGINE_ITEM_INFO(g.lastItemData.id, label, g.lastItemData.statusFlags / ItemStatusFlag.Inputable)
// Step buttons
val backupFramePadding = Vec2(style.framePadding)
@@ -216,12 +165,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)
@@ -236,8 +185,7 @@ interface widgetsInputWithKeyboard {
popID()
endGroup()
- } else if (inputText(label, buf, flags))
- valueChanged = dataTypeApplyFromText(buf.cStr, dataType, pData, format)
+ }
if (valueChanged)
markItemEdited(g.lastItemData.id)
@@ -245,38 +193,21 @@ interface widgetsInputWithKeyboard {
return valueChanged
}
- fun inputScalarN(label: String, dataType: DataType, v: Any, components: Int, step: N? = null, stepFast: N? = null,
- format: String? = null, flags: InputTextFlags = 0): 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 = none): Boolean where N : Number, N : Comparable =
+ ImGui.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()
+inline fun ImGui.input(label: String, pData: KMutableProperty0, step: N? = null, stepFast: N? = null, format: String? = null, flags: InputTextSingleFlags = none): Boolean where N : Number, N : Comparable =
+ numberOps().input(label, pData, step, stepFast, format, flags)
- 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 = none, properties: (Int) -> KMutableProperty0): Boolean where N : Number, N : Comparable =
+ ImGui.inputN(label, components, step, stepFast, format, flags, properties)
- endGroup()
- return valueChanged
- }
-}
+inline fun ImGui.inputN(label: String, components: Int, step: N? = null, stepFast: N? = null, format: String? = null, flags: InputTextSingleFlags = none, properties: (Int) -> KMutableProperty0): Boolean where N : Number, N : Comparable =
+ numberOps().inputN(label, components, step, stepFast, format, flags, properties)
+
+inline fun NumberOps.inputN(label: String, components: Int, step: N? = null, stepFast: N? = null, format: String? = null, flags: InputTextSingleFlags = none, 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..f6de185f7 100644
--- a/core/src/main/kotlin/imgui/api/widgetsListBoxes.kt
+++ b/core/src/main/kotlin/imgui/api/widgetsListBoxes.kt
@@ -25,11 +25,12 @@ import imgui.ImGui.style
import imgui.ImGui.textLineHeightWithSpacing
import imgui.WindowFlag
import imgui.classes.ListClipper
+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 +91,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. */
@@ -100,7 +101,7 @@ interface widgetsListBoxes {
val itemsCount = items.size
// Calculate size from "height_in_items"
- val heightInItems = if (heightInItems_ < 0) heightInItems_ else itemsCount min 7
+ val heightInItems = if (heightInItems_ < 0) itemsCount min 7 else heightInItems_
val heightInItemsF = heightInItems + 0.25f
val size = Vec2(0f, floor(textLineHeightWithSpacing * heightInItemsF + g.style.framePadding.y * 2f))
@@ -110,28 +111,25 @@ interface widgetsListBoxes {
// you can create a custom version of ListBox() in your code without using the clipper.
var valueChanged = false
// We know exactly our line height here so we pass it as a minor optimization, but generally you don't need to.
- 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()
+ listClipper(itemsCount, textLineHeightWithSpacing) {
+ for (i in it.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)
markItemEdited(g.lastItemData.id)
- clipper.end()
return valueChanged
}
@@ -144,7 +142,7 @@ interface widgetsListBoxes {
* Tip: If your vertical size is calculated from an item count (e.g. 10 * item_height) consider adding a fractional part to facilitate seeing scrolling boundaries (e.g. 10.25 * item_height). */
fun beginListBox(label: String, itemsCount: Int, heightInItems: Int = -1): Boolean {
// If height_in_items == -1, default height is maximum 7.
- val heightInItemsF = (if(heightInItems < 0) itemsCount min 7 else heightInItems) + 0.25f
+ val heightInItemsF = (if (heightInItems < 0) itemsCount min 7 else heightInItems) + 0.25f
val size = Vec2(0f, textLineHeightWithSpacing * heightInItemsF + g.style.framePadding.y * 2f)
return beginListBox(label, size)
diff --git a/core/src/main/kotlin/imgui/api/widgetsMain.kt b/core/src/main/kotlin/imgui/api/widgetsMain.kt
index 2c5e40ae7..57c2aa42e 100644
--- a/core/src/main/kotlin/imgui/api/widgetsMain.kt
+++ b/core/src/main/kotlin/imgui/api/widgetsMain.kt
@@ -1,8 +1,8 @@
package imgui.api
-import glm_.*
+import glm_.glm
+import glm_.max
import glm_.vec2.Vec2
-import glm_.vec4.Vec4
import imgui.*
import imgui.ImGui.arrowButtonEx
import imgui.ImGui.buttonBehavior
@@ -10,16 +10,13 @@ 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.getColorU32
-import imgui.ImGui.imageButtonEx
import imgui.ImGui.itemAdd
import imgui.ImGui.itemSize
import imgui.ImGui.logRenderedText
import imgui.ImGui.markItemEdited
-import imgui.ImGui.popID
-import imgui.ImGui.pushID
import imgui.ImGui.renderBullet
import imgui.ImGui.renderCheckMark
import imgui.ImGui.renderFrame
@@ -34,41 +31,29 @@ import imgui.internal.floor
import imgui.internal.lerp
import imgui.internal.round
import imgui.internal.saturate
-import imgui.internal.sections.*
+import imgui.internal.sections.ButtonFlags
+import imgui.internal.sections.IMGUI_TEST_ENGINE_ITEM_INFO
+import imgui.internal.sections.ItemFlag
+import imgui.internal.sections.ItemStatusFlag
import kool.getValue
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.
interface widgetsMain {
/** button */
- fun button(label: String, sizeArg: Vec2 = Vec2()): Boolean = buttonEx(label, sizeArg, Bf.None.i)
+ fun button(label: String, sizeArg: Vec2 = Vec2()): Boolean = buttonEx(label, sizeArg, none)
/** button with FramePadding = (0,0) to easily embed within text
* Small buttons fits within text without additional vertical spacing. */
fun smallButton(label: String): Boolean {
val backupPaddingY = style.framePadding.y
style.framePadding.y = 0f
- val pressed = buttonEx(label, Vec2(), Bf.AlignTextBaseLine.i)
+ val pressed = buttonEx(label, Vec2(), Bf.AlignTextBaseLine)
style.framePadding.y = backupPaddingY
return pressed
}
@@ -78,7 +63,7 @@ interface widgetsMain {
* Tip: use pushId()/popId() to push indices or pointers in the ID stack.
* Then you can keep 'strid' empty or the same for all your buttons (instead of creating a string based on a
* non-string id) */
- fun invisibleButton(strId: String, sizeArg: Vec2, flags: ButtonFlags = Bf.None.i): Boolean {
+ fun invisibleButton(strId: String, sizeArg: Vec2, flags: ButtonFlags = none): Boolean {
val window = currentWindow
if (window.skipItems) return false
@@ -96,15 +81,10 @@ interface widgetsMain {
return pressed
}
- fun arrowButton(id: String, dir: Dir): Boolean = arrowButtonEx(id, dir, Vec2(frameHeight), Bf.None.i)
+ fun arrowButton(id: String, dir: Dir): Boolean = arrowButtonEx(id, dir, Vec2(frameHeight), none)
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 {
@@ -120,7 +100,7 @@ interface widgetsMain {
val totalBb = Rect(pos, pos + Vec2(squareSz + if (labelSize.x > 0f) style.itemInnerSpacing.x + labelSize.x else 0f, labelSize.y + style.framePadding.y * 2f))
itemSize(totalBb, style.framePadding.y)
if (!itemAdd(totalBb, id)) {
- IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.lastItemData.statusFlags or ItemStatusFlag.Checkable or if(v) ItemStatusFlag.Checked else ItemStatusFlag.None)
+ IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.lastItemData.statusFlags or ItemStatusFlag.Checkable or if (v) ItemStatusFlag.Checked else none)
return false
}
@@ -145,52 +125,26 @@ interface widgetsMain {
window.drawList.renderCheckMark(checkBb.min + pad, checkCol, squareSz - pad * 2f)
}
- val renderTextPos = Vec2(checkBb.max.x + style.itemInnerSpacing.x, checkBb.min.y + style.framePadding.y)
+ val labelPos = Vec2(checkBb.max.x + style.itemInnerSpacing.x, checkBb.min.y + style.framePadding.y)
if (g.logEnabled)
- logRenderedText(renderTextPos, if (mixedValue) "[~]" else if (v) "[x]" else "[ ]")
+ logRenderedText(labelPos, if (mixedValue) "[~]" else if (v) "[x]" else "[ ]")
if (labelSize.x > 0f)
- renderText(renderTextPos, label)
+ renderText(labelPos, label)
- val flags = ItemStatusFlag.Checkable or if(v) ItemStatusFlag.Checked else ItemStatusFlag.None
+ val flags = ItemStatusFlag.Checkable or if (v) ItemStatusFlag.Checked else none
IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.lastItemData.statusFlags or flags)
return pressed
}
- fun checkboxFlags(label: String, flags: IntArray, flagsValue: Int): Boolean {
- _b = (flags[0] and flagsValue) == flagsValue // ~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
- }
+ // We use JvmName to ensure that the function can be seen in Java as checkboxFlags
+ // Suppressing the warning since we're in an interface.
+ @Suppress("INAPPLICABLE_JVM_NAME")
+ @JvmName("checkboxFlags")
+ fun > checkboxFlags(label: String, flags: FlagArray, flagsValue: Flag): Boolean = checkboxFlagsT(label, flags mutablePropertyAt 0, flagsValue)
- fun checkboxFlags(label: String, flagsPtr: KMutableProperty0, flagsValue: Int): Boolean {
- var flags by flagsPtr
- val v = booleanArrayOf((flags and flagsValue) == flagsValue)
- 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 {
@@ -220,27 +174,34 @@ interface widgetsMain {
renderNavHighlight(totalBb, id)
val col = if (held && hovered) Col.FrameBgActive else if (hovered) Col.FrameBgHovered else Col.FrameBg
- window.drawList.addCircleFilled(center, radius, col.u32, 16)
+ val numSegment = window.drawList._calcCircleAutoSegmentCount(radius)
+ window.drawList.addCircleFilled(center, radius, col.u32, numSegment)
if (active) {
val pad = 1f max floor(squareSz / 6f)
- window.drawList.addCircleFilled(center, radius - pad, Col.CheckMark.u32, 16)
+ window.drawList.addCircleFilled(center, radius - pad, Col.CheckMark.u32)
}
if (style.frameBorderSize > 0f) {
- window.drawList.addCircle(center + Vec2(1), radius, Col.BorderShadow.u32, 16, style.frameBorderSize)
- window.drawList.addCircle(center, radius, Col.Border.u32, 16, style.frameBorderSize)
+ window.drawList.addCircle(center + Vec2(1), radius, Col.BorderShadow.u32, numSegment, style.frameBorderSize)
+ window.drawList.addCircle(center, radius, Col.Border.u32, numSegment, style.frameBorderSize)
}
- val renderTextPos = Vec2(checkBb.max.x + style.itemInnerSpacing.x, checkBb.min.y + style.framePadding.y)
+ val labelPos = Vec2(checkBb.max.x + style.itemInnerSpacing.x, checkBb.min.y + style.framePadding.y)
if (g.logEnabled)
- logRenderedText(renderTextPos, if (active) "(x)" else "( )")
+ logRenderedText(labelPos, if (active) "(x)" else "( )")
if (labelSize.x > 0f)
- renderText(renderTextPos, label)
+ renderText(labelPos, label)
IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.lastItemData.statusFlags)
return pressed
}
+ /** shortcut to handle the above pattern when value is an integer
+ *
+ * FIXME: This would work nicely if it was a public template, e.g. 'template RadioButton(const char* label, T* v, T v_button)', but I'm not sure how we would expose it.. */
+ fun > radioButton(label: String, v: KMutableProperty0, vButton: F): Boolean =
+ radioButton(label, v() == vButton).also { if (it) v.set(vButton) }
+
/** shortcut to handle the above pattern when value is an integer
*
* FIXME: This would work nicely if it was a public template, e.g. 'template RadioButton(const char* label, T* v, T v_button)', but I'm not sure how we would expose it.. */
diff --git a/core/src/main/kotlin/imgui/api/widgetsMenus.kt b/core/src/main/kotlin/imgui/api/widgetsMenus.kt
index 6208b2680..e832da441 100644
--- a/core/src/main/kotlin/imgui/api/widgetsMenus.kt
+++ b/core/src/main/kotlin/imgui/api/widgetsMenus.kt
@@ -1,6 +1,6 @@
package imgui.api
-import gli_.has
+import glm_.has
import glm_.max
import imgui.*
import imgui.ImGui.alignTextToFramePadding
@@ -26,6 +26,7 @@ import imgui.ImGui.pushClipRect
import imgui.ImGui.pushID
import imgui.ImGui.setNavID
import imgui.ImGui.style
+import imgui.internal.classes.FocusRequestFlag
import imgui.internal.classes.Rect
import imgui.internal.round
import imgui.internal.sections.*
@@ -148,12 +149,12 @@ interface widgetsMenus {
// When the user has left the menu layer (typically: closed menus through activation of an item), we restore focus to the previous window
// FIXME: With this strategy we won't be able to restore a NULL focus.
if (g.currentWindow == g.navWindow && g.navLayer == NavLayer.Main && !g.navAnyRequest)
- focusTopMostWindowUnderOne(g.navWindow)
+ focusTopMostWindowUnderOne(g.navWindow, flags = FocusRequestFlag.UnlessBelowModal / FocusRequestFlag.RestoreFocusedChild)
end()
}
- fun beginMenu(label: String, enabled: Boolean = true) = beginMenuEx(label, "", enabled)
+ fun beginMenu(label: String, enabled: Boolean = true): Boolean = beginMenuEx(label, "", enabled)
/** Only call EndMenu() if BeginMenu() returns true! */
fun endMenu() {
diff --git a/core/src/main/kotlin/imgui/api/widgetsSelectables.kt b/core/src/main/kotlin/imgui/api/widgetsSelectables.kt
index 5782b7513..85e1624a9 100644
--- a/core/src/main/kotlin/imgui/api/widgetsSelectables.kt
+++ b/core/src/main/kotlin/imgui/api/widgetsSelectables.kt
@@ -2,30 +2,34 @@ package imgui.api
import glm_.vec2.Vec2
import imgui.*
+import imgui.ImGui.beginDisabled
import imgui.ImGui.buttonBehavior
import imgui.ImGui.calcTextSize
import imgui.ImGui.closeCurrentPopup
import imgui.ImGui.currentWindow
+import imgui.ImGui.endDisabled
import imgui.ImGui.itemAdd
import imgui.ImGui.itemSize
import imgui.ImGui.markItemEdited
import imgui.ImGui.popColumnsBackground
-import imgui.ImGui.endDisabled
import imgui.ImGui.pushColumnsBackground
-import imgui.ImGui.beginDisabled
+import imgui.ImGui.rectAbsToRel
import imgui.ImGui.renderFrame
import imgui.ImGui.renderNavHighlight
import imgui.ImGui.renderTextClipped
-import imgui.ImGui.setItemAllowOverlap
import imgui.ImGui.setNavID
import imgui.ImGui.style
import imgui.ImGui.tablePopBackgroundChannel
import imgui.ImGui.tablePushBackgroundChannel
import imgui.internal.classes.Rect
import imgui.internal.floor
-import imgui.internal.sections.*
+import imgui.internal.sections.ButtonFlags
+import imgui.internal.sections.IMGUI_TEST_ENGINE_ITEM_INFO
+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
@@ -41,7 +45,7 @@ interface widgetsSelectables {
/** Tip: pass a non-visible label (e.g. "##hello") then you can use the space to draw other text or image.
* But you need to make sure the ID is unique, e.g. enclose calls in PushID/PopID or use ##unique_id.
- * With this scheme, ImGuiSelectableFlags_SpanAllColumns and ImGuiSelectableFlags_AllowItemOverlap are also frequently used flags.
+ * With this scheme, ImGuiSelectableFlags_SpanAllColumns and ImGuiSelectableFlags_AllowOverlap are also frequently used flags.
* FIXME: Selectable() with (size.x == 0.0f) and (SelectableTextAlign.x > 0.0f) followed by SameLine() is currently not supported.
*
* "bool selected" carry the selection state (read-only). Selectable() is clicked is returns true so you can modify
@@ -50,7 +54,7 @@ interface widgetsSelectables {
* size.x > 0f -> specify width
* size.y == 0f -> use label height
* size.y > 0f -> specify height */
- fun selectable(label: String, selected_: Boolean = false, flags: SelectableFlags = 0, sizeArg: Vec2 = Vec2()): Boolean {
+ fun selectable(label: String, selected_: Boolean = false, flags: SelectableFlags = none, sizeArg: Vec2 = Vec2()): Boolean {
var selected = selected_
val window = currentWindow
@@ -80,7 +84,7 @@ interface widgetsSelectables {
// Selectables are meant to be tightly packed together with no click-gap, so we extend their box to cover spacing between selectable.
val bb = Rect(minX, pos.y, textMax.x, textMax.y)
if (flags hasnt Sf._NoPadWithHalfSpacing) {
- val spacingX = if(spanAllColumns) 0f else style.itemSpacing.x
+ val spacingX = if (spanAllColumns) 0f else style.itemSpacing.x
val spacingY = style.itemSpacing.y
val spacingL = floor(spacingX * 0.5f)
val spacingU = floor(spacingY * 0.5f)
@@ -100,7 +104,7 @@ interface widgetsSelectables {
}
val disabledItem = flags has Sf.Disabled
- val itemAdd = itemAdd(bb, id, null, if(disabledItem) If.Disabled.i else If.None.i)
+ val itemAdd = itemAdd(bb, id, null, if (disabledItem) If.Disabled else none)
if (spanAllColumns) {
window.clipRect.min.x = backupClipRectMinX
@@ -122,18 +126,20 @@ interface widgetsSelectables {
tablePushBackgroundChannel()
// We use NoHoldingActiveID on menus so user can click and _hold_ on a menu then drag to browse child entries
- var buttonFlags = 0
+ var buttonFlags: ButtonFlags = none
if (flags has Sf._NoHoldingActiveID) buttonFlags /= Bf.NoHoldingActiveId
if (flags has Sf._NoSetKeyOwner) buttonFlags /= Bf.NoSetKeyOwner
if (flags has Sf._SelectOnClick) buttonFlags /= Bf.PressedOnClick
if (flags has Sf._SelectOnRelease) buttonFlags /= Bf.PressedOnRelease
if (flags has Sf.AllowDoubleClick) buttonFlags /= Bf.PressedOnClickRelease or Bf.PressedOnDoubleClick
- if (flags has Sf.AllowItemOverlap) buttonFlags /= Bf.AllowItemOverlap
+ if (flags has Sf.AllowOverlap|| g.lastItemData.inFlags has If.AllowOverlap) buttonFlags /= Bf.AllowOverlap
val wasSelected = selected
- var (pressed, h, held) = buttonBehavior(bb, id, buttonFlags)
- var hovered = h
+ val behaviour = buttonBehavior(bb, id, buttonFlags)
+ var pressed = behaviour[0]
+ val hovered = behaviour[1]
+ val held = behaviour[2]
// Auto-select when moved into
// - This will be more fully fleshed in the range-select branch
@@ -143,22 +149,19 @@ interface widgetsSelectables {
// - (2) usage will fail with clipped items
// The multi-select API aim to fix those issues, e.g. may be replaced with a BeginSelection() API.
if (flags has Sf._SelectOnNav && g.navJustMovedToId != 0 && g.navJustMovedToFocusScopeId == g.currentFocusScopeId)
- if (g.navJustMovedToId == id) {
- selected = true; pressed = true
- }
+ if (g.navJustMovedToId == id) {
+ selected = true; pressed = true
+ }
// Update NavId when clicking or when Hovering (this doesn't happen on most widgets), so navigation can be resumed with gamepad/keyboard
if (pressed || (hovered && flags has Sf._SetNavIdOnHover))
if (!g.navDisableMouseHover && g.navWindow === window && g.navLayer == window.dc.navLayerCurrent) {
- setNavID(id, window.dc.navLayerCurrent, g.currentFocusScopeId, Rect(bb.min - window.pos, bb.max - window.pos)) // (bb == NavRect)
+ setNavID(id, window.dc.navLayerCurrent, g.currentFocusScopeId, window rectAbsToRel bb) // (bb == NavRect)
g.navDisableHighlight = true
}
if (pressed)
markItemEdited(id)
- if (flags has Sf.AllowItemOverlap)
- setItemAllowOverlap()
-
// In this branch, Selectable() cannot toggle the selection so this will never trigger.
if (selected != wasSelected)
g.lastItemData.statusFlags /= ItemStatusFlag.ToggledSelection
@@ -189,11 +192,7 @@ interface widgetsSelectables {
}
/** "bool* p_selected" point to the selection state (read-write), as a convenient helper. */
- fun selectable(label: String, selected: BooleanArray, index: Int, flags: SelectableFlags = 0, size: Vec2 = Vec2()): Boolean =
- selectable(label, selected mutablePropertyAt index, flags, size)
-
- /** "bool* p_selected" point to the selection state (read-write), as a convenient helper. */
- fun selectable(label: String, selectedPtr: KMutableProperty0, flags: SelectableFlags = 0, size: Vec2 = Vec2()): Boolean {
+ fun selectable(label: String, selectedPtr: KMutableProperty0, flags: SelectableFlags = none, size: Vec2 = Vec2()): Boolean {
var selected by selectedPtr
return if (selectable(label, selected, flags, size)) {
selected = !selected
diff --git a/core/src/main/kotlin/imgui/api/widgetsSliders.kt b/core/src/main/kotlin/imgui/api/widgetsSliders.kt
index 2ab52d337..6f1da0321 100644
--- a/core/src/main/kotlin/imgui/api/widgetsSliders.kt
+++ b/core/src/main/kotlin/imgui/api/widgetsSliders.kt
@@ -9,134 +9,75 @@ 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.ActivateFlag
import imgui.internal.sections.IMGUI_TEST_ENGINE_ITEM_INFO
import imgui.internal.sections.ItemFlag
import imgui.internal.sections.ItemStatusFlag
import kool.getValue
import kool.setValue
+import java.lang.Exception
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 = SliderFlag.None.i): Boolean =
- withFloat(v, ptr) { sliderFloat(label, it, 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 = SliderFlag.None.i): 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 = SliderFlag.None.i): 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 = SliderFlag.None.i): 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 = SliderFlag.None.i): 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 = SliderFlag.None.i): 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 = SliderFlag.None.i): 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 = SliderFlag.None.i): 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 = SliderFlag.None.i): Boolean {
+ fun slider2(label: String, v: FloatArray, vMin: Float, vMax: Float, format: String = "%.3f", flags: SliderFlags = none): 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 = none): 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 = none): 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 = none): 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 = none): 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 = none): 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 = none): 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 = SliderFlag.None.i): 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 = SliderFlag.None.i): Boolean =
- sliderScalar(label, DataType.Int, v, vMin.asMutableProperty, vMax.asMutableProperty, format, flags)
+ fun slider2(label: String, v: IntArray, vMin: Int, vMax: Int, format: String = "%d", flags: SliderFlags = none): Boolean = sliderN(label, 2, vMin, vMax, format, flags, v::mutablePropertyAt)
- fun sliderInt2(label: String, v: IntArray, vMin: Int, vMax: Int,
- format: String = "%d", flags: SliderFlags = SliderFlag.None.i): Boolean =
- sliderScalarN(label, DataType.Int, v, 2, vMin.asMutableProperty, vMax.asMutableProperty, format, flags)
+ fun slider2(label: String, v: Vec2i, vMin: Int, vMax: Int, format: String = "%d", flags: SliderFlags = none): Boolean = sliderN(label, 2, vMin, vMax, format, flags, v::mutablePropertyAt)
- fun sliderVec2i(label: String, v: Vec2i, vMin: Int, vMax: Int,
- format: String = "%d", flags: SliderFlags = SliderFlag.None.i): Boolean =
- sliderScalarN(label, DataType.Int, v to _ia, 2, vMin.asMutableProperty, vMax.asMutableProperty, format, flags)
- .also { v put _ia }
+ fun slider3(label: String, v: IntArray, vMin: Int, vMax: Int, format: String = "%d", flags: SliderFlags = none): Boolean = sliderN(label, 3, vMin, vMax, format, flags, v::mutablePropertyAt)
- fun sliderInt3(label: String, v: IntArray, vMin: Int, vMax: Int,
- format: String = "%d", flags: SliderFlags = SliderFlag.None.i): Boolean =
- sliderScalarN(label, DataType.Int, v, 3, vMin.asMutableProperty, vMax.asMutableProperty, format, flags)
+ fun slider3(label: String, v: Vec3i, vMin: Int, vMax: Int, format: String = "%d", flags: SliderFlags = none): Boolean = sliderN(label, 3, vMin, vMax, format, flags, v::mutablePropertyAt)
- fun sliderVec3i(label: String, v: Vec3i, vMin: Int, vMax: Int,
- format: String = "%d", flags: SliderFlags = SliderFlag.None.i): Boolean =
- sliderScalarN(label, DataType.Int, v to _ia, 3, vMin.asMutableProperty, vMax.asMutableProperty, format, flags)
- .also { v put _ia }
+ fun slider4(label: String, v: IntArray, vMin: Int, vMax: Int, format: String = "%d", flags: SliderFlags = none): Boolean = sliderN(label, 4, vMin, vMax, format, flags, v::mutablePropertyAt)
- fun sliderInt4(label: String, v: IntArray, vMin: Int, vMax: Int,
- format: String = "%d", flags: SliderFlags = SliderFlag.None.i): 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 = SliderFlag.None.i): Boolean =
- sliderScalarN(label, DataType.Int, v to _ia, 4, vMin.asMutableProperty, vMax.asMutableProperty, format, flags)
- .also { v put _ia }
+ fun slider4(label: String, v: Vec4i, vMin: Int, vMax: Int, format: String = "%d", flags: SliderFlags = none): 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 = 0): Boolean
- where N : Number, N : Comparable {
+ fun NumberOps.slider(label: String, pData: KMutableProperty0, min: N, max: N, format_: String? = null, flags: SliderFlags = none): Boolean where N : Number, N : Comparable {
val window = ImGui.currentWindow
if (window.skipItems) return false
@@ -150,24 +91,24 @@ 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.i else 0))
+ if (!ImGui.itemAdd(totalBb, id, frameBb, if (tempInputAllowed) ItemFlag.Inputable else none))
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 hovered = ImGui.itemHoverable(frameBb, id, g.lastItemData.inFlags)
var tempInputIsActive = tempInputAllowed && ImGui.tempInputIsActive(id)
if (!tempInputIsActive) {
// Tabbing or CTRL-clicking on Slider turns it into an input box
val inputRequestedByTabbing = tempInputAllowed && g.lastItemData.statusFlags has ItemStatusFlag.FocusedByTabbing
val clicked = hovered && MouseButton.Left.isClicked(id)
- val makeActive = inputRequestedByTabbing || clicked || g.navActivateId == id || g.navActivateInputId == id
+ val makeActive = inputRequestedByTabbing || clicked || g.navActivateId == id
if (makeActive && clicked)
Key.MouseLeft.setOwner(id)
if (makeActive && tempInputAllowed)
- if (inputRequestedByTabbing || (clicked && g.io.keyCtrl) || g.navActivateInputId == id)
+ if (inputRequestedByTabbing || (clicked && g.io.keyCtrl) || (g.navActivateId == id && g.navActivateFlags has ActivateFlag.PreferInput))
tempInputIsActive = true
if (makeActive && !tempInputIsActive) {
@@ -181,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
@@ -195,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) {
@@ -206,9 +146,9 @@ 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)
+ val value = pData().format(format)
if (g.logEnabled)
- logSetNextTextDecoration("{", "}");
+ logSetNextTextDecoration("{", "}")
ImGui.renderTextClipped(frameBb.min, frameBb.max, value, null, Vec2(0.5f))
if (labelSize.x > 0f) {
@@ -216,67 +156,12 @@ interface widgetsSliders {
ImGui.renderText(pos, label)
}
- IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.lastItemData.statusFlags)
- 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 = 0): 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()
+ IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.lastItemData.statusFlags / if (tempInputAllowed) ItemStatusFlag.Inputable else none)
return valueChanged
}
- fun vSliderFloat(label: String, size: Vec2, v: KMutableProperty0, vMin: Float, vMax: Float,
- format: String = "%.3f", flags: SliderFlags = SliderFlag.None.i): 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 = SliderFlag.None.i): 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 = 0): Boolean
- where N : Number, N : Comparable {
+ fun NumberOps.vSlider(label: String, size: Vec2, pData: KMutableProperty0, min: N, max: N, format_: String? = null, flags: SliderFlags = none): Boolean where N : Number, N : Comparable {
val window = ImGui.currentWindow
if (window.skipItems) return false
@@ -291,11 +176,11 @@ 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 hovered = ImGui.itemHoverable(frameBb, id, g.lastItemData.inFlags)
val clicked = hovered && MouseButton.Left.isClicked(id)
- if (clicked || g.navActivateId == id || g.navActivateInputId == id) {
+ if (clicked || g.navActivateId == id) {
if (clicked)
Key.MouseLeft.setOwner(id)
ImGui.setActiveID(id, window)
@@ -314,23 +199,46 @@ 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.i, 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 = none): Boolean where N : Number, N : Comparable =
+ ImGui.slider(label, pData, min, max, format_, flags)
+
+inline fun ImGui.slider(label: String, pData: KMutableProperty0, min: N, max: N, format_: String? = null, flags: SliderFlags = none): 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 = none, properties: (Int) -> KMutableProperty0): Boolean where N : Number, N : Comparable =
+ ImGui.sliderN(label, components, min, max, format, flags, properties)
+
+/** Add multiple sliders on 1 line for compact edition of multiple components */
+inline fun ImGui.sliderN(label: String, components: Int, min: N, max: N, format: String? = null, flags: SliderFlags = none, 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 = none, 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 = none): Boolean where N : Number, N : Comparable =
+ ImGui.vSlider(label, size, pData, min, max, format_, flags)
+
+inline fun ImGui.vSlider(label: String, size: Vec2, pData: KMutableProperty0