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 @@ [![Build Status](https://github.com/kotlin-graphics/imgui/workflows/build/badge.svg)](https://github.com/kotlin-graphics/imgui/actions?workflow=build) [![license](https://img.shields.io/badge/License-MIT-orange.svg)](https://github.com/kotlin-graphics/imgui/blob/master/LICENSE) -[![Release](https://jitpack.io/v/kotlin-graphics/imgui.svg)](https://jitpack.io/#kotlin-graphics/imgui) ![Size](https://github-size-badge.herokuapp.com/kotlin-graphics/imgui.svg) [![Github All Releases](https://img.shields.io/github/downloads/kotlin-graphics/imgui/total.svg)]() @@ -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, min: N, max: N, format_: String? = null, flags: SliderFlags = none): Boolean where N : Number, N : Comparable = + numberOps().vSlider(label, size, pData, min, max, format_, flags) \ No newline at end of file diff --git a/core/src/main/kotlin/imgui/api/widgetsText.kt b/core/src/main/kotlin/imgui/api/widgetsText.kt index 7b9a7ea14..9079b70d7 100644 --- a/core/src/main/kotlin/imgui/api/widgetsText.kt +++ b/core/src/main/kotlin/imgui/api/widgetsText.kt @@ -3,10 +3,12 @@ package imgui.api import glm_.max import glm_.vec2.Vec2 import glm_.vec4.Vec4 -import imgui.* +import imgui.Col +import imgui.Flag import imgui.ImGui.calcItemWidth import imgui.ImGui.calcTextSize import imgui.ImGui.currentWindow +import imgui.ImGui.findRenderedTextEnd import imgui.ImGui.itemAdd import imgui.ImGui.itemSize import imgui.ImGui.popStyleColor @@ -16,12 +18,14 @@ import imgui.ImGui.pushTextWrapPos import imgui.ImGui.renderBullet import imgui.ImGui.renderText import imgui.ImGui.renderTextClipped +import imgui.ImGui.separatorTextEx import imgui.ImGui.style import imgui.ImGui.textEx +import imgui.dsl +import imgui.get import imgui.internal.classes.Rect -import imgui.internal.sections.TextFlag -import imgui.internal.formatString import imgui.internal.formatStringToTempBuffer +import imgui.internal.sections.TextFlag /** Widgets: Text */ @@ -31,6 +35,7 @@ interface widgetsText { * A) doesn't require null terminated string if 'text_end' is specified, * B) it's faster, no memory copy is done, no buffer size limits, recommended for long chunks of text. */ fun textUnformatted(text: String, textEnd: Int = -1) = textEx(text, textEnd, TextFlag.NoWidthForLargeClippedText) + fun textUnformatted(text: ByteArray, textEnd: Int = -1) = textEx(text, textEnd, TextFlag.NoWidthForLargeClippedText) /** formatted text */ fun text(fmt: String, vararg args: Any) { @@ -39,7 +44,7 @@ interface widgetsText { if (window.skipItems) return val textEnd = formatStringToTempBuffer(fmt, *args) - textEx(g.tempBuffer, textEnd, TextFlag.NoWidthForLargeClippedText.i) + textEx(g.tempBuffer, textEnd, TextFlag.NoWidthForLargeClippedText) } /** shortcut for @@ -48,10 +53,7 @@ interface widgetsText { * PopStyleColor(); */ fun textColored(col: Vec4, fmt: String, vararg args: Any) { pushStyleColor(Col.Text, col) - if (fmt == "%s") - textEx(args[0] as String, -1, TextFlag.NoWidthForLargeClippedText) // Skip formatting - else - text(fmt, *args) + text(fmt, *args) popStyleColor() } @@ -61,10 +63,7 @@ interface widgetsText { * popStyleColor() */ fun textDisabled(fmt: String, vararg args: Any) = dsl.withStyleColor(Col.Text, style.colors[Col.TextDisabled]) { - if (fmt == "%s") - textEx(args[0] as String, -1, TextFlag.NoWidthForLargeClippedText) // Skip formatting - else - text(fmt, *args) + text(fmt, *args) } /** shortcut for PushTextWrapPos(0.0f); Text(fmt, ...); PopTextWrapPos();. Note that this won't work on an @@ -74,10 +73,7 @@ interface widgetsText { val needBackup = g.currentWindow!!.dc.textWrapPos < 0f // Keep existing wrap position is one ia already set if (needBackup) pushTextWrapPos(0f) - if (fmt == "%s") - textEx(args[0] as String, -1, TextFlag.NoWidthForLargeClippedText) // Skip formatting - else - text(fmt, *args) + text(fmt, *args) if (needBackup) popTextWrapPos() } @@ -93,7 +89,7 @@ interface widgetsText { val valueTextBegin = g.tempBuffer val valueTextEnd = formatStringToTempBuffer(fmt, args) - val valueSize = calcTextSize(valueTextBegin, valueTextEnd, hideTextAfterDoubleHash = false) + val valueSize = calcTextSize(valueTextBegin, 0, valueTextEnd, false) val labelSize = calcTextSize(label, hideTextAfterDoubleHash = true) val pos = window.dc.cursorPos // [JVM] careful, same instance @@ -117,7 +113,7 @@ interface widgetsText { if (window.skipItems) return - val text = fmt.format(style.locale, *args) + val text = fmt.format(style.locale, *args.map { if (it is Flag<*>) it.i else it }.toTypedArray()) val labelSize = calcTextSize(text, hideTextAfterDoubleHash = false) val totalSize = Vec2(g.fontSize + if (labelSize.x > 0f) (labelSize.x + style.framePadding.x * 2) else 0f, labelSize.y) // Empty text doesn't add padding val pos = Vec2(window.dc.cursorPos) @@ -131,4 +127,20 @@ interface widgetsText { window.drawList.renderBullet(bb.min + Vec2(style.framePadding.x + g.fontSize * 0.5f, g.fontSize * 0.5f), textCol) renderText(bb.min + Vec2(g.fontSize + style.framePadding.x * 2, 0f), text, false) } -} \ No newline at end of file + + // currently: formatted text with an horizontal line + fun separatorText(label: String) { + + val window = currentWindow + if (window.skipItems) + return + + // The SeparatorText() vs SeparatorTextEx() distinction is designed to be considerate that we may want: + // - allow separator-text to be draggable items (would require a stable ID + a noticeable highlight) + // - this high-level entry point to allow formatting? (which in turns may require ID separate from formatted string) + // - because of this we probably can't turn 'const char* label' into 'const char* fmt, ...' + // Otherwise, we can decide that users wanting to drag this would layout a dedicated drag-item, + // and then we can turn this into a format function. + separatorTextEx(0, label, findRenderedTextEnd(label), 0f) + } +} diff --git a/core/src/main/kotlin/imgui/api/widgetsTrees.kt b/core/src/main/kotlin/imgui/api/widgetsTrees.kt index b5527c135..6500d4e4b 100644 --- a/core/src/main/kotlin/imgui/api/widgetsTrees.kt +++ b/core/src/main/kotlin/imgui/api/widgetsTrees.kt @@ -1,6 +1,6 @@ package imgui.api -import gli_.has +import glm_.has import glm_.max import glm_.vec2.Vec2 import imgui.* @@ -31,22 +31,22 @@ interface widgetsTrees { fun treeNode(label: String): Boolean { val window = currentWindow if (window.skipItems) return false - return treeNodeBehavior(window.getID(label), 0, label) + return treeNodeBehavior(window.getID(label), label = label) } /** read the FAQ about why and how to use ID. to align arbitrary text at the same level as a TreeNode() you can use * Bullet(). */ - fun treeNode(strID: String, fmt: String, vararg args: Any): Boolean = treeNodeEx(strID, 0, fmt, *args) + fun treeNode(strID: String, fmt: String, vararg args: Any): Boolean = treeNodeEx(strID, none, fmt, *args) /** read the FAQ about why and how to use ID. to align arbitrary text at the same level as a TreeNode() you can use * Bullet(). */ - fun treeNode(ptrID: Any, fmt: String, vararg args: Any): Boolean = treeNodeEx(ptrID, 0, fmt, *args) + fun treeNode(ptrID: Any, fmt: String, vararg args: Any): Boolean = treeNodeEx(ptrID, none, fmt, *args) /** read the FAQ about why and how to use ID. to align arbitrary text at the same level as a TreeNode() you can use * Bullet(). */ - fun treeNode(intPtr: Long, fmt: String, vararg args: Any): Boolean = treeNodeEx(intPtr, 0, fmt, *args) + fun treeNode(intPtr: Long, fmt: String, vararg args: Any): Boolean = treeNodeEx(intPtr, none, fmt, *args) - fun treeNodeEx(label: String, flags: TreeNodeFlags = 0): Boolean { + fun treeNodeEx(label: String, flags: TreeNodeFlags = none): Boolean { val window = currentWindow if (window.skipItems) return false @@ -58,7 +58,7 @@ interface widgetsTrees { val window = currentWindow if (window.skipItems) return false - val labelEnd = formatStringToTempBuffer(fmt, args) + val labelEnd = formatStringToTempBuffer(fmt, *args) return treeNodeBehavior(window.getID(strID), flags, g.tempBuffer, labelEnd) } @@ -122,7 +122,7 @@ interface widgetsTrees { * treeNodeEx(label, TreeNodeFlag.CollapsingHeader) * You can remove the _NoTreePushOnOpen flag if you want behavior closer to normal TreeNode(). * If returning 'true' the header is open. doesn't indent nor push on ID stack. user doesn't have to call TreePop(). */ - fun collapsingHeader(label: String, flags: TreeNodeFlags = 0): Boolean { + fun collapsingHeader(label: String, flags: TreeNodeFlags = none): Boolean { val window = currentWindow if (window.skipItems) @@ -137,7 +137,7 @@ interface widgetsTrees { * p_visible != NULL && *p_visible == true : show a small close button on the corner of the header, clicking the button will set *p_visible = false * p_visible != NULL && *p_visible == false : do not show the header at all * Do not mistake this with the Open state of the header itself, which you can adjust with SetNextItemOpen() or ImGuiTreeNodeFlags_DefaultOpen. */ - fun collapsingHeader(label: String, visible: KMutableProperty0?, flags_: TreeNodeFlags = 0): Boolean { + fun collapsingHeader(label: String, visible: KMutableProperty0?, flags_: TreeNodeFlags = none): Boolean { val window = currentWindow if (window.skipItems) return false @@ -147,7 +147,7 @@ interface widgetsTrees { val id = window.getID(label) var flags = flags_ or Tnf.CollapsingHeader if (visible != null) - flags = flags or Tnf.AllowItemOverlap or Tnf._ClipLabelForTrailingButton + flags /= Tnf.AllowOverlap or Tnf.ClipLabelForTrailingButton val isOpen = treeNodeBehavior(id, flags, label) if (visible != null) { // Create a small overlapping close button diff --git a/core/src/main/kotlin/imgui/api/windowScrolling.kt b/core/src/main/kotlin/imgui/api/windowScrolling.kt index 1ff97f4ab..2e5c940d7 100644 --- a/core/src/main/kotlin/imgui/api/windowScrolling.kt +++ b/core/src/main/kotlin/imgui/api/windowScrolling.kt @@ -7,7 +7,7 @@ import imgui.ImGui.setScrollFromPosY import imgui.ImGui.setScrollX import imgui.ImGui.setScrollY import imgui.ImGui.style -import imgui.lerp +import imgui.internal.lerp // Windows Scrolling // - Any change of Scroll will be applied at the beginning of next frame in the first call to Begin(). @@ -41,7 +41,7 @@ interface windowScrolling { /** center_x_ratio: 0.0f left of last item, 0.5f horizontal center of last item, 1.0f right of last item. * * adjust scrolling amount to make current cursor position visible. center_x_ratio=0.0: left, 0.5: center, 1.0: right. When using to make a "default/current item" visible, consider using SetItemDefaultFocus() instead. */ - fun setScrollHereX(centerXRatio: Float) { + fun setScrollHereX(centerXRatio: Float = 0.5f) { val window = g.currentWindow!! val spacingX = window.windowPadding.x max style.itemSpacing.x val targetPosX = lerp(g.lastItemData.rect.min.x - spacingX, g.lastItemData.rect.max.x + spacingX, centerXRatio) @@ -64,7 +64,7 @@ interface windowScrolling { window.scrollTargetEdgeSnapDist.y = 0f max (window.windowPadding.y - spacingY) } - fun setScrollFromPosX(localX: Float, centerXratio: Float) = g.currentWindow!!.setScrollFromPosX(localX, centerXratio) + fun setScrollFromPosX(localX: Float, centerXratio: Float = 0.5f) = g.currentWindow!!.setScrollFromPosX(localX, centerXratio) - fun setScrollFromPosY(localY: Float, centerYratio: Float) = g.currentWindow!!.setScrollFromPosY(localY, centerYratio) + fun setScrollFromPosY(localY: Float, centerYratio: Float = 0.5f) = g.currentWindow!!.setScrollFromPosY(localY, centerYratio) } \ No newline at end of file diff --git a/core/src/main/kotlin/imgui/api/windows.kt b/core/src/main/kotlin/imgui/api/windows.kt index 036f9b42e..4c39a069d 100644 --- a/core/src/main/kotlin/imgui/api/windows.kt +++ b/core/src/main/kotlin/imgui/api/windows.kt @@ -1,13 +1,10 @@ package imgui.api -import gli_.has -import gli_.hasnt import glm_.d import glm_.f import glm_.max import glm_.vec2.Vec2 import imgui.* -import imgui.ImGui.bringToDisplayBehind import imgui.ImGui.debugLocateItemResolveWithLastItem import imgui.ImGui.endColumns import imgui.ImGui.errorCheckUsingSetCursorPosToExtendParentBoundaries @@ -17,7 +14,6 @@ import imgui.ImGui.focusWindow import imgui.ImGui.gcAwakeTransientBuffers import imgui.ImGui.io import imgui.ImGui.isMouseHoveringRect -import imgui.ImGui.isWithinBeginStackOf import imgui.ImGui.logFinish import imgui.ImGui.markIniSettingsDirty import imgui.ImGui.navInitWindow @@ -30,14 +26,14 @@ import imgui.ImGui.setLastItemData import imgui.ImGui.setPos import imgui.ImGui.setSize import imgui.ImGui.style -import imgui.ImGui.topMostPopupModal import imgui.ImGui.updateParentAndRootLinks +import imgui.internal.classes.FocusRequestFlag import imgui.internal.classes.Rect import imgui.internal.classes.WindowStackData import imgui.internal.floor import imgui.internal.lengthSqr import imgui.internal.sections.* -import imgui.static.* +import imgui.statics.* import kotlin.math.max import kotlin.reflect.KMutableProperty0 import imgui.WindowFlag as Wf @@ -58,10 +54,7 @@ import imgui.internal.sections.LayoutType as Lt // - Note that the bottom of window stack always contains a window called "Debug". interface windows { - fun begin(name: String, pOpen: BooleanArray, flags: WindowFlags): Boolean = begin(name, pOpen, 0, flags) - - fun begin(name: String, pOpen: BooleanArray?, index: Int, flags: WindowFlags): Boolean = - begin(name, pOpen?.mutablePropertyAt(index), flags) + fun begin(name: String, pOpen: BooleanArray, flags: WindowFlags): Boolean = begin(name, pOpen mutablePropertyAt 0, flags) /** Push a new Dear ImGui window to add widgets to: - A default window called "Debug" is automatically stacked at the beginning of every frame so you can use @@ -78,7 +71,7 @@ interface windows { @return isOpen */ - fun begin(name: String, pOpen: KMutableProperty0? = null, flags_: WindowFlags = 0): Boolean { + fun begin(name: String, pOpen: KMutableProperty0? = null, flags_: WindowFlags = none): Boolean { assert(name.isNotEmpty()) { "Window name required" } assert(g.withinFrameScope) { "Forgot to call ImGui::newFrame()" } @@ -88,11 +81,13 @@ interface windows { // Find or create var windowJustCreated = false - val window = findWindowByName(name) ?: createNewWindow(name, flags).also { windowJustCreated = true } + val window = findWindowByName(name) ?: createNewWindow(name, flags).also { + windowJustCreated = true + } // Automatically disable manual moving/resizing when NoInputs is set - if ((flags and Wf.NoInputs) == Wf.NoInputs.i) - flags = flags or Wf.NoMove or Wf.NoResize + if (Wf.NoInputs in flags) + flags /= Wf.NoMove or Wf.NoResize if (flags has Wf._NavFlattened) assert(flags has Wf._ChildWindow) @@ -110,7 +105,7 @@ interface windows { } window.appearing = windowJustActivatedByUser if (window.appearing) - window.setConditionAllowFlags(Cond.Appearing.i, true) + window.setConditionAllowFlags(Cond.Appearing, true) // Update Flags, LastFrameActive, BeginOrderXXX fields if (firstBeginOfTheFrame) { @@ -139,7 +134,7 @@ interface windows { val windowStackData = WindowStackData() windowStackData.window = window windowStackData.parentLastItemDataBackup put g.lastItemData - windowStackData.stackSizesOnBegin.setToCurrentState() + windowStackData.stackSizesOnBegin.setToCurrentState(g) g.currentWindowStack += windowStackData if (flags has Wf._ChildMenu) g.beginMenuCount++ @@ -171,11 +166,12 @@ interface windows { var windowSizeYsetByApi = false if (g.nextWindowData.flags has NextWindowDataFlag.HasPos) { windowPosSetByApi = window.setWindowPosAllowFlags has g.nextWindowData.posCond - if (windowPosSetByApi && g.nextWindowData.posPivotVal.lengthSqr > 0.00001f) {/* May be processed on the next frame if this is our first frame and we are measuring size - FIXME: Look into removing the branch so everything can go through this same code path for consistency. */ + if (windowPosSetByApi && g.nextWindowData.posPivotVal.lengthSqr > 0.00001f) { + // May be processed on the next frame if this is our first frame and we are measuring size + // FIXME: Look into removing the branch so everything can go through this same code path for consistency. window.setWindowPosVal put g.nextWindowData.posVal window.setWindowPosPivot put g.nextWindowData.posPivotVal - window.setWindowPosAllowFlags = window.setWindowPosAllowFlags and (Cond.Once or Cond.FirstUseEver or Cond.Appearing).inv() + window.setWindowPosAllowFlags = window.setWindowPosAllowFlags wo (Cond.Once or Cond.FirstUseEver or Cond.Appearing) } else window.setPos(g.nextWindowData.posVal, g.nextWindowData.posCond) } if (g.nextWindowData.flags has NextWindowDataFlag.HasSize) { @@ -201,7 +197,7 @@ interface windows { window.setCollapsed(g.nextWindowData.collapsedVal, g.nextWindowData.collapsedCond) if (g.nextWindowData.flags has NextWindowDataFlag.HasFocus) focusWindow(window) if (window.appearing) - window.setConditionAllowFlags(Cond.Appearing.i, false) + window.setConditionAllowFlags(Cond.Appearing, false) // When reusing window again multiple times a frame, just append content (don't need to setup again) if (firstBeginOfTheFrame) { @@ -244,9 +240,8 @@ interface windows { if (windowJustCreated && (!windowSizeXsetByApi || !windowSizeYsetByApi)) window.hiddenFramesCannotSkipItems = 1 - /* Hide popup/tooltip window when re-opening while we measure size (because we recycle the windows) - We reset Size/SizeContents for reappearing popups/tooltips early in this function, - so further code won't be tempted to use the old size. */ + // Hide popup/tooltip window when re-opening while we measure size (because we recycle the windows) + // We reset Size/ContentSize for reappearing popups/tooltips early in this function, so further code won't be tempted to use the old size. if (windowJustActivatedByUser && flags has (Wf._Popup or Wf._Tooltip)) { window.hiddenFramesCannotSkipItems = 1 if (flags has Wf.AlwaysAutoResize) { @@ -278,9 +273,9 @@ interface windows { else -> style.windowBorderSize } window.windowPadding put style.windowPadding - if (flags has Wf._ChildWindow && !(flags has (Wf.AlwaysUseWindowPadding or Wf._Popup)) && window.windowBorderSize == 0f) window.windowPadding.put( - 0f, - if (flags has Wf.MenuBar) style.windowPadding.y else 0f) + if (flags has Wf._ChildWindow && !(flags has (Wf.AlwaysUseWindowPadding or Wf._Popup)) && window.windowBorderSize == 0f) + window.windowPadding.put(0f, + if (flags has Wf.MenuBar) style.windowPadding.y else 0f) // Lock menu offset so size calculation can use it as menu-bar windows need a minimum size. window.dc.menuBarOffset.x = (window.windowPadding.x max style.itemSpacing.x) max g.nextWindowData.menuBarOffsetMinVal.x @@ -332,13 +327,11 @@ interface windows { // Auto-fit may only grow window during the first few frames // We still process initial auto-fit on collapsed windows to get a window width, but otherwise don't honor ImGuiWindowFlags_AlwaysAutoResize when collapsed. if (!windowSizeXsetByApi && window.autoFitFrames.x > 0) { - window.sizeFull.x = - if (window.autoFitOnlyGrows) max(window.sizeFull.x, sizeAutoFit.x) else sizeAutoFit.x + window.sizeFull.x = if (window.autoFitOnlyGrows) max(window.sizeFull.x, sizeAutoFit.x) else sizeAutoFit.x useCurrentSizeForScrollbarX = true } if (!windowSizeYsetByApi && window.autoFitFrames.y > 0) { - window.sizeFull.y = - if (window.autoFitOnlyGrows) max(window.sizeFull.y, sizeAutoFit.y) else sizeAutoFit.y + window.sizeFull.y = if (window.autoFitOnlyGrows) max(window.sizeFull.y, sizeAutoFit.y) else sizeAutoFit.y useCurrentSizeForScrollbarY = true } if (!window.collapsed) window.markIniSettingsDirty() @@ -415,42 +408,26 @@ interface windows { wantFocus = true if (flags hasnt (Wf._ChildWindow or Wf._Tooltip)) wantFocus = true - - val modal = topMostPopupModal - if (modal != null && !window.isWithinBeginStackOf(modal)) { - // Avoid focusing a window that is created outside of active modal. This will prevent active modal from being closed. - // Since window is not focused it would reappear at the same display position like the last time it was visible. - // In case of completely new windows it would go to the top (over current modal), but input to such window would still be blocked by modal. - // Position window behind a modal that is not a begin-parent of this window. - wantFocus = false - if (window === window.rootWindow) { - val blockingModal = findBlockingModal(window) - check(blockingModal != null) - window bringToDisplayBehind blockingModal - } - } } - // [Test Engine] Register whole window in the item system + // [Test Engine] Register whole window in the item system (before submitting further decorations) if (IMGUI_ENABLE_TEST_ENGINE && g.testEngineHookItems) { assert(window.idStack.size == 1) val id = window.idStack.pop() // As window->IDStack[0] == window->ID here, make sure TestEngine doesn't erroneously see window as parent of itself. - IMGUI_TEST_ENGINE_ITEM_ADD(window.rect(), window.id) - IMGUI_TEST_ENGINE_ITEM_INFO(window.id, window.name, if (g.hoveredWindow === window) ItemStatusFlag.HoveredRect.i else ItemStatusFlag.None.i) + IMGUI_TEST_ENGINE_ITEM_ADD(window.id, window.rect(), null) + IMGUI_TEST_ENGINE_ITEM_INFO(window.id, window.name, if (g.hoveredWindow === window) ItemStatusFlag.HoveredRect else none) window.idStack += id } // Handle manual resize: Resize Grips, Borders, Gamepad var borderHeld = -1 val resizeGripCol = IntArray(4) - val resizeGripCount = - if (io.configWindowsResizeFromEdges) 2 else 1 // Allow resize from lower-left if we have the mouse cursor feedback for it. + val resizeGripCount = if (io.configWindowsResizeFromEdges) 2 else 1 // Allow resize from lower-left if we have the mouse cursor feedback for it. val resizeGripDrawSize = floor(max(g.fontSize * 1.1f, window.windowRounding + 1f + g.fontSize * 0.2f)) if (!window.collapsed) { val (held, ret) = window.updateManualResize(sizeAutoFit, borderHeld, resizeGripCount, resizeGripCol, visibilityRect) if (ret) { - useCurrentSizeForScrollbarX = true - useCurrentSizeForScrollbarY = true + useCurrentSizeForScrollbarX = true; useCurrentSizeForScrollbarY = true } borderHeld = held } @@ -545,6 +522,8 @@ interface windows { window.scrollMax.y = max(0f, window.contentSize.y + window.windowPadding.y * 2f - window.innerRect.height) // Apply scrolling + if (g.frameCount >= 746 && name == "Dear ImGui Test Engine/Log_4C7FD7FA/Log_CAB00879") + print("") window.scroll = window.calcNextScrollFromScrollTargetAndClamp() window.scrollTarget put Float.MAX_VALUE window.decoInnerSizeX1 = 0f; window.decoInnerSizeY1 = 0f @@ -567,8 +546,8 @@ interface windows { // - We disable this when the parent window has zero vertices, which is a common pattern leading to laying out multiple overlapping childs val previousChild = if (parentWindow!!.dc.childWindows.size >= 2) parentWindow.dc.childWindows[parentWindow.dc.childWindows.size - 2] else null val previousChildOverlapping = previousChild?.rect()?.overlaps(window.rect()) ?: false - val parentIsEmpty = parentWindow.drawList.vtxBuffer.rem > 0 - if (window.drawList.cmdBuffer.last().elemCount == 0 && parentIsEmpty && !previousChildOverlapping) + val parentIsEmpty = parentWindow.drawList.vtxBuffer.rem == 0 + if (window.drawList.cmdBuffer.last().elemCount == 0 && !parentIsEmpty && !previousChildOverlapping) renderDecorationsInParent = true } if (renderDecorationsInParent) @@ -641,8 +620,9 @@ interface windows { dc.navLayerCurrent = NavLayer.Main dc.navLayersActiveMask = dc.navLayersActiveMaskNext dc.navLayersActiveMaskNext = 0x00 + dc.navIsScrollPushableX = true dc.navHideHighlightOneFrame = false - dc.navHasScroll = scrollMax.y > 0f + dc.navWindowHasScrollY = scrollMax.y > 0f dc.menuBarAppending = false dc.menuColumns.update(style.itemSpacing.x, windowJustActivatedByUser) @@ -664,10 +644,13 @@ interface windows { } // Apply focus (we need to call FocusWindow() AFTER setting DC.CursorStartPos so our initial navigation reference rectangle can start around there) - if (wantFocus) { - focusWindow(window) - navInitWindow(window, false) - } + // We ImGuiFocusRequestFlags_UnlessBelowModal to: + // - Avoid focusing a window that is created outside of a modal. This will prevent active modal from being closed. + // - Position window behind the modal that is not a begin-parent of this window. + if (wantFocus) + focusWindow(window, flags = FocusRequestFlag.UnlessBelowModal) + if (wantFocus && window === g.navWindow) + navInitWindow(window, false) // <-- this is in the way for us to be able to defer and sort reappearing FocusWindow() calls // Title bar if (flags hasnt Wf.NoTitleBar) { @@ -690,18 +673,18 @@ interface windows { // We fill last item data based on Title Bar/Tab, in order for IsItemHovered() and IsItemActive() to be usable after Begin(). // This is useful to allow creating context menus on title bar only, etc. - val itemFlag = if (isMouseHoveringRect(titleBarRect.min, titleBarRect.max, false)) ItemStatusFlag.HoveredRect else ItemStatusFlag.None - setLastItemData(window.moveId, g.currentItemFlags, itemFlag.i, titleBarRect) + val itemFlag = if (isMouseHoveringRect(titleBarRect.min, titleBarRect.max, false)) ItemStatusFlag.HoveredRect else none + setLastItemData(window.moveId, g.currentItemFlags, itemFlag, titleBarRect) // [DEBUG] if (!IMGUI_DISABLE_DEBUG_TOOLS) if (g.debugLocateId != 0 && (window.id == g.debugLocateId || window.moveId == g.debugLocateId)) debugLocateItemResolveWithLastItem() - // [Test Engine] Register title bar / tab + // [Test Engine] Register title bar / tab with MoveId. if (IMGUI_ENABLE_TEST_ENGINE) if (window.flags hasnt Wf.NoTitleBar) - IMGUI_TEST_ENGINE_ITEM_ADD(g.lastItemData.rect, g.lastItemData.id) + IMGUI_TEST_ENGINE_ITEM_ADD(g.lastItemData.id, g.lastItemData.rect, g.lastItemData) } else // Append setCurrentWindow(window) @@ -737,20 +720,28 @@ interface windows { window.apply { // Update the Hidden flag - val hiddenRegulare = hiddenFramesCanSkipItems > 0 || hiddenFramesCannotSkipItems > 0 || hiddenFramesForRenderOnly > 0 - hidden = hiddenRegulare || hiddenFramesForRenderOnly > 0 + val hiddenRegular = hiddenFramesCanSkipItems > 0 || hiddenFramesCannotSkipItems > 0 || hiddenFramesForRenderOnly > 0 + hidden = hiddenRegular || hiddenFramesForRenderOnly > 0 // Disable inputs for requested number of frames if (disableInputsFrames > 0) { disableInputsFrames-- - flags = flags or Wf.NoInputs + flags /= Wf.NoInputs } // Update the SkipItems flag, used to early out of all items functions (no layout required) - skipItems = (collapsed || !active || hiddenRegulare) && autoFitFrames allLessThanEqual 0 && hiddenFramesCannotSkipItems <= 0 + skipItems = (collapsed || !active || hiddenRegular) && autoFitFrames allLessThanEqual 0 && hiddenFramesCannotSkipItems <= 0 } } + // [DEBUG] io.ConfigDebugBeginReturnValue override return value to test Begin/End and BeginChild/EndChild behaviors. + // (The implicit fallback window is NOT automatically ended allowing it to always be able to receive commands without crashing) + if (!window.isFallbackWindow && (g.io.configDebugBeginReturnValueOnce && windowJustCreated || g.io.configDebugBeginReturnValueLoop && g.debugBeginReturnValueCullDepth == g.currentWindowStack.size)) { + if (window.autoFitFrames.x > 0) window.autoFitFrames.x++ + if (window.autoFitFrames.y > 0) window.autoFitFrames.y++ + return false + } + return !window.skipItems } @@ -789,7 +780,7 @@ interface windows { g.beginMenuCount-- if (window.flags has Wf._Popup) g.beginPopupStack.pop() - g.currentWindowStack.last().stackSizesOnBegin.compareWithCurrentState() + g.currentWindowStack.last().stackSizesOnBegin.compareWithCurrentState(g) g.currentWindowStack.pop() setCurrentWindow(g.currentWindowStack.lastOrNull()?.window) } diff --git a/core/src/main/kotlin/imgui/api/windowsUtilities.kt b/core/src/main/kotlin/imgui/api/windowsUtilities.kt index e12756813..6e49d95bb 100644 --- a/core/src/main/kotlin/imgui/api/windowsUtilities.kt +++ b/core/src/main/kotlin/imgui/api/windowsUtilities.kt @@ -1,8 +1,8 @@ package imgui.api -import glm_.hasnt import glm_.vec2.Vec2 import imgui.* +import imgui.HoveredFlag import imgui.ImGui.currentWindow import imgui.ImGui.currentWindowRead import imgui.ImGui.findWindowByName @@ -31,10 +31,7 @@ interface windowsUtilities { get() = currentWindowRead!!.collapsed /** is current window focused? or its root/child, depending on flags. see flags for options. */ - fun isWindowFocused(flag: Ff): Boolean = isWindowFocused(flag.i) - - /** is current window focused? or its root/child, depending on flags. see flags for options. */ - fun isWindowFocused(flags: FocusedFlags = Ff.None.i): Boolean { + fun isWindowFocused(flags: FocusedFlags = none): Boolean { val refWindow = g.navWindow ?: return false var curWindow = g.currentWindow @@ -44,28 +41,27 @@ interface windowsUtilities { check(curWindow != null) { "Not inside a Begin() / End()" } val popupHierarchy = flags hasnt Ff.NoPopupHierarchy - if (flags has Hf.RootWindow) + if (flags has Ff.RootWindow) curWindow = getCombinedRootWindow(curWindow, popupHierarchy) return when { - flags has Hf.ChildWindows -> refWindow.isChildOf(curWindow, popupHierarchy) + flags has Ff.ChildWindows -> refWindow.isChildOf(curWindow, popupHierarchy) else -> refWindow === curWindow } } - /** iis current window hovered (and typically: not blocked by a popup/modal)? see flag for options. */ - fun isWindowHovered(flag: Hf) = isWindowHovered(flag.i) /** Is current window hovered (and typically: not blocked by a popup/modal)? see flags for options. * NB: If you are trying to check whether your mouse should be dispatched to imgui or to your app, you should use * the 'io.wantCaptureMouse' boolean for that! Please read the FAQ! */ - fun isWindowHovered(flags: HoveredFlags = Hf.None.i): Boolean { - assert(flags hasnt (Hf.AllowWhenOverlapped or Hf.AllowWhenDisabled)) { "Flags not supported by this function" } + fun isWindowHovered(flags: HoveredFlags = none): Boolean { + + assert((flags wo HoveredFlag.AllowedMaskForIsWindowHovered) == none) { "Invalid flags for IsWindowHovered()!" } val refWindow = g.hoveredWindow ?: return false var curWindow = g.currentWindow if (flags hasnt Hf.AnyWindow) { - check(curWindow != null) { "Not inside a Begin () / End()" } + check(curWindow != null) { "Not inside a Begin()/End()" } val popupHierarchy = flags hasnt Hf.NoPopupHierarchy if (flags has Hf.RootWindow) curWindow = getCombinedRootWindow(curWindow, popupHierarchy) @@ -83,6 +79,7 @@ interface windowsUtilities { if (flags hasnt Hf.AllowWhenBlockedByActiveItem) if (g.activeId != 0 && !g.activeIdAllowOverlap && g.activeId != refWindow.moveId) return false + return true } diff --git a/core/src/main/kotlin/imgui/classes/Context.kt b/core/src/main/kotlin/imgui/classes/Context.kt index 66a47dbff..a872f2125 100644 --- a/core/src/main/kotlin/imgui/classes/Context.kt +++ b/core/src/main/kotlin/imgui/classes/Context.kt @@ -10,18 +10,18 @@ import imgui.ImGui.saveIniSettingsToDisk import imgui.ImGui.tableSettingsAddSettingsHandler import imgui.api.g import imgui.api.gImGui +import imgui.api.gImGuiNullable import imgui.font.Font import imgui.font.FontAtlas -import imgui.internal.BitArray import imgui.internal.DrawChannel import imgui.internal.classes.* import imgui.internal.hashStr import imgui.internal.sections.* -import imgui.static.* +import imgui.statics.* +import org.lwjgl.system.Platform import java.io.File import java.nio.ByteBuffer import java.util.* -import kotlin.collections.ArrayList /** Main Dear ImGui context * @@ -33,13 +33,7 @@ class Context(sharedFontAtlas: FontAtlas? = null) { /** Io.Fonts-> is owned by the ImGuiContext and will be destructed along with it. */ var fontAtlasOwnedByContext = sharedFontAtlas == null - var io = IO(sharedFontAtlas) - - /** Input events which will be tricked/written into IO structure. */ - val inputEventsQueue = ArrayList() - - /** Past input events processed in NewFrame(). This is to allow domain-specific application to access e.g mouse/pen trail. */ - val inputEventsTrail = ArrayList() + var io = IO(sharedFontAtlas).apply { ctx = this@Context } var style = Style() @@ -79,6 +73,18 @@ class Context(sharedFontAtlas: FontAtlas? = null) { /** Test engine user data */ var testEngine: Any? = null + // Inputs + + /** Input events which will be tricked/written into IO structure. */ + val inputEventsQueue = ArrayList() + + /** Past input events processed in NewFrame(). This is to allow domain-specific application to access e.g mouse/pen trail. */ + val inputEventsTrail = ArrayList() + + var inputEventsNextMouseSource: MouseSource = MouseSource.Mouse + + var inputEventsNextEventId = 1u + // Windows state @@ -179,10 +185,10 @@ class Context(sharedFontAtlas: FontAtlas? = null) { var activeIdWindow: Window? = null - /** Activating with mouse or nav (gamepad/keyboard) */ + /** Activating source: ImGuiInputSource_Mouse OR ImGuiInputSource_Keyboard OR ImGuiInputSource_Gamepad */ var activeIdSource = InputSource.None - var activeIdMouseButton = -1 + var activeIdMouseButton = MouseButton.None var activeIdPreviousFrame: ID = 0 @@ -220,7 +226,7 @@ class Context(sharedFontAtlas: FontAtlas? = null) { var currentFocusScopeId: ID = 0 /** == g.ItemFlagsStack.back() */ - var currentItemFlags = ItemFlag.None.i + var currentItemFlags: ItemFlags = none /** Storage for DebugLocateItemOnHover() feature: this is read by ItemAdd() so we keep it in a hot/cached location */ var debugLocateId: ID = 0 @@ -281,19 +287,16 @@ class Context(sharedFontAtlas: FontAtlas? = null) { /** Identify a selection scope (selection code often wants to "clear other items" when landing on an item of the selection set) */ var navFocusScopeId = 0 - /** ~~ (g.ActiveId == 0) && (IsKeyPressed(ImGuiKey_Space) || IsKeyPressed(ImGuiKey_NavGamepadActivate)) ? NavId : 0, also set when calling ActivateItem() */ + /** ~~ (g.ActiveId == 0) && (IsKeyPressed(ImGuiKey_Space) || IsKeyDown(ImGuiKey_Enter) || IsKeyPressed(ImGuiKey_NavGamepadActivate)) ? NavId : 0, also set when calling ActivateItem() */ var navActivateId: ID = 0 - /** ~~ IsKeyDown(ImGuiKey_Space) || IsKeyDown(ImGuiKey_NavGamepadActivate) ? NavId : 0 */ + /** ~~ IsKeyDown(ImGuiKey_Space) || IsKeyDown(ImGuiKey_Enter) || IsKeyDown(ImGuiKey_NavGamepadActivate) ? NavId : 0 */ var navActivateDownId: ID = 0 - /** ~~ IsKeyPressed(ImGuiKey_Space) || IsKeyPressed(ImGuiKey_NavGamepadActivate) ? NavId : 0 (no repeat) */ + /** ~~ IsKeyPressed(ImGuiKey_Space) || IsKeyDown(ImGuiKey_Enter) || IsKeyPressed(ImGuiKey_NavGamepadActivate) ? NavId : 0 (no repeat) */ var navActivatePressedId: ID = 0 - /** ~~ IsKeyPressed(ImGuiKey_Enter) || IsKeyPressed(ImGuiKey_NavGamepadInput) ? NavId : 0; ImGuiActivateFlags_PreferInput will be set and NavActivateId will be 0. */ - var navActivateInputId: ID = 0 - - var navActivateFlags = ActivateFlag.None.i + var navActivateFlags: ActivateFlags = none /** Just navigated to this id (result of a successfully MoveRequest) */ var navJustMovedToId: ID = 0 @@ -301,15 +304,15 @@ class Context(sharedFontAtlas: FontAtlas? = null) { /** Just navigated to this focus scope id (result of a successfully MoveRequest). */ var navJustMovedToFocusScopeId: ID = 0 - var navJustMovedToKeyMods: KeyChord = Key.Mod_None.i + var navJustMovedToKeyMods: KeyChord = Key.Mod_None /** Set by ActivateItem(), queued until next frame */ var navNextActivateId: ID = 0 - var navNextActivateFlags = ActivateFlag.None.i + var navNextActivateFlags: ActivateFlags = none - /** Keyboard or Gamepad mode? THIS WILL ONLY BE None or NavGamepad or NavKeyboard. */ - var navInputSource = InputSource.None + /** Keyboard or Gamepad mode? THIS CAN ONLY BE ImGuiInputSource_Keyboard or ImGuiInputSource_Mouse */ + var navInputSource = InputSource.Keyboard /** Layer we are navigating on. For now the system is hard-coded for 0 = main contents and 1 = menu/title bar, * may expose layers later. */ @@ -341,10 +344,7 @@ class Context(sharedFontAtlas: FontAtlas? = null) { var navInitRequestFromMove = false /** Init request result (first item of the window, or one for which SetItemDefaultFocus() was called) */ - var navInitResultId: ID = 0 - - /** Init request result rectangle (relative to parent window) */ - var navInitResultRectRel = Rect() + val navInitResult = NavItemData() /** Move request submitted, will process result on next NewFrame() */ var navMoveSubmitted = false @@ -354,11 +354,11 @@ class Context(sharedFontAtlas: FontAtlas? = null) { var navMoveForwardToNextFrame = false - var navMoveFlags: NavMoveFlags = NavMoveFlag.None.i + var navMoveFlags: NavMoveFlags = none - var navMoveScrollFlags = ScrollFlag.None.i + var navMoveScrollFlags: ScrollFlags = none - var navMoveKeyMods: KeyChord = Key.Mod_None.i + var navMoveKeyMods: KeyChord = Key.Mod_None /** Direction of the move request (left/right/up/down), direction of the previous move request */ var navMoveDir = Dir.None @@ -433,8 +433,6 @@ class Context(sharedFontAtlas: FontAtlas? = null) { /** 0.0..1.0 animation when fading in a dimming background (for modal window and CTRL+TAB list) */ var dimBgRatio = 0f - var mouseCursor = MouseCursor.Arrow - //------------------------------------------------------------------ // Drag and Drop @@ -447,7 +445,7 @@ class Context(sharedFontAtlas: FontAtlas? = null) { /** Set when within a BeginDragDropXXX/EndDragDropXXX block for a drag target. */ var dragDropWithinTarget = false - var dragDropSourceFlags = DragDropFlag.None.i + var dragDropSourceFlags: DragDropFlags = none var dragDropSourceFrameCount = -1 @@ -460,7 +458,7 @@ class Context(sharedFontAtlas: FontAtlas? = null) { var dragDropTargetId: ID = 0 - var dragDropAcceptFlags = DragDropFlag.None.i + var dragDropAcceptFlags: DragDropFlags = none /** Target item surface (we resolve overlapping targets by prioritizing the smaller surface) */ var dragDropAcceptIdCurrRectSurface = 0f @@ -505,44 +503,67 @@ class Context(sharedFontAtlas: FontAtlas? = null) { // Tab bars var currentTabBar: TabBar? = null - val tabBars = TabBarPool() + val tabBars = Pool { TabBar() } val currentTabBarStack = Stack() val shrinkWidthBuffer = ArrayList() // Hover Delay system - var hoverDelayId: ID = 0 - var hoverDelayIdPreviousFrame: ID = 0 + var hoverItemDelayId: ID = 0 + var hoverItemDelayIdPreviousFrame: ID = 0 + + /** Currently used by IsItemHovered() */ + var hoverItemDelayTimer = 0f + + /** Currently used by IsItemHovered(): grace time before g.TooltipHoverTimer gets cleared. */ + var hoverItemDelayClearTimer = 0f - /** Currently used IsItemHovered(), generally inferred from g.HoveredIdTimer but kept uncleared until clear timer elapse. */ - var hoverDelayTimer = 0f + /** Mouse has once been stationary on this item. Only reset after departing the item. */ + var hoverItemUnlockedStationaryId: ID = 0 - /** Currently used IsItemHovered(): grace time before g.TooltipHoverTimer gets cleared. */ - var hoverDelayClearTimer = 0f + /** Mouse has once been stationary on this window. Only reset after departing the window. */ + var hoverWindowUnlockedStationaryId: ID = 0 //------------------------------------------------------------------ - // Widget state + // Mouse state //------------------------------------------------------------------ + var mouseCursor = MouseCursor.Arrow + + /** Time the mouse has been stationary (with some loose heuristic) */ + var mouseStationaryTimer = 0f val mouseLastValidPos = Vec2() + + //------------------------------------------------------------------ + // Widget state + //------------------------------------------------------------------ + var inputTextState = InputTextState(this) + val inputTextDeactivatedState = InputTextDeactivatedState() + var inputTextPasswordFont = Font() /** Temporary text input when CTRL+clicking on a slider, etc. */ var tempInputId: ID = 0 /** Store user options for color edit widgets */ - var colorEditOptions: ColorEditFlags = ColorEditFlag.DefaultOptions.i + var colorEditOptions: ColorEditFlags = ColorEditFlag.DefaultOptions + + /** Set temporarily while inside of the parent-most ColorEdit4/ColorPicker4 (because they call each others). */ + var colorEditCurrentID: ID = 0 + + /** ID we are saving/restoring HS for */ + var colorEditSavedID: ID = 0 /** Backup of last Hue associated to LastColor, so we can restore Hue in lossy RGB<>HSV round trips */ - var colorEditLastHue = 0f + var colorEditSavedHue = 0f /** Backup of last Saturation associated to LastColor, so we can restore Saturation in lossy RGB<>HSV round trips */ - var colorEditLastSat = 0f + var colorEditSavedSat = 0f - var colorEditLastColor = 0 + var colorEditSavedColor = 0 /** Initial/reference color at the time of opening the color picker. */ val colorPickerRef = Vec4() @@ -576,7 +597,8 @@ class Context(sharedFontAtlas: FontAtlas? = null) { var tooltipOverrideCount = 0 /** If no custom clipboard handler is defined */ - var clipboardHandlerData = "" + // [JVM] useless +// var clipboardHandlerData = "" /** A list of menu IDs that were rendered at least once */ val menusIdSubmittedThisFrame = ArrayList() @@ -659,14 +681,19 @@ class Context(sharedFontAtlas: FontAtlas? = null) { // Debug Tools - var debugLogFlags = DebugLogFlag.OutputToTTY or DebugLogFlag.EventMask_ wo DebugLogFlag.EventClipper + var debugLogFlags: DebugLogFlags = if (DEBUG) DebugLogFlag.OutputToTTY or DebugLogFlag.EventMask wo DebugLogFlag.EventClipper else none val debugLogBuf = StringBuilder() val debugLogIndex = TextIndex() + var debugLogClipperAutoDisableFrames = 0 + /** For DebugLocateItemOnHover(). This is used together with DebugLocateId which is in a hot/cached spot above. */ var debugLocateFrames = 0 + /** Cycle between 0..9 then wrap around. */ + var debugBeginReturnValueCullDepth = -1 + /** Item picker is active (started with DebugStartItemPicker()) */ var debugItemPickerActive = false @@ -736,6 +763,13 @@ class Context(sharedFontAtlas: FontAtlas? = null) { // Setup default localization table localizeRegisterEntries(gLocalizationEntriesEnUS) + // Setup default platform clipboard/IME handlers. + g.io.getClipboardTextFn = getClipboardTextFn_DefaultImpl // Platform dependent default implementations + g.io.setClipboardTextFn = setClipboardTextFn_DefaultImpl + g.io.clipboardUserData = g // Default implementation use the ImGuiContext as user data (ideally those would be arguments to the function) + if (Platform.get() == Platform.WINDOWS) + g.io.setPlatformImeDataFn = setPlatformImeDataFn_DefaultImpl + // Create default viewport val viewport = ViewportP() g.viewports += viewport @@ -806,7 +840,7 @@ class Context(sharedFontAtlas: FontAtlas? = null) { tablesTempData.clear() drawChannelsTempMergeBuffer.clear() // TODO check if this needs proper deallocation - clipboardHandlerData = "" +// clipboardHandlerData = "" menusIdSubmittedThisFrame.clear() inputTextState.textW = CharArray(0) inputTextState.initialTextA = ByteArray(0) @@ -834,13 +868,13 @@ class Context(sharedFontAtlas: FontAtlas? = null) { // ctx = GImGui; setCurrent() shutdown() - if (prevCtx !== this) - prevCtx?.setCurrent() + gImGuiNullable = if (prevCtx !== this) prevCtx else null } companion object { // IMPORTANT: ###xxx suffixes must be same in ALL languages val gLocalizationEntriesEnUS = listOf( + LocEntry(LocKey.VersionStr, "Dear ImGui $IMGUI_VERSION ($IMGUI_VERSION_NUM)"), LocEntry(LocKey.TableSizeOne, "Size column to fit###SizeOne"), LocEntry(LocKey.TableSizeAllFit, "Size all columns to fit###SizeAll"), LocEntry(LocKey.TableSizeAllDefault, "Size all columns to default###SizeAll"), diff --git a/core/src/main/kotlin/imgui/classes/DrawList.kt b/core/src/main/kotlin/imgui/classes/DrawList.kt index dc1b5a7b3..3e97f219b 100644 --- a/core/src/main/kotlin/imgui/classes/DrawList.kt +++ b/core/src/main/kotlin/imgui/classes/DrawList.kt @@ -1,6 +1,6 @@ package imgui.classes -import gli_.hasnt +import glm_.hasnt import glm_.* import glm_.func.common.abs import glm_.func.common.max @@ -24,6 +24,7 @@ import uno.kotlin.plusAssign import java.nio.ByteBuffer import java.util.Stack import kotlin.math.ceil +import kotlin.math.max import kotlin.math.sqrt /** A single draw command list (generally one per window, conceptually you may see this as a dynamic "mesh" builder) @@ -57,7 +58,7 @@ class DrawList(sharedData: DrawListSharedData?) { var vtxBuffer = DrawVert_Buffer(0) /** Flags, you may poke into these to adjust anti-aliasing settings per-primitive. */ - var flags: DrawListFlags = DrawListFlag.None.i + var flags: DrawListFlags = none // ----------------------------------------------------------------------------------------------------------------- @@ -99,7 +100,7 @@ class DrawList(sharedData: DrawListSharedData?) { /** Render-level scissoring. This is passed down to your render function but not used for CPU-side coarse clipping. * Prefer using higher-level ImGui::PushClipRect() to affect logic (hit-testing and widget culling) */ fun pushClipRect(rect: Rect, intersectWithCurrentClipRect: Boolean = false) = - pushClipRect(rect.min, rect.max, intersectWithCurrentClipRect) + pushClipRect(rect.min, rect.max, intersectWithCurrentClipRect) fun pushClipRect(crMin: Vec2, crMax: Vec2, intersectWithCurrentClipRect: Boolean = false) { @@ -123,7 +124,7 @@ class DrawList(sharedData: DrawListSharedData?) { /** [JVM] */ inline fun withClipRect(rect: Rect, intersectWithCurrentClipRect: Boolean = false, block: DrawList.() -> Unit) = - withClipRect(rect.min, rect.max, intersectWithCurrentClipRect, block) + withClipRect(rect.min, rect.max, intersectWithCurrentClipRect, block) /** [JVM] */ inline fun withClipRect(crMin: Vec2, crMax: Vec2, intersectWithCurrentClipRect: Boolean = false, block: DrawList.() -> Unit) { @@ -164,30 +165,30 @@ class DrawList(sharedData: DrawListSharedData?) { /** JVM it's safe to pass directly Vec2 istances, they wont be modified */ fun addLine(p1: Vec2, p2: Vec2, col: Int, thickness: Float = 1f) { if (col hasnt COL32_A_MASK) return - pathLineTo(p1 + Vec2(0.5f)) - pathLineTo(p2 + Vec2(0.5f)) - pathStroke(col, 0, thickness) + pathLineTo(p1 + 0.5f) + pathLineTo(p2 + 0.5f) + pathStroke(col, thickness = thickness) } /** Note we don't render 1 pixels sized rectangles properly. * @param pMin: upper-left * @param pMax: lower-right * (== upper-left + size) */ - fun addRect(pMin: Vec2, pMax: Vec2, col: Int, rounding: Float = 0f, flags: DrawFlags = 0, thickness: Float = 1f) { + fun addRect(pMin: Vec2, pMax: Vec2, col: Int, rounding: Float = 0f, flags: DrawFlags = none, thickness: Float = 1f) { if (col hasnt COL32_A_MASK) return if (this.flags has DrawListFlag.AntiAliasedLines) pathRect(pMin + 0.5f, pMax - 0.5f, rounding, flags) else // Better looking lower-right corner and rounded non-AA shapes. pathRect(pMin + 0.5f, pMax - 0.49f, rounding, flags) - pathStroke(col, DrawFlag.Closed.i, thickness) + pathStroke(col, DrawFlag.Closed, thickness) } /** @param pMin: upper-left * @param pMax: lower-right * (== upper-left + size) */ - fun addRectFilled(pMin: Vec2, pMax: Vec2, col: Int, rounding: Float = 0f, flags: DrawFlags = 0) { + fun addRectFilled(pMin: Vec2, pMax: Vec2, col: Int, rounding: Float = 0f, flags: DrawFlags = none) { if (col hasnt COL32_A_MASK) return - if (rounding < 0.5f || (flags and DrawFlag.RoundCornersMask_) == DrawFlag.RoundCornersNone.i) { + if (rounding < 0.5f || (flags and DrawFlag.RoundCornersMask) == DrawFlag.RoundCornersNone) { primReserve(6, 4) primRect(Vec2(pMin), pMax, col) // [JVM] `pMin` safety first } else { @@ -224,7 +225,7 @@ class DrawList(sharedData: DrawListSharedData?) { pathLineTo(p2) pathLineTo(p3) pathLineTo(p4) - pathStroke(col, DrawFlag.Closed.i, thickness) + pathStroke(col, DrawFlag.Closed, thickness) } fun addQuadFilled(p1: Vec2, p2: Vec2, p3: Vec2, p4: Vec2, col: Int) { @@ -245,7 +246,7 @@ class DrawList(sharedData: DrawListSharedData?) { pathLineTo(p1) pathLineTo(p2) pathLineTo(p3) - pathStroke(col, DrawFlag.Closed.i, thickness) + pathStroke(col, DrawFlag.Closed, thickness) } fun addTriangleFilled(p1: Vec2, p2: Vec2, p3: Vec2, col: Int) { @@ -276,7 +277,7 @@ class DrawList(sharedData: DrawListSharedData?) { pathArcTo(center, radius - 0.5f, 0f, aMax, numSegments - 1) } - pathStroke(col, DrawFlag.Closed.i, thickness) + pathStroke(col, DrawFlag.Closed, thickness) } fun addCircleFilled(center: Vec2, radius: Float, col: Int, numSegments_: Int = 0) { @@ -308,7 +309,7 @@ class DrawList(sharedData: DrawListSharedData?) { // Because we are filling a closed shape we remove 1 from the count of segments/points val aMax = (glm.πf * 2f) * (numSegments.f - 1f) / numSegments.f pathArcTo(center, radius - 0.5f, 0f, aMax, numSegments - 1) - pathStroke(col, DrawFlag.Closed.i, thickness) + pathStroke(col, DrawFlag.Closed, thickness) } /** Guaranteed to honor 'num_segments' */ @@ -361,7 +362,7 @@ class DrawList(sharedData: DrawListSharedData?) { var thickness = thickness_ val pointsCount = points.size - if (pointsCount < 2) + if (pointsCount < 2 || col hasnt COL32_A_MASK) return val closed = flags has DrawFlag.Closed @@ -385,7 +386,7 @@ class DrawList(sharedData: DrawListSharedData?) { // - If AA_SIZE is not 1.0f we cannot use the texture path. val useTexture = this.flags has DrawListFlag.AntiAliasedLinesUseTex && integerThickness < DRAWLIST_TEX_LINES_WIDTH_MAX && fractionalThickness <= 0.00001f && AA_SIZE == 1f - ASSERT_PARANOID(!useTexture || _data.font!!.containerAtlas.flags hasnt FontAtlas.Flag.NoBakedLines.i) { "We should never hit this, because NewFrame() doesn't set ImDrawListFlags_AntiAliasedLinesUseTex unless ImFontAtlasFlags_NoBakedLines is off" } + ASSERT_PARANOID(!useTexture || _data.font!!.containerAtlas.flags hasnt FontAtlas.Flag.NoBakedLines) { "We should never hit this, because NewFrame() doesn't set ImDrawListFlags_AntiAliasedLinesUseTex unless ImFontAtlasFlags_NoBakedLines is off" } val idxCount = if (useTexture) count * 6 else (count * if (thickLine) 18 else 12) val vtxCount = if (useTexture) pointsCount * 2 else (pointsCount * if (thickLine) 4 else 3) @@ -614,7 +615,7 @@ class DrawList(sharedData: DrawListSharedData?) { fun addConvexPolyFilled(points: ArrayList, col: Int) { val pointsCount = points.size - if (pointsCount < 3) + if (pointsCount < 3 || col hasnt COL32_A_MASK) return val uv = Vec2(_data.texUvWhitePixel) @@ -707,7 +708,7 @@ class DrawList(sharedData: DrawListSharedData?) { pathLineTo(p1) pathBezierCubicCurveTo(p2, p3, p4, numSegments) - pathStroke(col, 0, thickness) + pathStroke(col, thickness = thickness) } /** Quad Bezier (3 control points) @@ -719,7 +720,7 @@ class DrawList(sharedData: DrawListSharedData?) { pathLineTo(p1) pathBezierQuadraticCurveTo(p2, p3, numSegments) - pathStroke(col, 0, thickness) + pathStroke(col, thickness = thickness) } @@ -731,9 +732,9 @@ class DrawList(sharedData: DrawListSharedData?) { // ----------------------------------------------------------------------------------------------------------------- fun addImage( - userTextureId: TextureID, pMin: Vec2, pMax: Vec2, - uvMin: Vec2 = Vec2(0), uvMax: Vec2 = Vec2(1), col: Int = COL32_WHITE, - ) { + userTextureId: TextureID, pMin: Vec2, pMax: Vec2, + uvMin: Vec2 = Vec2(0), uvMax: Vec2 = Vec2(1), col: Int = COL32_WHITE, + ) { if (col hasnt COL32_A_MASK) return @@ -747,10 +748,10 @@ class DrawList(sharedData: DrawListSharedData?) { } fun addImageQuad( - userTextureId: TextureID, p1: Vec2, p2: Vec2, p3: Vec2, p4: Vec2, - uv1: Vec2 = Vec2(0), uv2: Vec2 = Vec2(1, 0), - uv3: Vec2 = Vec2(1), uv4: Vec2 = Vec2(0, 1), col: Int = COL32_WHITE, - ) { + userTextureId: TextureID, p1: Vec2, p2: Vec2, p3: Vec2, p4: Vec2, + uv1: Vec2 = Vec2(0), uv2: Vec2 = Vec2(1, 0), + uv3: Vec2 = Vec2(1), uv4: Vec2 = Vec2(0, 1), col: Int = COL32_WHITE, + ) { if (col hasnt COL32_A_MASK) return @@ -765,12 +766,12 @@ class DrawList(sharedData: DrawListSharedData?) { popTextureID() } - fun addImageRounded(userTextureId: TextureID, pMin: Vec2, pMax: Vec2, uvMin: Vec2, uvMax: Vec2, col: Int, rounding: Float, flags_: DrawFlags = 0) { + fun addImageRounded(userTextureId: TextureID, pMin: Vec2, pMax: Vec2, uvMin: Vec2, uvMax: Vec2, col: Int, rounding: Float, flags_: DrawFlags = none) { if (col hasnt COL32_A_MASK) return val flags = fixRectCornerFlags(flags_) - if (rounding < 0.5f || (flags and DrawFlag.RoundCornersMask_) == DrawFlag.RoundCornersNone.i) { + if (rounding < 0.5f || (flags and DrawFlag.RoundCornersMask) == DrawFlag.RoundCornersNone) { addImage(userTextureId, pMin, pMax, uvMin, uvMax, col) return } @@ -803,7 +804,8 @@ class DrawList(sharedData: DrawListSharedData?) { fun pathFillConvex(col: Int) = addConvexPolyFilled(_path, col).also { pathClear() } /** rounding_corners_flags: 4 bits corresponding to which corner to round */ - fun pathStroke(col: Int, flags: DrawFlags = 0, thickness: Float = 1.0f) = addPolyline(_path, col, flags, thickness).also { pathClear() } + fun pathStroke(col: Int, flags: DrawFlags = none, thickness: Float = 1.0f) = + addPolyline(_path, col, flags, thickness).also { pathClear() } /** @param center must be a new instance */ fun pathArcTo(center: Vec2, radius: Float, aMin: Float, aMax: Float, numSegments: Int = 0) { @@ -928,10 +930,12 @@ class DrawList(sharedData: DrawListSharedData?) { var flags = flags_ // If this triggers, please update your code replacing hardcoded values with new ImDrawFlags_RoundCorners* values. // Note that ImDrawFlags_Closed (== 0x01) is an invalid flag for AddRect(), AddRectFilled(), PathRect() etc... - check(flags hasnt 0x0F) { "Misuse of legacy hardcoded ImDrawCornerFlags values!" } + // [JVM]: Since we're using type-safe flags, this shouldn't be an issue. THe only DrawFlag that could've + // triggered the previous type-unsafe version of this is Df.Closed, and so we handle it here. + check(flags hasnt DrawFlag.Closed) { "Misuse of legacy hardcoded ImDrawCornerFlags values!" } - if (flags hasnt DrawFlag.RoundCornersMask_) - flags = flags or DrawFlag.RoundCornersAll + if (flags hasnt DrawFlag.RoundCornersMask) + flags /= DrawFlag.RoundCornersAll return flags } @@ -955,14 +959,14 @@ class DrawList(sharedData: DrawListSharedData?) { } } - fun pathRect(a: Vec2, b: Vec2, rounding_: Float = 0f, flags_: DrawFlags = 0) { + fun pathRect(a: Vec2, b: Vec2, rounding_: Float = 0f, flags_: DrawFlags = none) { val flags = fixRectCornerFlags(flags_) - var cond = ((flags and DrawFlag.RoundCornersTop) == DrawFlag.RoundCornersTop.i) or ((flags and DrawFlag.RoundCornersBottom) == DrawFlag.RoundCornersBottom.i) + var cond = (DrawFlag.RoundCornersTop in flags) or (DrawFlag.RoundCornersBottom in flags) var rounding = rounding_ min ((b.x - a.x).abs * (if (cond) 0.5f else 1f) - 1f) - cond = ((flags and DrawFlag.RoundCornersLeft) == DrawFlag.RoundCornersLeft.i) or ((flags and DrawFlag.RoundCornersRight) == DrawFlag.RoundCornersRight.i) + cond = (DrawFlag.RoundCornersLeft in flags) or (DrawFlag.RoundCornersRight in flags) rounding = rounding min ((b.y - a.y).abs * (if (cond) 0.5f else 1f) - 1f) - if (rounding < 0.5f || (flags and DrawFlag.RoundCornersMask_) == DrawFlag.RoundCornersNone.i) { + if (rounding < 0.5f || (flags and DrawFlag.RoundCornersMask) == DrawFlag.RoundCornersNone) { pathLineTo(a) pathLineTo(Vec2(b.x, a.y)) pathLineTo(b) @@ -1008,7 +1012,7 @@ class DrawList(sharedData: DrawListSharedData?) { idxOffset = idxBuffer.rem } assert(drawCmd.clipRect.x <= drawCmd.clipRect.z && drawCmd.clipRect.y <= drawCmd.clipRect.w) - cmdBuffer.add(drawCmd) + cmdBuffer += drawCmd } /** Create a clone of the CmdBuffer/IdxBuffer/VtxBuffer. */ @@ -1166,6 +1170,9 @@ class DrawList(sharedData: DrawListSharedData?) { // IM_STATIC_ASSERT(IM_OFFSETOF(ImDrawCmd, ClipRect) == 0); // IM_STATIC_ASSERT(IM_OFFSETOF(ImDrawCmd, TextureId) == sizeof(ImVec4)); // IM_STATIC_ASSERT(IM_OFFSETOF(ImDrawCmd, VtxOffset) == sizeof(ImVec4) + sizeof(ImTextureID)); + if (_splitter._count > 1) + _splitter merge this + cmdBuffer.clear() // we dont assign because it wont create a new instance for sure idxBuffer = idxBuffer.resize(0) @@ -1193,7 +1200,7 @@ class DrawList(sharedData: DrawListSharedData?) { idxBuffer = idxBuffer.resize(0) vtxBuffer = vtxBuffer.resize(0) } - flags = DrawListFlag.None.i + flags = none _vtxCurrentIdx = 0 _vtxWritePtr = 0 _idxWritePtr = 0 @@ -1206,9 +1213,8 @@ class DrawList(sharedData: DrawListSharedData?) { // _splitter.clearFreeMemory(destroy) } - /** Pop trailing draw command (used before merging or presenting to user) - * Note that this leaves the ImDrawList in a state unfit for further commands, - * as most code assume that CmdBuffer.Size > 0 && CmdBuffer.back().UserCallback == NULL */ + // Pop trailing draw command (used before merging or presenting to user) + // Note that this leaves the ImDrawList in a state unfit for further commands, as most code assume that CmdBuffer.Size > 0 && CmdBuffer.back().UserCallback == NULL fun _popUnusedDrawCmd() { while (cmdBuffer.isNotEmpty()) { val currCmd = cmdBuffer.last() @@ -1289,20 +1295,18 @@ class DrawList(sharedData: DrawListSharedData?) { } fun _calcCircleAutoSegmentCount(radius: Float): Int = - // Automatic segment count - when (val radiusIdx = (radius + 0.999999f).i) { // ceil to never reduce accuracy - in _data.circleSegmentCounts.indices -> _data.circleSegmentCounts[radiusIdx] // Use cached value - else -> DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC(radius, _data.circleSegmentMaxError) - } + // Automatic segment count + when (val radiusIdx = (radius + 0.999999f).i) { // ceil to never reduce accuracy + in _data.circleSegmentCounts.indices -> _data.circleSegmentCounts[radiusIdx] // Use cached value + else -> DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC(radius, _data.circleSegmentMaxError) + } /** @center must be a new instance */ - fun _pathArcToFastEx(center: Vec2, radius: Float, aMinSample_: Int, aMaxSample_: Int, aStep_: Int) { + fun _pathArcToFastEx(center: Vec2, radius: Float, aMinSample: Int, aMaxSample: Int, aStep_: Int) { if (radius < 0.5f) { _path += center return } - var aMaxSample = aMaxSample_ - var aMinSample = aMinSample_ // Calculate arc auto segment step size var aStep = when { @@ -1349,7 +1353,7 @@ class DrawList(sharedData: DrawListSharedData?) { val s = _data.arcFastVtx[sampleIndex] _path += Vec2(center.x + s.x * radius, - center.y + s.y * radius) + center.y + s.y * radius) a += aStep; sampleIndex += aStep; aStep = aNextStep } @@ -1362,7 +1366,7 @@ class DrawList(sharedData: DrawListSharedData?) { val s = _data.arcFastVtx[sampleIndex] _path += Vec2(center.x + s.x * radius, - center.y + s.y * radius) + center.y + s.y * radius) a -= aStep; sampleIndex -= aStep; aStep = aNextStep } @@ -1375,7 +1379,7 @@ class DrawList(sharedData: DrawListSharedData?) { val s = _data.arcFastVtx[normalizedMaxSample] _path += Vec2(center.x + s.x * radius, - center.y + s.y * radius) + center.y + s.y * radius) } // IM_ASSERT_PARANOID(_Path.Data + _Path.Size == out_ptr) @@ -1431,12 +1435,14 @@ class DrawList(sharedData: DrawListSharedData?) { } private fun DrawVert_Buffer(size: Int = 0) = DrawVert_Buffer(ByteBuffer(size)) -inline class DrawVert_Buffer(val data: ByteBuffer) { + +@JvmInline +value class DrawVert_Buffer(val data: ByteBuffer) { operator fun get(index: Int) = DrawVert( - Vec2(data, index * DrawVert.SIZE), - Vec2(data, index * DrawVert.SIZE + DrawVert.OFS_UV), - data.getInt(index * DrawVert.SIZE + DrawVert.OFS_COL)) + Vec2(data, index * DrawVert.SIZE), + Vec2(data, index * DrawVert.SIZE + DrawVert.OFS_UV), + data.getInt(index * DrawVert.SIZE + DrawVert.OFS_COL)) operator fun plusAssign(v: Vec2) { data.putFloat(v.x) @@ -1475,7 +1481,7 @@ inline class DrawVert_Buffer(val data: ByteBuffer) { inline val sizeByte: Int get() = data.rem - inline val adr: Ptr + inline val adr: ULong get() = data.adr fun hasRemaining(): Boolean = rem > 0 @@ -1501,7 +1507,7 @@ inline class DrawVert_Buffer(val data: ByteBuffer) { return this val newData = ByteBuffer(newCapacity * DrawVert.SIZE) if (lim > 0) - MemoryUtil.memCopy(data.adr, newData.adr, data.lim.L) + MemoryUtil.memCopy(data.adr.L, newData.adr.L, data.lim.L) data.free() return DrawVert_Buffer(newData) } diff --git a/core/src/main/kotlin/imgui/classes/IO.kt b/core/src/main/kotlin/imgui/classes/IO.kt index b5a889d15..14241b956 100644 --- a/core/src/main/kotlin/imgui/classes/IO.kt +++ b/core/src/main/kotlin/imgui/classes/IO.kt @@ -5,21 +5,18 @@ import glm_.vec2.Vec2 import glm_.vec2.Vec2i import imgui.* import imgui.ImGui.data +import imgui.ImGui.getData import imgui.ImGui.isAlias import imgui.ImGui.isGamepad import imgui.ImGui.isNamedOrMod -import imgui.api.g +import imgui.api.gImGui import imgui.font.Font import imgui.font.FontAtlas import imgui.internal.floorSigned import imgui.internal.sections.InputEvent import imgui.internal.sections.InputSource import imgui.internal.textCharFromUtf8 -import imgui.static.findLatestInputEvent -import imgui.static.getClipboardTextFn_DefaultImpl -import imgui.static.setClipboardTextFn_DefaultImpl -import imgui.static.setPlatformImeDataFn_DefaultImpl -import org.lwjgl.system.Platform +import imgui.statics.findLatestInputEvent import uno.kotlin.NUL //----------------------------------------------------------------------------- @@ -45,7 +42,6 @@ class KeyData { /** 0.0f..1.0f for gamepad values */ var analogValue = 0f } - class IO(sharedFontAtlas: FontAtlas? = null) { //------------------------------------------------------------------ @@ -53,10 +49,10 @@ class IO(sharedFontAtlas: FontAtlas? = null) { //------------------------------------------------------------------ /** See ConfigFlags enum. Set by user/application. Gamepad/keyboard navigation options, etc. */ - var configFlags: ConfigFlags = ConfigFlag.None.i + var configFlags: ConfigFlags = none /** Set ImGuiBackendFlags_ enum. Set by imgui_impl_xxx files or custom backend to communicate features supported by the backend. */ - var backendFlags: BackendFlags = BackendFlag.None.i + var backendFlags: BackendFlags = none /** Main display size, in pixels (generally == GetMainViewport()->Size). May change every frame. */ var displaySize = Vec2i(-1) @@ -73,27 +69,6 @@ class IO(sharedFontAtlas: FontAtlas? = null) { /** Path to .log file (default parameter to ImGui::LogToFile when no file is specified). */ var logFilename = "imgui_log.txt" - /** Time for a double-click, in seconds. */ - var mouseDoubleClickTime = 0.3f - - /** Distance threshold to stay in to validate a double-click, in pixels. */ - var mouseDoubleClickMaxDist = 6f - - /** Distance threshold before considering we are dragging. */ - var mouseDragThreshold = 6f - - /** When holding a key/button, time before it starts repeating, in seconds (for buttons in Repeat mode, etc.). */ - var keyRepeatDelay = 0.275f - - /** When holding a key/button, rate at which it repeats, in seconds. */ - var keyRepeatRate = 0.05f - - /** Delay on hovering before IsItemHovered(ImGuiHoveredFlags_DelayNormal) returns true. */ - var hoverDelayNormal = 0.3f - - /** Delay on hovering before IsItemHovered(ImGuiHoveredFlags_DelayShort) returns true. */ - var hoverDelayShort = 0.1f - /** Store your own data. */ var userData: Any? = null @@ -148,8 +123,59 @@ class IO(sharedFontAtlas: FontAtlas? = null) { /** Timer (in seconds) to free transient windows/tables memory buffers when unused. Set to -1.0f to disable. */ var configMemoryCompactTimer = 60f + //------------------------------------------------------------------ - // User Functions + // Inputs Behaviors + // (other variables, ones which are expected to be tweaked within UI code, are exposed in ImGuiStyle) + //------------------------------------------------------------------ + + /** Time for a double-click, in seconds. */ + var mouseDoubleClickTime = 0.3f + + /** Distance threshold to stay in to validate a double-click, in pixels. */ + var mouseDoubleClickMaxDist = 6f + + /** Distance threshold before considering we are dragging. */ + var mouseDragThreshold = 6f + + /** When holding a key/button, time before it starts repeating, in seconds (for buttons in Repeat mode, etc.). */ + var keyRepeatDelay = 0.275f + + /** When holding a key/button, rate at which it repeats, in seconds. */ + var keyRepeatRate = 0.05f + + + //------------------------------------------------------------------ + // Debug options + //------------------------------------------------------------------ + + // Tools to test correct Begin/End and BeginChild/EndChild behaviors. + // Presently Begin()/End() and BeginChild()/EndChild() needs to ALWAYS be called in tandem, regardless of return value of BeginXXX() + // This is inconsistent with other BeginXXX functions and create confusion for many users. + // We expect to update the API eventually. In the meanwhile we provide tools to facilitate checking user-code behavior. + + /** First-time calls to Begin()/BeginChild() will return false. NEEDS TO BE SET AT APPLICATION BOOT TIME if you don't want to miss windows. */ + var configDebugBeginReturnValueOnce = false + + /** Some calls to Begin()/BeginChild() will return false. Will cycle through window depths then repeat. Suggested use: add "io.ConfigDebugBeginReturnValue = io.KeyShift" in your main loop then occasionally press SHIFT. Windows should be flickering while running. */ + var configDebugBeginReturnValueLoop = false + + + // Option to deactivate io.AddFocusEvent(false) handling. May facilitate interactions with a debugger when focus loss leads to clearing inputs data. + // Backends may have other side-effects on focus loss, so this will reduce side-effects but not necessary remove all of them. + // Consider using e.g. Win32's IsDebuggerPresent() as an additional filter (or see ImOsIsDebuggerPresent() in imgui_test_engine/imgui_te_utils.cpp for a Unix compatible version). + + /** Ignore io.AddFocusEvent(false), consequently not calling io.ClearInputKeys() in input processing. */ + var configDebugIgnoreFocusLoss = false + + // Options to audit .ini data + + // - tools to audit ini data + var configDebugIniSettings = false // = false // Save .ini data with extra comments (particularly helpful for Docking, but makes saving slower) + + //------------------------------------------------------------------ + // Platform Functions + // (the imgui_impl_xxxx backend files are setting those up for you) //------------------------------------------------------------------ // Optional: Platform/Renderer backend name (informational only! will be displayed in About Window) @@ -166,22 +192,24 @@ class IO(sharedFontAtlas: FontAtlas? = null) { /** User data for non C++ programming language backend */ var backendLanguageUserData: Any? = null + // Platform Functions + // Note: Initialize() will setup default clipboard/ime handlers. // Optional: Access OS clipboard // (default to use native Win32 clipboard on Windows, otherwise uses a private clipboard. Override to access OS clipboard on other architectures) - var getClipboardTextFn: ((userData: Any?) -> String?)? = getClipboardTextFn_DefaultImpl - var setClipboardTextFn: ((userData: Any?, text: String) -> Unit)? = setClipboardTextFn_DefaultImpl + var getClipboardTextFn: ((userDataCtx: Any?) -> String?)? = null + var setClipboardTextFn: ((userDataCtx: Any?, text: String) -> Unit)? = null var clipboardUserData: Any? = null // Optional: Notify OS Input Method Editor of the screen position of your cursor for text input position (e.g. when using Japanese/Chinese IME in Windows) // (default to use native imm32 api on Windows) - val setPlatformImeDataFn: ((viewport: Viewport, data: PlatformImeData) -> Unit)? = setPlatformImeDataFn_DefaultImpl.takeIf { Platform.get() == Platform.WINDOWS } + var setPlatformImeDataFn: ((viewport: Viewport, data: PlatformImeData) -> Unit)? = null // [JVM] copy function for backup fun copy() = IO().also { it.configFlags = configFlags; it.backendFlags = backendFlags; it.displaySize = displaySize; it.deltaTime = deltaTime; it.iniSavingRate = iniSavingRate it.iniFilename = iniFilename; it.logFilename = logFilename; it.mouseDoubleClickTime = mouseDoubleClickTime; it.mouseDoubleClickMaxDist = mouseDoubleClickMaxDist - it.mouseDragThreshold = mouseDragThreshold; it.keyRepeatDelay = keyRepeatDelay; it.keyRepeatRate = keyRepeatRate; it.hoverDelayNormal = hoverDelayNormal - it.hoverDelayShort = hoverDelayShort; it.fonts = fonts; it.fontGlobalScale = fontGlobalScale; it.fontAllowUserScaling = fontAllowUserScaling; it.fontDefault = fontDefault + it.mouseDragThreshold = mouseDragThreshold; it.keyRepeatDelay = keyRepeatDelay; it.keyRepeatRate = keyRepeatRate; + it.fonts = fonts; it.fontGlobalScale = fontGlobalScale; it.fontAllowUserScaling = fontAllowUserScaling; it.fontDefault = fontDefault it.displayFramebufferScale = displayFramebufferScale; it.mouseDrawCursor = mouseDrawCursor; it.configMacOSXBehaviors = configMacOSXBehaviors it.configInputTrickleEventQueue = configInputTrickleEventQueue; it.configInputTextCursorBlink = configInputTextCursorBlink; it.configInputTextEnterKeepActive = configInputTextEnterKeepActive; it.configDragClickToInputText = configDragClickToInputText @@ -189,6 +217,9 @@ class IO(sharedFontAtlas: FontAtlas? = null) { it.configMemoryCompactTimer = configMemoryCompactTimer; it.backendPlatformName = backendPlatformName; it.backendPlatformUserData = backendPlatformUserData it.backendRendererUserData = backendRendererUserData; it.backendLanguageUserData = backendLanguageUserData; it.getClipboardTextFn = getClipboardTextFn it.setClipboardTextFn = setClipboardTextFn; it.clipboardUserData = clipboardUserData /*it.setPlatformImeDataFn = setPlatformImeDataFn*/ + it.ctx = ctx; /*it.mousePos put mousePos; repeat(5) { i -> it.mouseDown[i] = mouseDown[i] }; it.mouseWheel = mouseWheelH + it.mouseWheelH = mouseWheelH; it.keyCtrl = keyCtrl; it.keyShift = keyShift; it.keyAlt = keyAlt; it.keySuper = keySuper; it.mouseSource + repeat(NavInput.COUNT) { i -> it.navInputs[i] = navInputs[i] }; it.keyMods = keyMods; repeat(Key.COUNT) {i -> it.keysData[i]}*/ } //------------------------------------------------------------------ @@ -209,12 +240,18 @@ class IO(sharedFontAtlas: FontAtlas? = null) { } /** Queue a new key down/up event for analog values (e.g. ImGuiKey_Gamepad_ values). Dead-zones should be handled by the backend. */ + // Queue a new key down/up event. + // - ImGuiKey key: Translated key (as in, generally ImGuiKey_A matches the key end-user would use to emit an 'A' character) + // - bool down: Is the key down? use false to signify a key release. + // - float analog_value: 0.0f..1.0f + // IMPORTANT: THIS FUNCTION AND OTHER "ADD" GRABS THE CONTEXT FROM OUR INSTANCE. + // WE NEED TO ENSURE THAT ALL FUNCTION CALLS ARE FULLFILLING THIS, WHICH IS WHY GetKeyData() HAS AN EXPLICIT CONTEXT. fun addKeyAnalogEvent(key: Key, down: Boolean, analogValue: Float) { //if (e->Down) { IMGUI_DEBUG_LOG_IO("AddKeyEvent() Key='%s' %d, NativeKeycode = %d, NativeScancode = %d\n", ImGui::GetKeyName(e->Key), e->Down, e->NativeKeycode, e->NativeScancode); } + val g = ctx!! if (key == Key.None || !appAcceptingEvents) return - assert(g.io === this) { "Can only add events to current context." } assert(key.isNamedOrMod) { "Backend needs to pass a valid ImGuiKey_ constant . 0..511 values are legacy native key codes which are not accepted by this API." } assert(!key.isAlias) { "Backend cannot submit ImGuiKey_MouseXXX values they are automatically inferred from AddMouseXXX() events ." } assert(key != Key.Mod_Shortcut) { "We could easily support the translation here but it seems saner to not accept it(TestEngine perform a translation itself)" } @@ -224,20 +261,20 @@ class IO(sharedFontAtlas: FontAtlas? = null) { backendUsingLegacyNavInputArray = false // Filter duplicate (in particular: key mods and gamepad analog values are commonly spammed) - val latestEvent = findLatestInputEvent(key.i) - val keyData = key.data + val latestEvent = findLatestInputEvent(g, key) + val keyData = key.getData(g) val latestKeyDown = latestEvent?.down ?: keyData.down val latestKeyAnalog = latestEvent?.analogValue ?: keyData.analogValue if (latestKeyDown == down && latestKeyAnalog == analogValue) return // Add event - g.inputEventsQueue += InputEvent.Key(key, down, analogValue, if (key.isGamepad) InputSource.Gamepad else InputSource.Keyboard) + g.inputEventsQueue += InputEvent.Key(key, down, analogValue, if (key.isGamepad) InputSource.Gamepad else InputSource.Keyboard, g.inputEventsNextEventId++) } /** Queue a mouse position update. Use -FLT_MAX,-FLT_MAX to signify no mouse (e.g. app not focused and not hovered) */ fun addMousePosEvent(x: Float, y: Float) { - assert(g.io === this) { "Can only add events to current context." } + val g = ctx!! if (!appAcceptingEvents) return @@ -245,54 +282,62 @@ class IO(sharedFontAtlas: FontAtlas? = null) { val pos = Vec2(if (x > -Float.MAX_VALUE) floorSigned(x) else x, if (y > -Float.MAX_VALUE) floorSigned(y) else y) // Filter duplicate - val latestEvent = findLatestInputEvent() + val latestEvent = findLatestInputEvent(g) val latestPos = latestEvent?.let { Vec2(it.posX, it.posY) } ?: g.io.mousePos if (latestPos.x == pos.x && latestPos.y == pos.y) return - g.inputEventsQueue += InputEvent.MousePos(pos.x, pos.y) + g.inputEventsQueue += InputEvent.MousePos(pos.x, pos.y, g.inputEventsNextMouseSource, g.inputEventsNextEventId++) } /** Queue a mouse button change */ - fun addMouseButtonEvent(mouseButton: Int, down: Boolean) { - assert(g.io === this) { "Can only add events to current context." } - assert(mouseButton >= 0 && mouseButton < MouseButton.COUNT) + fun addMouseButtonEvent(mouseButton: MouseButton, down: Boolean) { + val g = ctx!! if (!appAcceptingEvents) return // Filter duplicate - val latestEvent = findLatestInputEvent(mouseButton) - val latestButtonDown = latestEvent?.down ?: g.io.mouseDown[mouseButton] + val latestEvent = findLatestInputEvent(g, mouseButton) + val latestButtonDown = latestEvent?.down ?: g.io.mouseDown[mouseButton.i] if (latestButtonDown == down) return - g.inputEventsQueue += InputEvent.MouseButton(mouseButton, down) + g.inputEventsQueue += InputEvent.MouseButton(mouseButton, down, g.inputEventsNextMouseSource, g.inputEventsNextEventId++) } - /** Queue a mouse wheel update */ + /** Queue a mouse wheel update. wheel_y<0: scroll down, wheel_y>0: scroll up, wheel_x<0: scroll right, wheel_x>0: scroll left. + * + * Queue a mouse wheel event (some mouse/API may only have a Y component) */ fun addMouseWheelEvent(wheelX: Float, wheelY: Float) { - assert(g.io === this) { "Can only add events to current context." } + val g = ctx!! // Filter duplicate (unlike most events, wheel values are relative and easy to filter) if (!appAcceptingEvents || (wheelX == 0f && wheelY == 0f)) return - g.inputEventsQueue += InputEvent.MouseWheel(wheelX, wheelY) + g.inputEventsQueue += InputEvent.MouseWheel(wheelX, wheelY, g.inputEventsNextMouseSource, g.inputEventsNextEventId++) + } + + // Queue a mouse source change (Mouse/TouchScreen/Pen) + // This is not a real event, the data is latched in order to be stored in actual Mouse events. + // This is so that duplicate events (e.g. Windows sending extraneous WM_MOUSEMOVE) gets filtered and are not leading to actual source changes. + fun addMouseSourceEvent(source: MouseSource) { +// IM_ASSERT(Ctx != NULL); + val g = ctx!! + g.inputEventsNextMouseSource = source } /** Queue a gain/loss of focus for the application (generally based on OS/platform focus of your window) */ fun addFocusEvent(focused: Boolean) { - // We intentionally overwrite this and process in NewFrame(), in order to give a chance - // to multi-viewports backends to queue AddFocusEvent(false),AddFocusEvent(true) in same frame. - assert(g.io === this) { "Can only add events to current context." } + val g = ctx!! // Filter duplicate - val latestEvent = findLatestInputEvent() + val latestEvent = findLatestInputEvent(g) val latestFocused = latestEvent?.focused ?: !g.io.appFocusLost - if (latestFocused == focused) + if (latestFocused == focused || (configDebugIgnoreFocusLoss && !focused)) return - g.inputEventsQueue += InputEvent.AppFocused(focused) + g.inputEventsQueue += InputEvent.AppFocused(focused, g.inputEventsNextEventId++) } /** Queue a new character input @@ -301,11 +346,11 @@ class IO(sharedFontAtlas: FontAtlas? = null) { * - with glfw you can get those from the callback set in glfwSetCharCallback() * - on Windows you can get those using ToAscii+keyboard state, or via the WM_CHAR message */ fun addInputCharacter(c: Char) { - assert(g.io === this) { "Can only add events to current context." } + val g = ctx!! if (c == NUL || !appAcceptingEvents) return - g.inputEventsQueue += InputEvent.Text(c) + g.inputEventsQueue += InputEvent.Text(c, g.inputEventsNextEventId++) } /** Queue a new character input from a UTF-16 character, it can be a surrogate @@ -343,11 +388,10 @@ class IO(sharedFontAtlas: FontAtlas? = null) { if (!appAcceptingEvents) return var p = 0 - while (p < utf8Chars.size || utf8Chars[p] == 0.b) { + while (p < utf8Chars.size && utf8Chars[p] != 0.b) { val (c, bytes) = textCharFromUtf8(utf8Chars) p += bytes - if (c != 0) - addInputCharacter(c.c) + addInputCharacter(c.c) } } @@ -376,7 +420,7 @@ class IO(sharedFontAtlas: FontAtlas? = null) { keyData.downDurationPrev = -1f } keyCtrl = false; keyShift = false; keyAlt = false; keySuper = false - keyMods = Key.Mod_None.i + keyMods = Key.Mod_None mousePos put -Float.MAX_VALUE for (n in mouseDown.indices) { mouseDown[n] = false @@ -443,6 +487,13 @@ class IO(sharedFontAtlas: FontAtlas? = null) { // [Internal] Dear ImGui will maintain those fields. Forward compatibility not guaranteed! //------------------------------------------------------------------ + /** Parent UI context (needs to be set explicitly by parent). */ + var ctx: Context? = null + + // Main Input State + // (this block used to be written by backend, since 1.87 it is best to NOT write to those directly, call the AddXXX functions above instead) + // (reading from those variables is fair game, as they are extremely unlikely to be moving anywhere) + /** Mouse position, in pixels. Set to ImVec2(-FLT_MAX, -FLT_MAX) if mouse is unavailable (on another screen, etc.) */ val mousePos = Vec2(-Float.MAX_VALUE) @@ -451,12 +502,15 @@ class IO(sharedFontAtlas: FontAtlas? = null) { * to user as a convenience via IsMouse** API. */ val mouseDown = BooleanArray(5) - /** Mouse wheel Vertical: 1 unit scrolls about 5 lines text. */ + /** Mouse wheel Vertical: 1 unit scrolls about 5 lines text. >0 scrolls Up, <0 scrolls Down. Hold SHIFT to turn vertical scroll into horizontal scroll. */ var mouseWheel = 0f - /** Mouse wheel Horizontal. Most users don't have a mouse with a horizontal wheel, may not be filled by all backends. */ + /** Mouse wheel Horizontal. >0 scrolls Left, <0 scrolls Right. Most users don't have a mouse with a horizontal wheel, may not be filled by all backends. */ var mouseWheelH = 0f + /** Mouse actual input peripheral (Mouse/TouchScreen/Pen). */ + var mouseSource: MouseSource = MouseSource.Mouse + /** Keyboard modifier down: Control */ var keyCtrl = false @@ -475,9 +529,8 @@ class IO(sharedFontAtlas: FontAtlas? = null) { // Other state maintained from data above + IO function calls - /** Key mods flags (any of ImGuiMod_Ctrl/ImGuiMod_Shift/ImGuiMod_Alt/ImGuiMod_Super flags, same as io.KeyCtrl/KeyShift/KeyAlt/KeySuper but merged into flags. DOES NOT CONTAINS ImGuiMod_Shortcut which is pretranslated). Read-only, updated by NewFrame() */ - var keyMods: KeyChord = 0 + var keyMods: KeyChord = none /** Key state for all known keys. Use IsKeyXXX() functions to access this. */ val keysData = Array(Key.COUNT) { KeyData().apply { downDuration = -1f; downDurationPrev = -1f } } @@ -515,6 +568,9 @@ class IO(sharedFontAtlas: FontAtlas? = null) { /** Track if button was clicked inside a dear imgui window. */ val mouseDownOwnedUnlessPopupClose = BooleanArray(5) + /** On a non-Mac system, holding SHIFT requests WheelY to perform the equivalent of a WheelX event. On a Mac system this is already enforced by the system. */ + var mouseWheelRequestAxisSwap = false + /** Duration the mouse button has been down (0.0f == just clicked) */ val mouseDownDuration = FloatArray(5) { -1f } @@ -548,4 +604,5 @@ class IO(sharedFontAtlas: FontAtlas? = null) { /** Queue of _characters_ input (obtained by platform backend). Fill using AddInputCharacter() helper. */ val inputQueueCharacters = ArrayList() -} \ No newline at end of file +} + diff --git a/core/src/main/kotlin/imgui/classes/InputTextCallbackData.kt b/core/src/main/kotlin/imgui/classes/InputTextCallbackData.kt index 65f069e7f..67b837d9c 100644 --- a/core/src/main/kotlin/imgui/classes/InputTextCallbackData.kt +++ b/core/src/main/kotlin/imgui/classes/InputTextCallbackData.kt @@ -20,13 +20,15 @@ import kotlin.math.max * * Helper functions for text manipulation. * Use those function to benefit from the CallbackResize behaviors. Calling those function reset the selection. */ -class InputTextCallbackData { +class InputTextCallbackData( + /** Parent UI context */ + var ctx: Context) { /** One ImGuiInputTextFlags_Callback* // Read-only */ - var eventFlag: InputTextFlags = 0 + var eventFlag: InputTextFlags = none /** What user passed to InputText() // Read-only */ - var flags: InputTextFlags = 0 + var flags: InputTextFlags = none /** What user passed to InputText() // Read-only */ var userData: Any? = null @@ -40,7 +42,7 @@ class InputTextCallbackData { var eventChar = NUL /** Key pressed (Up/Down/TAB) Read-only [Completion,History] */ - var eventKey = Key.Tab + var eventKey: Key = Key.None /** Text buffer Read-write [Resize] Can replace pointer / [Completion,History,Always] Only write to pointed data, don't replace the actual pointer! */ var buf = ByteArray(0) @@ -93,13 +95,19 @@ class InputTextCallbackData { fun insertChars(pos: Int, newText: ByteArray, newTextEnd: Int = -1) { + // Accept null ranges + if (0 == newTextEnd) + return + val isResizable = flags has InputTextFlag.CallbackResize val newTextLen = if (newTextEnd != -1) newTextEnd else newText.strlen() if (newTextLen + bufTextLen >= bufSize) { - if (!isResizable) return + if (!isResizable) + return // Contrary to STB_TEXTEDIT_INSERTCHARS() this is working in the UTF8 buffer, hence the mildly similar code (until we remove the U16 buffer altogether!) + val g = ctx val editState = g.inputTextState assert(editState.id != 0 && g.activeId == editState.id) assert(buf === editState.textA) @@ -115,6 +123,7 @@ class InputTextCallbackData { buf[pos + newTextLen + i] = buf[pos + i] for (i in 0 until newTextLen) buf[pos + i] = newText[i] + buf[bufTextLen + newTextLen] = 0 if (cursorPos >= pos) cursorPos += newTextLen diff --git a/core/src/main/kotlin/imgui/classes/ListClipper.kt b/core/src/main/kotlin/imgui/classes/ListClipper.kt index e6fcf552e..634d3ba6b 100644 --- a/core/src/main/kotlin/imgui/classes/ListClipper.kt +++ b/core/src/main/kotlin/imgui/classes/ListClipper.kt @@ -1,3 +1,5 @@ +@file:OptIn(ExperimentalStdlibApi::class) + package imgui.classes import glm_.* @@ -5,6 +7,7 @@ import imgui.Dir import imgui.ImGui.endRow import imgui.ImGui.rectRelToAbs import imgui.api.g +import imgui.api.gImGui import imgui.clamp import imgui.has import imgui.internal.isAboveGuaranteedIntegerPrecision @@ -36,22 +39,21 @@ import imgui.internal.sections.NavMoveFlag * - The clipper also handles various subtleties related to keyboard/gamepad navigation, wrapping etc. */ class ListClipper { + /** Parent UI context */ + var ctx: Context = gImGui + var displayStart = 0 var displayEnd = 0 val display - get() = displayStart until displayEnd + get() = displayStart ..< displayEnd var itemsCount = -1 var itemsHeight = 0f // [Internal] Height of item after a first step and item submission can calculate it var startPosY = 0f // [Internal] Cursor position at the time of Begin() or after table frozen rows are all processed var tempData: Any? = null // [Internal] Internal data - fun dispose() { - assert(itemsCount == -1) { "Forgot to call End(), or to Step() until false?" } - end() - } - fun begin(itemsCount: Int, itemsHeight: Float = -1f) { + val g = ctx val window = g.currentWindow!! IMGUI_DEBUG_LOG_CLIPPER("Clipper: Begin($itemsCount,%.2f) in '${window.name}'", itemsHeight) @@ -80,6 +82,7 @@ class ListClipper { fun end() { (tempData as? ListClipperData)?.let { data -> + val g = ctx // In theory here we should assert that we are already at the right position, but it seems saner to just seek at the end and not assert/crash the user. IMGUI_DEBUG_LOG_CLIPPER("Clipper: End() in '${g.currentWindow!!.name}'") if (itemsCount >= 0 && itemsCount < Int.MAX_VALUE && displayStart >= 0) @@ -100,16 +103,19 @@ class ListClipper { itemsCount = 1 } - fun forceDisplayRangeByIndices(itemMin: Int, itemMax: Int) { + // Call IncludeRangeByIndices() *BEFORE* first call to Step() if you need a range of items to not be clipped, regardless of their visibility. + // (Due to alignment / padding of certain items it is possible that an extra item may be included on either end of the display range). + fun includeRangeByIndices(itemBegin: Int, itemEnd: Int) { // item_end is exclusive e.g. use (42, 42+1) to make item 42 never clipped. val data = tempData as ListClipperData assert(displayStart < 0) { "Only allowed after Begin () and if there has not been a specified range yet ." } - assert(itemMin <= itemMax) - if (itemMin < itemMax) - data.ranges += ListClipperRange.fromIndices(itemMin, itemMax) + assert(itemBegin <= itemEnd) + if (itemBegin < itemEnd) + data.ranges += ListClipperRange.fromIndices(itemBegin, itemEnd) } /** Call until it returns false. The DisplayStart/DisplayEnd fields will be set and you can process/draw those items. */ fun step(): Boolean { + val g = ctx val needItemsHeight = itemsHeight <= 0f var ret = stepInternal() if (ret && displayStart == displayEnd) @@ -129,6 +135,7 @@ class ListClipper { private fun stepInternal(): Boolean { + val g = ctx val window = g.currentWindow!! val data = tempData as ListClipperData @@ -301,6 +308,15 @@ class ListClipper { } } +// [JVM] utility construct to easier the usage od the ListClipper (eg: native code destructor) +inline fun listClipper(itemsCount: Int, itemsHeight: Float = -1f, step: (ListClipper) -> Unit) { + val clipper = ListClipper() + clipper.begin(itemsCount, itemsHeight) + while (clipper.step()) + step(clipper) + clipper.end() +} + // FIXME-TABLE: This prevents us from using ImGuiListClipper _inside_ a table cell. // The problem we have is that without a Begin/End scheme for rows using the clipper is ambiguous. val skipItemForListClipping: Boolean diff --git a/core/src/main/kotlin/imgui/classes/Style.kt b/core/src/main/kotlin/imgui/classes/Style.kt index ec1f0dbc2..66ada620d 100644 --- a/core/src/main/kotlin/imgui/classes/Style.kt +++ b/core/src/main/kotlin/imgui/classes/Style.kt @@ -3,7 +3,7 @@ package imgui.classes import glm_.vec2.Vec2 import glm_.vec2.Vec2i import glm_.vec4.Vec4 -import imgui.Dir +import imgui.* import imgui.ImGui.styleColorsClassic import imgui.internal.floor import java.util.* @@ -70,7 +70,7 @@ class Style { var itemInnerSpacing = Vec2(4) /** Padding within a table cell */ - val cellPadding = Vec2(4,2) + val cellPadding = Vec2(4, 2) /** Expand reactive bounding box for touch-based system where touch position is not accurate enough. Unfortunately * we don't sort widgets so priority on overlap will always be given to the first widget. So don't grow this too much! */ @@ -115,6 +115,15 @@ class Style { /** Alignment of selectable text. Defaults to (0.0f, 0.0f) (top-left aligned). It's generally important to keep this left-aligned if you want to lay multiple items on a same line. */ var selectableTextAlign = Vec2() + /** Thickkness of border in SeparatorText() */ + var separatorTextBorderSize = 3f + + /** Alignment of text within the separator. Defaults to (0.0f, 0.5f) (left aligned, center). */ + val separatorTextAlign = Vec2(0f, 0.5f) + + /** Horizontal offset of text from each edge of the separator + spacing on other axis. Generally small values. .y is recommended to be == FramePadding.y. */ + val separatorTextPadding = Vec2(20f, 3f) + /** Window position are clamped to be visible within the display area or monitors by at least this amount. * Only applies to regular windows. */ var displayWindowPadding = Vec2(19) @@ -149,6 +158,24 @@ class Style { val colors = ArrayList() + // Behaviors + // (It is possible to modify those fields mid-frame if specific behavior need it, unlike e.g. configuration fields in ImGuiIO) + + /** Delay for IsItemHovered(ImGuiHoveredFlags_Stationary). Time required to consider mouse stationary. */ + var hoverStationaryDelay = 0.15f + + /** Delay for IsItemHovered(ImGuiHoveredFlags_DelayShort). Usually used along with HoverStationaryDelay. */ + var hoverDelayShort = 0.15f + + /** Delay for IsItemHovered(ImGuiHoveredFlags_DelayNormal). " */ + var hoverDelayNormal = 0.4f + + /** Default flags when using IsItemHovered(ImGuiHoveredFlags_ForTooltip) or BeginItemTooltip()/SetItemTooltip() while using mouse. */ + var hoverFlagsForTooltipMouse: HoveredFlags = HoveredFlag.Stationary / HoveredFlag.DelayShort + + /** Default flags when using IsItemHovered(ImGuiHoveredFlags_ForTooltip) or BeginItemTooltip()/SetItemTooltip() while using keyboard/gamepad. */ + var hoverFlagsForTooltipNav: HoveredFlags = HoveredFlag.NoSharedDelay / HoveredFlag.DelayNormal + /** JVM IMGUI */ var locale: Locale = Locale.US // var locale: Locale = Locale("no", "NO") @@ -156,16 +183,18 @@ class Style { // [JVM] fun copy() = Style().also { - it.alpha = alpha; it.disabledAlpha = disabledAlpha; it.windowPadding = windowPadding; it.windowRounding = windowRounding; it.windowBorderSize = windowBorderSize - it.windowMinSize = windowMinSize; it.windowTitleAlign = windowTitleAlign; it.windowMenuButtonPosition = windowMenuButtonPosition; it.childRounding = childRounding - it.childBorderSize = childBorderSize; it.popupRounding = popupRounding; it.popupBorderSize = popupBorderSize; it.framePadding = framePadding; it.frameRounding = frameRounding - it.frameBorderSize = frameBorderSize; it.itemSpacing = itemSpacing; it.itemInnerSpacing = itemInnerSpacing; it.cellPadding put cellPadding - it.touchExtraPadding = touchExtraPadding; it.indentSpacing = indentSpacing; it.columnsMinSpacing = columnsMinSpacing; it.scrollbarSize = scrollbarSize; + it.alpha = alpha; it.disabledAlpha = disabledAlpha; it.windowPadding put windowPadding; it.windowRounding = windowRounding; it.windowBorderSize = windowBorderSize + it.windowMinSize put windowMinSize; it.windowTitleAlign put windowTitleAlign; it.windowMenuButtonPosition = windowMenuButtonPosition; it.childRounding = childRounding + it.childBorderSize = childBorderSize; it.popupRounding = popupRounding; it.popupBorderSize = popupBorderSize; it.framePadding put framePadding; it.frameRounding = frameRounding + it.frameBorderSize = frameBorderSize; it.itemSpacing put itemSpacing; it.itemInnerSpacing put itemInnerSpacing; it.cellPadding put cellPadding + it.touchExtraPadding put touchExtraPadding; it.indentSpacing = indentSpacing; it.columnsMinSpacing = columnsMinSpacing; it.scrollbarSize = scrollbarSize it.scrollbarRounding = scrollbarRounding; it.grabMinSize = grabMinSize; it.grabRounding = grabRounding; it.logSliderDeadzone = logSliderDeadzone; it.tabRounding = tabRounding - it.tabBorderSize = tabBorderSize; it.tabMinWidthForCloseButton = tabMinWidthForCloseButton; it.colorButtonPosition = colorButtonPosition; it.buttonTextAlign = buttonTextAlign - it.selectableTextAlign = selectableTextAlign; it.displayWindowPadding = displayWindowPadding; it.displaySafeAreaPadding = displaySafeAreaPadding + it.tabBorderSize = tabBorderSize; it.tabMinWidthForCloseButton = tabMinWidthForCloseButton; it.colorButtonPosition = colorButtonPosition; it.buttonTextAlign put buttonTextAlign + it.selectableTextAlign put selectableTextAlign; it.displayWindowPadding put displayWindowPadding; it.displaySafeAreaPadding put displaySafeAreaPadding it.mouseCursorScale = mouseCursorScale; it.antiAliasedLines = antiAliasedLines; it.antiAliasedLinesUseTex = antiAliasedLinesUseTex; it.antiAliasedFill = antiAliasedFill it.curveTessellationTol = curveTessellationTol; it.circleTessellationMaxError = circleTessellationMaxError; it.colors += colors; it.locale = locale + it.hoverStationaryDelay = hoverStationaryDelay; it.hoverDelayShort = hoverDelayShort; it.hoverDelayNormal = hoverDelayNormal + it.hoverFlagsForTooltipMouse = hoverFlagsForTooltipMouse; it.hoverFlagsForTooltipNav = hoverFlagsForTooltipNav } init { @@ -225,7 +254,7 @@ class Style { fun scaleAllSizes(scaleFactor: Float) { windowPadding = floor(windowPadding * scaleFactor) windowRounding = floor(windowRounding * scaleFactor) - windowMinSize.put(floor(windowMinSize.x * scaleFactor), floor(windowMinSize.y * scaleFactor)) + windowMinSize.put(floor(windowMinSize * scaleFactor)) childRounding = floor(childRounding * scaleFactor) popupRounding = floor(popupRounding * scaleFactor) framePadding = floor(framePadding * scaleFactor) @@ -242,7 +271,8 @@ class Style { grabRounding = floor(grabRounding * scaleFactor) logSliderDeadzone = floor(logSliderDeadzone * scaleFactor) tabRounding = floor(tabRounding * scaleFactor) - tabMinWidthForCloseButton = if(tabMinWidthForCloseButton != Float.MAX_VALUE) floor(tabMinWidthForCloseButton * scaleFactor) else Float.MAX_VALUE + tabMinWidthForCloseButton = if (tabMinWidthForCloseButton != Float.MAX_VALUE) floor(tabMinWidthForCloseButton * scaleFactor) else Float.MAX_VALUE + separatorTextPadding put floor(separatorTextPadding * scaleFactor) displayWindowPadding = floor(displayWindowPadding * scaleFactor) displaySafeAreaPadding = floor(displaySafeAreaPadding * scaleFactor) mouseCursorScale = floor(mouseCursorScale * scaleFactor) diff --git a/core/src/main/kotlin/imgui/classes/misc.kt b/core/src/main/kotlin/imgui/classes/misc.kt index fa64bbda6..a55b5118e 100644 --- a/core/src/main/kotlin/imgui/classes/misc.kt +++ b/core/src/main/kotlin/imgui/classes/misc.kt @@ -7,6 +7,7 @@ import imgui.ImGui.colorConvertHSVtoRGB import imgui.ImGui.frameCount import imgui.ImGui.inputText import imgui.ImGui.setNextItemWidth +import imgui.stb.te class Color { @@ -130,9 +131,6 @@ class TableColumnSortSpecs { class TableSortSpecs { /** Pointer to sort spec array. */ var specs = ArrayList() - fun specs(n: Int) = specsArray[specsPtr + n] - lateinit var specsArray: Array - var specsPtr = 0 /** Sort spec count. Most often 1. May be > 1 when ImGuiTableFlags_SortMulti is enabled. May be == 0 when ImGuiTableFlags_SortTristate is enabled. */ var specsCount = 0 @@ -194,12 +192,13 @@ class TextFilter(defaultFilter: String = "") { return valueChanged } - fun passFilter(text_: String?, textEnd: Int = -1): Boolean { + fun passFilter(text_: String?, textEnd_: Int = -1): Boolean { if (exc.isEmpty() && inc.isEmpty()) return true var text = text_ ?: "" + val textEnd = if (textEnd_ == -1) text.length else textEnd_ text = if (textEnd != text.length) text.substring(0, textEnd) else text if (exc.any { it in text }) return false if (inc.any { it in text }) return true @@ -229,8 +228,9 @@ class TextFilter(defaultFilter: String = "") { inc.clear() exc.clear() countGrep = 0 - inputBuf.cStr.split(",").map { it.trim() }.filter(String::isNotEmpty).forEach { f -> - if (f[0] == '-') exc += f + inputBuf.cStr.split(",").map(String::trim).filter(String::isNotEmpty).forEach { f -> + if (f[0] == '-') + exc += f.drop(1) else { inc += f countGrep++ diff --git a/core/src/main/kotlin/imgui/demo/ExampleApp.kt b/core/src/main/kotlin/imgui/demo/DemoWindow.kt similarity index 76% rename from core/src/main/kotlin/imgui/demo/ExampleApp.kt rename to core/src/main/kotlin/imgui/demo/DemoWindow.kt index b200f690b..132730f50 100644 --- a/core/src/main/kotlin/imgui/demo/ExampleApp.kt +++ b/core/src/main/kotlin/imgui/demo/DemoWindow.kt @@ -4,12 +4,14 @@ import glm_.f import glm_.vec2.Vec2 import imgui.* import imgui.ImGui.begin +import imgui.ImGui.beginDisabled import imgui.ImGui.beginTable import imgui.ImGui.bulletText import imgui.ImGui.button import imgui.ImGui.checkbox import imgui.ImGui.checkboxFlags import imgui.ImGui.end +import imgui.ImGui.endDisabled import imgui.ImGui.endTable import imgui.ImGui.fontSize import imgui.ImGui.io @@ -24,10 +26,12 @@ import imgui.ImGui.popItemWidth import imgui.ImGui.pushItemWidth import imgui.ImGui.sameLine import imgui.ImGui.separator +import imgui.ImGui.separatorText import imgui.ImGui.setNextWindowPos import imgui.ImGui.setNextWindowSize import imgui.ImGui.showDebugLogWindow import imgui.ImGui.showMetricsWindow +import imgui.ImGui.showStackToolWindow import imgui.ImGui.showUserGuide import imgui.ImGui.spacing import imgui.ImGui.tableNextColumn @@ -46,7 +50,10 @@ import imgui.dsl.window import kotlin.reflect.KMutableProperty0 import imgui.WindowFlag as Wf -object ExampleApp { +// Demonstrate most Dear ImGui features (this is big function!) +// You may execute this function to experiment with the UI and understand what it does. +// You may then search for keywords in the code when you are interested by a specific feature. +object DemoWindow { object show { // Examples Apps (accessible from the "Examples" menu) @@ -90,6 +97,10 @@ object ExampleApp { operator fun invoke(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. + check(ImGui.currentContext != null) { "Missing dear imgui context. Refer to examples app!" } + var open = open_ if (show.mainMenuBar) MainMenuBar() @@ -111,7 +122,7 @@ object ExampleApp { if (show.debugLog) showDebugLogWindow(show::debugLog) if (show.stackTool) - showMetricsWindow(show::stackTool) + showStackToolWindow(show::stackTool) if (show.about) ShowAboutWindow(show::about) if (show.styleEditor) @@ -119,18 +130,19 @@ object ExampleApp { StyleEditor() } - var windowFlags = 0 - if (noTitlebar) windowFlags = windowFlags or Wf.NoTitleBar - if (noScrollbar) windowFlags = windowFlags or Wf.NoScrollbar - if (!noMenu) windowFlags = windowFlags or Wf.MenuBar - if (noMove) windowFlags = windowFlags or Wf.NoMove - if (noResize) windowFlags = windowFlags or Wf.NoResize - if (noCollapse) windowFlags = windowFlags or Wf.NoCollapse - if (noNav) windowFlags = windowFlags or Wf.NoNav - if (noBackground) windowFlags = windowFlags or Wf.NoBackground - if (noBringToFront) windowFlags = windowFlags or Wf.NoBringToFrontOnFocus - if (unsavedDocument) windowFlags = windowFlags or Wf.UnsavedDocument + var windowFlags: WindowFlags = none + if (noTitlebar) windowFlags /= Wf.NoTitleBar + if (noScrollbar) windowFlags /= Wf.NoScrollbar + if (!noMenu) windowFlags /= Wf.MenuBar + if (noMove) windowFlags /= Wf.NoMove + if (noResize) windowFlags /= Wf.NoResize + if (noCollapse) windowFlags /= Wf.NoCollapse + if (noNav) windowFlags /= Wf.NoNav + if (noBackground) windowFlags /= Wf.NoBackground + if (noBringToFront) windowFlags /= Wf.NoBringToFrontOnFocus + if (unsavedDocument) windowFlags /= Wf.UnsavedDocument if (noClose) open = null // Don't pass our bool* to Begin + // We specify a default position/size in case there's no data in the .ini file. // We only do it to make the demo applications a little more welcoming, but typically this isn't required. val mainViewport = mainViewport @@ -139,7 +151,8 @@ object ExampleApp { // Main body of the Demo window starts here. if (!begin("Dear ImGui Demo", open, windowFlags)) { - end() // Early out if the window is collapsed, as an optimization. + // Early out if the window is collapsed, as an optimization. + end() return } @@ -173,7 +186,7 @@ object ExampleApp { //if (ImGui::MenuItem("MenuItem")) {} // You can also use MenuItem() inside a menu bar! menu("Tools") { - val hasDebugTools = IMGUI_DISABLE_DEBUG_TOOLS + val hasDebugTools = !IMGUI_DISABLE_DEBUG_TOOLS menuItem("Metrics/Debugger", "", show::metrics, hasDebugTools) menuItem("Debug Log", "", show::debugLog, hasDebugTools) @@ -188,37 +201,35 @@ object ExampleApp { collapsingHeader("Help") { - text("ABOUT THIS DEMO:") + separatorText("ABOUT THIS DEMO:") bulletText("Sections below are demonstrating many aspects of the library.") bulletText("The \"Examples\" menu above leads to more demo contents.") bulletText("The \"Tools\" menu above gives access to: About Box, Style Editor,\n" + - "and Metrics/Debugger (general purpose Dear ImGui debugging tool).") - separator() + "and Metrics/Debugger (general purpose Dear ImGui debugging tool).") - text("PROGRAMMER GUIDE:") + separatorText("PROGRAMMER GUIDE:") bulletText("See the ShowDemoWindow() code in imgui_demo.cpp. <- you are here!") bulletText("See comments in imgui.cpp.") bulletText("See example applications in the examples/ folder.") bulletText("Read the FAQ at http://www.dearimgui.org/faq/") bulletText("Set 'io.ConfigFlags |= NavEnableKeyboard' for keyboard controls.") bulletText("Set 'io.ConfigFlags |= NavEnableGamepad' for gamepad controls.") - separator() - text("USER GUIDE:") + separatorText("USER GUIDE:") showUserGuide() } collapsingHeader("Configuration") { treeNode("Configuration##2") { - - checkboxFlags("io.ConfigFlags: NavEnableKeyboard", io::configFlags, ConfigFlag.NavEnableKeyboard.i) + separatorText("General") + checkboxFlags("io.ConfigFlags: NavEnableKeyboard", io::configFlags, ConfigFlag.NavEnableKeyboard) sameLine(); helpMarker("Enable keyboard controls.") - checkboxFlags("io.ConfigFlags: NavEnableGamepad", io::configFlags, ConfigFlag.NavEnableGamepad.i) + checkboxFlags("io.ConfigFlags: NavEnableGamepad", io::configFlags, ConfigFlag.NavEnableGamepad) sameLine(); helpMarker("Enable gamepad controls. Require backend to feed in gamepad inputs in io.NavInputs[] and set io.BackendFlags |= ImGuiBackendFlags_HasGamepad.\n\nRead instructions in imgui.cpp for details.") - checkboxFlags("io.ConfigFlags: NavEnableSetMousePos", io::configFlags, ConfigFlag.NavEnableSetMousePos.i) + checkboxFlags("io.ConfigFlags: NavEnableSetMousePos", io::configFlags, ConfigFlag.NavEnableSetMousePos) sameLine(); helpMarker("Instruct navigation to move the mouse cursor. See comment for ImGuiConfigFlags_NavEnableSetMousePos.") - checkboxFlags("io.ConfigFlags: NoMouse", io::configFlags, ConfigFlag.NoMouse.i) + checkboxFlags("io.ConfigFlags: NoMouse", io::configFlags, ConfigFlag.NoMouse) if (io.configFlags has ConfigFlag.NoMouse) { // The "NoMouse" option can get us stuck with a disabled mouse! Let's provide an alternative way to fix it: if ((time.f % 0.4f) < 0.2f) { @@ -228,10 +239,14 @@ object ExampleApp { if (Key.Space.isPressed) io.configFlags = io.configFlags wo ConfigFlag.NoMouse } - checkboxFlags("io.ConfigFlags: NoMouseCursorChange", io::configFlags, ConfigFlag.NoMouseCursorChange.i) + checkboxFlags("io.ConfigFlags: NoMouseCursorChange", io::configFlags, ConfigFlag.NoMouseCursorChange) sameLine(); helpMarker("Instruct backend to not alter mouse cursor shape and visibility.") checkbox("io.ConfigInputTrickleEventQueue", io::configInputTrickleEventQueue) sameLine(); helpMarker("Enable input queue trickling: some types of events submitted during the same frame (e.g. button down + up) will be spread over multiple frames, improving interactions with low framerates.") + checkbox("io.MouseDrawCursor", io::mouseDrawCursor) + sameLine(); helpMarker("Instruct Dear ImGui to render a mouse cursor itself. Note that a mouse cursor rendered via your application GPU rendering path will feel more laggy than hardware cursor, but will be more in sync with your other visuals.\n\nSome desktop applications may use both kinds of cursors (e.g. enable software cursor only when resizing/dragging something).") + + separatorText("Widgets") checkbox("io.ConfigCursorBlink", io::configInputTextCursorBlink) sameLine(); helpMarker("Enable blinking cursor (optional as some users consider it to be distracting).") checkbox("io.ConfigInputTextEnterKeepActive", io::configInputTextEnterKeepActive) @@ -241,42 +256,54 @@ object ExampleApp { checkbox("io.ConfigWindowsResizeFromEdges", io::configWindowsResizeFromEdges) sameLine(); helpMarker("Enable resizing of windows from their edges and from the lower-left corner.\nThis requires (io.BackendFlags & ImGuiBackendFlags_HasMouseCursors) because it needs mouse cursor feedback.") checkbox("io.configWindowsMoveFromTitleBarOnly", io::configWindowsMoveFromTitleBarOnly) - checkbox("io.MouseDrawCursor", io::mouseDrawCursor) - sameLine(); helpMarker("Instruct Dear ImGui to render a mouse cursor itself. Note that a mouse cursor rendered via your application GPU rendering path will feel more laggy than hardware cursor, but will be more in sync with your other visuals.\n\nSome desktop applications may use both kinds of cursors (e.g. enable software cursor only when resizing/dragging something).") + checkbox("io.ConfigMacOSXBehaviors", io::configMacOSXBehaviors) text("Also see Style->Rendering for rendering options.") - separator() + + separatorText("Debug") + beginDisabled() + checkbox("io.ConfigDebugBeginReturnValueOnce", io::configDebugBeginReturnValueOnce) // . + endDisabled() + sameLine(); helpMarker("First calls to Begin()/BeginChild() will return false.\n\nTHIS OPTION IS DISABLED because it needs to be set at application boot-time to make sense. Showing the disabled option is a way to make this feature easier to discover") + checkbox("io.ConfigDebugBeginReturnValueLoop", io::configDebugBeginReturnValueLoop) + sameLine(); helpMarker("Some calls to Begin()/BeginChild() will return false.\n\nWill cycle through window depths then repeat. Windows should be flickering while running.") + checkbox("io.ConfigDebugIgnoreFocusLoss", io::configDebugIgnoreFocusLoss) + sameLine(); helpMarker("Option to deactivate io.AddFocusEvent(false) handling. May facilitate interactions with a debugger when focus loss leads to clearing inputs data.") + checkbox("io.ConfigDebugIniSettings", io::configDebugIniSettings); + sameLine(); helpMarker("Option to save .ini data with extra comments (particularly helpful for Docking, but makes saving slower).") + + spacing() } treeNode("Backend Flags") { helpMarker(""" Those flags are set by the backends (imgui_impl_xxx files) to specify their capabilities. Here we expose them as read-only fields to avoid breaking interactions with your backend.""".trimIndent()) - // Make a local copy to avoid modifying actual backend flags. - // FIXME: We don't use BeginDisabled() to keep label bright, maybe we need a BeginReadonly() equivalent.. - val backendFlags = intArrayOf(io.backendFlags) - checkboxFlags("io.BackendFlags: HasGamepad", backendFlags, BackendFlag.HasGamepad.i) - checkboxFlags("io.BackendFlags: HasMouseCursors", backendFlags, BackendFlag.HasMouseCursors.i) - checkboxFlags("io.BackendFlags: HasSetMousePos", backendFlags, BackendFlag.HasSetMousePos.i) - checkboxFlags("io.BackendFlags: RendererHasVtxOffset", backendFlags, BackendFlag.RendererHasVtxOffset.i) - separator() + // FIXME: Maybe we need a BeginReadonly() equivalent to keep label bright? + beginDisabled() + checkboxFlags("io.BackendFlags: HasGamepad", io::backendFlags, BackendFlag.HasGamepad) + checkboxFlags("io.BackendFlags: HasMouseCursors", io::backendFlags, BackendFlag.HasMouseCursors) + checkboxFlags("io.BackendFlags: HasSetMousePos", io::backendFlags, BackendFlag.HasSetMousePos) + checkboxFlags("io.BackendFlags: RendererHasVtxOffset", io::backendFlags, BackendFlag.RendererHasVtxOffset) + endDisabled() + spacing() } treeNode("Style") { helpMarker("The same contents can be accessed in 'Tools->Style Editor' or by calling the ShowStyleEditor() function.") StyleEditor() - separator() + spacing() } treeNode("Capture/Logging") { - textWrapped(""" + helpMarker(""" The logging API redirects all text output so you can easily capture the content of a window or a block. Tree nodes can be automatically expanded. - Try opening any of the contents below in this window and then click one of the \"Log To\" button.""".trimIndent()) + Try opening any of the contents below in this window and then click one of the "Log To" button.""".trimIndent()) logButtons() helpMarker("You can also call ImGui::LogText() to output directly to the log without a visual output.") - if (button("Copy \"Hello, world!\" to clipboard")) { + if (button("""Copy "Hello, world!" to clipboard""")) { logToClipboard() - logText("%s", "Hello, world!") + logText("Hello, world!") logFinish() } } diff --git a/core/src/main/kotlin/imgui/demo/ShowDemoWindowColumns.kt b/core/src/main/kotlin/imgui/demo/ShowDemoWindowColumns.kt index f832c3d20..5dac57578 100644 --- a/core/src/main/kotlin/imgui/demo/ShowDemoWindowColumns.kt +++ b/core/src/main/kotlin/imgui/demo/ShowDemoWindowColumns.kt @@ -8,11 +8,10 @@ import imgui.ImGui.checkbox import imgui.ImGui.columnIndex import imgui.ImGui.columns import imgui.ImGui.contentRegionAvail -import imgui.ImGui.dragInt import imgui.ImGui.fontSize import imgui.ImGui.getColumnOffset import imgui.ImGui.getColumnWidth -import imgui.ImGui.inputFloat +import imgui.ImGui.input import imgui.ImGui.isItemHovered import imgui.ImGui.nextColumn import imgui.ImGui.sameLine @@ -28,7 +27,9 @@ import imgui.ImGui.treePop import imgui.SelectableFlag import imgui.WindowFlag import imgui.api.demoDebugInformations.Companion.helpMarker +import imgui.api.drag import imgui.classes.ListClipper +import imgui.classes.listClipper import imgui.dsl.child import imgui.dsl.collapsingHeader import imgui.dsl.selectable @@ -66,21 +67,19 @@ object ShowDemoWindowColumns { treeNode("Horizontal Scrolling") { setNextWindowContentSize(Vec2(1500f, 0f)) val childSize = Vec2(0f, fontSize * 20f) - child("##ScrollingRegion", childSize, false, WindowFlag.HorizontalScrollbar.i) { + child("##ScrollingRegion", childSize, false, WindowFlag.HorizontalScrollbar) { columns(10) // Also demonstrate using clipper for large vertical lists val ITEMS_COUNT = 2000 - val clipper = ListClipper() - clipper.begin(ITEMS_COUNT) - while (clipper.step()) - for (i in clipper.displayStart until clipper.displayEnd) + listClipper(ITEMS_COUNT) { + for (i in it.display) for (j in 0..9) { text("Line $i Column $j...") nextColumn() } + } columns(1) - clipper.end() } } @@ -142,7 +141,7 @@ object ShowDemoWindowColumns { val names = listOf("One", "Two", "Three") val paths = listOf("/path/one", "/path/two", "/path/three") for (i in 0..2) { - selectable("%04d".format(style.locale, i), selected == i, SelectableFlag.SpanAllColumns.i) { + selectable("%04d".format(style.locale, i), selected == i, SelectableFlag.SpanAllColumns) { selected = i } nextColumn() @@ -165,7 +164,7 @@ object ShowDemoWindowColumns { // NB: Future columns API should allow automatic horizontal borders. val linesCount = 3 setNextItemWidth(fontSize * 8) - dragInt("##columns_count", ::columnsCount, 0.1f, 2, 10, "%d columns") + drag("##columns_count", ::columnsCount, 0.1f, 2, 10, "%d columns") if (columnsCount < 2) columnsCount = 2 sameLine() @@ -207,13 +206,13 @@ object ShowDemoWindowColumns { text("ImGui") button("Apple") - inputFloat("red", ::foo, 0.05f, 0f, "%.3f") + input("red", ::foo, 0.05f, 0f, "%.3f") text("An extra line here.") nextColumn() text("Sailor") button("Corniflower") - inputFloat("blue", ::bar, 0.05f, 0f, "%.3f") + input("blue", ::bar, 0.05f, 0f, "%.3f") nextColumn() collapsingHeader("Category A") { text("Blah blah blah") }; nextColumn() diff --git a/core/src/main/kotlin/imgui/demo/ShowDemoWindowInputs.kt b/core/src/main/kotlin/imgui/demo/ShowDemoWindowInputs.kt index df682c5fa..5682b1d7a 100644 --- a/core/src/main/kotlin/imgui/demo/ShowDemoWindowInputs.kt +++ b/core/src/main/kotlin/imgui/demo/ShowDemoWindowInputs.kt @@ -2,6 +2,7 @@ package imgui.demo import glm_.i import glm_.vec2.Vec2 +import glm_.vec3.Vec3 import glm_.vec4.Vec4 import imgui.* import imgui.ImGui.beginDisabled @@ -9,19 +10,17 @@ import imgui.ImGui.bullet import imgui.ImGui.button import imgui.ImGui.checkboxFlags import imgui.ImGui.colorButton -import imgui.ImGui.data import imgui.ImGui.endDisabled import imgui.ImGui.foregroundDrawList import imgui.ImGui.getMouseDragDelta import imgui.ImGui.inputText import imgui.ImGui.io import imgui.ImGui.isDown +import imgui.ImGui.isDragging import imgui.ImGui.isItemActive -import imgui.ImGui.isMouseDown -import imgui.ImGui.isMouseDragging import imgui.ImGui.isMousePosValid -import imgui.ImGui.popAllowKeyboardFocus -import imgui.ImGui.pushAllowKeyboardFocus +import imgui.ImGui.popTabStop +import imgui.ImGui.pushTabStop import imgui.ImGui.sameLine import imgui.ImGui.selectable import imgui.ImGui.setKeyboardFocusHere @@ -29,11 +28,11 @@ import imgui.ImGui.setNextFrameWantCaptureKeyboard import imgui.ImGui.setNextFrameWantCaptureMouse import imgui.ImGui.setNextItemOpen import imgui.ImGui.setNextItemWidth -import imgui.ImGui.sliderFloat3 -import imgui.ImGui.sliderInt +import imgui.ImGui.slider3 import imgui.ImGui.text import imgui.ImGui.textWrapped import imgui.api.demoDebugInformations.Companion.helpMarker +import imgui.api.slider import imgui.dsl.collapsingHeader import imgui.dsl.treeNode @@ -50,25 +49,24 @@ object ShowDemoWindowInputs { setNextItemOpen(true, Cond.Once) treeNode("Inputs") { helpMarker("This is a simplified view. See more detailed input state:\n" + - "- in 'Tools->Metrics/Debugger->Inputs'.\n" + - "- in 'Tools->Debug Log->IO'.") + "- in 'Tools->Metrics/Debugger->Inputs'.\n" + + "- in 'Tools->Debug Log->IO'.") if (isMousePosValid()) text("Mouse pos: (%g, %g)", io.mousePos.x, io.mousePos.y) else text("Mouse pos: ") text("Mouse delta: (%g, %g)", io.mouseDelta.x, io.mouseDelta.y) text("Mouse down:") - for (i in io.mouseDown.indices) if (isMouseDown(MouseButton.of(i))) {; sameLine(); text("b$i (%.02f secs)", io.mouseDownDuration[i]); } + for (i in io.mouseDown.indices) if (MouseButton.of(i).isDown) {; sameLine(); text("b$i (%.02f secs)", io.mouseDownDuration[i]); } text("Mouse wheel: %.1f", io.mouseWheel) // 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. text("Keys down:") - for (keyIdx in 0 until Key.Count.ordinal) { - val key = Key of keyIdx - if (!key.isDown) continue - sameLine(); text('"' + key.name + '"'); sameLine(); text("(%.02f)", key.data.downDuration) + for (key in Key.Named) { + if (!key.isDown) + continue } val ctrl = if (io.keyCtrl) "CTRL " else "" val shift = if (io.keyShift) "SHIFT " else "" @@ -88,13 +86,12 @@ object ShowDemoWindowInputs { // IMGUI_DEMO_MARKER("Inputs & Focus/Outputs"); setNextItemOpen(true, Cond.Once) treeNode("Outputs") { - helpMarker( - "The value of io.WantCaptureMouse and io.WantCaptureKeyboard are normally set by Dear ImGui " + - "to instruct your application of how to route inputs. Typically, when a value is true, it means " + - "Dear ImGui wants the corresponding inputs and we expect the underlying application to ignore them.\n\n" + - "The most typical case is: when hovering a window, Dear ImGui set io.WantCaptureMouse to true, " + - "and underlying application should ignore mouse inputs (in practice there are many and more subtle " + - "rules leading to how those flags are set).") + helpMarker("The value of io.WantCaptureMouse and io.WantCaptureKeyboard are normally set by Dear ImGui " + + "to instruct your application of how to route inputs. Typically, when a value is true, it means " + + "Dear ImGui wants the corresponding inputs and we expect the underlying application to ignore them.\n\n" + + "The most typical case is: when hovering a window, Dear ImGui set io.WantCaptureMouse to true, " + + "and underlying application should ignore mouse inputs (in practice there are many and more subtle " + + "rules leading to how those flags are set).") text("io.WantCaptureMouse: ${io.wantCaptureMouse.i}") text("io.WantCaptureMouseUnlessPopupClose: ${io.wantCaptureMouseUnlessPopupClose.i}") text("io.WantCaptureKeyboard: ${io.wantCaptureKeyboard.i}") @@ -104,14 +101,13 @@ object ShowDemoWindowInputs { // IMGUI_DEMO_MARKER("Inputs & Focus/Outputs/WantCapture override"); treeNode("WantCapture override") { - helpMarker( - "Hovering the colored canvas will override io.WantCaptureXXX fields.\n" + - "Notice how normally (when set to none), the value of io.WantCaptureKeyboard would be false when hovering and true when clicking.") + helpMarker("Hovering the colored canvas will override io.WantCaptureXXX fields.\n" + + "Notice how normally (when set to none), the value of io.WantCaptureKeyboard would be false when hovering and true when clicking.") val captureOverrideDesc = listOf("None", "Set to false", "Set to true") setNextItemWidth(ImGui.fontSize * 15) - sliderInt("SetNextFrameWantCaptureMouse() on hover", ::captureOverrideMouse, -1, +1, captureOverrideDesc[captureOverrideMouse + 1], SliderFlag.AlwaysClamp.i) + slider("SetNextFrameWantCaptureMouse() on hover", ::captureOverrideMouse, -1, +1, captureOverrideDesc[captureOverrideMouse + 1], SliderFlag.AlwaysClamp) setNextItemWidth(ImGui.fontSize * 15) - sliderInt("SetNextFrameWantCaptureKeyboard() on hover", ::captureOverrideKeyboard, -1, +1, captureOverrideDesc[captureOverrideKeyboard + 1], SliderFlag.AlwaysClamp.i) + slider("SetNextFrameWantCaptureKeyboard() on hover", ::captureOverrideKeyboard, -1, +1, captureOverrideDesc[captureOverrideKeyboard + 1], SliderFlag.AlwaysClamp) colorButton("##panel", Vec4(0.7f, 0.1f, 0.7f, 1f), ColorEditFlag.NoTooltip or ColorEditFlag.NoDragDrop, Vec2(128f, 96f)) // Dummy item if (ImGui.isItemHovered() && captureOverrideMouse != -1) @@ -130,13 +126,13 @@ object ShowDemoWindowInputs { val current = ImGui.mouseCursor text("Current mouse cursor = ${current.i}: $current") beginDisabled(true) - checkboxFlags("io.BackendFlags: HasMouseCursors", io::backendFlags, BackendFlag.HasMouseCursors.i) + checkboxFlags("io.BackendFlags: HasMouseCursors", io::backendFlags, BackendFlag.HasMouseCursors) endDisabled() text("Hover to see mouse cursors:") sameLine(); helpMarker("Your application can render a different mouse cursor based on what ImGui::GetMouseCursor() returns. " + - "If software cursor rendering (io.MouseDrawCursor) is set ImGui will draw the right cursor for you, " + - "otherwise your backend needs to handle it.") + "If software cursor rendering (io.MouseDrawCursor) is set ImGui will draw the right cursor for you, " + + "otherwise your backend needs to handle it.") for (i in 0 until MouseCursor.COUNT) { val cursor = MouseCursor of i val label = "Mouse cursor $i: $cursor" @@ -153,12 +149,12 @@ object ShowDemoWindowInputs { // IMGUI_DEMO_MARKER("Inputs & Focus/Dragging"); treeNode("Dragging") { textWrapped("You can use getMouseDragDelta(0) to query for the dragged amount on any widget.") - for (button in MouseButton.values()) + for (button in MouseButton.traditionalValues) if (button != MouseButton.None) { text("IsMouseDragging(${button.i}):") - text(" w/ default threshold: ${isMouseDragging(button).i},") - text(" w/ zero threshold: ${isMouseDragging(button, 0f).i},") - text(" w/ large threshold: ${isMouseDragging(button, 20f)},") + text(" w/ default threshold: ${button.isDragging().i},") + text(" w/ zero threshold: ${button.isDragging(0f).i},") + text(" w/ large threshold: ${button.isDragging(20f)},") } button("Drag Me") if (isItemActive) @@ -177,6 +173,7 @@ object ShowDemoWindowInputs { } } } + object Tabbing { var buf = "hello".toByteArray(32) operator fun invoke() { @@ -186,10 +183,10 @@ object ShowDemoWindowInputs { inputText("1", buf) inputText("2", buf) inputText("3", buf) - pushAllowKeyboardFocus(false) + pushTabStop(false) inputText("4 (tab skip)", buf) sameLine(); helpMarker("Item won't be cycled through when using TAB or Shift+Tab.") - popAllowKeyboardFocus() + popTabStop() inputText("5", buf) } } @@ -197,7 +194,7 @@ object ShowDemoWindowInputs { object `Focus from code` { var buf = "click on a button to set focus".toByteArray(128) - val f3 = FloatArray(3) + val f3 = Vec3() operator fun invoke() { // IMGUI_DEMO_MARKER("Inputs & Focus/Focus from code"); treeNode("Focus from code") { @@ -214,12 +211,12 @@ object ShowDemoWindowInputs { inputText("2", buf) if (isItemActive) hasFocus = 2 - pushAllowKeyboardFocus(false) + pushTabStop(false) if (focus3) setKeyboardFocusHere() inputText("3 (tab skip)", buf) if (isItemActive) hasFocus = 3 sameLine(); helpMarker("Item won't be cycled through when using TAB or Shift+Tab.") - popAllowKeyboardFocus() + popTabStop() text("Item with focus: ${if (hasFocus != 0) "$hasFocus" else ""}") @@ -229,7 +226,7 @@ object ShowDemoWindowInputs { if (button("Focus on Y")) focusAhead = 1; sameLine() if (button("Focus on Z")) focusAhead = 2 if (focusAhead != -1) setKeyboardFocusHere(focusAhead) - sliderFloat3("Float3", f3, 0f, 1f) + slider3("Float3", f3, 0f, 1f) textWrapped("NB: Cursor & selection are preserved when refocusing last used item in code.") } diff --git a/core/src/main/kotlin/imgui/demo/ShowDemoWindowLayout.kt b/core/src/main/kotlin/imgui/demo/ShowDemoWindowLayout.kt index 3ce01dfe0..8d6f9a0a3 100644 --- a/core/src/main/kotlin/imgui/demo/ShowDemoWindowLayout.kt +++ b/core/src/main/kotlin/imgui/demo/ShowDemoWindowLayout.kt @@ -1,8 +1,10 @@ +@file:OptIn(ExperimentalStdlibApi::class) + package imgui.demo -import gli_.has import glm_.L import glm_.f +import glm_.has import glm_.i import glm_.vec2.Vec2 import glm_.vec4.Vec4 @@ -26,9 +28,7 @@ import imgui.ImGui.combo import imgui.ImGui.contentRegionAvail import imgui.ImGui.cursorScreenPos import imgui.ImGui.cursorStartPos -import imgui.ImGui.dragFloat -import imgui.ImGui.dragInt -import imgui.ImGui.dragVec2 +import imgui.ImGui.drag2 import imgui.ImGui.dummy import imgui.ImGui.end import imgui.ImGui.endChild @@ -44,8 +44,8 @@ import imgui.ImGui.getColumnWidth import imgui.ImGui.getID import imgui.ImGui.invisibleButton import imgui.ImGui.io +import imgui.ImGui.isDragging import imgui.ImGui.isItemActive -import imgui.ImGui.isItemHovered import imgui.ImGui.itemRectMax import imgui.ImGui.itemRectSize import imgui.ImGui.listBox @@ -66,15 +66,14 @@ import imgui.ImGui.scrollX import imgui.ImGui.scrollY import imgui.ImGui.selectable import imgui.ImGui.separator +import imgui.ImGui.separatorText +import imgui.ImGui.setItemTooltip import imgui.ImGui.setNextItemWidth import imgui.ImGui.setNextWindowContentSize import imgui.ImGui.setScrollFromPosX import imgui.ImGui.setScrollFromPosY import imgui.ImGui.setScrollHereX import imgui.ImGui.setScrollHereY -import imgui.ImGui.setTooltip -import imgui.ImGui.sliderFloat -import imgui.ImGui.sliderInt import imgui.ImGui.smallButton import imgui.ImGui.spacing import imgui.ImGui.style @@ -90,6 +89,8 @@ import imgui.ImGui.windowContentRegionMax import imgui.ImGui.windowDrawList import imgui.ImGui.windowPos import imgui.api.demoDebugInformations.Companion.helpMarker +import imgui.api.drag +import imgui.api.slider import imgui.classes.Color import imgui.demo.showExampleApp.MenuFile import imgui.dsl.child @@ -120,10 +121,9 @@ object ShowDemoWindowLayout { treeNode("Groups") { - helpMarker( - "BeginGroup() basically locks the horizontal position for new line. " + - "EndGroup() bundles the whole group so that you can use \"item\" functions such as " + - "IsItemHovered()/IsItemActive() or SameLine() etc. on the whole group.") + helpMarker("BeginGroup() basically locks the horizontal position for new line. " + + "EndGroup() bundles the whole group so that you can use \"item\" functions such as " + + "IsItemHovered()/IsItemActive() or SameLine() etc. on the whole group.") beginGroup() group { button("AAA") @@ -137,7 +137,7 @@ object ShowDemoWindowLayout { sameLine() button("EEE") } - if (isItemHovered()) setTooltip("First group hovered") + setItemTooltip("First group hovered") // Capture the group size and create widgets using the same size val size = Vec2(itemRectSize) @@ -164,9 +164,8 @@ object ShowDemoWindowLayout { run { bulletText("Text baseline:") - sameLine(); helpMarker( - "This is testing the vertical alignment that gets applied on text to keep it aligned with widgets. " + - "Lines only composed of text or \"small\" widgets use less vertical space than lines with framed widgets.") + sameLine(); helpMarker("This is testing the vertical alignment that gets applied on text to keep it aligned with widgets. " + + "Lines only composed of text or \"small\" widgets use less vertical space than lines with framed widgets.") indent { text("KO Blahblah"); sameLine() @@ -287,13 +286,15 @@ object ShowDemoWindowLayout { treeNode("Child Windows") { + separatorText("Child windows") + helpMarker("Use child windows to begin into a self-contained independent scrolling/clipping regions within a host window.") checkbox("Disable Mouse Wheel", ::disableMouseWheel) checkbox("Disable Menu", ::disableMenu) // Child 1: no border, enable horizontal scrollbar run { - var windowFlags = Wf.HorizontalScrollbar.i + var windowFlags: WindowFlags = Wf.HorizontalScrollbar if (disableMouseWheel) windowFlags = windowFlags or Wf.NoScrollWithMouse child("ChildL", Vec2(contentRegionAvail.x * 0.5f, 260), false, windowFlags) { @@ -305,11 +306,11 @@ object ShowDemoWindowLayout { // Child 2: rounded border run { - var windowFlags = Wf.None.i + var windowFlags: WindowFlags = none if (disableMouseWheel) - windowFlags = windowFlags or Wf.NoScrollWithMouse + windowFlags /= Wf.NoScrollWithMouse if (!disableMenu) - windowFlags = windowFlags or Wf.MenuBar + windowFlags /= Wf.MenuBar withStyleVar(StyleVar.ChildRounding, 5f) { child("ChildR", Vec2(0, 260), true, windowFlags) { if (!disableMenu && beginMenuBar()) { @@ -320,7 +321,7 @@ object ShowDemoWindowLayout { endMenuBar() } columns(2) - if (beginTable("split", 2, TableFlag.Resizable or TableFlag.NoSavedSettings)) { + if (beginTable("split", 2, TableFlag.Resizable / TableFlag.NoSavedSettings)) { for (i in 0..99) { val text = "%03d".format(style.locale, i) tableNextColumn() @@ -332,7 +333,7 @@ object ShowDemoWindowLayout { } } - separator() + separatorText("Misc/Advanced") // Demonstrate a few extra things // - Changing ImGuiCol_ChildBg (which is transparent black in default styles) @@ -343,11 +344,11 @@ object ShowDemoWindowLayout { // the POV of the parent window). See 'Demo->Querying Status (Active/Focused/Hovered etc.)' for details. run { setNextItemWidth(fontSize * 8) - dragInt("Offset X", ::offsetX, 1f, -1000, 1000) + drag("Offset X", ::offsetX, 1f, -1000, 1000) ImGui.cursorPosX += offsetX withStyleColor(Col.ChildBg, COL32(255, 0, 0, 100)) { - beginChild("Red", Vec2(200, 100), true, Wf.None.i) + beginChild("Red", Vec2(200, 100), true) for (n in 0..49) text("Some test $n") endChild() @@ -380,40 +381,40 @@ object ShowDemoWindowLayout { text("SetNextItemWidth/PushItemWidth(100)") sameLine(); helpMarker("Fixed width.") pushItemWidth(100) - dragFloat("float##1b", ::f) + drag("float##1b", ::f) if (showIndentedItems) indent { - dragFloat("float (indented)##1b", ::f) + drag("float (indented)##1b", ::f) } popItemWidth() text("SetNextItemWidth/PushItemWidth(-100)") sameLine(); helpMarker("Align to right edge minus 100") pushItemWidth(-100) - dragFloat("float##2a", ::f) + drag("float##2a", ::f) if (showIndentedItems) indent { - dragFloat("float (indented)##2b", ::f) + drag("float (indented)##2b", ::f) } popItemWidth() text("SetNextItemWidth/PushItemWidth(GetContentRegionAvail().x * 0.5f)") sameLine(); helpMarker("Half of available width.\n(~ right-cursor_pos)\n(works within a column set)") pushItemWidth(contentRegionAvail.x * 0.5f) - dragFloat("float##3a", ::f) + drag("float##3a", ::f) if (showIndentedItems) indent { - dragFloat("float (indented)##3b", ::f) + drag("float (indented)##3b", ::f) } popItemWidth() text("SetNextItemWidth/PushItemWidth(-GetContentRegionAvail().x * 0.5f)") sameLine(); helpMarker("Align to right edge minus half") pushItemWidth(-ImGui.contentRegionAvail.x * 0.5f) - dragFloat("float##4a", ::f) + drag("float##4a", ::f) if (showIndentedItems) indent { - dragFloat("float (indented)##4b", ::f) + drag("float (indented)##4b", ::f) } popItemWidth() @@ -423,10 +424,10 @@ object ShowDemoWindowLayout { sameLine(); helpMarker("Align to right edge") pushItemWidth(-Float.MIN_VALUE) - dragFloat("##float5a", ::f) + drag("##float5a", ::f) if (showIndentedItems) indent { - dragFloat("float (indented)##5b", ::f) + drag("float (indented)##5b", ::f) } popItemWidth() } @@ -488,9 +489,9 @@ object ShowDemoWindowLayout { val items = arrayOf("AAAA", "BBBB", "CCCC", "DDDD") withItemWidth(80f) { combo("Combo", ::item, items); sameLine() - sliderFloat("X", ::f0, 0f, 5f); sameLine() - sliderFloat("Y", ::f1, 0f, 5f); sameLine() - sliderFloat("Z", ::f2, 0f, 5f) + slider("X", ::f0, 0f, 5f); sameLine() + slider("Y", ::f1, 0f, 5f); sameLine() + slider("Z", ::f2, 0f, 5f) } withItemWidth(80f) { @@ -498,11 +499,9 @@ object ShowDemoWindowLayout { for (i in 0..3) { if (i > 0) sameLine() withID(i) { - withInt(selection, i) { - listBox("", it, items) - } + listBox("", selection mutablePropertyAt i, items) } - //if (IsItemHovered()) SetTooltip("ListBox %d hovered", i); + //ImGui::SetItemTooltip("ListBox %d hovered", i); } } @@ -559,13 +558,13 @@ object ShowDemoWindowLayout { checkbox("Track", ::enableTrack) pushItemWidth(100) - sameLine(140); enableTrack = dragInt("##item", ::trackItem, 0.25f, 0, 99, "Item = %d") or enableTrack + sameLine(140); enableTrack = drag("##item", ::trackItem, 0.25f, 0, 99, "Item = %d") or enableTrack var scrollToOff = button("Scroll Offset") - sameLine(140); scrollToOff = dragFloat("##off", ::scrollToOffPx, 1f, 0f, Float.MAX_VALUE, "+%.0f px") or scrollToOff + sameLine(140); scrollToOff = drag("##off", ::scrollToOffPx, 1f, 0f, Float.MAX_VALUE, "+%.0f px") or scrollToOff var scrollToPos = button("Scroll To Pos") - sameLine(140); scrollToPos = dragFloat("##pos", ::scrollToPosPx, 1f, -10f, Float.MAX_VALUE, "X/Y = %.0f px") or scrollToPos + sameLine(140); scrollToPos = drag("##pos", ::scrollToPosPx, 1f, -10f, Float.MAX_VALUE, "X/Y = %.0f px") or scrollToPos popItemWidth() if (scrollToOff || scrollToPos) @@ -581,9 +580,9 @@ object ShowDemoWindowLayout { val names = arrayOf("Top", "25%%", "Center", "75%%", "Bottom") // double quote for ::format escaping textUnformatted(names[i]) - val childFlags = if (enableExtraDecorations) Wf.MenuBar else Wf.None + val childFlags = if (enableExtraDecorations) Wf.MenuBar else none val childId = getID(i.L) - val childIsVisible = beginChild(childId, Vec2(childW, 200f), true, childFlags.i) + val childIsVisible = beginChild(childId, Vec2(childW, 200f), true, childFlags) menuBar { textUnformatted("abc") } if (scrollToOff) scrollY = scrollToOffPx @@ -607,15 +606,14 @@ object ShowDemoWindowLayout { // Horizontal scroll functions spacing() - helpMarker( - "Use SetScrollHereX() or SetScrollFromPosX() to scroll to a given horizontal position.\n\n" + - "Because the clipping rectangle of most window hides half worth of WindowPadding on the " + - "left/right, using SetScrollFromPosX(+1) will usually result in clipped text whereas the " + - "equivalent SetScrollFromPosY(+1) wouldn't.") + helpMarker("Use SetScrollHereX() or SetScrollFromPosX() to scroll to a given horizontal position.\n\n" + + "Because the clipping rectangle of most window hides half worth of WindowPadding on the " + + "left/right, using SetScrollFromPosX(+1) will usually result in clipped text whereas the " + + "equivalent SetScrollFromPosY(+1) wouldn't.") pushID("##HorizontalScrolling") for (i in 0..4) { val childHeight = textLineHeight + style.scrollbarSize + style.windowPadding.y * 2f - val childFlags = Wf.HorizontalScrollbar or if (enableExtraDecorations) Wf.AlwaysVerticalScrollbar else Wf.None + val childFlags = Wf.HorizontalScrollbar or if (enableExtraDecorations) Wf.AlwaysVerticalScrollbar else none val childId = getID(i.L) val childIsVisible = beginChild(childId, Vec2(-100f, childHeight), true, childFlags) if (scrollToOff) @@ -641,21 +639,20 @@ object ShowDemoWindowLayout { // Miscellaneous Horizontal Scrolling Demo - helpMarker( - "Horizontal scrolling for a window is enabled via the ImGuiWindowFlags_HorizontalScrollbar flag.\n\n" + - "You may want to also explicitly specify content width by using SetNextWindowContentWidth() before Begin().") - sliderInt("Lines", ::lines, 1, 15) + helpMarker("Horizontal scrolling for a window is enabled via the ImGuiWindowFlags_HorizontalScrollbar flag.\n\n" + + "You may want to also explicitly specify content width by using SetNextWindowContentWidth() before Begin().") + slider("Lines", ::lines, 1, 15) pushStyleVar(StyleVar.FrameRounding, 3f) pushStyleVar(StyleVar.FramePadding, Vec2(2f, 1f)) val scrollingChildSize = Vec2(0f, ImGui.frameHeightWithSpacing * 7 + 30) - beginChild("scrolling", scrollingChildSize, true, Wf.HorizontalScrollbar.i) + beginChild("scrolling", scrollingChildSize, true, Wf.HorizontalScrollbar) for (line in 0 until lines) { // Display random stuff. For the sake of this trivial demo we are using basic Button() + SameLine() // If you want to create your own time line for a real application you may be better off manipulating // the cursor position yourself, aka using SetCursorPos/SetCursorScreenPos to position the widgets // yourself. You may also want to use the lower-level ImDrawList API. val numButtons = 10 + (line * if (line has 1) 9 else 3) - for (n in 0 until numButtons) { + for (n in 0.. 0) sameLine() pushID(n + line * 1000) val label = if (n % 15 == 0) "FizzBuzz" else if (n % 3 == 0) "Fizz" else if (n % 5 == 0) "Buzz" else "$n" @@ -697,7 +694,7 @@ object ShowDemoWindowLayout { if (showHorizontalContentsSizeDemoWindow) { if (explicitContentSize) setNextWindowContentSize(Vec2(contentsSizeX, 0f)) - begin("Horizontal contents size demo window", ::showHorizontalContentsSizeDemoWindow, if (showHscrollbar) Wf.HorizontalScrollbar.i else Wf.None.i) + begin("Horizontal contents size demo window", ::showHorizontalContentsSizeDemoWindow, if (showHscrollbar) Wf.HorizontalScrollbar else none) pushStyleVar(StyleVar.ItemSpacing, Vec2(2, 0)) pushStyleVar(StyleVar.FramePadding, Vec2(2, 0)) helpMarker("Test of different widgets react and impact the work rectangle growing when horizontal scrolling is enabled.\n\nUse 'Metrics->Tools->Show windows rectangles' to visualize rectangles.") @@ -713,7 +710,7 @@ object ShowDemoWindowLayout { if (explicitContentSize) { sameLine() setNextItemWidth(100f) - dragFloat("##csx", ::contentsSizeX) + drag("##csx", ::contentsSizeX) val p = cursorScreenPos windowDrawList.addRectFilled(p, Vec2(p.x + 10, p.y + 10), COL32_WHITE) windowDrawList.addRectFilled(Vec2(p.x + contentsSizeX - 10, p.y), Vec2(p.x + contentsSizeX, p.y + 10), COL32_WHITE) @@ -736,7 +733,7 @@ object ShowDemoWindowLayout { textWrapped("This text should automatically wrap on the edge of the work rectangle.") if (showColumns) { text("Tables:") - if (beginTable("table", 4, TableFlag.Borders.i)) { + if (beginTable("table", 4, TableFlag.Borders)) { for (n in 0..3) { tableNextColumn() text("Width %.2f", ImGui.contentRegionAvail.x) @@ -777,19 +774,18 @@ object ShowDemoWindowLayout { val offset = Vec2(30) operator fun invoke() { treeNode("Clipping") { - dragVec2("size", size, 0.5f, 1f, 200f, "%.0f") + drag2("size", size, 0.5f, 1f, 200f, "%.0f") textWrapped("(Click and drag to scroll)") - helpMarker( - "(Left) Using ImGui::PushClipRect():\n" + - "Will alter ImGui hit-testing logic + ImDrawList rendering.\n" + - "(use this if you want your clipping rectangle to affect interactions)\n\n" + - "(Center) Using ImDrawList::PushClipRect():\n" + - "Will alter ImDrawList rendering only.\n" + - "(use this as a shortcut if you are only using ImDrawList calls)\n\n" + - "(Right) Using ImDrawList::AddText() with a fine ClipRect:\n" + - "Will alter only this specific ImDrawList::AddText() rendering.\n" + - "This is often used internally to avoid altering the clipping rectangle and minimize draw calls.") + helpMarker("(Left) Using ImGui::PushClipRect():\n" + + "Will alter ImGui hit-testing logic + ImDrawList rendering.\n" + + "(use this if you want your clipping rectangle to affect interactions)\n\n" + + "(Center) Using ImDrawList::PushClipRect():\n" + + "Will alter ImDrawList rendering only.\n" + + "(use this as a shortcut if you are only using ImDrawList calls)\n\n" + + "(Right) Using ImDrawList::AddText() with a fine ClipRect:\n" + + "Will alter only this specific ImDrawList::AddText() rendering.\n" + + "This is often used internally to avoid altering the clipping rectangle and minimize draw calls.") for (n in 0..2) { if (n > 0) @@ -798,7 +794,7 @@ object ShowDemoWindowLayout { pushID(n) invisibleButton("##canvas", size) - if (ImGui.isItemActive && ImGui.isMouseDragging(MouseButton.Left)) + if (ImGui.isItemActive && MouseButton.Left.isDragging()) offset += io.mouseDelta popID() if (!ImGui.isItemVisible) // Skip rendering as ImDrawList elements are not clipped. diff --git a/core/src/main/kotlin/imgui/demo/ShowDemoWindowPopus.kt b/core/src/main/kotlin/imgui/demo/ShowDemoWindowPopus.kt index 8e8c1f00d..c0a98e732 100644 --- a/core/src/main/kotlin/imgui/demo/ShowDemoWindowPopus.kt +++ b/core/src/main/kotlin/imgui/demo/ShowDemoWindowPopus.kt @@ -1,6 +1,5 @@ package imgui.demo -import glm_.glm import glm_.vec2.Vec2 import glm_.vec4.Vec4 import imgui.* @@ -13,12 +12,10 @@ import imgui.ImGui.closeCurrentPopup import imgui.ImGui.collapsingHeader import imgui.ImGui.colorEdit4 import imgui.ImGui.combo -import imgui.ImGui.dragFloat import imgui.ImGui.endMenu import imgui.ImGui.endMenuBar import imgui.ImGui.endPopup import imgui.ImGui.inputText -import imgui.ImGui.isItemHovered import imgui.ImGui.mainViewport import imgui.ImGui.menuItem import imgui.ImGui.openPopup @@ -26,25 +23,25 @@ import imgui.ImGui.openPopupOnItemClick import imgui.ImGui.sameLine import imgui.ImGui.selectable import imgui.ImGui.separator +import imgui.ImGui.separatorText import imgui.ImGui.setItemDefaultFocus +import imgui.ImGui.setItemTooltip import imgui.ImGui.setNextItemWidth import imgui.ImGui.setNextWindowPos -import imgui.ImGui.setTooltip import imgui.ImGui.text import imgui.ImGui.textEx import imgui.ImGui.textWrapped import imgui.api.demoDebugInformations.Companion.helpMarker +import imgui.api.drag +import imgui.demo.showExampleApp.MenuFile import imgui.dsl.button import imgui.dsl.menu +import imgui.dsl.menuBar import imgui.dsl.popup import imgui.dsl.popupContextItem import imgui.dsl.popupModal import imgui.dsl.treeNode -import imgui.dsl.withID -import imgui.dsl.withItemWidth import imgui.dsl.withStyleVar -import imgui.demo.showExampleApp.MenuFile -import imgui.dsl.menuBar import imgui.WindowFlag as Wf object ShowDemoWindowPopups { @@ -94,8 +91,7 @@ object ShowDemoWindowPopups { // This may be a bit confusing at first but it should quickly make sense. Follow on the examples below. treeNode("Popups") { - textWrapped( - "When a popup is active, it inhibits interacting with windows that are behind the popup. " + + textWrapped("When a popup is active, it inhibits interacting with windows that are behind the popup. " + "Clicking outside the popup closes it.") val names = arrayOf("Bream", "Haddock", "Mackerel", "Pollock", "Tilefish") @@ -107,8 +103,7 @@ object ShowDemoWindowPopups { sameLine() textEx(names.getOrElse(selectedFish) { "" }) popup("my_select_popup") { - text("Aquarium") - separator() + separatorText("Aquarium") names.forEachIndexed { i, n -> if (selectable(n)) selectedFish = i } } @@ -122,7 +117,7 @@ object ShowDemoWindowPopups { separator() text("Tooltip here") - if (isItemHovered()) setTooltip("I am a tooltip over a popup") + setItemTooltip("I am a tooltip over a popup") if (button("Stacked Popup")) openPopup("another popup") popup("another popup") { @@ -137,7 +132,7 @@ object ShowDemoWindowPopups { // Call the more complete ShowExampleMenuFile which we use in various places of this demo if (button("With a menu..")) - openPopup("my_file_popup", Wf.MenuBar.i) + openPopup("my_file_popup") popup("my_file_popup") { menuBar { menu("File") { @@ -157,12 +152,13 @@ object ShowDemoWindowPopups { object `Context Menus` { var selected = -1 var value = 0.5f + // [JVM] this needs to by a ByteArray to hold a reference, since Strings are final by design var name = "Label1" operator fun invoke() { treeNode("Context menus") { - helpMarker("\"Context\" functions are simple helpers to associate a Popup to a given Item or Window identifier.") + helpMarker(""""Context" functions are simple helpers to associate a Popup to a given Item or Window identifier.""") // BeginPopupContextItem() is a helper to provide common/simple popup behavior of essentially doing: // if (id == 0) @@ -178,18 +174,17 @@ object ShowDemoWindowPopups { // and BeginPopupContextItem() will use the last item ID as the popup ID. run { val names = listOf("Label1", "Label2", "Label3", "Label4", "Label5") - for (n in 0..4) { - selectable(names[n]) - if (selectable(names[n], selected == n)) + for (n in names.indices) { + val name = names[n] + if (selectable(name, selected == n)) selected = n popupContextItem { // <-- use last item id as popup id selected = n - text("This a popup for \"${names[n]}\"!") + text("""This a popup for "$name"!""") if (button("Close")) closeCurrentPopup() } - if (isItemHovered()) - setTooltip("Right-click to open popup") + setItemTooltip("Right-click to open popup") } } @@ -203,14 +198,14 @@ object ShowDemoWindowPopups { if (selectable("Set to zero")) value = 0.0f if (selectable("Set to PI")) value = 3.1415f setNextItemWidth(-Float.MIN_VALUE) - dragFloat("##Value", ::value, 0.1f, 0f, 0f) + drag("##Value", ::value, 0.1f, 0f, 0f) } // We can also use OpenPopupOnItemClick() to toggle the visibility of a given popup. // Here we make it that right-clicking this other text element opens the same popup as above. // The popup itself will be submitted by the code above. text("(2) Or right-click this text") - openPopupOnItemClick("my popup", PopupFlag.MouseButtonRight.i) + openPopupOnItemClick("my popup", PopupFlag.MouseButtonRight) // Back to square one: manually open the same popup. if (button("(3) Or click this button")) @@ -253,15 +248,17 @@ object ShowDemoWindowPopups { val center = mainViewport.center setNextWindowPos(center, Cond.Appearing, Vec2(0.5f)) - popupModal("Delete?", null, Wf.AlwaysAutoResize.i) { + popupModal("Delete?", null, Wf.AlwaysAutoResize) { - text("All those beautiful files will be deleted.\nThis operation cannot be undone!\n\n") + text("All those beautiful files will be deleted.\nThis operation cannot be undone!") separator() //static int unused_i = 0; //ImGui::Combo("Combo", &unused_i, "Delete\0Delete harder\0"); - withStyleVar(StyleVar.FramePadding, Vec2()) { checkbox("Don't ask me next time", ::dontAskMeNextTime) } + withStyleVar(StyleVar.FramePadding, Vec2()) { + checkbox("Don't ask me next time", ::dontAskMeNextTime) + } button("OK", Vec2(120, 0)) { closeCurrentPopup() } setItemDefaultFocus() @@ -270,7 +267,7 @@ object ShowDemoWindowPopups { } button("Stacked modals..") { openPopup("Stacked 1") } - popupModal("Stacked 1", null, Wf.MenuBar.i) { + popupModal("Stacked 1", null, Wf.MenuBar) { if (beginMenuBar()) { if (beginMenu("File")) { @@ -291,7 +288,7 @@ object ShowDemoWindowPopups { // Also demonstrate passing a bool* to BeginPopupModal(), this will create a regular close button which // will close the popup. Note that the visibility state of popups is owned by imgui, so the input value // of the bool actually doesn't matter here. - val unusedOpen = true.asMutableProperty + val unusedOpen = true.mutableReference if (beginPopupModal("Stacked 2", unusedOpen)) { text("Hello from Stacked The Second!") button("Close") { closeCurrentPopup() } diff --git a/core/src/main/kotlin/imgui/demo/ShowDemoWindowTables.kt b/core/src/main/kotlin/imgui/demo/ShowDemoWindowTables.kt index 30e5244c3..8453e3bff 100644 --- a/core/src/main/kotlin/imgui/demo/ShowDemoWindowTables.kt +++ b/core/src/main/kotlin/imgui/demo/ShowDemoWindowTables.kt @@ -4,7 +4,6 @@ import glm_.f import glm_.i import glm_.vec2.Vec2 import glm_.vec4.Vec4 -import glm_.wo import imgui.* import imgui.ImGui.alignTextToFramePadding import imgui.ImGui.beginDisabled @@ -17,14 +16,13 @@ import imgui.ImGui.closeCurrentPopup import imgui.ImGui.collapsingHeader import imgui.ImGui.combo import imgui.ImGui.contentRegionAvail -import imgui.ImGui.dragFloat -import imgui.ImGui.dragInt -import imgui.ImGui.dragVec2 +import imgui.ImGui.drag2 import imgui.ImGui.endDisabled import imgui.ImGui.endTable import imgui.ImGui.indent import imgui.ImGui.inputText import imgui.ImGui.io +import imgui.ImGui.isReleased import imgui.ImGui.openPopup import imgui.ImGui.popButtonRepeat import imgui.ImGui.popID @@ -42,8 +40,7 @@ import imgui.ImGui.selectable import imgui.ImGui.separator import imgui.ImGui.setNextItemOpen import imgui.ImGui.setNextItemWidth -import imgui.ImGui.sliderFloat -import imgui.ImGui.sliderVec2 +import imgui.ImGui.slider2 import imgui.ImGui.smallButton import imgui.ImGui.spacing import imgui.ImGui.style @@ -69,9 +66,11 @@ import imgui.ImGui.treeNodeEx import imgui.ImGui.treePop import imgui.ImGui.unindent import imgui.api.demoDebugInformations.Companion.helpMarker +import imgui.api.drag +import imgui.api.slider import imgui.classes.DrawList -import imgui.classes.ListClipper import imgui.classes.TableSortSpecs +import imgui.classes.listClipper import imgui.dsl.popup import imgui.dsl.popupContextItem import imgui.dsl.table @@ -82,6 +81,7 @@ import imgui.dsl.useCombo import imgui.dsl.withID import imgui.dsl.withItemWidth import imgui.dsl.withTextWrapPos +import kotlin.reflect.KMutableProperty0 import imgui.TableColumnFlag as Tcf import imgui.TableFlag as Tf import imgui.TableRowFlag as Trf @@ -105,78 +105,79 @@ object ShowDemoWindowTables { class EnumDesc(val value: TableFlags, val name: String, val tooltip: String) val policies = arrayOf( - EnumDesc(Tf.None.i, "Default", "Use default sizing policy:\n- ImGuiTableFlags_SizingFixedFit if ScrollX is on or if host window has ImGuiWindowFlags_AlwaysAutoResize.\n- ImGuiTableFlags_SizingStretchSame otherwise."), - EnumDesc(Tf.SizingFixedFit.i, "ImGuiTableFlags_SizingFixedFit", "Columns default to _WidthFixed (if resizable) or _WidthAuto (if not resizable), matching contents width."), - EnumDesc(Tf.SizingFixedSame.i, "ImGuiTableFlags_SizingFixedSame", "Columns are all the same width, matching the maximum contents width.\nImplicitly disable ImGuiTableFlags_Resizable and enable ImGuiTableFlags_NoKeepColumnsVisible."), - EnumDesc(Tf.SizingStretchProp.i, "ImGuiTableFlags_SizingStretchProp", "Columns default to _WidthStretch with weights proportional to their widths."), - EnumDesc(Tf.SizingStretchSame.i, "ImGuiTableFlags_SizingStretchSame", "Columns default to _WidthStretch with same weights.")) + EnumDesc(none, "Default", "Use default sizing policy:\n- ImGuiTableFlags_SizingFixedFit if ScrollX is on or if host window has ImGuiWindowFlags_AlwaysAutoResize.\n- ImGuiTableFlags_SizingStretchSame otherwise."), + EnumDesc(Tf.SizingFixedFit, "ImGuiTableFlags_SizingFixedFit", "Columns default to _WidthFixed (if resizable) or _WidthAuto (if not resizable), matching contents width."), + EnumDesc(Tf.SizingFixedSame, "ImGuiTableFlags_SizingFixedSame", "Columns are all the same width, matching the maximum contents width.\nImplicitly disable ImGuiTableFlags_Resizable and enable ImGuiTableFlags_NoKeepColumnsVisible."), + EnumDesc(Tf.SizingStretchProp, "ImGuiTableFlags_SizingStretchProp", "Columns default to _WidthStretch with weights proportional to their widths."), + EnumDesc(Tf.SizingStretchSame, "ImGuiTableFlags_SizingStretchSame", "Columns default to _WidthStretch with same weights.") + ) /** Show a combo box with a choice of sizing policies */ - fun editTableSizingFlags(flags: IntArray, ptr: Int) { - var flag = flags[ptr] - val idx = policies.indexOfFirst { it.value == flag and Tf._SizingMask } + fun editTableSizingFlags(pFlags: KMutableProperty0) { + var flag by pFlags + val idx = policies.indexOfFirst { it.value == (flag and Tf._SizingMask) } val previewText = policies.getOrNull(idx)?.name?.substringAfter("ImGuiTableFlags") ?: "" useCombo("Sizing Policy", previewText) { for (n in policies.indices) if (selectable(policies[n].name, idx == n)) - flag = (flag wo Tf._SizingMask.i) or policies[n].value + flag = (flag wo Tf._SizingMask) / policies[n].value } sameLine() textDisabled("(?)") - if (ImGui.isItemHovered()) + if (ImGui.beginItemTooltip()) tooltip { withTextWrapPos(ImGui.fontSize * 50f) { for (m in policies.indices) { separator() text("${policies[m].name}:") separator() - ImGui.cursorPosX = ImGui.cursorPosX + style.indentSpacing * 0.5f + ImGui.cursorPosX += style.indentSpacing * 0.5f textUnformatted(policies[m].tooltip) } } } } - fun editTableColumnsFlags(flags: TableColumnFlags): TableColumnFlags { - _i32 = flags - checkboxFlags("_Disabled", ::_i32, Tcf.Disabled.i); sameLine(); helpMarker("Master disable flag (also hide from context menu)") - checkboxFlags("_DefaultHide", ::_i32, Tcf.DefaultHide.i) - checkboxFlags("_DefaultSort", ::_i32, Tcf.DefaultSort.i) - if (checkboxFlags("_WidthStretch", ::_i32, Tcf.WidthStretch.i)) - _i32 = _i32 wo (Tcf.WidthMask_ xor Tcf.WidthStretch) - if (checkboxFlags("_WidthFixed", ::_i32, Tcf.WidthFixed.i)) - _i32 = _i32 wo (Tcf.WidthMask_ xor Tcf.WidthFixed) - checkboxFlags("_NoResize", ::_i32, Tcf.NoResize.i) - checkboxFlags("_NoReorder", ::_i32, Tcf.NoReorder.i) - checkboxFlags("_NoHide", ::_i32, Tcf.NoHide.i) - checkboxFlags("_NoClip", ::_i32, Tcf.NoClip.i) - checkboxFlags("_NoSort", ::_i32, Tcf.NoSort.i) - checkboxFlags("_NoSortAscending", ::_i32, Tcf.NoSortAscending.i) - checkboxFlags("_NoSortDescending", ::_i32, Tcf.NoSortDescending.i) - checkboxFlags("_NoHeaderLabel", ::_i32, Tcf.NoHeaderLabel.i) - checkboxFlags("_NoHeaderWidth", ::_i32, Tcf.NoHeaderWidth.i) - checkboxFlags("_PreferSortAscending", ::_i32, Tcf.PreferSortAscending.i) - checkboxFlags("_PreferSortDescending", ::_i32, Tcf.PreferSortDescending.i) - checkboxFlags("_IndentEnable", ::_i32, Tcf.IndentEnable.i); sameLine(); helpMarker("Default for column 0") - checkboxFlags("_IndentDisable", ::_i32, Tcf.IndentDisable.i); sameLine(); helpMarker("Default for column >0") - return _i32 + fun editTableColumnsFlags(flags: TableColumnSetupFlags): TableColumnSetupFlags { + val flagsRef = flags.mutableReference + var flags by flagsRef + checkboxFlags("_Disabled", flagsRef, Tcf.Disabled); sameLine(); helpMarker("Master disable flag (also hide from context menu)") + checkboxFlags("_DefaultHide", flagsRef, Tcf.DefaultHide) + checkboxFlags("_DefaultSort", flagsRef, Tcf.DefaultSort) + if (checkboxFlags("_WidthStretch", flagsRef, Tcf.WidthStretch)) + flags -= Tcf.WidthMask xor Tcf.WidthStretch + if (checkboxFlags("_WidthFixed", flagsRef, Tcf.WidthFixed)) + flags -= Tcf.WidthMask xor Tcf.WidthFixed + checkboxFlags("_NoResize", flagsRef, Tcf.NoResize) + checkboxFlags("_NoReorder", flagsRef, Tcf.NoReorder) + checkboxFlags("_NoHide", flagsRef, Tcf.NoHide) + checkboxFlags("_NoClip", flagsRef, Tcf.NoClip) + checkboxFlags("_NoSort", flagsRef, Tcf.NoSort) + checkboxFlags("_NoSortAscending", flagsRef, Tcf.NoSortAscending) + checkboxFlags("_NoSortDescending", flagsRef, Tcf.NoSortDescending) + checkboxFlags("_NoHeaderLabel", flagsRef, Tcf.NoHeaderLabel) + checkboxFlags("_NoHeaderWidth", flagsRef, Tcf.NoHeaderWidth) + checkboxFlags("_PreferSortAscending", flagsRef, Tcf.PreferSortAscending) + checkboxFlags("_PreferSortDescending", flagsRef, Tcf.PreferSortDescending) + checkboxFlags("_IndentEnable", flagsRef, Tcf.IndentEnable); sameLine(); helpMarker("Default for column 0") + checkboxFlags("_IndentDisable", flagsRef, Tcf.IndentDisable); sameLine(); helpMarker("Default for column >0") + return flags } fun showTableColumnsStatusFlags(flags: TableColumnFlags) { - _i32 = flags - checkboxFlags("_IsEnabled", ::_i32, Tcf.IsEnabled.i) - checkboxFlags("_IsVisible", ::_i32, Tcf.IsVisible.i) - checkboxFlags("_IsSorted", ::_i32, Tcf.IsSorted.i) - checkboxFlags("_IsHovered", ::_i32, Tcf.IsHovered.i) + val flag = flags.mutableReference + checkboxFlags("_IsEnabled", flag, Tcf.IsEnabled) + checkboxFlags("_IsVisible", flag, Tcf.IsVisible) + checkboxFlags("_IsSorted", flag, Tcf.IsSorted) + checkboxFlags("_IsHovered", flag, Tcf.IsHovered) } /* Stretch + ScrollX */ var flags11 = Tf.SizingStretchSame or Tf.ScrollX or Tf.ScrollY or Tf.BordersOuter or Tf.RowBg or Tf.ContextMenuInBody var innerWidth0 = 1000f - val templateItemsNames = arrayOf( - "Banana", "Apple", "Cherry", "Watermelon", "Grapefruit", "Strawberry", "Mango", - "Kiwi", "Orange", "Pineapple", "Blueberry", "Plum", "Coconut", "Pear", "Apricot") + val templateItemsNames = arrayOf("Banana", "Apple", "Cherry", "Watermelon", "Grapefruit", "Strawberry", "Mango", + "Kiwi", "Orange", "Pineapple", "Blueberry", "Plum", "Coconut", "Pear", "Apricot") // We are passing our own identifier to TableSetupColumn() to facilitate identifying columns in the sorting code. // This identifier will be passed down into ImGuiTableSortSpec::ColumnUserID. @@ -200,7 +201,7 @@ object ShowDemoWindowTables { for (n in 0 until currentSortSpecs!!.specsCount) { // Here we identify columns using the ColumnUserID value that we ourselves passed to TableSetupColumn() // We could also choose to identify columns based on their index (sort_spec->ColumnIndex), which is simpler! - val sortSpec = currentSortSpecs!!.specs(n) + val sortSpec = currentSortSpecs!!.specs[n] val delta = when (sortSpec.columnUserID) { MyItemColumnID.ID.ordinal -> a.id - b.id MyItemColumnID.Name.ordinal -> a.name.compareTo(b.name) @@ -374,12 +375,12 @@ object ShowDemoWindowTables { tableSetupColumn("B1") tableHeadersRow() - tableNextRow(Trf.None.i, rowsHeight) + tableNextRow(rowMinHeight = rowsHeight) tableNextColumn() text("B0 Row 0") tableNextColumn() text("B1 Row 0") - tableNextRow(Trf.None.i, rowsHeight) + tableNextRow(rowMinHeight = rowsHeight) tableNextColumn() text("B0 Row 1") tableNextColumn() @@ -399,7 +400,7 @@ object ShowDemoWindowTables { table("table_row_height", 1, Tf.BordersOuter or Tf.BordersInnerV) { for (row in 0..9) { val minRowHeight = (TEXT_BASE_HEIGHT * 0.3f * row).i.f - tableNextRow(Trf.None.i, minRowHeight) + tableNextRow(rowMinHeight = minRowHeight) tableNextColumn() text("min_row_height = %.2f", minRowHeight) } @@ -471,32 +472,32 @@ object ShowDemoWindowTables { // Expose a few Borders related flags interactively pushingStyleCompact { - checkboxFlags("ImGuiTableFlags_RowBg", ::flags, Tf.RowBg.i) - checkboxFlags("ImGuiTableFlags_Borders", ::flags, Tf.Borders.i) + checkboxFlags("ImGuiTableFlags_RowBg", ::flags, Tf.RowBg) + checkboxFlags("ImGuiTableFlags_Borders", ::flags, Tf.Borders) sameLine(); helpMarker("ImGuiTableFlags_Borders\n = ImGuiTableFlags_BordersInnerV\n | ImGuiTableFlags_BordersOuterV\n | ImGuiTableFlags_BordersInnerV\n | ImGuiTableFlags_BordersOuterH") indent() - checkboxFlags("ImGuiTableFlags_BordersH", ::flags, Tf.BordersH.i) + checkboxFlags("ImGuiTableFlags_BordersH", ::flags, Tf.BordersH) indent() - checkboxFlags("ImGuiTableFlags_BordersOuterH", ::flags, Tf.BordersOuterH.i) - checkboxFlags("ImGuiTableFlags_BordersInnerH", ::flags, Tf.BordersInnerH.i) + checkboxFlags("ImGuiTableFlags_BordersOuterH", ::flags, Tf.BordersOuterH) + checkboxFlags("ImGuiTableFlags_BordersInnerH", ::flags, Tf.BordersInnerH) unindent() - checkboxFlags("ImGuiTableFlags_BordersV", ::flags, Tf.BordersV.i) + checkboxFlags("ImGuiTableFlags_BordersV", ::flags, Tf.BordersV) indent() - checkboxFlags("ImGuiTableFlags_BordersOuterV", ::flags, Tf.BordersOuterV.i) - checkboxFlags("ImGuiTableFlags_BordersInnerV", ::flags, Tf.BordersInnerV.i) + checkboxFlags("ImGuiTableFlags_BordersOuterV", ::flags, Tf.BordersOuterV) + checkboxFlags("ImGuiTableFlags_BordersInnerV", ::flags, Tf.BordersInnerV) unindent() - checkboxFlags("ImGuiTableFlags_BordersOuter", ::flags, Tf.BordersOuter.i) - checkboxFlags("ImGuiTableFlags_BordersInner", ::flags, Tf.BordersInner.i) + checkboxFlags("ImGuiTableFlags_BordersOuter", ::flags, Tf.BordersOuter) + checkboxFlags("ImGuiTableFlags_BordersInner", ::flags, Tf.BordersInner) unindent() alignTextToFramePadding(); text("Cell contents:") sameLine(); radioButton("Text", ::contentsType, ContentsType.Text.ordinal) sameLine(); radioButton("FillButton", ::contentsType, ContentsType.FillButton.ordinal) checkbox("Display headers", ::displayHeaders) - checkboxFlags("ImGuiTableFlags_NoBordersInBody", ::flags, Tf.NoBordersInBody.i); sameLine(); helpMarker("Disable vertical borders in columns Body (borders will always appear in Headers") + checkboxFlags("ImGuiTableFlags_NoBordersInBody", ::flags, Tf.NoBordersInBody); sameLine(); helpMarker("Disable vertical borders in columns Body (borders will always appear in Headers") } table("table1", 3, flags) { @@ -532,8 +533,8 @@ object ShowDemoWindowTables { // By default, if we don't enable ScrollX the sizing policy for each columns is "Stretch" // All columns maintain a sizing weight, and they will occupy all available width. pushingStyleCompact { - checkboxFlags("ImGuiTableFlags_Resizable", ::flags, Tf.Resizable.i) - checkboxFlags("ImGuiTableFlags_BordersV", ::flags, Tf.BordersV.i) + checkboxFlags("ImGuiTableFlags_Resizable", ::flags, Tf.Resizable) + checkboxFlags("ImGuiTableFlags_BordersV", ::flags, Tf.BordersV) sameLine(); helpMarker("Using the _Resizable flag automatically enables the _BordersInnerV flag as well, this is why the resize borders are still showing when unchecking this.") } @@ -558,12 +559,11 @@ object ShowDemoWindowTables { // So columns will adopt the "Fixed" policy and will maintain a fixed width regardless of the whole available width (unless table is small) // If there is not enough available width to fit all columns, they will however be resized down. // FIXME-TABLE: Providing a stretch-on-init would make sense especially for tables which don't have saved settings - helpMarker( - "Using _Resizable + _SizingFixedFit flags.\n" + - "Fixed-width columns generally makes more sense if you want to use horizontal scrolling.\n\n" + - "Double-click a column border to auto-fit the column to its contents.") + helpMarker("Using _Resizable + _SizingFixedFit flags.\n" + + "Fixed-width columns generally makes more sense if you want to use horizontal scrolling.\n\n" + + "Double-click a column border to auto-fit the column to its contents.") pushingStyleCompact { - checkboxFlags("ImGuiTableFlags_NoHostExtendX", ::flags, Tf.NoHostExtendX.i) + checkboxFlags("ImGuiTableFlags_NoHostExtendX", ::flags, Tf.NoHostExtendX) } table("table1", 3, flags) { @@ -584,12 +584,12 @@ object ShowDemoWindowTables { operator fun invoke() { treeNode("Resizable, mixed") { helpMarker("Using TableSetupColumn() to alter resizing policy on a per-column basis.\n\n" + - "When combining Fixed and Stretch columns, generally you only want one, maybe two trailing columns to use _WidthStretch.") + "When combining Fixed and Stretch columns, generally you only want one, maybe two trailing columns to use _WidthStretch.") table("table1", 3, flags) { - tableSetupColumn("AAA", Tcf.WidthFixed.i) - tableSetupColumn("BBB", Tcf.WidthFixed.i) - tableSetupColumn("CCC", Tcf.WidthStretch.i) + tableSetupColumn("AAA", Tcf.WidthFixed) + tableSetupColumn("BBB", Tcf.WidthFixed) + tableSetupColumn("CCC", Tcf.WidthStretch) tableHeadersRow() for (row in 0..4) { tableNextRow() @@ -600,11 +600,11 @@ object ShowDemoWindowTables { } } table("table2", 6, flags) { - tableSetupColumn("AAA", Tcf.WidthFixed.i) - tableSetupColumn("BBB", Tcf.WidthFixed.i) + tableSetupColumn("AAA", Tcf.WidthFixed) + tableSetupColumn("BBB", Tcf.WidthFixed) tableSetupColumn("CCC", Tcf.WidthFixed or Tcf.DefaultHide) - tableSetupColumn("DDD", Tcf.WidthStretch.i) - tableSetupColumn("EEE", Tcf.WidthStretch.i) + tableSetupColumn("DDD", Tcf.WidthStretch) + tableSetupColumn("EEE", Tcf.WidthStretch) tableSetupColumn("FFF", Tcf.WidthStretch or Tcf.DefaultHide) tableHeadersRow() for (row in 0..4) { @@ -625,13 +625,13 @@ object ShowDemoWindowTables { treeNode("Reorderable, hideable, with headers") { helpMarker( "Click and drag column headers to reorder columns.\n\n" + - "Right-click on a header to open a context menu.") + "Right-click on a header to open a context menu.") pushingStyleCompact { - checkboxFlags("ImGuiTableFlags_Resizable", ::flags, Tf.Resizable.i) - checkboxFlags("ImGuiTableFlags_Reorderable", ::flags, Tf.Reorderable.i) - checkboxFlags("ImGuiTableFlags_Hideable", ::flags, Tf.Hideable.i) - checkboxFlags("ImGuiTableFlags_NoBordersInBody", ::flags, Tf.NoBordersInBody.i) - checkboxFlags("ImGuiTableFlags_NoBordersInBodyUntilResize", ::flags, Tf.NoBordersInBodyUntilResize.i); sameLine(); helpMarker("Disable vertical borders in columns Body until hovered for resize (borders will always appear in Headers)") + checkboxFlags("ImGuiTableFlags_Resizable", ::flags, Tf.Resizable) + checkboxFlags("ImGuiTableFlags_Reorderable", ::flags, Tf.Reorderable) + checkboxFlags("ImGuiTableFlags_Hideable", ::flags, Tf.Hideable) + checkboxFlags("ImGuiTableFlags_NoBordersInBody", ::flags, Tf.NoBordersInBody) + checkboxFlags("ImGuiTableFlags_NoBordersInBodyUntilResize", ::flags, Tf.NoBordersInBodyUntilResize); sameLine(); helpMarker("Disable vertical borders in columns Body until hovered for resize (borders will always appear in Headers)") } table("##table1", 3, flags) { @@ -669,7 +669,7 @@ object ShowDemoWindowTables { } object Padding { - var flags1 = Tf.BordersV.i + var flags1 = Tf.BordersV var showHeaders = false var flags2 = Tf.Borders or Tf.RowBg val cellPadding = Vec2() @@ -681,22 +681,22 @@ object ShowDemoWindowTables { // First example: showcase use of padding flags and effect of BorderOuterV/BorderInnerV on X padding. // We don't expose BorderOuterH/BorderInnerH here because they have no effect on X padding. helpMarker("We often want outer padding activated when any using features which makes the edges of a column visible:\n" + - "e.g.:\n" + - "- BorderOuterV\n" + - "- any form of row selection\n" + - "Because of this, activating BorderOuterV sets the default to PadOuterX. Using PadOuterX or NoPadOuterX you can override the default.\n\n" + - "Actual padding values are using style.CellPadding.\n\n" + - "In this demo we don't show horizontal borders to emphasize how they don't affect default horizontal padding.") + "e.g.:\n" + + "- BorderOuterV\n" + + "- any form of row selection\n" + + "Because of this, activating BorderOuterV sets the default to PadOuterX. Using PadOuterX or NoPadOuterX you can override the default.\n\n" + + "Actual padding values are using style.CellPadding.\n\n" + + "In this demo we don't show horizontal borders to emphasize how they don't affect default horizontal padding.") pushingStyleCompact { - checkboxFlags("ImGuiTableFlags_PadOuterX", this::flags1, Tf.PadOuterX.i) + checkboxFlags("ImGuiTableFlags_PadOuterX", this::flags1, Tf.PadOuterX) sameLine(); helpMarker("Enable outer-most padding (default if ImGuiTableFlags_BordersOuterV is set)") - checkboxFlags("ImGuiTableFlags_NoPadOuterX", this::flags1, Tf.NoPadOuterX.i) + checkboxFlags("ImGuiTableFlags_NoPadOuterX", this::flags1, Tf.NoPadOuterX) sameLine(); helpMarker("Disable outer-most padding (default if ImGuiTableFlags_BordersOuterV is not set)") - checkboxFlags("ImGuiTableFlags_NoPadInnerX", this::flags1, Tf.NoPadInnerX.i) + checkboxFlags("ImGuiTableFlags_NoPadInnerX", this::flags1, Tf.NoPadInnerX) sameLine(); helpMarker("Disable inner padding between columns (double inner padding if BordersOuterV is on, single inner padding if BordersOuterV is off)") - checkboxFlags("ImGuiTableFlags_BordersOuterV", this::flags1, Tf.BordersOuterV.i) - checkboxFlags("ImGuiTableFlags_BordersInnerV", this::flags1, Tf.BordersInnerV.i) + checkboxFlags("ImGuiTableFlags_BordersOuterV", this::flags1, Tf.BordersOuterV) + checkboxFlags("ImGuiTableFlags_BordersInnerV", this::flags1, Tf.BordersInnerV) checkbox("show_headers", ::showHeaders) } @@ -727,15 +727,15 @@ object ShowDemoWindowTables { helpMarker("Setting style.CellPadding to (0,0) or a custom value.") pushingStyleCompact { - checkboxFlags("ImGuiTableFlags_Borders", ::flags2, Tf.Borders.i) - checkboxFlags("ImGuiTableFlags_BordersH", ::flags2, Tf.BordersH.i) - checkboxFlags("ImGuiTableFlags_BordersV", ::flags2, Tf.BordersV.i) - checkboxFlags("ImGuiTableFlags_BordersInner", ::flags2, Tf.BordersInner.i) - checkboxFlags("ImGuiTableFlags_BordersOuter", ::flags2, Tf.BordersOuter.i) - checkboxFlags("ImGuiTableFlags_RowBg", ::flags2, Tf.RowBg.i) - checkboxFlags("ImGuiTableFlags_Resizable", ::flags2, Tf.Resizable.i) + checkboxFlags("ImGuiTableFlags_Borders", ::flags2, Tf.Borders) + checkboxFlags("ImGuiTableFlags_BordersH", ::flags2, Tf.BordersH) + checkboxFlags("ImGuiTableFlags_BordersV", ::flags2, Tf.BordersV) + checkboxFlags("ImGuiTableFlags_BordersInner", ::flags2, Tf.BordersInner) + checkboxFlags("ImGuiTableFlags_BordersOuter", ::flags2, Tf.BordersOuter) + checkboxFlags("ImGuiTableFlags_RowBg", ::flags2, Tf.RowBg) + checkboxFlags("ImGuiTableFlags_Resizable", ::flags2, Tf.Resizable) checkbox("show_widget_frame_bg", ::showWidgetFrameBg) - sliderVec2("CellPadding", cellPadding, 0f, 10f, "%.0f") + slider2("CellPadding", cellPadding, 0f, 10f, "%.0f") } pushStyleVar(StyleVar.CellPadding, cellPadding) @@ -761,27 +761,27 @@ object ShowDemoWindowTables { } object `Sizing policies` { - var flags1 = Tf.BordersV or Tf.BordersOuterH or Tf.RowBg or Tf.ContextMenuInBody - val sizingPolicyFlags = intArrayOf(Tf.SizingFixedFit.i, Tf.SizingFixedSame.i, Tf.SizingStretchProp.i, Tf.SizingStretchSame.i) + var flags1 = Tf.BordersV / Tf.BordersOuterH / Tf.RowBg / Tf.ContextMenuInBody + val sizingPolicyFlags = flagArrayOf(Tf.SizingFixedFit, Tf.SizingFixedSame, Tf.SizingStretchProp, Tf.SizingStretchSame) enum class ContentsType { ShowWidth, ShortText, LongText, Button, FillButton, InputText } - var flags = Tf.ScrollY or Tf.Borders or Tf.RowBg or Tf.Resizable - var contentsType1 = ContentsType.ShowWidth + var flags = Tf.ScrollY / Tf.Borders / Tf.RowBg / Tf.Resizable + var contentsType = ContentsType.ShowWidth var columnCount = 3 val textBuf = ByteArray(32) operator fun invoke() { treeNode("Sizing policies") { pushingStyleCompact { - checkboxFlags("ImGuiTableFlags_Resizable", ::flags1, Tf.Resizable.i) - checkboxFlags("ImGuiTableFlags_NoHostExtendX", ::flags1, Tf.NoHostExtendX.i) + checkboxFlags("ImGuiTableFlags_Resizable", ::flags1, Tf.Resizable) + checkboxFlags("ImGuiTableFlags_NoHostExtendX", ::flags1, Tf.NoHostExtendX) } - for (tableN in 0..3) + for (tableN in sizingPolicyFlags.indices) withID(tableN) { setNextItemWidth(TEXT_BASE_WIDTH * 30) - editTableSizingFlags(sizingPolicyFlags, tableN) + editTableSizingFlags(sizingPolicyFlags mutablePropertyAt tableN) // To make it easier to understand the different sizing policy, // For each policy: we display one table where the columns have equal contents width, and one where the columns have different contents width. @@ -811,24 +811,22 @@ object ShowDemoWindowTables { pushingStyleCompact { withID("Advanced") { withItemWidth(TEXT_BASE_WIDTH * 30) { - val f = intArrayOf(flags) - editTableSizingFlags(f, 0) - flags = f[0] - _i32 = contentsType1.ordinal - combo("Contents", ::_i32, "Show width\u0000Short Text\u0000Long Text\u0000Button\u0000Fill Button\u0000InputText\u0000") - contentsType1 = ContentsType.values()[_i32] - if (contentsType1 == ContentsType.FillButton) { + editTableSizingFlags(::flags) + val ordinalRef = contentsType.ordinal.mutableReference + val ordinal by ordinalRef + combo("Contents", ordinalRef, "Show width\u0000Short Text\u0000Long Text\u0000Button\u0000Fill Button\u0000InputText\u0000") + contentsType = ContentsType.values()[ordinal] + if (contentsType == ContentsType.FillButton) { sameLine() helpMarker("Be mindful that using right-alignment (e.g. size.x = -FLT_MIN) creates a feedback loop where contents width can feed into auto-column width can feed into contents width.") } - dragInt("Columns", ::columnCount, 0.1f, 1, 64, "%d", SliderFlag.AlwaysClamp.i) - checkboxFlags("ImGuiTableFlags_Resizable", ::flags, Tf.Resizable.i) - checkboxFlags("ImGuiTableFlags_PreciseWidths", ::flags, Tf.PreciseWidths.i) + drag("Columns", ::columnCount, 0.1f, 1, 64, "%d", SliderFlag.AlwaysClamp) + checkboxFlags("ImGuiTableFlags_Resizable", ::flags, Tf.Resizable) + checkboxFlags("ImGuiTableFlags_PreciseWidths", ::flags, Tf.PreciseWidths) sameLine(); helpMarker("Disable distributing remainder width to stretched columns (width allocation on a 100-wide table with 3 columns: Without this flag: 33,33,34. With this flag: 33,33,33). With larger number of columns, resizing will appear to be less smooth.") - checkboxFlags("ImGuiTableFlags_Resizable", ::flags, Tf.Resizable.i) - checkboxFlags("ImGuiTableFlags_ScrollX", ::flags, Tf.ScrollX.i) - checkboxFlags("ImGuiTableFlags_ScrollY", ::flags, Tf.ScrollY.i) - checkboxFlags("ImGuiTableFlags_NoClip", ::flags, Tf.NoClip.i) + checkboxFlags("ImGuiTableFlags_ScrollX", ::flags, Tf.ScrollX) + checkboxFlags("ImGuiTableFlags_ScrollY", ::flags, Tf.ScrollY) + checkboxFlags("ImGuiTableFlags_NoClip", ::flags, Tf.NoClip) } } } @@ -841,7 +839,7 @@ object ShowDemoWindowTables { withID(cell) { val label = "Hello $column,$row" - when (contentsType1) { + when (contentsType) { ContentsType.ShortText -> textUnformatted(label) ContentsType.LongText -> text("Some ${if (column == 0) "long" else "longeeer"} text $column,$row\nOver two lines..") ContentsType.ShowWidth -> text("W: %.1f", contentRegionAvail.x) @@ -866,7 +864,7 @@ object ShowDemoWindowTables { helpMarker("Here we activate ScrollY, which will create a child window container to allow hosting scrollable contents.\n\nWe also demonstrate using ImGuiListClipper to virtualize the submission of many items.") pushingStyleCompact { - checkboxFlags("ImGuiTableFlags_ScrollY", ::flags, Tf.ScrollY.i) + checkboxFlags("ImGuiTableFlags_ScrollY", ::flags, Tf.ScrollY) } // When using ScrollX or ScrollY we need to specify a size for our table container! @@ -874,23 +872,21 @@ object ShowDemoWindowTables { val outerSize = Vec2(0f, TEXT_BASE_HEIGHT * 8) table("table_scrolly", 3, flags, outerSize) { tableSetupScrollFreeze(0, 1) // Make top row always visible - tableSetupColumn("One", Tcf.None.i) - tableSetupColumn("Two", Tcf.None.i) - tableSetupColumn("Three", Tcf.None.i) + tableSetupColumn("One") + tableSetupColumn("Two") + tableSetupColumn("Three") tableHeadersRow() // Demonstrate using clipper for large vertical lists - val clipper = ListClipper() - clipper.begin(1000) - while (clipper.step()) - for (row in clipper.display) { + listClipper(1000) { + for (row in it.display) { tableNextRow() for (column in 0..2) { tableSetColumnIndex(column) text("Hello $column,$row") } } - clipper.end() + } } } } @@ -904,18 +900,18 @@ object ShowDemoWindowTables { treeNode("Horizontal scrolling") { helpMarker( "When ScrollX is enabled, the default sizing policy becomes ImGuiTableFlags_SizingFixedFit, " + - "as automatically stretching columns doesn't make much sense with horizontal scrolling.\n\n" + - "Also note that as of the current version, you will almost always want to enable ScrollY along with ScrollX," + - "because the container window won't automatically extend vertically to fix contents (this may be improved in future versions).") + "as automatically stretching columns doesn't make much sense with horizontal scrolling.\n\n" + + "Also note that as of the current version, you will almost always want to enable ScrollY along with ScrollX," + + "because the container window won't automatically extend vertically to fix contents (this may be improved in future versions).") pushingStyleCompact { - checkboxFlags("ImGuiTableFlags_Resizable", ::flags, Tf.Resizable.i) - checkboxFlags("ImGuiTableFlags_ScrollX", ::flags, Tf.ScrollX.i) - checkboxFlags("ImGuiTableFlags_ScrollY", ::flags, Tf.ScrollY.i) + checkboxFlags("ImGuiTableFlags_Resizable", ::flags, Tf.Resizable) + checkboxFlags("ImGuiTableFlags_ScrollX", ::flags, Tf.ScrollX) + checkboxFlags("ImGuiTableFlags_ScrollY", ::flags, Tf.ScrollY) setNextItemWidth(ImGui.frameHeight) - dragInt("freeze_cols", ::freezeCols, 0.2f, 0, 9, null, SliderFlag.NoInput.i) + drag("freeze_cols", ::freezeCols, 0.2f, 0, 9, null, SliderFlag.NoInput) setNextItemWidth(ImGui.frameHeight) - dragInt("freeze_rows", ::freezeRows, 0.2f, 0, 9, null, SliderFlag.NoInput.i) + drag("freeze_rows", ::freezeRows, 0.2f, 0, 9, null, SliderFlag.NoInput) } // When using ScrollX or ScrollY we need to specify a size for our table container! @@ -923,7 +919,7 @@ object ShowDemoWindowTables { val outerSize = Vec2(0f, TEXT_BASE_HEIGHT * 8) table("table_scrollx", 7, flags, outerSize) { tableSetupScrollFreeze(freezeCols, freezeRows) - tableSetupColumn("Line #", Tcf.NoHide.i) // Make the first column not hideable to match our use of TableSetupScrollFreeze() + tableSetupColumn("Line #", Tcf.NoHide) // Make the first column not hideable to match our use of TableSetupScrollFreeze() tableSetupColumn("One") tableSetupColumn("Two") tableSetupColumn("Three") @@ -955,13 +951,13 @@ object ShowDemoWindowTables { sameLine() helpMarker( "Showcase using Stretch columns + ScrollX together: " + - "this is rather unusual and only makes sense when specifying an 'inner_width' for the table!\n" + - "Without an explicit value, inner_width is == outer_size.x and therefore using Stretch columns + ScrollX together doesn't make sense.") + "this is rather unusual and only makes sense when specifying an 'inner_width' for the table!\n" + + "Without an explicit value, inner_width is == outer_size.x and therefore using Stretch columns + ScrollX together doesn't make sense.") pushingStyleCompact { withID("flags3") { withItemWidth(TEXT_BASE_WIDTH * 30) { - checkboxFlags("ImGuiTableFlags_ScrollX", ::flags11, Tf.ScrollX.i) - dragFloat("inner_width", ::innerWidth0, 1f, 0f, Float.MAX_VALUE, "%.1f") + checkboxFlags("ImGuiTableFlags_ScrollX", ::flags11, Tf.ScrollX) + drag("inner_width", ::innerWidth0, 1f, 0f, Float.MAX_VALUE, "%.1f") } } } @@ -978,14 +974,15 @@ object ShowDemoWindowTables { object `Columns flags` { var columnCount = 3 val columnNames = arrayOf("One", "Two", "Three") - val columnFlags = intArrayOf(Tcf.DefaultSort.i, Tcf.None.i, Tcf.DefaultHide.i) - val columnFlagsOut = IntArray(columnCount) // Output from TableGetColumnFlags() + val columnFlags = flagArrayOf(Tcf.DefaultSort, none, Tcf.DefaultHide) + val columnFlagsOut = FlagArray>(columnCount)// Output from TableGetColumnFlags() + operator fun invoke() { treeNode("Columns flags") { // Create a first table just to show all the options/flags we want to make visible in our example! // -> begin of the class, as static - table("table_columns_flags_checkboxes", columnCount, Tf.None.i) { + table("table_columns_flags_checkboxes", columnCount) { pushingStyleCompact { for (column in 0 until columnCount) { tableNextColumn() @@ -1034,20 +1031,20 @@ object ShowDemoWindowTables { object `Columns widths` { var flags1 = Tf.Borders or Tf.NoBordersInBodyUntilResize - var flags2 = Tf.None.i + var flags2: TableFlags = none operator fun invoke() { treeNode("Columns widths") { helpMarker("Using TableSetupColumn() to setup default width.") pushingStyleCompact { - checkboxFlags("ImGuiTableFlags_Resizable", ::flags1, Tf.Resizable.i) - checkboxFlags("ImGuiTableFlags_NoBordersInBodyUntilResize", ::flags1, Tf.NoBordersInBodyUntilResize.i) + checkboxFlags("ImGuiTableFlags_Resizable", ::flags1, Tf.Resizable) + checkboxFlags("ImGuiTableFlags_NoBordersInBodyUntilResize", ::flags1, Tf.NoBordersInBodyUntilResize) } table("table1", 3, flags1) { // We could also set ImGuiTableFlags_SizingFixedFit on the table and all columns will default to ImGuiTableColumnFlags_WidthFixed. - tableSetupColumn("one", Tcf.WidthFixed.i, 100f) // Default to 100.0f - tableSetupColumn("two", Tcf.WidthFixed.i, 200f) // Default to 200.0f - tableSetupColumn("three", Tcf.WidthFixed.i) // Default to auto + tableSetupColumn("one", Tcf.WidthFixed, 100f) // Default to 100.0f + tableSetupColumn("two", Tcf.WidthFixed, 200f) // Default to 200.0f + tableSetupColumn("three", Tcf.WidthFixed) // Default to auto tableHeadersRow() for (row in 0..3) { tableNextRow() @@ -1064,17 +1061,17 @@ object ShowDemoWindowTables { helpMarker("Using TableSetupColumn() to setup explicit width.\n\nUnless _NoKeepColumnsVisible is set, fixed columns with set width may still be shrunk down if there's not enough space in the host.") pushingStyleCompact { - checkboxFlags("ImGuiTableFlags_NoKeepColumnsVisible", ::flags2, Tf.NoKeepColumnsVisible.i) - checkboxFlags("ImGuiTableFlags_BordersInnerV", ::flags2, Tf.BordersInnerV.i) - checkboxFlags("ImGuiTableFlags_BordersOuterV", ::flags2, Tf.BordersOuterV.i) + checkboxFlags("ImGuiTableFlags_NoKeepColumnsVisible", ::flags2, Tf.NoKeepColumnsVisible) + checkboxFlags("ImGuiTableFlags_BordersInnerV", ::flags2, Tf.BordersInnerV) + checkboxFlags("ImGuiTableFlags_BordersOuterV", ::flags2, Tf.BordersOuterV) } table("table2", 4, flags2) { // We could also set ImGuiTableFlags_SizingFixedFit on the table and all columns will default to ImGuiTableColumnFlags_WidthFixed. - tableSetupColumn("", Tcf.WidthFixed.i, 100f) - tableSetupColumn("", Tcf.WidthFixed.i, TEXT_BASE_WIDTH * 15f) - tableSetupColumn("", Tcf.WidthFixed.i, TEXT_BASE_WIDTH * 30f) - tableSetupColumn("", Tcf.WidthFixed.i, TEXT_BASE_WIDTH * 15f) + tableSetupColumn("", Tcf.WidthFixed, 100f) + tableSetupColumn("", Tcf.WidthFixed, TEXT_BASE_WIDTH * 15f) + tableSetupColumn("", Tcf.WidthFixed, TEXT_BASE_WIDTH * 30f) + tableSetupColumn("", Tcf.WidthFixed, TEXT_BASE_WIDTH * 15f) for (row in 0..4) { tableNextRow() for (column in 0..3) { @@ -1098,9 +1095,9 @@ object ShowDemoWindowTables { // Important to that note how the two flags have slightly different behaviors! text("Using NoHostExtendX and NoHostExtendY:") pushingStyleCompact { - checkboxFlags("ImGuiTableFlags_NoHostExtendX", ::flags, Tf.NoHostExtendX.i) + checkboxFlags("ImGuiTableFlags_NoHostExtendX", ::flags, Tf.NoHostExtendX) sameLine(); helpMarker("Make outer width auto-fit to columns, overriding outer_size.x value.\n\nOnly available when ScrollX/ScrollY are disabled and Stretch columns are not used.") - checkboxFlags("ImGuiTableFlags_NoHostExtendY", ::flags, Tf.NoHostExtendY.i) + checkboxFlags("ImGuiTableFlags_NoHostExtendY", ::flags, Tf.NoHostExtendY) sameLine(); helpMarker("Make outer height stop exactly at outer_size.y (prevent auto-extending table past the limit).\n\nOnly available when ScrollX/ScrollY are disabled. Data below the limit will be clipped and not visible.") } @@ -1132,7 +1129,7 @@ object ShowDemoWindowTables { sameLine() table("table3", 3, Tf.Borders or Tf.RowBg, Vec2(TEXT_BASE_WIDTH * 30, 0f)) { for (row in 0..2) { - tableNextRow(0, TEXT_BASE_HEIGHT * 1.5f) + tableNextRow(rowMinHeight = TEXT_BASE_HEIGHT * 1.5f) for (column in 0..2) { tableNextColumn() text("Cell $column,$row") @@ -1144,7 +1141,7 @@ object ShowDemoWindowTables { } object `Background color` { - var flags = Tf.RowBg.i + var flags: TableFlags = Tf.RowBg var rowBgType = 1 var rowBgTarget = 1 var cellBgType = 1 @@ -1152,8 +1149,8 @@ object ShowDemoWindowTables { treeNode("Background color") { pushingStyleCompact { - checkboxFlags("ImGuiTableFlags_Borders", ::flags, Tf.Borders.i) - checkboxFlags("ImGuiTableFlags_RowBg", ::flags, Tf.RowBg.i) + checkboxFlags("ImGuiTableFlags_Borders", ::flags, Tf.Borders) + checkboxFlags("ImGuiTableFlags_RowBg", ::flags, Tf.RowBg) sameLine(); helpMarker("ImGuiTableFlags_RowBg automatically sets RowBg0 to alternative colors pulled from the Style.") combo("row bg type", ::rowBgType, "None\u0000Red\u0000Gradient\u0000") combo("row bg target", ::rowBgTarget, "RowBg0\u0000RowBg1\u0000"); sameLine(); helpMarker("Target RowBg0 to override the alternating odd/even colors,\nTarget RowBg1 to blend with them.") @@ -1207,7 +1204,7 @@ object ShowDemoWindowTables { val node = nodes[index] val isFolder = node.childCount > 0 if (isFolder) { - val open = treeNodeEx(node.name, Tnf.SpanFullWidth.i) + val open = treeNodeEx(node.name, Tnf.SpanFullWidth) tableNextColumn() textDisabled("--") tableNextColumn() @@ -1243,9 +1240,9 @@ object ShowDemoWindowTables { treeNode("Tree view") { table("3ways", 3, flags) { // The first column will use the default _WidthStretch when ScrollX is Off and _WidthFixed when ScrollX is On - tableSetupColumn("Name", Tcf.NoHide.i) - tableSetupColumn("Size", Tcf.WidthFixed.i, TEXT_BASE_WIDTH * 12f) - tableSetupColumn("Type", Tcf.WidthFixed.i, TEXT_BASE_WIDTH * 18f) + tableSetupColumn("Name", Tcf.NoHide) + tableSetupColumn("Size", Tcf.WidthFixed, TEXT_BASE_WIDTH * 12f) + tableSetupColumn("Type", Tcf.WidthFixed, TEXT_BASE_WIDTH * 18f) tableHeadersRow() MyTreeNode.displayNode(nodes, 0) @@ -1260,8 +1257,9 @@ object ShowDemoWindowTables { treeNode("Item width") { helpMarker( "Showcase using PushItemWidth() and how it is preserved on a per-column basis.\n\n" + - "Note that on auto-resizing non-resizable fixed columns, querying the content width for e.g. right-alignment doesn't make sense.") - table("table_item_width", 3, Tf.Borders.i) { + "Note that on auto-resizing non-resizable fixed columns, querying the content width for e.g. right-alignment doesn't make sense." + ) + table("table_item_width", 3, Tf.Borders) { tableSetupColumn("small") tableSetupColumn("half") tableSetupColumn("right-align") @@ -1282,11 +1280,11 @@ object ShowDemoWindowTables { // Draw our contents withID(row) { tableSetColumnIndex(0) - sliderFloat("float0", ::dummyF, 0f, 1f) + slider("float0", ::dummyF, 0f, 1f) tableSetColumnIndex(1) - sliderFloat("float1", ::dummyF, 0f, 1f) + slider("float1", ::dummyF, 0f, 1f) tableSetColumnIndex(2) - sliderFloat("##float2", ::dummyF, 0f, 1f) // No visible label since right-aligned + slider("##float2", ::dummyF, 0f, 1f) // No visible label since right-aligned } } } @@ -1308,7 +1306,7 @@ object ShowDemoWindowTables { // FIXME: It would be nice to actually demonstrate full-featured selection using those checkbox. // Instead of calling TableHeadersRow() we'll submit custom headers ourselves - tableNextRow(Trf.Headers.i) + tableNextRow(Trf.Headers) for (column in 0 until COLUMNS_COUNT) { tableSetColumnIndex(column) val columnName = tableGetColumnName(column) // Retrieve name passed to TableSetupColumn() @@ -1325,7 +1323,7 @@ object ShowDemoWindowTables { tableNextRow() for (column in 0..2) { tableSetColumnIndex(column) - selectable("Cell $column,$row", columnSelected, column) + selectable("Cell $column,$row", columnSelected mutablePropertyAt column) } } } @@ -1340,10 +1338,10 @@ object ShowDemoWindowTables { treeNode("Synced instances") { helpMarker("Multiple tables with the same identifier will share their settings, width, visibility, order etc.") - checkboxFlags("ImGuiTableFlags_ScrollY", ::flags, Tf.ScrollY.i) - checkboxFlags("ImGuiTableFlags_SizingFixedFit", ::flags, Tf.SizingFixedFit.i) + checkboxFlags("ImGuiTableFlags_ScrollY", ::flags, Tf.ScrollY) + checkboxFlags("ImGuiTableFlags_SizingFixedFit", ::flags, Tf.SizingFixedFit) for (n in 0..2) { - val open = collapsingHeader("Synced Table $n", Tnf.DefaultOpen.i) + val open = collapsingHeader("Synced Table $n", Tnf.DefaultOpen) if (open && beginTable("Table", 3, flags, Vec2(0f, textLineHeightWithSpacing * 5))) { tableSetupColumn("One") tableSetupColumn("Two") @@ -1368,7 +1366,7 @@ object ShowDemoWindowTables { helpMarker("By default, right-clicking over a TableHeadersRow()/TableHeader() line will open the default context-menu.\nUsing ImGuiTableFlags_ContextMenuInBody we also allow right-clicking over columns body.") pushingStyleCompact { - checkboxFlags("ImGuiTableFlags_ContextMenuInBody", ::flags, Tf.ContextMenuInBody.i) + checkboxFlags("ImGuiTableFlags_ContextMenuInBody", ::flags, Tf.ContextMenuInBody) } // Context Menus: first example @@ -1435,7 +1433,7 @@ object ShowDemoWindowTables { pushID(column) if (tableGetColumnFlags(column) has Tcf.IsHovered) hoveredColumn = column - if (hoveredColumn == column && !ImGui.isAnyItemHovered && ImGui.isMouseReleased(MouseButton.Right)) + if (hoveredColumn == column && !ImGui.isAnyItemHovered && MouseButton.Right.isReleased) openPopup("MyPopup") popup("MyPopup") { if (column == COLUMNS_COUNT) @@ -1470,9 +1468,9 @@ object ShowDemoWindowTables { // Options pushingStyleCompact { - checkboxFlags("ImGuiTableFlags_SortMulti", ::flags, Tf.SortMulti.i) + checkboxFlags("ImGuiTableFlags_SortMulti", ::flags, Tf.SortMulti) sameLine(); helpMarker("When sorting is enabled: hold shift when clicking headers to sort on multiple column. TableGetSortSpecs() may return specs where (SpecsCount > 1).") - checkboxFlags("ImGuiTableFlags_SortTristate", ::flags, Tf.SortTristate.i) + checkboxFlags("ImGuiTableFlags_SortTristate", ::flags, Tf.SortTristate) sameLine(); helpMarker("When sorting is enabled: allow no sorting, disable default sorting. TableGetSortSpecs() may return specs where (SpecsCount == 0).") } @@ -1485,7 +1483,7 @@ object ShowDemoWindowTables { // - ImGuiTableColumnFlags_NoSort / ImGuiTableColumnFlags_NoSortAscending / ImGuiTableColumnFlags_NoSortDescending // - ImGuiTableColumnFlags_PreferSortAscending / ImGuiTableColumnFlags_PreferSortDescending tableSetupColumn("ID", Tcf.DefaultSort or Tcf.WidthFixed, 0f, MyItemColumnID.ID.ordinal) - tableSetupColumn("Name", Tcf.WidthFixed.i, 0f, MyItemColumnID.Name.ordinal) + tableSetupColumn("Name", Tcf.WidthFixed, 0f, MyItemColumnID.Name.ordinal) tableSetupColumn("Action", Tcf.NoSort or Tcf.WidthFixed, 0f, MyItemColumnID.Action.ordinal) tableSetupColumn("Quantity", Tcf.PreferSortDescending or Tcf.WidthStretch, 0f, MyItemColumnID.Quantity.ordinal) tableSetupScrollFreeze(0, 1) // Make row always visible @@ -1503,10 +1501,8 @@ object ShowDemoWindowTables { } // Demonstrate using clipper for large vertical lists - val clipper = ListClipper() - clipper.begin(items.size) - while (clipper.step()) - for (rowN in clipper.display) { + listClipper(items.size) { + for (rowN in it.display) { // Display a data item val item = items[rowN] pushID(item.id) @@ -1521,15 +1517,15 @@ object ShowDemoWindowTables { text("${item.quantity}") popID() } - clipper.end() + } } } } } object Advanced { - var flags = Tf.Resizable or Tf.Reorderable or Tf.Hideable or Tf.Sortable or Tf.SortMulti or Tf.RowBg or - Tf.Borders or Tf.NoBordersInBody or Tf.ScrollX or Tf.ScrollY or Tf.SizingFixedFit + var flags = Tf.Resizable / Tf.Reorderable / Tf.Hideable / Tf.Sortable / Tf.SortMulti / Tf.RowBg / + Tf.Borders / Tf.NoBordersInBody / Tf.ScrollX / Tf.ScrollY / Tf.SizingFixedFit enum class ContentsType { Text, Button, SmallButton, FillButton, Selectable, SelectableSpanRow } @@ -1557,73 +1553,71 @@ object ShowDemoWindowTables { pushingStyleCompact { pushItemWidth(TEXT_BASE_WIDTH * 28f) - treeNodeEx("Features:", Tnf.DefaultOpen.i) { - checkboxFlags("ImGuiTableFlags_Resizable", ::flags, Tf.Resizable.i) - checkboxFlags("ImGuiTableFlags_Reorderable", ::flags, Tf.Reorderable.i) - checkboxFlags("ImGuiTableFlags_Hideable", ::flags, Tf.Hideable.i) - checkboxFlags("ImGuiTableFlags_Sortable", ::flags, Tf.Sortable.i) - checkboxFlags("ImGuiTableFlags_NoSavedSettings", ::flags, Tf.NoSavedSettings.i) - checkboxFlags("ImGuiTableFlags_ContextMenuInBody", ::flags, Tf.ContextMenuInBody.i) + treeNodeEx("Features:", Tnf.DefaultOpen) { + checkboxFlags("ImGuiTableFlags_Resizable", ::flags, Tf.Resizable) + checkboxFlags("ImGuiTableFlags_Reorderable", ::flags, Tf.Reorderable) + checkboxFlags("ImGuiTableFlags_Hideable", ::flags, Tf.Hideable) + checkboxFlags("ImGuiTableFlags_Sortable", ::flags, Tf.Sortable) + checkboxFlags("ImGuiTableFlags_NoSavedSettings", ::flags, Tf.NoSavedSettings) + checkboxFlags("ImGuiTableFlags_ContextMenuInBody", ::flags, Tf.ContextMenuInBody) } - treeNodeEx("Decorations:", Tnf.DefaultOpen.i) { - checkboxFlags("ImGuiTableFlags_RowBg", ::flags, Tf.RowBg.i) - checkboxFlags("ImGuiTableFlags_BordersV", ::flags, Tf.BordersV.i) - checkboxFlags("ImGuiTableFlags_BordersOuterV", ::flags, Tf.BordersOuterV.i) - checkboxFlags("ImGuiTableFlags_BordersInnerV", ::flags, Tf.BordersInnerV.i) - checkboxFlags("ImGuiTableFlags_BordersH", ::flags, Tf.BordersH.i) - checkboxFlags("ImGuiTableFlags_BordersOuterH", ::flags, Tf.BordersOuterH.i) - checkboxFlags("ImGuiTableFlags_BordersInnerH", ::flags, Tf.BordersInnerH.i) - checkboxFlags("ImGuiTableFlags_NoBordersInBody", ::flags, Tf.NoBordersInBody.i); sameLine(); helpMarker("Disable vertical borders in columns Body (borders will always appear in Headers") - checkboxFlags("ImGuiTableFlags_NoBordersInBodyUntilResize", ::flags, Tf.NoBordersInBodyUntilResize.i); sameLine(); helpMarker("Disable vertical borders in columns Body until hovered for resize (borders will always appear in Headers)") + treeNodeEx("Decorations:", Tnf.DefaultOpen) { + checkboxFlags("ImGuiTableFlags_RowBg", ::flags, Tf.RowBg) + checkboxFlags("ImGuiTableFlags_BordersV", ::flags, Tf.BordersV) + checkboxFlags("ImGuiTableFlags_BordersOuterV", ::flags, Tf.BordersOuterV) + checkboxFlags("ImGuiTableFlags_BordersInnerV", ::flags, Tf.BordersInnerV) + checkboxFlags("ImGuiTableFlags_BordersH", ::flags, Tf.BordersH) + checkboxFlags("ImGuiTableFlags_BordersOuterH", ::flags, Tf.BordersOuterH) + checkboxFlags("ImGuiTableFlags_BordersInnerH", ::flags, Tf.BordersInnerH) + checkboxFlags("ImGuiTableFlags_NoBordersInBody", ::flags, Tf.NoBordersInBody); sameLine(); helpMarker("Disable vertical borders in columns Body (borders will always appear in Headers") + checkboxFlags("ImGuiTableFlags_NoBordersInBodyUntilResize", ::flags, Tf.NoBordersInBodyUntilResize); sameLine(); helpMarker("Disable vertical borders in columns Body until hovered for resize (borders will always appear in Headers)") } - treeNodeEx("Sizing:", Tnf.DefaultOpen.i) { - val flags = intArrayOf(flags) - editTableSizingFlags(flags, 0) - this.flags = flags[0] + treeNodeEx("Sizing:", Tnf.DefaultOpen) { + editTableSizingFlags(::flags) sameLine(); helpMarker("In the Advanced demo we override the policy of each column so those table-wide settings have less effect that typical.") - checkboxFlags("ImGuiTableFlags_NoHostExtendX", this::flags, Tf.NoHostExtendX.i) + checkboxFlags("ImGuiTableFlags_NoHostExtendX", this::flags, Tf.NoHostExtendX) sameLine(); helpMarker("Make outer width auto-fit to columns, overriding outer_size.x value.\n\nOnly available when ScrollX/ScrollY are disabled and Stretch columns are not used.") - checkboxFlags("ImGuiTableFlags_NoHostExtendY", this::flags, Tf.NoHostExtendY.i) + checkboxFlags("ImGuiTableFlags_NoHostExtendY", this::flags, Tf.NoHostExtendY) sameLine(); helpMarker("Make outer height stop exactly at outer_size.y (prevent auto-extending table past the limit).\n\nOnly available when ScrollX/ScrollY are disabled. Data below the limit will be clipped and not visible.") - checkboxFlags("ImGuiTableFlags_NoKeepColumnsVisible", this::flags, Tf.NoKeepColumnsVisible.i) + checkboxFlags("ImGuiTableFlags_NoKeepColumnsVisible", this::flags, Tf.NoKeepColumnsVisible) sameLine(); helpMarker("Only available if ScrollX is disabled.") - checkboxFlags("ImGuiTableFlags_PreciseWidths", this::flags, Tf.PreciseWidths.i) + checkboxFlags("ImGuiTableFlags_PreciseWidths", this::flags, Tf.PreciseWidths) sameLine(); helpMarker("Disable distributing remainder width to stretched columns (width allocation on a 100-wide table with 3 columns: Without this flag: 33,33,34. With this flag: 33,33,33). With larger number of columns, resizing will appear to be less smooth.") - checkboxFlags("ImGuiTableFlags_NoClip", this::flags, Tf.NoClip.i) + checkboxFlags("ImGuiTableFlags_NoClip", this::flags, Tf.NoClip) sameLine(); helpMarker("Disable clipping rectangle for every individual columns (reduce draw command count, items will be able to overflow into other columns). Generally incompatible with ScrollFreeze options.") } - treeNodeEx("Padding:", Tnf.DefaultOpen.i) { - checkboxFlags("ImGuiTableFlags_PadOuterX", ::flags, Tf.PadOuterX.i) - checkboxFlags("ImGuiTableFlags_NoPadOuterX", ::flags, Tf.NoPadOuterX.i) - checkboxFlags("ImGuiTableFlags_NoPadInnerX", ::flags, Tf.NoPadInnerX.i) + treeNodeEx("Padding:", Tnf.DefaultOpen) { + checkboxFlags("ImGuiTableFlags_PadOuterX", ::flags, Tf.PadOuterX) + checkboxFlags("ImGuiTableFlags_NoPadOuterX", ::flags, Tf.NoPadOuterX) + checkboxFlags("ImGuiTableFlags_NoPadInnerX", ::flags, Tf.NoPadInnerX) } - treeNodeEx("Scrolling:", Tnf.DefaultOpen.i) { - checkboxFlags("ImGuiTableFlags_ScrollX", ::flags, Tf.ScrollX.i) + treeNodeEx("Scrolling:", Tnf.DefaultOpen) { + checkboxFlags("ImGuiTableFlags_ScrollX", ::flags, Tf.ScrollX) sameLine() setNextItemWidth(ImGui.frameHeight) - dragInt("freeze_cols", ::freezeCols, 0.2f, 0, 9, null, SliderFlag.NoInput.i) - checkboxFlags("ImGuiTableFlags_ScrollY", ::flags, Tf.ScrollY.i) + drag("freeze_cols", ::freezeCols, 0.2f, 0, 9, null, SliderFlag.NoInput) + checkboxFlags("ImGuiTableFlags_ScrollY", ::flags, Tf.ScrollY) sameLine() setNextItemWidth(ImGui.frameHeight) - dragInt("freeze_rows", ::freezeRows, 0.2f, 0, 9, null, SliderFlag.NoInput.i) + drag("freeze_rows", ::freezeRows, 0.2f, 0, 9, null, SliderFlag.NoInput) } - treeNodeEx("Sorting:", Tnf.DefaultOpen.i) { - checkboxFlags("ImGuiTableFlags_SortMulti", ::flags, Tf.SortMulti.i) + treeNodeEx("Sorting:", Tnf.DefaultOpen) { + checkboxFlags("ImGuiTableFlags_SortMulti", ::flags, Tf.SortMulti) sameLine(); helpMarker("When sorting is enabled: hold shift when clicking headers to sort on multiple column. TableGetSortSpecs() may return specs where (SpecsCount > 1).") - checkboxFlags("ImGuiTableFlags_SortTristate", ::flags, Tf.SortTristate.i) + checkboxFlags("ImGuiTableFlags_SortTristate", ::flags, Tf.SortTristate) sameLine(); helpMarker("When sorting is enabled: allow no sorting, disable default sorting. TableGetSortSpecs() may return specs where (SpecsCount == 0).") } - treeNodeEx("Other:", Tnf.DefaultOpen.i) { + treeNodeEx("Other:", Tnf.DefaultOpen) { checkbox("show_headers", ::showHeaders) checkbox("show_wrapped_text", ::showWrappedText) - dragVec2("##OuterSize", outerSizeValue) + drag2("##OuterSize", outerSizeValue) sameLine(0f, style.itemInnerSpacing.x) checkbox("outer_size", ::outerSizeEnabled) sameLine() @@ -1636,12 +1630,12 @@ object ShowDemoWindowTables { // From a user point of view we will tend to use 'inner_width' differently depending on whether our table is embedding scrolling. // toying with this demo we will actually pass 0.0f to the BeginTable() when ScrollX is disabled. - dragFloat("inner_width (when ScrollX active)", ::innerWidthWithScroll, 1f, 0f, Float.MAX_VALUE) + drag("inner_width (when ScrollX active)", ::innerWidthWithScroll, 1f, 0f, Float.MAX_VALUE) - dragFloat("row_min_height", ::rowMinHeight, 1f, 0f, Float.MAX_VALUE) + drag("row_min_height", ::rowMinHeight, 1f, 0f, Float.MAX_VALUE) sameLine(); helpMarker("Specify height of the Selectable item.") - dragInt("items_count", ::itemsCount, 0.1f, 0, 9999) + drag("items_count", ::itemsCount, 0.1f, 0, 9999) combo("items_type (first column)", ::contentsType, contentsTypeNames) //filter.Draw("filter"); } @@ -1675,10 +1669,10 @@ object ShowDemoWindowTables { // We use the "user_id" parameter of TableSetupColumn() to specify a user id that will be stored in the sort specifications. // This is so our sort function can identify a column given our own identifier. We could also identify them based on their index! tableSetupColumn("ID", Tcf.DefaultSort or Tcf.WidthFixed or Tcf.NoHide, 0f, MyItemColumnID.ID.ordinal) - tableSetupColumn("Name", Tcf.WidthFixed.i, 0f, MyItemColumnID.Name.ordinal) + tableSetupColumn("Name", Tcf.WidthFixed, 0f, MyItemColumnID.Name.ordinal) tableSetupColumn("Action", Tcf.NoSort or Tcf.WidthFixed, 0f, MyItemColumnID.Action.ordinal) - tableSetupColumn("Quantity", Tcf.PreferSortDescending.i, 0f, MyItemColumnID.Quantity.ordinal) - tableSetupColumn("Description", if (flags has Tf.NoHostExtendX) 0 else Tcf.WidthStretch.i, 0f, MyItemColumnID.Description.ordinal) + tableSetupColumn("Quantity", Tcf.PreferSortDescending, 0f, MyItemColumnID.Quantity.ordinal) + tableSetupColumn("Description", if (flags has Tf.NoHostExtendX) none else Tcf.WidthStretch, 0f, MyItemColumnID.Description.ordinal) tableSetupColumn("Hidden", Tcf.DefaultHide or Tcf.NoSort) tableSetupScrollFreeze(freezeCols, freezeRows) @@ -1707,10 +1701,8 @@ object ShowDemoWindowTables { pushButtonRepeat(true) // #if 1 // Demonstrate using clipper for large vertical lists - val clipper = ListClipper() - clipper.begin(items.size) - while (clipper.step()) { - for (rowN in clipper.display) + listClipper(items.size) { + for (rowN in it.display) // #else // // Without clipper // { @@ -1723,7 +1715,7 @@ object ShowDemoWindowTables { val itemIsSelected = item.id in selection pushID(item.id) - tableNextRow(Trf.None.i, rowMinHeight) + tableNextRow(rowMinHeight = rowMinHeight) // For the demo purpose we can select among different type of items submitted in the first column tableSetColumnIndex(0) @@ -1739,8 +1731,8 @@ object ShowDemoWindowTables { button(label, Vec2(-Float.MIN_VALUE, 0f)) else if (type == ContentsType.Selectable || type == ContentsType.SelectableSpanRow) { val selectableFlags = when (type) { - ContentsType.SelectableSpanRow -> SelectableFlag.SpanAllColumns or SelectableFlag.AllowItemOverlap - else -> SelectableFlag.None.i + ContentsType.SelectableSpanRow -> SelectableFlag.SpanAllColumns or SelectableFlag.AllowOverlap + else -> none } if (selectable(label, itemIsSelected, selectableFlags, Vec2(0f, rowMinHeight))) if (io.keyCtrl) @@ -1783,7 +1775,6 @@ object ShowDemoWindowTables { popID() } - clipper.end() } popButtonRepeat() @@ -1803,7 +1794,6 @@ object ShowDemoWindowTables { text(": DrawCmd: +${tableDrawListDrawCmdCount - 1} (in child window), Scroll: (%.f/%.f) (%.f/%.f)", tableScrollCur.x, tableScrollMax.x, tableScrollCur.y, tableScrollMax.y) } - treePop() } } } diff --git a/core/src/main/kotlin/imgui/demo/ShowDemoWindowWidgets.kt b/core/src/main/kotlin/imgui/demo/ShowDemoWindowWidgets.kt index 0e91d0dc5..9619d23d5 100644 --- a/core/src/main/kotlin/imgui/demo/ShowDemoWindowWidgets.kt +++ b/core/src/main/kotlin/imgui/demo/ShowDemoWindowWidgets.kt @@ -1,6 +1,8 @@ +@file:OptIn(ExperimentalStdlibApi::class) + package imgui.demo -import gli_.has +import glm_.has import glm_.* import glm_.func.sin import glm_.vec2.Vec2 @@ -16,10 +18,10 @@ import imgui.ImGui.beginCombo import imgui.ImGui.beginDisabled import imgui.ImGui.beginDragDropSource import imgui.ImGui.beginDragDropTarget +import imgui.ImGui.beginItemTooltip import imgui.ImGui.beginListBox import imgui.ImGui.beginPopupContextItem import imgui.ImGui.beginTable -import imgui.ImGui.beginTooltip import imgui.ImGui.bullet import imgui.ImGui.bulletText import imgui.ImGui.button @@ -35,18 +37,10 @@ import imgui.ImGui.colorPicker4 import imgui.ImGui.combo import imgui.ImGui.cursorPos import imgui.ImGui.cursorScreenPos -import imgui.ImGui.dragFloat -import imgui.ImGui.dragFloat2 -import imgui.ImGui.dragFloat3 -import imgui.ImGui.dragFloat4 -import imgui.ImGui.dragFloatRange2 -import imgui.ImGui.dragInt -import imgui.ImGui.dragInt2 -import imgui.ImGui.dragInt3 -import imgui.ImGui.dragInt4 -import imgui.ImGui.dragIntRange2 -import imgui.ImGui.dragScalar -import imgui.ImGui.dragVec4 +import imgui.ImGui.drag2 +import imgui.ImGui.drag3 +import imgui.ImGui.drag4 +import imgui.ImGui.dragRange import imgui.ImGui.end import imgui.ImGui.endChild import imgui.ImGui.endCombo @@ -56,26 +50,21 @@ import imgui.ImGui.endDragDropTarget import imgui.ImGui.endListBox import imgui.ImGui.endPopup import imgui.ImGui.endTable -import imgui.ImGui.endTooltip import imgui.ImGui.fontSize import imgui.ImGui.getMouseDragDelta +import imgui.ImGui.getStyleColorVec4 import imgui.ImGui.image import imgui.ImGui.imageButton import imgui.ImGui.indent -import imgui.ImGui.inputDouble -import imgui.ImGui.inputFloat -import imgui.ImGui.inputFloat2 -import imgui.ImGui.inputFloat3 -import imgui.ImGui.inputFloat4 -import imgui.ImGui.inputInt -import imgui.ImGui.inputInt2 -import imgui.ImGui.inputInt3 -import imgui.ImGui.inputInt4 -import imgui.ImGui.inputScalar +import imgui.ImGui.input +import imgui.ImGui.input2 +import imgui.ImGui.input3 +import imgui.ImGui.input4 import imgui.ImGui.inputText import imgui.ImGui.inputTextMultiline import imgui.ImGui.inputTextWithHint import imgui.ImGui.io +import imgui.ImGui.isDoubleClicked import imgui.ImGui.isItemActivated import imgui.ImGui.isItemActive import imgui.ImGui.isItemClicked @@ -86,7 +75,6 @@ import imgui.ImGui.isItemFocused import imgui.ImGui.isItemHovered import imgui.ImGui.isItemToggledOpen import imgui.ImGui.isItemVisible -import imgui.ImGui.isMouseDoubleClicked import imgui.ImGui.isWindowFocused import imgui.ImGui.isWindowHovered import imgui.ImGui.itemRectMax @@ -111,22 +99,18 @@ import imgui.ImGui.resetMouseDragDelta import imgui.ImGui.sameLine import imgui.ImGui.selectable import imgui.ImGui.separator +import imgui.ImGui.separatorText import imgui.ImGui.setColorEditOptions import imgui.ImGui.setDragDropPayload import imgui.ImGui.setItemDefaultFocus +import imgui.ImGui.setItemTooltip import imgui.ImGui.setNextItemOpen import imgui.ImGui.setNextItemWidth import imgui.ImGui.setTooltip +import imgui.ImGui.slider2 +import imgui.ImGui.slider3 +import imgui.ImGui.slider4 import imgui.ImGui.sliderAngle -import imgui.ImGui.sliderFloat -import imgui.ImGui.sliderFloat2 -import imgui.ImGui.sliderFloat3 -import imgui.ImGui.sliderFloat4 -import imgui.ImGui.sliderInt -import imgui.ImGui.sliderInt2 -import imgui.ImGui.sliderInt3 -import imgui.ImGui.sliderInt4 -import imgui.ImGui.sliderScalar import imgui.ImGui.smallButton import imgui.ImGui.spacing import imgui.ImGui.style @@ -145,9 +129,8 @@ import imgui.ImGui.treeNodeEx import imgui.ImGui.treeNodeToLabelSpacing import imgui.ImGui.treePop import imgui.ImGui.unindent -import imgui.ImGui.vSliderFloat -import imgui.ImGui.vSliderInt import imgui.ImGui.windowDrawList +import imgui.api.* import imgui.api.demoDebugInformations.Companion.helpMarker import imgui.classes.Color import imgui.classes.InputTextCallbackData @@ -167,8 +150,6 @@ import imgui.dsl.withItemWidth import imgui.dsl.withStyleColor import imgui.dsl.withStyleVar import imgui.dsl.withTextWrapPos -import imgui.internal.sections.ItemFlags -import imgui.or import unsigned.Ubyte import unsigned.Uint import unsigned.Ulong @@ -202,10 +183,11 @@ object ShowDemoWindowWidgets { /* Text Input */ object Funcs2 { val MyResizeCallback: InputTextCallback = { data -> - if (data.eventFlag == Itf.CallbackResize.i) { + if (data.eventFlag == Itf.CallbackResize) { val myString = data.userData as ByteArray assert(myString.contentEquals(data.buf)) - data.userData = ByteArray(data.bufSize) // NB: On resizing calls, generally data->BufSize == data->BufTextLen + 1 + // NB: On resizing calls, generally data->BufSize == data->BufTextLen + 1 + data.userData = ByteArray(data.bufSize) data.buf = myString } false @@ -213,11 +195,10 @@ object ShowDemoWindowWidgets { // Note: Because ImGui:: is a namespace you would typically add your own function into the namespace. // For example, you code may declare a function 'ImGui::InputText(const char* label, MyString* my_str)' - val MyInputTextMultiline: (label: String, myStr: ByteArray, size: Vec2, flags: ItemFlags) -> Boolean = - { label, myStr, size, flags -> - assert(flags hasnt Itf.CallbackResize) - inputTextMultiline(label, String(myStr), size, flags or Itf.CallbackResize, MyResizeCallback, myStr) - } + val MyInputTextMultiline: (label: String, myStr: ByteArray, size: Vec2, flags: InputTextSingleFlags) -> Boolean = { label, myStr, size, flags -> + assert(flags hasnt Itf.CallbackResize) + inputTextMultiline(label, String(myStr), size, flags or Itf.CallbackResize, MyResizeCallback, myStr) + } } var myStr = ByteArray(0) @@ -231,6 +212,7 @@ object ShowDemoWindowWidgets { beginDisabled() Basic() + Tooltips() Trees() `Collapsing Headers`() @@ -245,6 +227,7 @@ object ShowDemoWindowWidgets { Text() Images() Combo() + ListBoxes() Selectables() `Text Input`() Tabs() @@ -256,9 +239,8 @@ object ShowDemoWindowWidgets { `Multi-component Widgets`() `Vertical Sliders`() `Drag and Drop`() - `Querying Item Status (Edited,Active,Focused,Hovered etc)`() + `Querying Item Status (Edited,Active,Hovered etc)`() `Querying Window Status (Focused-Hovered etc,)`() - `Text Filter`() // Demonstrate BeginDisabled/EndDisabled using a checkbox located at the bottom of the section (which is a bit odd: // logically we'd have this checkbox at the top of the section, but we don't want this feature to steal that space) @@ -269,6 +251,8 @@ object ShowDemoWindowWidgets { checkbox("Disable entire section above", ::disableAll) sameLine(); helpMarker("Demonstrate using BeginDisabled()/EndDisabled() across this section.") } + + `Text Filter`() } object Basic { @@ -276,15 +260,14 @@ object ShowDemoWindowWidgets { var clicked = 0 var check = true var e = 0 - val arr = floatArrayOf(0.6f, 0.1f, 1f, 0.5f, 0.92f, 0.1f, 0.2f) - var currentItem0 = 0 + var itemCurrent1 = 0 var str0 = "Hello, world!".toByteArray(128) var str1 = ByteArray(128) var i0 = 123 var f0 = 0.001f var f1 = 1e10f var d0 = 999999.00000001 - val vec4 = floatArrayOf(0.1f, 0.2f, 0.3f, 0.44f) + val vec4 = Vec4(0.1f, 0.2f, 0.3f, 0.44f) var i1 = 50 var i2 = 42 var f2 = 1f @@ -297,13 +280,16 @@ object ShowDemoWindowWidgets { enum class Element { Fire, Earth, Air, Water } var elem = Element.Fire.ordinal - val col1 = floatArrayOf(1f, 0f, 0.2f) - val col2 = floatArrayOf(0.4f, 0.7f, 0f, 0.5f) - var itemCurrent = 1 + val col1 = Vec3(1f, 0f, 0.2f) + val col2 = Vec4(0.4f, 0.7f, 0f, 0.5f) + var itemCurrent0 = 1 operator fun invoke() { treeNode("Basic") { - if (button("Button")) clicked++ + separatorText("General") + + if (button("Button")) + clicked++ if (clicked has 1) { sameLine() text("Thanks for clicking me!") @@ -344,16 +330,12 @@ object ShowDemoWindowWidgets { sameLine() text("$counter") - separator() + button("Tooltip") + setItemTooltip("I am a tooltip") + labelText("label", "Value") - run { - // Using the _simplified_ one-liner Combo() api here - // See "Combo" section for examples of how to use the more flexible BeginCombo()/EndCombo() api. - val items = listOf("AAAA", "BBBB", "CCCC", "DDDD", "EEEE", "FFFF", "GGGG", "HHHH", "IIIIIII", "JJJJ", "KKKKKKK") - combo("combo", ::currentItem0, items) - sameLine(); helpMarker("Using the simplified one-liner Combo API here.\nRefer to the \"Combo\" section below for an explanation of how to use the more flexible and general BeginCombo/EndCombo API.") - } + separatorText("Inputs") run { // To wire InputText() with std::string or any other custom string type, @@ -375,38 +357,43 @@ object ShowDemoWindowWidgets { inputTextWithHint("input text (w/ hint)", "enter text here", str1) - inputInt("input int", ::i0) + ImGui.input("input int", ::i0) - inputFloat("input float", ::f0, 0.01f, 1f, "%.3f") + ImGui.input("input float", ::f0, 0.01f, 1f, "%.3f") - inputDouble("input double", ::d0, 0.01, 1.0, "%.8f") + ImGui.input("input double", ::d0, 0.01, 1.0, "%.8f") - inputFloat("input scientific", ::f1, 0f, 0f, "%e") + ImGui.input("input scientific", ::f1, 0f, 0f, "%e") sameLine(); helpMarker(""" You can input value using the scientific notation, e.g. \"1e+8\" becomes \"100000000\".""".trimIndent()) - inputFloat3("input float3", vec4) + ImGui.input3("input float3", vec4) } + + separatorText("Drags") + run { - dragInt("drag int", ::i1, 1f) + drag("drag int", ::i1, 1f) sameLine(); helpMarker(""" Click and drag to edit value. Hold SHIFT/ALT for faster/slower edit. Double-click or CTRL+click to input value.""".trimIndent()) - dragInt("drag int 0..100", ::i2, 1f, 0, 100, "%d%%", SliderFlag.AlwaysClamp.i) + drag("drag int 0..100", ::i2, 1f, 0, 100, "%d%%", SliderFlag.AlwaysClamp) - dragFloat("drag float", ::f2, 0.005f) - dragFloat("drag small float", ::f3, 0.0001f, 0f, 0f, "%.06f ns") + drag("drag float", ::f2, 0.005f) + drag("drag small float", ::f3, 0.0001f, 0f, 0f, "%.06f ns") } + + separatorText("Sliders") + run { - sliderInt("slider int", ::i3, -1, 3) + slider("slider int", ::i3, -1, 3) sameLine(); helpMarker("CTRL+click to input value.") - sliderFloat("slider float", ::f4, 0f, 1f, "ratio = %.3f") - // TODO - // sliderFloat("slider float (curve)", ::f5, -10f, 10f, "%.4f", 2f) + slider("slider float", ::f4, 0f, 1f, "ratio = %.3f") + slider("slider float (log)", ::f5, -10f, 10f, "%.4f", SliderFlag.Logarithmic) sliderAngle("slider angle", ::angle) @@ -414,10 +401,12 @@ object ShowDemoWindowWidgets { // Here we completely omit '%d' from the format string, so it'll only display a name. // This technique can also be used with DragInt(). val elemName = Element.values().getOrNull(elem)?.name ?: "Unknown" - sliderInt("slider enum", ::elem, 0, Element.values().lastIndex, elemName) + slider("slider enum", ::elem, 0, Element.values().lastIndex, elemName) // Use ImGuiSliderFlags_NoInput flag to disable CTRL+Click here. sameLine(); helpMarker("Using the format string parameter to display a name instead of the underlying integer.") } + separatorText("Selectors/Pickers") + run { colorEdit3("color 1", col1) sameLine(); helpMarker(""" @@ -429,44 +418,103 @@ object ShowDemoWindowWidgets { colorEdit4("color 2", col2) } + run { + // Using the _simplified_ one-liner Combo() api here + // See "Combo" section for examples of how to use the more flexible BeginCombo()/EndCombo() api. +// IMGUI_DEMO_MARKER("Widgets/Basic/Combo"); + val items = listOf("AAAA", "BBBB", "CCCC", "DDDD", "EEEE", "FFFF", "GGGG", "HHHH", "IIIIIII", "JJJJ", "KKKKKKK") + combo("combo", ::itemCurrent1, items) + sameLine(); helpMarker("Using the simplified one-liner Combo API here.\nRefer to the \"Combo\" section below for an explanation of how to use the more flexible and general BeginCombo/EndCombo API.") + } + run { // Using the _simplified_ one-liner ListBox() api here // See "List boxes" section for examples of how to use the more flexible BeginListBox()/EndListBox() api. val items = arrayOf("Apple", "Banana", "Cherry", "Kiwi", "Mango", "Orange", "Pineapple", "Strawberry", "Watermelon") - listBox("listbox", ::itemCurrent, items, 4) - + listBox("listbox", ::itemCurrent0, items, 4) sameLine(); helpMarker("Using the simplified one-liner ListBox API here.\nRefer to the \"List boxes\" section below for an explanation of how to use the more flexible and general BeginListBox/EndListBox API.") } + } + } + } - run { - alignTextToFramePadding() - text("Tooltips:") + object Tooltips { - sameLine() - button("Button") - if (isItemHovered()) - setTooltip("I am a tooltip") + val arr = floatArrayOf(0.6f, 0.1f, 1f, 0.5f, 0.92f, 0.1f, 0.2f) + var alwaysOn = 0 - sameLine() - button("Fancy") - if (isItemHovered()) { - beginTooltip() - text("I am a fancy tooltip") - plotLines("Curve", arr) - text("Sin(time) = ${ImGui.time.f.sin}") - endTooltip() - } + operator fun invoke() { - sameLine() - button("Delayed") - if (isItemHovered(HoveredFlag.DelayNormal)) // Delay best used on items that highlight on hover, so this not a great example! - setTooltip("I am a tooltip with a delay.") +// IMGUI_DEMO_MARKER("Widgets/Tooltips"); + treeNode("Tooltips") { - sameLine() - helpMarker("Tooltip are created by using the IsItemHovered() function over any kind of item.") + // Tooltips are windows following the mouse. They do not take focus away. + ImGui.separatorText("General") + + // Typical use cases: + // - Short-form (text only): SetItemTooltip("Hello"); + // - Short-form (any contents): if (BeginItemTooltip()) { Text("Hello"); EndTooltip(); } + + // - Full-form (text only): if (IsItemHovered(...)) { SetTooltip("Hello"); } + // - Full-form (any contents): if (IsItemHovered(...) && BeginTooltip()) { Text("Hello"); EndTooltip(); } + + helpMarker("Tooltip are typically created by using a IsItemHovered() + SetTooltip() sequence.\n\n" + + "We provide a helper SetItemTooltip() function to perform the two with standards flags.") + + val sz = Vec2(-Float.MIN_VALUE, 0f) + ImGui.button("Basic", sz) + ImGui.setItemTooltip("I am a tooltip") + + ImGui.button("Fancy", sz) + if (ImGui.beginItemTooltip()) { + ImGui.text("I am a fancy tooltip") + ImGui.plotLines("Curve", arr) + ImGui.text("Sin(time) = " + ImGui.time.sin) + ImGui.endTooltip() + } + + ImGui.separatorText("Always On") + + // Showcase NOT relying on a IsItemHovered() to emit a tooltip. + // Here the tooltip is always emitted when 'always_on == true'. + ImGui.radioButton("Off", ::alwaysOn, 0) + ImGui.sameLine() + ImGui.radioButton("Always On (Simple)", ::alwaysOn, 1) + ImGui.sameLine() + ImGui.radioButton("Always On (Advanced)", ::alwaysOn, 2) + if (alwaysOn == 1) + ImGui.setTooltip("I am following you around.") + else if (alwaysOn == 2 && ImGui.beginTooltip()) { + ImGui.progressBar(ImGui.time.f.sin * 0.5f + 0.5f, Vec2(ImGui.fontSize * 25, 0f)) + ImGui.endTooltip() } + ImGui.separatorText("Custom") + + // The following examples are passed for documentation purpose but may not be useful to most users. + // Passing ImGuiHoveredFlags_Tooltip to IsItemHovered() will pull ImGuiHoveredFlags flags values from + // 'style.HoverFlagsForTooltipMouse' or 'style.HoverFlagsForTooltipNav' depending on whether mouse or gamepad/keyboard is being used. + // With default settings, ImGuiHoveredFlags_Tooltip is equivalent to ImGuiHoveredFlags_DelayShort + ImGuiHoveredFlags_Stationary. + ImGui.button("Manual", sz) + if (ImGui.isItemHovered(HoveredFlag.ForTooltip)) + ImGui.setTooltip("I am a manually emitted tooltip") + + ImGui.button("DelayNone", sz) + if (ImGui.isItemHovered(HoveredFlag.DelayNone)) + ImGui.setTooltip("I am a tooltip with no delay.") + + ImGui.button("DelayShort", sz) + if (ImGui.isItemHovered(HoveredFlag.DelayShort / HoveredFlag.NoSharedDelay)) + ImGui.setTooltip("I am a tooltip with a short delay (%0.2f sec).", ImGui.style.hoverDelayShort) + + ImGui.button("DelayLong", sz) + if (ImGui.isItemHovered(HoveredFlag.DelayNormal / HoveredFlag.NoSharedDelay)) + ImGui.setTooltip("I am a tooltip with a long delay (%0.2f sec)", ImGui.style.hoverDelayNormal) + + ImGui.button("Stationary", sz) + if (ImGui.isItemHovered(HoveredFlag.Stationary)) + ImGui.setTooltip("I am a tooltip requiring mouse to be stationary before activating.") } } } @@ -479,7 +527,7 @@ object ShowDemoWindowWidgets { operator fun invoke() { treeNode("Trees") { treeNode("Basic trees") { - for (i in 0..4) { + for (i in 0..<5) { // Use SetNextItemOpen() so set the default state of a node to be open. We could // also use TreeNodeEx() with the ImGuiTreeNodeFlags_DefaultOpen flag to achieve the same thing! if (i == 0) @@ -498,10 +546,10 @@ object ShowDemoWindowWidgets { helpMarker(""" This is a more typical looking tree with selectable nodes. Click to select, CTRL+Click to toggle, click on arrows or double-click to open.""".trimIndent()) - checkboxFlags("ImGuiTreeNodeFlags_OpenOnArrow", ::baseFlags, Tnf.OpenOnArrow.i) - checkboxFlags("ImGuiTreeNodeFlags_OpenOnDoubleClick", ::baseFlags, Tnf.OpenOnDoubleClick.i) - checkboxFlags("ImGuiTreeNodeFlags_SpanAvailWidth", ::baseFlags, Tnf.SpanAvailWidth.i); sameLine(); helpMarker("Extend hit area to all available width instead of allowing more items to be laid out after the node.") - checkboxFlags("ImGuiTreeNodeFlags_SpanFullWidth", ::baseFlags, Tnf.SpanFullWidth.i) + checkboxFlags("ImGuiTreeNodeFlags_OpenOnArrow", ::baseFlags, Tnf.OpenOnArrow) + checkboxFlags("ImGuiTreeNodeFlags_OpenOnDoubleClick", ::baseFlags, Tnf.OpenOnDoubleClick) + checkboxFlags("ImGuiTreeNodeFlags_SpanAvailWidth", ::baseFlags, Tnf.SpanAvailWidth); sameLine(); helpMarker("Extend hit area to all available width instead of allowing more items to be laid out after the node.") + checkboxFlags("ImGuiTreeNodeFlags_SpanFullWidth", ::baseFlags, Tnf.SpanFullWidth) checkbox("Align label with current X position", ::alignLabelWithCurrentXposition) checkbox("Test tree node as drag source", ::testDragAndDrop) text("Hello!") @@ -567,7 +615,7 @@ object ShowDemoWindowWidgets { operator fun invoke() { treeNode("Collapsing Headers") { checkbox("Show 2nd header", ::closableGroup) - collapsingHeader("Header", Tnf.None.i) { + collapsingHeader("Header") { text("IsItemHovered: ${isItemHovered()}") for (i in 0..4) text("Some content $i") } @@ -586,7 +634,7 @@ object ShowDemoWindowWidgets { object Text { var wrapWidth = 200f - val buf = "日本語".toByteArray(32) // "nihongo" + val buf = utf8(0xe6, 0x97, 0xa5, 0xe6, 0x9c, 0xac, 0xe8, 0xaa, 0x9e).toByteArray(32) // "nihongo" operator fun invoke() { treeNode("Text") { treeNode("Color Text") { @@ -600,10 +648,10 @@ object ShowDemoWindowWidgets { // Using shortcut. You can use PushTextWrapPos()/PopTextWrapPos() for more flexibility. textWrapped( "This text should automatically wrap on the edge of the window. The current implementation " + - "for text wrapping follows simple rules suitable for English and possibly other languages.") + "for text wrapping follows simple rules suitable for English and possibly other languages.") spacing() - sliderFloat("Wrap width", ::wrapWidth, -20f, 600f, "%.0f") + slider("Wrap width", ::wrapWidth, -20f, 600f, "%.0f") val drawList = windowDrawList for (n in 0..1) { @@ -636,10 +684,10 @@ object ShowDemoWindowWidgets { // so you can safely copy & paste garbled characters into another application. textWrapped( "CJK text will only appear if the font was loaded with the appropriate CJK character ranges." + - "Call io.Fonts->AddFontFromFileTTF() manually to load extra character ranges." + - "Read docs/FONTS.txt for details.") - text("Hiragana: \u304b\u304d\u304f\u3051\u3053 (kakikukeko)") // Normally we would use u8"blah blah" with the proper characters directly in the string. - text("Kanjis: \u65e5\u672c\u8a9e (nihongo)") + "Call io.Fonts->AddFontFromFileTTF() manually to load extra character ranges." + + "Read docs/FONTS.txt for details.") + text("Hiragana: ${utf8(0xe3, 0x81, 0x8b, 0xe3, 0x81, 0x8d, 0xe3, 0x81, 0x8f, 0xe3, 0x81, 0x91, 0xe3, 0x81, 0x93)} (kakikukeko)") // Normally we would use u8"blah blah" with the proper characters directly in the string. + text("Kanjis: ${utf8(0xe6, 0x97, 0xa5, 0xe6, 0x9c, 0xac, 0xe8, 0xaa, 0x9e)} (nihongo)") inputText("UTF-8 input", buf) } } @@ -648,12 +696,14 @@ object ShowDemoWindowWidgets { object Images { var pressedCount = 0 + var useTextColorForTint = false operator fun invoke() { treeNode("Images") { textWrapped( "Below we are displaying the font texture (which is the only texture we have access to in this demo). " + - "Use the 'ImTextureID' type as storage to pass pointers or identifier to your own texture data. " + - "Hover the texture for a zoomed view!") + "Use the 'ImTextureID' type as storage to pass pointers or identifier to your own texture data. " + + "Hover the texture for a zoomed view!") + // Below we are displaying the font texture because it is the only texture we have access to inside the demo! // Remember that ImTextureID is just storage for whatever you want it to be. It is essentially a value that // will be passed to the rendering backend via the ImDrawCmd structure. @@ -673,14 +723,15 @@ object ShowDemoWindowWidgets { val myTexSize = Vec2(io.fonts.texSize) run { + checkbox("Use Text Color for Tint", ::useTextColorForTint) text("%.0fx%.0f", myTexSize.x, myTexSize.y) val pos = cursorScreenPos val uvMin = Vec2(0f) // Top-left val uvMax = Vec2(1f) // Lower-right - val tintCol = Vec4(1f) // No tint - val borderCol = Vec4(1f, 1f, 1f, 0.5f) // 50% opaque white + val tintCol = if (useTextColorForTint) getStyleColorVec4(Col.Text) else Vec4(1f) // No tint + val borderCol = getStyleColorVec4(Col.Border) image(myTexId, Vec2(myTexSize.x, myTexSize.y), uvMin, uvMax, tintCol, borderCol) - if (isItemHovered()) + if (beginItemTooltip()) tooltip { val regionSz = 32f var regionX = io.mousePos.x - pos.x - regionSz * 0.5f @@ -729,7 +780,7 @@ object ShowDemoWindowWidgets { } object Combo { - var flags = ComboFlag.None.i + var flags: ComboFlags = none var itemCurrentIdx = 0 var itemCurrent2 = 0 var itemCurrent3 = 0 @@ -746,12 +797,12 @@ object ShowDemoWindowWidgets { treeNode("Combo") { // Combo Boxes are also called "Dropdown" in other systems // Expose flags as checkbox for the demo - checkboxFlags("ComboFlag.PopupAlignLeft", ::flags, ComboFlag.PopupAlignLeft.i) + checkboxFlags("ComboFlag.PopupAlignLeft", ::flags, ComboFlag.PopupAlignLeft) sameLine(); helpMarker("Only makes a difference if the popup is larger than the combo") - if (checkboxFlags("ComboFlag.NoArrowButton", ::flags, ComboFlag.NoArrowButton.i)) - flags = flags wo ComboFlag.NoPreview // Clear the other flag, as we cannot combine both - if (checkboxFlags("ComboFlag.NoPreview", ::flags, ComboFlag.NoPreview.i)) - flags = flags wo ComboFlag.NoArrowButton // Clear the other flag, as we cannot combine both + if (checkboxFlags("ComboFlag.NoArrowButton", ::flags, ComboFlag.NoArrowButton)) + flags -= ComboFlag.NoPreview // Clear the other flag, as we cannot combine both + if (checkboxFlags("ComboFlag.NoPreview", ::flags, ComboFlag.NoPreview)) + flags -= ComboFlag.NoArrowButton // Clear the other flag, as we cannot combine both // Using the generic BeginCombo() API, you have full control over how to display the combo contents. // (your selection data could be an index, a pointer to the object, an id for the object, a flag intrusively @@ -845,12 +896,12 @@ object ShowDemoWindowWidgets { // The earlier is more flexible, as in real application your selection may be stored in many different ways // and not necessarily inside a bool value (e.g. in flags within objects, as an external list, etc). treeNode("Basic") { - selectable("1. I am selectable", selection0, 0) - selectable("2. I am selectable", selection0, 1) + selectable("1. I am selectable", selection0 mutablePropertyAt 0) + selectable("2. I am selectable", selection0 mutablePropertyAt 1) text("(I am not selectable)") - selectable("4. I am selectable", selection0, 2) - if (selectable("5. I am double clickable", selection0[3], Sf.AllowDoubleClick.i)) - if (isMouseDoubleClicked(MouseButton.Left)) selection0[3] = !selection0[3] + selectable("4. I am selectable", selection0 mutablePropertyAt 2) + if (selectable("5. I am double clickable", selection0[3], Sf.AllowDoubleClick)) + if (MouseButton.Left.isDoubleClicked) selection0[3] = !selection0[3] } treeNode("Selection State: Single Selection") { for (n in 0..4) @@ -869,26 +920,26 @@ object ShowDemoWindowWidgets { treeNode("Rendering more text into the same line") { // Using the Selectable() override that takes "bool* p_selected" parameter, // this function toggle your bool value automatically. - selectable("main.c", selected1, 0); sameLine(300); text(" 2,345 bytes") - selectable("Hello.cpp", selected1, 1); sameLine(300); text("12,345 bytes") - selectable("Hello.h", selected1, 2); sameLine(300); text(" 2,345 bytes") + selectable("main.c", selected1 mutablePropertyAt 0); sameLine(300); text(" 2,345 bytes") + selectable("Hello.cpp", selected1 mutablePropertyAt 1); sameLine(300); text("12,345 bytes") + selectable("Hello.h", selected1 mutablePropertyAt 2); sameLine(300); text(" 2,345 bytes") } treeNode("In columns") { - if (beginTable("split1", 3, TableFlag.Resizable or TableFlag.NoSavedSettings)) { + if (beginTable("split1", 3, TableFlag.Resizable / TableFlag.NoSavedSettings / TableFlag.Borders)) { for (i in 0..9) { val label = "Item $i" tableNextColumn() - selectable(label, selected2, i) // FIXME-TABLE: Selection overlap + selectable(label, selected2 mutablePropertyAt i) // FIXME-TABLE: Selection overlap } endTable() } - separator() - if (beginTable("split2", 3, TableFlag.Resizable or TableFlag.NoSavedSettings)) { + spacing() + if (beginTable("split2", 3, TableFlag.Resizable / TableFlag.NoSavedSettings / TableFlag.Borders)) { for (i in 0..9) { val label = "Item $i" tableNextRow() tableNextColumn() - selectable(label, selected2, i, imgui.SelectableFlag.SpanAllColumns.i) + selectable(label, selected2 mutablePropertyAt i, imgui.SelectableFlag.SpanAllColumns) tableNextColumn() text("Some other contents") tableNextColumn() @@ -910,7 +961,7 @@ object ShowDemoWindowWidgets { if (x > 0) sameLine() pushID(y * 4 + x) - if (selectable("Sailor", selected3[y][x] != 0, 0, Vec2(50))) { + if (selectable("Sailor", selected3[y][x] != 0, sizeArg = Vec2(50))) { // Toggle clicked cell + toggle neighbors selected3[y][x] = selected3[y][x] xor 1 if (x > 0) selected3[y][x - 1] = selected3[y][x - 1] xor 1 @@ -925,8 +976,7 @@ object ShowDemoWindowWidgets { popStyleVar() } treeNode("Alignment") { - helpMarker( - """ + helpMarker(""" By default, Selectables uses style.SelectableTextAlign but it can be overridden on a per-item " + "basis using PushStyleVar(). You'll probably want to always keep your default situation to @@ -938,7 +988,7 @@ object ShowDemoWindowWidgets { val name = "(%.1f,%.1f)".format(alignment.x, alignment.y) if (x > 0) sameLine() pushStyleVar(StyleVar.SelectableTextAlign, alignment) - selectable(name, selected4, 3 * y + x, Sf.None.i, Vec2(80)) + selectable(name, selected4 mutablePropertyAt 3 * y + x, size = Vec2(80)) popStyleVar() } } @@ -961,11 +1011,18 @@ object ShowDemoWindowWidgets { ${'\t'}lock cmpxchg8b eax """.trimIndent().toByteArray(1024 * 16) - var flags = Itf.AllowTabInput.i - val bufs = Array(6) { ByteArray(64) } + var flags: InputTextSingleFlags = Itf.AllowTabInput + val bufs = Array(7) { ByteArray(64) } object TextFilters { - // Return 0 (pass) if the character is 'i' or 'm' or 'g' or 'u' or 'i' + // Modify character input by altering 'data->Eventchar' (ImGuiInputTextFlags_CallbackCharFilter callback) + val filterCasingSwap: InputTextCallback = { data -> + if (data.eventChar in 'a'..'z') data.eventChar -= 'a' - 'A' // Lowercase becomes uppercase + else if (data.eventChar in 'A'..'Z') data.eventChar += 'a' - 'A' // Uppercase becomes lowercase + false + } + + // Return 0 (pass) if the character is 'i' or 'm' or 'g' or 'u' or 'i', otherwise return 1 (filter out) val filterImGuiLetters: InputTextCallback = { data: InputTextCallbackData -> !(data.eventChar < 256 && data.eventChar in "imgui") } @@ -976,8 +1033,8 @@ object ShowDemoWindowWidgets { object Funcs1 { val myCallback: InputTextCallback = { data: InputTextCallbackData -> when (data.eventFlag) { - Itf.CallbackCompletion.i -> data.insertChars(data.cursorPos, "..") - Itf.CallbackHistory.i -> + Itf.CallbackCompletion -> data.insertChars(data.cursorPos, "..") + Itf.CallbackHistory -> if (data.eventKey == Key.UpArrow) { data.deleteChars(0, data.bufTextLen) data.insertChars(0, "Pressed Up!") @@ -987,7 +1044,8 @@ object ShowDemoWindowWidgets { data.insertChars(0, "Pressed Down!") data.selectAll() } - Itf.CallbackEdit.i -> { + + Itf.CallbackEdit -> { // Toggle casing of first character val c = data.buf[0].c if (c in 'a'..'z' || c in 'A'..'Z') @@ -1016,35 +1074,36 @@ object ShowDemoWindowWidgets { /* Note: we are using a fixed-sized buffer for simplicity here. See ImGuiInputTextFlags_CallbackResize and the code in misc/cpp/imgui_stdlib.h for how to setup InputText() for dynamically resizing strings. */ helpMarker("You can use the InputTextFlag.CallbackResize facility if you need to wire InputTextMultiline() to a dynamic string type. See misc/cpp/imgui_stl.h for an example. (This is not demonstrated in imgui_demo.cpp because we don't want to include in here)") // TODO fix bug, some '?' appear at the end of the line - checkboxFlags("ImGuiInputTextFlags_ReadOnly", ::flags, Itf.ReadOnly.i) - checkboxFlags("ImGuiInputTextFlags_AllowTabInput", ::flags, Itf.AllowTabInput.i) - checkboxFlags("ImGuiInputTextFlags_CtrlEnterForNewLine", ::flags, Itf.CtrlEnterForNewLine.i) + checkboxFlags("ImGuiInputTextFlags_ReadOnly", ::flags, Itf.ReadOnly) + checkboxFlags("ImGuiInputTextFlags_AllowTabInput", ::flags, Itf.AllowTabInput) + checkboxFlags("ImGuiInputTextFlags_CtrlEnterForNewLine", ::flags, Itf.CtrlEnterForNewLine) inputTextMultiline("##source", text, Vec2(-Float.MIN_VALUE, textLineHeight * 16), flags) } treeNode("Filtered Text Input") { inputText("default", bufs[0]) - inputText("decimal", bufs[1], Itf.CharsDecimal.i) + inputText("decimal", bufs[1], Itf.CharsDecimal) inputText("hexadecimal", bufs[2], Itf.CharsHexadecimal or Itf.CharsUppercase) - inputText("uppercase", bufs[3], Itf.CharsUppercase.i) - inputText("no blank", bufs[4], Itf.CharsNoBlank.i) - inputText("\"imgui\" letters", bufs[5], Itf.CallbackCharFilter.i, TextFilters.filterImGuiLetters) + inputText("uppercase", bufs[3], Itf.CharsUppercase) + inputText("no blank", bufs[4], Itf.CharsNoBlank) + inputText("casing swap", bufs[5], Itf.CallbackCharFilter, TextFilters.filterCasingSwap) // Use CharFilter callback to replace characters. + inputText("\"imgui\"", bufs[6], Itf.CallbackCharFilter, TextFilters.filterImGuiLetters) } treeNode("Password Input") { - inputText("password", password, Itf.Password.i) + inputText("password", password, Itf.Password) sameLine(); helpMarker("Display all characters as '*'.\nDisable clipboard cut and copy.\nDisable logging.") - inputTextWithHint("password (w/ hint)", "", password, Itf.Password.i) + inputTextWithHint("password (w/ hint)", "", password, Itf.Password) inputText("password (clear)", password) } treeNode("Completion, History, Edit Callbacks") { - inputText("Completion", buf1, Itf.CallbackCompletion.i, Funcs1.myCallback) + inputText("Completion", buf1, Itf.CallbackCompletion, Funcs1.myCallback) sameLine(); helpMarker("Here we append \"..\" each time Tab is pressed. See 'Examples>Console' for a more meaningful demonstration of using this callback.") - inputText("History", buf2, Itf.CallbackHistory.i, Funcs1.myCallback) + inputText("History", buf2, Itf.CallbackHistory, Funcs1.myCallback) sameLine(); helpMarker("Here we replace and select text each time Up/Down are pressed. See 'Examples>Console' for a more meaningful demonstration of using this callback.") - inputText("Edit", buf3, Itf.CallbackEdit.i, Funcs1.myCallback, ::editCount) + inputText("Edit", buf3, Itf.CallbackEdit, Funcs1.myCallback, ::editCount) sameLine(); helpMarker("Here we toggle the casing of the first character on every edit + count edits.") sameLine(); text("($editCount)") } @@ -1063,7 +1122,7 @@ object ShowDemoWindowWidgets { // than usually reported by a typical string class. if (myStr.isEmpty()) myStr = ByteArray(1) - Funcs2.MyInputTextMultiline("##MyStr", myStr, Vec2(-Float.MIN_VALUE, textLineHeight * 16), 0) + Funcs2.MyInputTextMultiline("##MyStr", myStr, Vec2(-Float.MIN_VALUE, textLineHeight * 16), none) text("Data: ${myStr.hashCode()}\nSize: ${myStr.strlen()}\nCapacity: ${myStr.size}") } } @@ -1071,7 +1130,7 @@ object ShowDemoWindowWidgets { } object Tabs { - var tabBarFlags1 = TabBarFlag.Reorderable.i + var tabBarFlags1: TabBarFlags = TabBarFlag.Reorderable val opened = BooleanArray(4) { true } // Persistent user state val activeTabs = ArrayList() var nextTabId = 0 @@ -1082,8 +1141,7 @@ object ShowDemoWindowWidgets { // Tabs treeNode("Tabs") { treeNode("Basic") { - val tabBarFlags = TabBarFlag.None.i - tabBar("MyTabBar", tabBarFlags) { + tabBar("MyTabBar") { tabItem("Avocado") { text("This is the Avocado tab!\nblah blah blah blah blah") } @@ -1099,16 +1157,16 @@ object ShowDemoWindowWidgets { treeNode("Advanced & Close Button") { // Expose a couple of the available flags. In most cases you may just call BeginTabBar() with no flags (0). - checkboxFlags("ImGuiTabBarFlags_Reorderable", ::tabBarFlags1, TabBarFlag.Reorderable.i) - checkboxFlags("ImGuiTabBarFlags_AutoSelectNewTabs", ::tabBarFlags1, TabBarFlag.AutoSelectNewTabs.i) - checkboxFlags("ImGuiTabBarFlags_TabListPopupButton", ::tabBarFlags1, TabBarFlag.TabListPopupButton.i) - checkboxFlags("ImGuiTabBarFlags_NoCloseWithMiddleMouseButton", ::tabBarFlags1, TabBarFlag.NoCloseWithMiddleMouseButton.i) - if (tabBarFlags1 hasnt TabBarFlag.FittingPolicyMask_) - tabBarFlags1 = tabBarFlags1 or TabBarFlag.FittingPolicyDefault_ - if (checkboxFlags("ImGuiTabBarFlags_FittingPolicyResizeDown", ::tabBarFlags1, TabBarFlag.FittingPolicyResizeDown.i)) - tabBarFlags1 = tabBarFlags1 wo (TabBarFlag.FittingPolicyMask_ xor TabBarFlag.FittingPolicyResizeDown) - if (checkboxFlags("ImGuiTabBarFlags_FittingPolicyScroll", ::tabBarFlags1, TabBarFlag.FittingPolicyScroll.i)) - tabBarFlags1 = tabBarFlags1 wo (TabBarFlag.FittingPolicyMask_ xor TabBarFlag.FittingPolicyScroll) + checkboxFlags("ImGuiTabBarFlags_Reorderable", ::tabBarFlags1, TabBarFlag.Reorderable) + checkboxFlags("ImGuiTabBarFlags_AutoSelectNewTabs", ::tabBarFlags1, TabBarFlag.AutoSelectNewTabs) + checkboxFlags("ImGuiTabBarFlags_TabListPopupButton", ::tabBarFlags1, TabBarFlag.TabListPopupButton) + checkboxFlags("ImGuiTabBarFlags_NoCloseWithMiddleMouseButton", ::tabBarFlags1, TabBarFlag.NoCloseWithMiddleMouseButton) + if (tabBarFlags1 hasnt TabBarFlag.FittingPolicyMask) + tabBarFlags1 = tabBarFlags1 or TabBarFlag.FittingPolicyDefault + if (checkboxFlags("ImGuiTabBarFlags_FittingPolicyResizeDown", ::tabBarFlags1, TabBarFlag.FittingPolicyResizeDown)) + tabBarFlags1 = tabBarFlags1 wo (TabBarFlag.FittingPolicyMask xor TabBarFlag.FittingPolicyResizeDown) + if (checkboxFlags("ImGuiTabBarFlags_FittingPolicyScroll", ::tabBarFlags1, TabBarFlag.FittingPolicyScroll)) + tabBarFlags1 = tabBarFlags1 wo (TabBarFlag.FittingPolicyMask xor TabBarFlag.FittingPolicyScroll) // Tab Bar val names = listOf("Artichoke", "Beetroot", "Celery", "Daikon") @@ -1122,7 +1180,7 @@ object ShowDemoWindowWidgets { tabBar("MyTabBar", tabBarFlags1) { for (n in opened.indices) if (opened[n]) - tabItem(names[n], opened, n, TabItemFlag.None.i) { + tabItem(names[n], opened mutablePropertyAt n) { text("This is the ${names[n]} tab!") if (n has 1) text("I am an odd tab.") @@ -1143,11 +1201,11 @@ object ShowDemoWindowWidgets { checkbox("Show Trailing TabItemButton()", ::showTrailingButton) // Expose some other flags which are useful to showcase how they interact with Leading/Trailing tabs - checkboxFlags("ImGuiTabBarFlags_TabListPopupButton", ::tabBarFlags2, TabBarFlag.TabListPopupButton.i) - if (checkboxFlags("ImGuiTabBarFlags_FittingPolicyResizeDown", ::tabBarFlags2, TabBarFlag.FittingPolicyResizeDown.i)) - tabBarFlags2 = tabBarFlags2 wo (TabBarFlag.FittingPolicyMask_ xor TabBarFlag.FittingPolicyResizeDown) - if (checkboxFlags("ImGuiTabBarFlags_FittingPolicyScroll", ::tabBarFlags2, TabBarFlag.FittingPolicyScroll.i)) - tabBarFlags2 = tabBarFlags2 wo (TabBarFlag.FittingPolicyMask_ xor TabBarFlag.FittingPolicyScroll) + checkboxFlags("ImGuiTabBarFlags_TabListPopupButton", ::tabBarFlags2, TabBarFlag.TabListPopupButton) + if (checkboxFlags("ImGuiTabBarFlags_FittingPolicyResizeDown", ::tabBarFlags2, TabBarFlag.FittingPolicyResizeDown)) + tabBarFlags2 = tabBarFlags2 wo (TabBarFlag.FittingPolicyMask xor TabBarFlag.FittingPolicyResizeDown) + if (checkboxFlags("ImGuiTabBarFlags_FittingPolicyScroll", ::tabBarFlags2, TabBarFlag.FittingPolicyScroll)) + tabBarFlags2 = tabBarFlags2 wo (TabBarFlag.FittingPolicyMask xor TabBarFlag.FittingPolicyScroll) tabBar("MyTabBar", tabBarFlags2) { // Demo a Leading TabItemButton(): click the "?" button to open a menu @@ -1167,17 +1225,14 @@ object ShowDemoWindowWidgets { // Submit our regular tabs var n = 0 while (n < activeTabs.size) { - var open = true + val openRef = true.mutableReference + val open by openRef val name = "%04d".format(activeTabs[n]) - _b = open - tabItem(name, ::_b, TabItemFlag.None.i) { + tabItem(name, openRef) { text("This is the $name tab!") } - open = _b - if (!open) - activeTabs.clear() - else - n++ + if (!open) activeTabs.clear() + else n++ } } separator() @@ -1233,15 +1288,18 @@ object ShowDemoWindowWidgets { } // Use functions to generate output - // FIXME: This is rather awkward because current plot API only pass in indices. + // FIXME: This is actually VERY awkward because current plot API only pass in indices. // We probably want an API passing floats and user provide sample rate/count. - separator() + separatorText("Functions") withItemWidth(fontSize * 8) { combo("func", ::funcType, "Sin\u0000Saw\u0000") } sameLine() - sliderInt("Sample count", ::displayCount, 1, 400) - val func = if (funcType == 0) Funcs3::sin else Funcs3::saw - plotLines("Lines", func, displayCount, 0, "", -1f, 1f, Vec2(0, 80)) - plotHistogram("Histogram", func, displayCount, 0, "", -1f, 1f, Vec2(0, 80)) + slider("Sample count", ::displayCount, 1, 400) + plotLines("Lines", displayCount, 0, "", -1f, 1f, Vec2(0, 80)) { + if (funcType == 0) Funcs3.sin(it) else Funcs3.saw(it) + } + plotHistogram("Histogram", displayCount, 0, "", -1f, 1f, Vec2(0, 80)) { + if (funcType == 0) Funcs3.sin(it) else Funcs3.saw(it) + } separator() // Animate a simple progress bar @@ -1279,17 +1337,19 @@ object ShowDemoWindowWidgets { operator fun invoke() { treeNode("Color/Picker Widgets") { + separatorText("Options") checkbox("With Alpha Preview", ::alphaPreview) checkbox("With Half Alpha Preview", ::alphaHalfPreview) checkbox("With Drag and Drop", ::dragAndDrop) checkbox("With Options Menu", ::optionsMenu); sameLine(); helpMarker("Right-click on the individual color widget to show options.") checkbox("With HDR", ::hdr); sameLine(); helpMarker("Currently all this does is to lift the 0..1 limits on dragging widgets.") - var miscFlags = if (hdr) Cef.HDR.i else Cef.None.i + var miscFlags = if (hdr) Cef.HDR else none if (!dragAndDrop) miscFlags = miscFlags or Cef.NoDragDrop if (alphaHalfPreview) miscFlags = miscFlags or Cef.AlphaPreviewHalf else if (alphaPreview) miscFlags = miscFlags or Cef.AlphaPreview if (!optionsMenu) miscFlags = miscFlags or Cef.NoOptions + separatorText("Inline color editor") text("Color widget:") sameLine(); helpMarker(""" Click on the color square to open a color picker. @@ -1306,14 +1366,14 @@ object ShowDemoWindowWidgets { text("Color button with Picker:") sameLine(); helpMarker( "With the ImGuiColorEditFlags_NoInputs flag you can hide all the slider/text inputs.\n" + - "With the ImGuiColorEditFlags_NoLabel flag you can pass a non-empty label which will only " + - "be used for the tooltip and picker popup.") + "With the ImGuiColorEditFlags_NoLabel flag you can pass a non-empty label which will only " + + "be used for the tooltip and picker popup.") colorEdit4("MyColor##3", color, Cef.NoInputs or Cef.NoLabel or miscFlags) text("Color button with Custom Picker Popup:") if (savedPaletteInit) savedPalette.forEachIndexed { n, c -> - colorConvertHSVtoRGB(n / 31f, 0.8f, 0.8f, c::x, c::y, c::z) + colorConvertHSVtoRGB(n / 31f, 0.8f, 0.8f, c) savedPalette[n].w = 1f // Alpha } savedPaletteInit = false @@ -1365,9 +1425,9 @@ object ShowDemoWindowWidgets { } text("Color button only:") checkbox("ImGuiColorEditFlags_NoBorder", ::noBorder) - colorButton("MyColor##3c", color, miscFlags or if (noBorder) Cef.NoBorder else Cef.None, Vec2(80)) + colorButton("MyColor##3c", color, miscFlags or if (noBorder) Cef.NoBorder else none, Vec2(80)) - text("Color picker:") + separatorText("Color picker") checkbox("With Alpha", ::alpha) checkbox("With Alpha Bar", ::alphaBar) checkbox("With Side Preview", ::sidePreview) @@ -1382,26 +1442,26 @@ object ShowDemoWindowWidgets { combo("Display Mode", ::displayMode, "Auto/Current\u0000None\u0000RGB Only\u0000HSV Only\u0000Hex Only\u0000") sameLine(); helpMarker( "ColorEdit defaults to displaying RGB inputs if you don't specify a display mode, " + - "but the user can change it with a right-click on those inputs.\n\nColorPicker defaults to displaying RGB+HSV+Hex " + - "if you don't specify a display mode.\n\nYou can change the defaults using SetColorEditOptions().") + "but the user can change it with a right-click on those inputs.\n\nColorPicker defaults to displaying RGB+HSV+Hex " + + "if you don't specify a display mode.\n\nYou can change the defaults using SetColorEditOptions().") sameLine(); helpMarker("When not specified explicitly (Auto/Current mode), user can right-click the picker to change mode.") var flags = miscFlags - if (!alpha) flags = flags or Cef.NoAlpha // This is by default if you call ColorPicker3() instead of ColorPicker4() - if (alphaBar) flags = flags or Cef.AlphaBar - if (!sidePreview) flags = flags or Cef.NoSidePreview - if (pickerMode == 1) flags = flags or Cef.PickerHueBar - if (pickerMode == 2) flags = flags or Cef.PickerHueWheel - if (displayMode == 1) flags = flags or Cef.NoInputs // Disable all RGB/HSV/Hex displays - if (displayMode == 2) flags = flags or Cef.DisplayRGB // Override display mode - if (displayMode == 3) flags = flags or Cef.DisplayHSV - if (displayMode == 4) flags = flags or Cef.DisplayHEX + if (!alpha) flags /= Cef.NoAlpha // This is by default if you call ColorPicker3() instead of ColorPicker4() + if (alphaBar) flags /= Cef.AlphaBar + if (!sidePreview) flags /= Cef.NoSidePreview + if (pickerMode == 1) flags /= Cef.PickerHueBar + if (pickerMode == 2) flags /= Cef.PickerHueWheel + if (displayMode == 1) flags /= Cef.NoInputs // Disable all RGB/HSV/Hex displays + if (displayMode == 2) flags /= Cef.DisplayRGB // Override display mode + if (displayMode == 3) flags /= Cef.DisplayHSV + if (displayMode == 4) flags /= Cef.DisplayHEX colorPicker4("MyColor##4", color, flags, refColorV.takeIf { refColor }) text("Set defaults in code:") sameLine(); helpMarker("SetColorEditOptions() is designed to allow you to set boot-time default.\n" + - "We don't have Push/Pop functions because you can force options on a per-widget basis if needed," + - "and the user can change non-forced ones with the options menu.\nWe don't have a getter to avoid" + - "encouraging you to persistently save values that aren't forward-compatible.") + "We don't have Push/Pop functions because you can force options on a per-widget basis if needed," + + "and the user can change non-forced ones with the options menu.\nWe don't have a getter to avoid" + + "encouraging you to persistently save values that aren't forward-compatible.") if (button("Default: Uint8 + HSV + Hue Bar")) setColorEditOptions(Cef.Uint8 or Cef.DisplayHSV or Cef.PickerHueBar) if (button("Default: Float + HDR + Hue Wheel")) @@ -1421,18 +1481,18 @@ object ShowDemoWindowWidgets { text("HSV encoded colors") sameLine(); helpMarker( "By default, colors are given to ColorEdit and ColorPicker in RGB, but ImGuiColorEditFlags_InputHSV" + - "allows you to store colors as HSV and pass them to ColorEdit and ColorPicker as HSV. This comes with the" + - "added benefit that you can manipulate hue values with the picker even when saturation or value are zero.") + "allows you to store colors as HSV and pass them to ColorEdit and ColorPicker as HSV. This comes with the" + + "added benefit that you can manipulate hue values with the picker even when saturation or value are zero.") text("Color widget with InputHSV:") colorEdit4("HSV shown as RGB##1", colorHsv, Cef.DisplayRGB or Cef.InputHSV or Cef.Float) colorEdit4("HSV shown as HSV##1", colorHsv, Cef.DisplayHSV or Cef.InputHSV or Cef.Float) - dragVec4("Raw HSV values", colorHsv, 0.01f, 0f, 1f) + drag4("Raw HSV values", colorHsv, 0.01f, 0f, 1f) } } } object `DragSlider Flags` { - var flags = SliderFlag.None.i + var flags: SliderFlags = none var dragF = 0.5f var dragI = 50 var sliderF = 0.5f @@ -1440,27 +1500,27 @@ object ShowDemoWindowWidgets { operator fun invoke() { treeNode("Drag/Slider Flags") { // Demonstrate using advanced flags for DragXXX and SliderXXX functions. Note that the flags are the same! - checkboxFlags("ImGuiSliderFlags_AlwaysClamp", ::flags, SliderFlag.AlwaysClamp.i) + checkboxFlags("ImGuiSliderFlags_AlwaysClamp", ::flags, SliderFlag.AlwaysClamp) sameLine(); helpMarker("Always clamp value to min/max bounds (if any) when input manually with CTRL+Click.") - checkboxFlags("ImGuiSliderFlags_Logarithmic", ::flags, SliderFlag.Logarithmic.i) + checkboxFlags("ImGuiSliderFlags_Logarithmic", ::flags, SliderFlag.Logarithmic) sameLine(); helpMarker("Enable logarithmic editing (more precision for small values).") - checkboxFlags("ImGuiSliderFlags_NoRoundToFormat", ::flags, SliderFlag.NoRoundToFormat.i) + checkboxFlags("ImGuiSliderFlags_NoRoundToFormat", ::flags, SliderFlag.NoRoundToFormat) sameLine(); helpMarker("Disable rounding underlying value to match precision of the format string (e.g. %.3f values are rounded to those 3 digits).") - checkboxFlags("ImGuiSliderFlags_NoInput", ::flags, SliderFlag.NoInput.i) + checkboxFlags("ImGuiSliderFlags_NoInput", ::flags, SliderFlag.NoInput) sameLine(); helpMarker("Disable CTRL+Click or Enter key allowing to input text directly into the widget.") // Drags text("Underlying float value: %f", dragF) - dragFloat("DragFloat (0 -> 1)", ::dragF, 0.005f, 0f, 1f, "%.3f", flags) - dragFloat("DragFloat (0 -> +inf)", ::dragF, 0.005f, 0f, Float.MAX_VALUE, "%.3f", flags) - dragFloat("DragFloat (-inf -> 1)", ::dragF, 0.005f, -Float.MAX_VALUE, 1f, "%.3f", flags) - dragFloat("DragFloat (-inf -> +inf)", ::dragF, 0.005f, -Float.MAX_VALUE, +Float.MAX_VALUE, "%.3f", flags) - dragInt("DragInt (0 -> 100)", ::dragI, 0.5f, 0, 100, "%d", flags) + drag("DragFloat (0 -> 1)", ::dragF, 0.005f, 0f, 1f, "%.3f", flags) + drag("DragFloat (0 -> +inf)", ::dragF, 0.005f, 0f, Float.MAX_VALUE, "%.3f", flags) + drag("DragFloat (-inf -> 1)", ::dragF, 0.005f, -Float.MAX_VALUE, 1f, "%.3f", flags) + drag("DragFloat (-inf -> +inf)", ::dragF, 0.005f, -Float.MAX_VALUE, +Float.MAX_VALUE, "%.3f", flags) + drag("DragInt (0 -> 100)", ::dragI, 0.5f, 0, 100, "%d", flags) // Sliders text("Underlying float value: %f", sliderF) - sliderFloat("SliderFloat (0 -> 1)", ::sliderF, 0f, 1f, "%.3f", flags) - sliderInt("SliderInt (0 -> 100)", ::sliderI, 0, 100, "%d", flags) + slider("SliderFloat (0 -> 1)", ::sliderF, 0f, 1f, "%.3f", flags) + slider("SliderInt (0 -> 100)", ::sliderI, 0, 100, "%d", flags) } } } @@ -1472,9 +1532,9 @@ object ShowDemoWindowWidgets { var endI = 1000 operator fun invoke() { treeNode("Range Widgets") { - dragFloatRange2("range float", ::begin, ::end, 0.25f, 0f, 100f, "Min: %.1f %%", "Max: %.1f %%", SliderFlag.AlwaysClamp.i) - dragIntRange2("range int", ::beginI, ::endI, 5f, 0, 1000, "Min: %d units", "Max: %d units") - dragIntRange2("range int (no bounds)", ::beginI, ::endI, 5f, 0, 0, "Min: %d units", "Max: %d units") + dragRange("range float", ::begin, ::end, 0.25f, 0f, 100f, "Min: %.1f %%", "Max: %.1f %%", SliderFlag.AlwaysClamp) + dragRange("range int", ::beginI, ::endI, 5f, 0, 1000, "Min: %d units", "Max: %d units") + dragRange("range int (no bounds)", ::beginI, ::endI, 5f, 0, 0, "Min: %d units", "Max: %d units") } } } @@ -1513,132 +1573,132 @@ object ShowDemoWindowWidgets { // Note: SliderScalar() functions have a maximum usable range of half the natural type maximum, hence the /2. // @formatter:off - var s8_zero: Byte = 0.b - var s8_one: Byte = 1.b - var s8_fifty: Byte = 50.b - var s8_min: Byte = (-128).b - var s8_max: Byte = 127.b - var u8_zero: Ubyte = Ubyte(0) - var u8_one: Ubyte = Ubyte(1) - var u8_fifty: Ubyte = Ubyte(50) - var u8_min: Ubyte = Ubyte(0) - var u8_max: Ubyte = Ubyte(255) - var s16_zero: Short = 0.s - var s16_one: Short = 1.s - var s16_fifty: Short = 50.s - var s16_min: Short = (-32768).s - var s16_max: Short = 32767.s - var u16_zero: Ushort = Ushort(0) - var u16_one = Ushort(1) - var u16_fifty: Ushort = Ushort(50) - var u16_min: Ushort = Ushort(0) - var u16_max: Ushort = Ushort(65535) - var s32_zero: Int = 0 - var s32_one: Int = 1 - var s32_fifty: Int = 50 - var s32_min: Int = Int.MIN_VALUE / 2 - var s32_max: Int = Int.MAX_VALUE / 2 - var s32_hi_a = Int.MAX_VALUE / 2 - 100 - var s32_hi_b = Int.MAX_VALUE / 2 - var u32_zero: Uint = Uint(0) - var u32_one: Uint = Uint(1) - var u32_fifty: Uint = Uint(50) - var u32_min: Uint = Uint(0) - var u32_max: Uint = Uint.MAX / 2 - var u32_hi_a = Uint.MAX / 2 - 100 - var u32_hi_b: Uint = Uint.MAX / 2 - var s64_zero: Long = 0L - var s64_one: Long = 1L - var s64_fifty: Long = 50L - var s64_min: Long = Long.MIN_VALUE / 2 - var s64_max: Long = Long.MAX_VALUE / 2 - var s64_hi_a: Long = Long.MAX_VALUE / 2 - 100 - var s64_hi_b: Long = Long.MAX_VALUE / 2 - var u64_zero: Ulong = Ulong(0) - var u64_one: Ulong = Ulong(1) - var u64_fifty: Ulong = Ulong(50) - var u64_min: Ulong = Ulong(0) - var u64_max: Ulong = Ulong.MAX / 2 - var u64_hi_a: Ulong = Ulong.MAX / 2 - 100 - var u64_hi_b: Ulong = Ulong.MAX / 2 - var f32_zero: Float = 0f - var f32_one: Float = 1f - var f32_lo_a: Float = -10_000_000_000f - var f32_hi_a: Float = +10_000_000_000f - var f64_zero: Double = 0.0 - var f64_one: Double = 1.0 - var f64_lo_a: Double = -1_000_000_000_000_000.0 - var f64_hi_a: Double = +1_000_000_000_000_000.0 + val s8_zero: Byte = 0.b + val s8_one: Byte = 1.b + val s8_fifty: Byte = 50.b + val s8_min: Byte = (-128).b + val s8_max: Byte = 127.b + val u8_zero: Ubyte = Ubyte(0) + val u8_one: Ubyte = Ubyte(1) + val u8_fifty: Ubyte = Ubyte(50) + val u8_min: Ubyte = Ubyte(0) + val u8_max: Ubyte = Ubyte(255) + val s16_zero: Short = 0.s + val s16_one: Short = 1.s + val s16_fifty: Short = 50.s + val s16_min: Short = (-32768).s + val s16_max: Short = 32767.s + val u16_zero: Ushort = Ushort(0) + val u16_one = Ushort(1) + val u16_fifty: Ushort = Ushort(50) + val u16_min: Ushort = Ushort(0) + val u16_max: Ushort = Ushort(65535) + val s32_zero: Int = 0 + val s32_one: Int = 1 + val s32_fifty: Int = 50 + val s32_min: Int = Int.MIN_VALUE / 2 + val s32_max: Int = Int.MAX_VALUE / 2 + val s32_hi_a = Int.MAX_VALUE / 2 - 100 + val s32_hi_b = Int.MAX_VALUE / 2 + val u32_zero: Uint = Uint(0) + val u32_one: Uint = Uint(1) + val u32_fifty: Uint = Uint(50) + val u32_min: Uint = Uint(0) + val u32_max: Uint = Uint.MAX / 2 + val u32_hi_a = Uint.MAX / 2 - 100 + val u32_hi_b: Uint = Uint.MAX / 2 + val s64_zero: Long = 0L + val s64_one: Long = 1L + val s64_fifty: Long = 50L + val s64_min: Long = Long.MIN_VALUE / 2 + val s64_max: Long = Long.MAX_VALUE / 2 + val s64_hi_a: Long = Long.MAX_VALUE / 2 - 100 + val s64_hi_b: Long = Long.MAX_VALUE / 2 + val u64_zero: Ulong = Ulong(0) + val u64_one: Ulong = Ulong(1) + val u64_fifty: Ulong = Ulong(50) + val u64_min: Ulong = Ulong(0) + val u64_max: Ulong = Ulong.MAX / 2 + val u64_hi_a: Ulong = Ulong.MAX / 2 - 100 + val u64_hi_b: Ulong = Ulong.MAX / 2 + val f32_zero: Float = 0f + val f32_one: Float = 1f + val f32_lo_a: Float = -10_000_000_000f + val f32_hi_a: Float = +10_000_000_000f + val f64_zero: Double = 0.0 + val f64_one: Double = 1.0 + val f64_lo_a: Double = -1_000_000_000_000_000.0 + val f64_hi_a: Double = +1_000_000_000_000_000.0 operator fun invoke() { treeNode("Data Types") { val dragSpeed = 0.2f - text("Drags:") + separatorText("Drags") checkbox("Clamp integers to 0..50", ::dragClamp) sameLine(); helpMarker( """As with every widget in dear imgui, we never modify values unless there is a user interaction. You can override the clamping limits by using CTRL+Click to input a value.""".trimIndent()) - dragScalar("drag s8", DataType.Byte, ::s8_v, dragSpeed, ::s8_zero.takeIf { dragClamp }, ::s8_fifty.takeIf { dragClamp }) - dragScalar("drag u8", DataType.Ubyte, ::u8_v, dragSpeed, ::u8_zero.takeIf { dragClamp }, ::u8_fifty.takeIf { dragClamp }, "%d ms") - dragScalar("drag s16", DataType.Short, ::s16_v, dragSpeed, ::s16_zero.takeIf { dragClamp }, ::s16_fifty.takeIf { dragClamp }) - dragScalar("drag u16", DataType.Ushort, ::u16_v, dragSpeed, ::u16_zero.takeIf { dragClamp }, ::u16_fifty.takeIf { dragClamp }, "%d ms") - dragScalar("drag s32", DataType.Int, ::s32_v, dragSpeed, ::s32_zero.takeIf { dragClamp }, ::s32_fifty.takeIf { dragClamp }) - dragScalar("drag s32 hex", DataType.Int, ::s32_v, dragSpeed, ::s32_zero.takeIf { dragClamp }, ::s32_fifty.takeIf { dragClamp }, "0x%08X") - dragScalar("drag u32", DataType.Uint, ::u32_v, dragSpeed, ::u32_zero.takeIf { dragClamp }, ::u32_fifty.takeIf { dragClamp }, "%d ms") - dragScalar("drag s64", DataType.Long, ::s64_v, dragSpeed, ::s64_zero.takeIf { dragClamp }, ::s64_fifty.takeIf { dragClamp }) - dragScalar("drag u64", DataType.Ulong, ::u64_v, dragSpeed, ::u64_zero.takeIf { dragClamp }, ::u64_fifty.takeIf { dragClamp }) - dragScalar("drag float", DataType.Float, ::f32_v, 0.005f, ::f32_zero, ::f32_one, "%f") - dragScalar("drag float log", DataType.Float, ::f32_v, 0.005f, ::f32_zero, ::f32_one, "%f", SliderFlag.Logarithmic.i) - dragScalar("drag double", DataType.Double, ::f64_v, 0.0005f, ::f64_zero, null, "%.10f grams") - dragScalar("drag double log", DataType.Double, ::f64_v, 0.0005f, ::f64_zero, ::f64_one, "0 < %.10f < 1", SliderFlag.Logarithmic.i) - - text("Sliders") - sliderScalar("slider s8 full", DataType.Byte, ::s8_v, ::s8_min, ::s8_max, "%d") - sliderScalar("slider u8 full", DataType.Ubyte, ::u8_v, ::u8_min, ::u8_max, "%d") - sliderScalar("slider s16 full", DataType.Short, ::s16_v, ::s16_min, ::s16_max, "%d") - sliderScalar("slider u16 full", DataType.Ushort, ::u16_v, ::u16_min, ::u16_max, "%d") - sliderScalar("slider s32 low", DataType.Int, ::s32_v, ::s32_zero, ::s32_fifty, "%d") - sliderScalar("slider s32 high", DataType.Int, ::s32_v, ::s32_hi_a, ::s32_hi_b, "%d") - sliderScalar("slider s32 full", DataType.Int, ::s32_v, ::s32_min, ::s32_max, "%d") - sliderScalar("slider s32 hex", DataType.Int, ::s32_v, ::s32_zero, ::s32_fifty, "0x%04X") - sliderScalar("slider u32 low", DataType.Uint, ::u32_v, ::u32_zero, ::u32_fifty, "%d") - sliderScalar("slider u32 high", DataType.Uint, ::u32_v, ::u32_hi_a, ::u32_hi_b, "%d") - sliderScalar("slider u32 full", DataType.Uint, ::u32_v, ::u32_min, ::u32_max, "%d") - sliderScalar("slider s64 low", DataType.Long, ::s64_v, ::s64_zero, ::s64_fifty, "%d") - sliderScalar("slider s64 high", DataType.Long, ::s64_v, ::s64_hi_a, ::s64_hi_b, "%d") - sliderScalar("slider s64 full", DataType.Long, ::s64_v, ::s64_min, ::s64_max, "%d") - sliderScalar("slider u64 low", DataType.Ulong, ::u64_v, ::u64_zero, ::u64_fifty, "%d ms") - sliderScalar("slider u64 high", DataType.Ulong, ::u64_v, ::u64_hi_a, ::u64_hi_b, "%d ms") - sliderScalar("slider u64 full", DataType.Ulong, ::u64_v, ::u64_min, ::u64_max, "%d ms") - sliderScalar("slider float low", DataType.Float, ::f32_v, ::f32_zero, ::f32_one) - sliderScalar("slider float low log", DataType.Float, ::f32_v, ::f32_zero, ::f32_one, "%.10f", SliderFlag.Logarithmic.i) - sliderScalar("slider float high", DataType.Float, ::f32_v, ::f32_lo_a, ::f32_hi_a, "%e") - sliderScalar("slider double low", DataType.Double, ::f64_v, ::f64_zero, ::f64_one, "%.10f grams") - sliderScalar("slider double low log", DataType.Double, ::f64_v, ::f64_zero, ::f64_one, "%.10f", SliderFlag.Logarithmic.i) - sliderScalar("slider double high", DataType.Double, ::f64_v, ::f64_lo_a, ::f64_hi_a, "%e grams") - - text("Sliders (reverse)") - sliderScalar("slider s8 reverse", DataType.Byte, ::s8_v, ::s8_max, ::s8_min, "%d") - sliderScalar("slider u8 reverse", DataType.Ubyte, ::u8_v, ::u8_max, ::u8_min, "%d") // [JVM] %u -> %d - sliderScalar("slider s32 reverse", DataType.Int, ::s32_v, ::s32_fifty, ::s32_zero, "%d") - sliderScalar("slider u32 reverse", DataType.Uint, ::u32_v, ::u32_fifty, ::u32_zero, "%s") // [JVM] %u -> %d - sliderScalar("slider s64 reverse", DataType.Long, ::s64_v, ::s64_fifty, ::s64_zero, "%d") // [JVM] %I64d -> %d - sliderScalar("slider u64 reverse", DataType.Ulong, ::u64_v, ::u64_fifty, ::u64_zero, "%d ms") // [JVM] %I64u -> %d - - text("Inputs") + drag("drag s8", ::s8_v, dragSpeed, s8_zero.takeIf { dragClamp }, s8_fifty.takeIf { dragClamp }) + drag("drag u8", ::u8_v, dragSpeed, u8_zero.takeIf { dragClamp }, u8_fifty.takeIf { dragClamp }, "%d ms") + drag("drag s16", ::s16_v, dragSpeed, s16_zero.takeIf { dragClamp }, s16_fifty.takeIf { dragClamp }) + drag("drag u16", ::u16_v, dragSpeed, u16_zero.takeIf { dragClamp }, u16_fifty.takeIf { dragClamp }, "%d ms") + drag("drag s32", ::s32_v, dragSpeed, s32_zero.takeIf { dragClamp }, s32_fifty.takeIf { dragClamp }) + drag("drag s32 hex", ::s32_v, dragSpeed, s32_zero.takeIf { dragClamp }, s32_fifty.takeIf { dragClamp }, "0x%08X") + drag("drag u32", ::u32_v, dragSpeed, u32_zero.takeIf { dragClamp }, u32_fifty.takeIf { dragClamp }, "%d ms") + drag("drag s64", ::s64_v, dragSpeed, s64_zero.takeIf { dragClamp }, s64_fifty.takeIf { dragClamp }) + drag("drag u64", ::u64_v, dragSpeed, u64_zero.takeIf { dragClamp }, u64_fifty.takeIf { dragClamp }) + drag("drag float", ::f32_v, 0.005f, f32_zero, f32_one, "%f") + drag("drag float log", ::f32_v, 0.005f, f32_zero, f32_one, "%f", SliderFlag.Logarithmic) + drag("drag double", ::f64_v, 0.0005f, f64_zero, null, "%.10f grams") + drag("drag double log", ::f64_v, 0.0005f, f64_zero, f64_one, "0 < %.10f < 1", SliderFlag.Logarithmic) + + separatorText("Sliders") + slider("slider s8 full", ::s8_v, s8_min, s8_max, "%d") + slider("slider u8 full", ::u8_v, u8_min, u8_max, "%d") + slider("slider s16 full", ::s16_v, s16_min, s16_max, "%d") + slider("slider u16 full", ::u16_v, u16_min, u16_max, "%d") + slider("slider s32 low", ::s32_v, s32_zero, s32_fifty, "%d") + slider("slider s32 high", ::s32_v, s32_hi_a, s32_hi_b, "%d") + slider("slider s32 full", ::s32_v, s32_min, s32_max, "%d") + slider("slider s32 hex", ::s32_v, s32_zero, s32_fifty, "0x%04X") + slider("slider u32 low", ::u32_v, u32_zero, u32_fifty, "%d") + slider("slider u32 high", ::u32_v, u32_hi_a, u32_hi_b, "%d") + slider("slider u32 full", ::u32_v, u32_min, u32_max, "%d") + slider("slider s64 low", ::s64_v, s64_zero, s64_fifty, "%d") + slider("slider s64 high", ::s64_v, s64_hi_a, s64_hi_b, "%d") + slider("slider s64 full", ::s64_v, s64_min, s64_max, "%d") + slider("slider u64 low", ::u64_v, u64_zero, u64_fifty, "%d ms") + slider("slider u64 high", ::u64_v, u64_hi_a, u64_hi_b, "%d ms") + slider("slider u64 full",::u64_v, u64_min, u64_max, "%d ms") + slider("slider float low", ::f32_v, f32_zero, f32_one) + slider("slider float low log",::f32_v, f32_zero, f32_one, "%.10f", SliderFlag.Logarithmic) + slider("slider float high", ::f32_v, f32_lo_a, f32_hi_a, "%e") + slider("slider double low", ::f64_v, f64_zero, f64_one, "%.10f grams") + slider("slider double low log", ::f64_v, f64_zero, f64_one, "%.10f", SliderFlag.Logarithmic) + slider("slider double high", ::f64_v, f64_lo_a, f64_hi_a, "%e grams") + + separatorText("Sliders (reverse)") + slider("slider s8 reverse", ::s8_v, s8_max, s8_min, "%d") + slider("slider u8 reverse", ::u8_v, u8_max, u8_min, "%d") // [JVM] %u -> %d + slider("slider s32 reverse", ::s32_v, s32_fifty, s32_zero, "%d") + slider("slider u32 reverse", ::u32_v, u32_fifty, u32_zero, "%s") // [JVM] %u -> %d + slider("slider s64 reverse", ::s64_v, s64_fifty, s64_zero, "%d") // [JVM] %I64d -> %d + slider("slider u64 reverse", ::u64_v, u64_fifty, u64_zero, "%d ms") // [JVM] %I64u -> %d + + separatorText("Inputs") checkbox("Show step buttons", ::inputsStep) - inputScalar("input s8", DataType.Byte, ::s8_v, s8_one.takeIf { inputsStep }, null, "%d") - inputScalar("input u8", DataType.Ubyte, ::u8_v, u8_one.takeIf { inputsStep }, null, "%d") - inputScalar("input s16", DataType.Short, ::s16_v, s16_one.takeIf { inputsStep }, null, "%d") - inputScalar("input u16", DataType.Ushort, ::u16_v, u16_one.takeIf { inputsStep }, null, "%d") - inputScalar("input s32", DataType.Int, ::s32_v, s32_one.takeIf { inputsStep }, null, "%d") - inputScalar("input s32 hex", DataType.Int, ::s32_v, s32_one.takeIf { inputsStep }, null, "%04X") - inputScalar("input u32", DataType.Uint, ::u32_v, u32_one.takeIf { inputsStep }, null, "%d") - inputScalar("input u32 hex", DataType.Uint, ::u32_v, u32_one.takeIf { inputsStep }, null, "%04X") - inputScalar("input s64", DataType.Long, ::s64_v, s64_one.takeIf { inputsStep }) - inputScalar("input u64", DataType.Ulong, ::u64_v, u64_one.takeIf { inputsStep }) - inputScalar("input float", DataType.Float, ::f32_v, f32_one.takeIf { inputsStep }) - inputScalar("input double", DataType.Double, ::f64_v, f64_one.takeIf { inputsStep }) + input("input s8", ::s8_v, s8_one.takeIf { inputsStep }, null, "%d") + input("input u8", ::u8_v, u8_one.takeIf { inputsStep }, null, "%d") + input("input s16", ::s16_v, s16_one.takeIf { inputsStep }, null, "%d") + input("input u16", ::u16_v, u16_one.takeIf { inputsStep }, null, "%d") + input("input s32", ::s32_v, s32_one.takeIf { inputsStep }, null, "%d") + input("input s32 hex", ::s32_v, s32_one.takeIf { inputsStep }, null, "%04X") + input("input u32", ::u32_v, u32_one.takeIf { inputsStep }, null, "%d") + input("input u32 hex", ::u32_v, u32_one.takeIf { inputsStep }, null, "%04X") + input("input s64", ::s64_v, s64_one.takeIf { inputsStep }) + input("input u64", ::u64_v, u64_one.takeIf { inputsStep }) + input("input float", ::f32_v, f32_one.takeIf { inputsStep }) + input("input double", ::f64_v, f64_one.takeIf { inputsStep }) // @formatter:on } } @@ -1650,28 +1710,29 @@ object ShowDemoWindowWidgets { operator fun invoke() { treeNode("Multi-component Widgets") { - inputFloat2("input float2", vec4f) - dragFloat2("drag float2", vec4f, 0.01f, 0f, 1f) - sliderFloat2("slider float2", vec4f, 0f, 1f) - inputInt2("input int2", vec4i) - dragInt2("drag int2", vec4i, 1f, 0, 255) - sliderInt2("slider int2", vec4i, 0, 255) - spacing() - - inputFloat3("input float3", vec4f) - dragFloat3("drag float3", vec4f, 0.01f, 0.0f, 1.0f) - sliderFloat3("slider float3", vec4f, 0.0f, 1.0f) - inputInt3("input int3", vec4i) - dragInt3("drag int3", vec4i, 1f, 0, 255) - sliderInt3("slider int3", vec4i, 0, 255) - spacing() - - inputFloat4("input float4", vec4f) - dragFloat4("drag float4", vec4f, 0.01f, 0.0f, 1.0f) - sliderFloat4("slider float4", vec4f, 0.0f, 1.0f) - inputInt4("input int4", vec4i) - dragInt4("drag int4", vec4i, 1f, 0, 255) - sliderInt4("slider int4", vec4i, 0, 255) + separatorText("2-wide") + input2("input float2", vec4f) + drag2("drag float2", vec4f, 0.01f, 0f, 1f) + slider2("slider float2", vec4f, 0f, 1f) + input2("input int2", vec4i) + drag2("drag int2", vec4i, 1f, 0, 255) + slider2("slider int2", vec4i, 0, 255) + + separatorText("3-wide") + input3("input float3", vec4f) + drag3("drag float3", vec4f, 0.01f, 0.0f, 1.0f) + slider3("slider float3", vec4f, 0.0f, 1.0f) + input3("input int3", vec4i) + drag3("drag int3", vec4i, 1f, 0, 255) + slider3("slider int3", vec4i, 0, 255) + + separatorText("4-wide") + input4("input float4", vec4f) + drag4("drag float4", vec4f, 0.01f, 0.0f, 1.0f) + slider4("slider float4", vec4f, 0.0f, 1.0f) + input4("input int4", vec4i) + drag4("drag int4", vec4i, 1f, 0, 255) + slider4("slider int4", vec4i, 0, 255) } } } @@ -1686,7 +1747,7 @@ object ShowDemoWindowWidgets { withStyleVar(StyleVar.ItemSpacing, Vec2(spacing)) { - vSliderInt("##int", Vec2(18, 160), ::intValue, 0, 5) + vSlider("##int", Vec2(18, 160), ::intValue, 0, 5) sameLine() withID("set1") { @@ -1699,7 +1760,7 @@ object ShowDemoWindowWidgets { Col.FrameBgActive, Color.hsv(i / 7f, 0.7f, 0.5f), Col.SliderGrab, Color.hsv(i / 7f, 0.9f, 0.9f)) { - withFloat(values, i) { vSliderFloat("##v", Vec2(18, 160), it, 0f, 1f, "") } + vSlider("##v", Vec2(18, 160), values mutablePropertyAt i, 0f, 1f, "") if (isItemActive || isItemHovered()) setTooltip("%.3f", values[i]) } } @@ -1715,9 +1776,7 @@ object ShowDemoWindowWidgets { group { for (ny in 0 until rows) { withID(nx * rows + ny) { - withFloat(values2, nx) { f -> - vSliderFloat("##v", smallSliderSize, f, 0f, 1f, "") - } + vSlider("##v", smallSliderSize, values2 mutablePropertyAt nx, 0f, 1f, "") if (isItemActive || isItemHovered()) setTooltip("%.3f", values2[nx]) } @@ -1732,9 +1791,7 @@ object ShowDemoWindowWidgets { if (i > 0) sameLine() withID(i) { withStyleVar(StyleVar.GrabMinSize, 40f) { - withFloat(values, i) { - vSliderFloat("##v", Vec2(40, 160), it, 0f, 1f, "%.2f\nsec") - } + vSlider("##v", Vec2(40, 160), values mutablePropertyAt i, 0f, 1f, "%.2f\nsec") } } } @@ -1745,16 +1802,13 @@ object ShowDemoWindowWidgets { } object `Drag and Drop` { - val col1 = floatArrayOf(1f, 0f, 0.2f) - val col2 = floatArrayOf(0.4f, 0.7f, 0f, 0.5f) + val col1 = Vec3(1f, 0f, 0.2f) + val col2 = Vec4(0.4f, 0.7f, 0f, 0.5f) enum class Mode { Copy, Move, Swap } var mode = Mode.Copy - val names = arrayOf( - "Bobby", "Beatrice", "Betty", - "Brianna", "Barry", "Bernard", - "Bibi", "Blaine", "Bryn") + val names = arrayOf("Bobby", "Beatrice", "Betty", "Brianna", "Barry", "Bernard", "Bibi", "Blaine", "Bryn") val itemNames = arrayOf("Item One", "Item Two", "Item Three", "Item Four", "Item Five") operator fun invoke() { treeNode("Drag and Drop") { @@ -1780,7 +1834,7 @@ object ShowDemoWindowWidgets { button(name, Vec2(60)) // Our buttons are both drag sources and drag targets here! - if (beginDragDropSource(DragDropFlag.None)) { + if (beginDragDropSource()) { // Set payload to carry the index of our item (could be anything) setDragDropPayload("DND_DEMO_CELL", n) @@ -1803,6 +1857,7 @@ object ShowDemoWindowWidgets { names[n] = names[payloadN] names[payloadN] = "" } + Mode.Swap -> { val tmp = names[n] names[n] = names[payloadN] @@ -1820,7 +1875,7 @@ object ShowDemoWindowWidgets { // Simple reordering helpMarker( "We don't use the drag and drop api at all here! " + - "Instead we query when the item is held but not hovered, and order items accordingly.") + "Instead we query when the item is held but not hovered, and order items accordingly.") itemNames.forEachIndexed { n, item -> selectable(item) @@ -1838,16 +1893,16 @@ object ShowDemoWindowWidgets { } } - object `Querying Item Status (Edited,Active,Focused,Hovered etc)` { + object `Querying Item Status (Edited,Active,Hovered etc)` { var itemType = 1 var itemDisabled = false var b0 = false - val col = floatArrayOf(1f, 0.5f, 0f, 1f) + val col = Vec4(1f, 0.5f, 0f, 1f) val str = ByteArray(16) var current1 = 1 var current2 = 1 operator fun invoke() { - treeNode("Querying Item Status (Edited/Active/Focused/Hovered etc.)") { + treeNode("Querying Item Status (Edited/Active/Hovered etc.)") { // Select an item type val itemNames = arrayOf( "Text", "Button", "Button (w/ repeat)", "Checkbox", "SliderFloat", "InputText", "InputTextMultiline", "InputFloat", @@ -1865,11 +1920,11 @@ object ShowDemoWindowWidgets { 1 -> button("ITEM: Button") // Testing button 2 -> withButtonRepeat(true) { button("ITEM: Button") } // Testing button (with repeater) 3 -> checkbox("ITEM: Checkbox", ::b0) // Testing checkbox - 4 -> sliderFloat("ITEM: SliderFloat", col, 0, 0f, 1f) // Testing basic item + 4 -> slider("ITEM: SliderFloat", col::x, 0f, 1f) // Testing basic item 5 -> inputText("ITEM: InputText", str) // Testing input text (which handles tabbing) 6 -> inputTextMultiline("ITEM: InputTextMultiline", str) // Testing input text (which uses a child window) - 7 -> inputFloat("ITEM: InputFloat", col, 1f) // Testing +/- buttons on scalar input - 8 -> inputFloat3("ITEM: InputFloat3", col) // Testing multi-component items (IsItemXXX flags are reported merged) + 7 -> input("ITEM: InputFloat", col::x, 1f) // Testing +/- buttons on scalar input + 8 -> input3("ITEM: InputFloat3", col) // Testing multi-component items (IsItemXXX flags are reported merged) 9 -> colorEdit4("ITEM: ColorEdit4", col) // Testing multi-component items (IsItemXXX flags are reported merged) 10 -> selectable("ITEM: Selectable") // Testing selectable item 11 -> menuItem("ITEM: MenuItem") // Testing menu item (they use ImGuiButtonFlags_PressedOnRelease button policy) @@ -1881,8 +1936,10 @@ object ShowDemoWindowWidgets { } val hoveredDelayNone = ImGui.isItemHovered() + val hoveredDelayStationary = ImGui.isItemHovered(HoveredFlag.Stationary) val hoveredDelayShort = ImGui.isItemHovered(HoveredFlag.DelayShort) val hoveredDelayNormal = ImGui.isItemHovered(HoveredFlag.DelayNormal) + val hoveredDelayTooltip = ImGui.isItemHovered(HoveredFlag.ForTooltip) // = Normal + Stationary // Display the values of IsItemHovered() and other common item state functions. // Note that the ImGuiHoveredFlags_XXX flags can be combined. @@ -1894,6 +1951,8 @@ object ShowDemoWindowWidgets { isItemHovered() = ${isItemHovered().i} isItemHovered(AllowWhenBlockedByPopup) = ${isItemHovered(HoveredFlag.AllowWhenBlockedByPopup).i} isItemHovered(AllowWhenBlockedByActiveItem) = ${isItemHovered(HoveredFlag.AllowWhenBlockedByActiveItem).i} + IsItemHovered(_AllowWhenOverlappedByItem) = ${isItemHovered(HoveredFlag.AllowWhenOverlappedByItem).i} + IsItemHovered(_AllowWhenOverlappedByWindow) = ${isItemHovered(HoveredFlag.AllowWhenOverlappedByWindow).i} isItemHovered(AllowWhenOverlapped) = ${isItemHovered(HoveredFlag.AllowWhenOverlapped).i} isItemHovered(RectOnly) = ${isItemHovered(HoveredFlag.RectOnly).i} isItemActive = ${isItemActive.i} @@ -1907,13 +1966,18 @@ object ShowDemoWindowWidgets { GetItemRectMin() = (%.1f, %.1f) GetItemRectMax() = (%.1f, %.1f) GetItemRectSize() = (%.1f, %.1f)""".trimIndent(), itemRectMin.x, itemRectMin.y, itemRectMax.x, itemRectMax.y, itemRectSize.x, itemRectSize.y) - bulletText("w/ Hovering Delay: None = ${hoveredDelayNone.i}, Fast ${hoveredDelayShort.i}, Normal = ${hoveredDelayNormal.i}") + bulletText("with Hovering Delay or Stationary test:\n" + + "IsItemHovered() = = ${hoveredDelayNone.i}\n" + + "IsItemHovered(_Stationary) = ${hoveredDelayStationary.i}\n" + + "IsItemHovered(_DelayShort) = ${hoveredDelayShort.i}\n" + + "IsItemHovered(_DelayNormal) = ${hoveredDelayNormal.i}\n" + + "IsItemHovered(_Tooltip) = ${hoveredDelayTooltip.i}") if (itemDisabled) endDisabled() val buf = ByteArray(1) - inputText("unused", buf, Itf.ReadOnly.i) + inputText("unused", buf, Itf.ReadOnly) sameLine() helpMarker("This widget is only here to be able to tab-out of the widgets above and see e.g. Deactivated() status.") } @@ -1932,26 +1996,27 @@ object ShowDemoWindowWidgets { // Testing IsWindowFocused() function with its various flags. bulletText("isWindowFocused() = ${isWindowFocused().i}\n" + - "isWindowFocused(_ChildWindows) = ${isWindowFocused(FocusedFlag.ChildWindows).i}\n" + - "IsWindowFocused(_ChildWindows|_NoPopupHierarchy) = ${isWindowFocused(FocusedFlag.ChildWindows or FocusedFlag.NoPopupHierarchy).i}\n" + - "isWindowFocused(_ChildWindows|_RootWindow) = ${isWindowFocused(FocusedFlag.ChildWindows or FocusedFlag.RootWindow).i}\n" + - "IsWindowFocused(_ChildWindows|_RootWindow|_NoPopupHierarchy) = ${isWindowFocused(FocusedFlag.ChildWindows or FocusedFlag.RootWindow or FocusedFlag.NoPopupHierarchy).i}\n" + - "isWindowFocused(_RootWindow) = ${isWindowFocused(FocusedFlag.RootWindow).i}\n" + - "IsWindowFocused(_RootWindow|_NoPopupHierarchy) = ${isWindowFocused(FocusedFlag.RootWindow or FocusedFlag.NoPopupHierarchy).i}\n" + - "isWindowFocused(_AnyWindow) = ${isWindowFocused(FocusedFlag.AnyWindow).i}\n") + "isWindowFocused(_ChildWindows) = ${isWindowFocused(FocusedFlag.ChildWindows).i}\n" + + "IsWindowFocused(_ChildWindows|_NoPopupHierarchy) = ${isWindowFocused(FocusedFlag.ChildWindows or FocusedFlag.NoPopupHierarchy).i}\n" + + "isWindowFocused(_ChildWindows|_RootWindow) = ${isWindowFocused(FocusedFlag.ChildWindows or FocusedFlag.RootWindow).i}\n" + + "IsWindowFocused(_ChildWindows|_RootWindow|_NoPopupHierarchy) = ${isWindowFocused(FocusedFlag.ChildWindows or FocusedFlag.RootWindow or FocusedFlag.NoPopupHierarchy).i}\n" + + "isWindowFocused(_RootWindow) = ${isWindowFocused(FocusedFlag.RootWindow).i}\n" + + "IsWindowFocused(_RootWindow|_NoPopupHierarchy) = ${isWindowFocused(FocusedFlag.RootWindow or FocusedFlag.NoPopupHierarchy).i}\n" + + "isWindowFocused(_AnyWindow) = ${isWindowFocused(FocusedFlag.AnyWindow).i}\n") // Testing IsWindowHovered() function with its various flags. bulletText("isWindowHovered() = ${isWindowHovered()}\n" + - "isWindowHovered(_AllowWhenBlockedByPopup) = ${isWindowHovered(HoveredFlag.AllowWhenBlockedByPopup).i}\n" + - "isWindowHovered(_AllowWhenBlockedByActiveItem) = ${isWindowHovered(HoveredFlag.AllowWhenBlockedByActiveItem).i}\n" + - "isWindowHovered(_ChildWindows) = ${isWindowHovered(HoveredFlag.ChildWindows).i}\n" + - "IsWindowHovered(_ChildWindows|_NoPopupHierarchy) = ${isWindowHovered(HoveredFlag.ChildWindows or HoveredFlag.NoPopupHierarchy).i}\n" + - "isWindowHovered(_ChildWindows|_RootWindow) = ${isWindowHovered(HoveredFlag.ChildWindows or HoveredFlag.RootWindow).i}\n" + - "isWindowHovered(_ChildWindows|_RootWindow|_NoPopupHierarchy) = ${isWindowHovered(HoveredFlag.ChildWindows or HoveredFlag.RootWindow or HoveredFlag.NoPopupHierarchy).i}\n" + - "isWindowHovered(_RootWindow) = ${isWindowHovered(HoveredFlag.RootWindow).i}\n" + - "IsWindowHovered(_RootWindow|_NoPopupHierarchy) = ${isWindowHovered(HoveredFlag.RootWindow or HoveredFlag.NoPopupHierarchy).i}\n" + - "IsWindowHovered(_ChildWindows|_AllowWhenBlockedByPopup) = ${isWindowHovered(HoveredFlag.ChildWindows or HoveredFlag.AllowWhenBlockedByPopup).i}\n" + - "isWindowHovered(_AnyWindow) = ${isWindowHovered(HoveredFlag.AnyWindow).i}\n") + "isWindowHovered(_AllowWhenBlockedByPopup) = ${isWindowHovered(HoveredFlag.AllowWhenBlockedByPopup).i}\n" + + "isWindowHovered(_AllowWhenBlockedByActiveItem) = ${isWindowHovered(HoveredFlag.AllowWhenBlockedByActiveItem).i}\n" + + "isWindowHovered(_ChildWindows) = ${isWindowHovered(HoveredFlag.ChildWindows).i}\n" + + "IsWindowHovered(_ChildWindows|_NoPopupHierarchy) = ${isWindowHovered(HoveredFlag.ChildWindows or HoveredFlag.NoPopupHierarchy).i}\n" + + "isWindowHovered(_ChildWindows|_RootWindow) = ${isWindowHovered(HoveredFlag.ChildWindows or HoveredFlag.RootWindow).i}\n" + + "isWindowHovered(_ChildWindows|_RootWindow|_NoPopupHierarchy) = ${isWindowHovered(HoveredFlag.ChildWindows or HoveredFlag.RootWindow or HoveredFlag.NoPopupHierarchy).i}\n" + + "isWindowHovered(_RootWindow) = ${isWindowHovered(HoveredFlag.RootWindow).i}\n" + + "IsWindowHovered(_RootWindow|_NoPopupHierarchy) = ${isWindowHovered(HoveredFlag.RootWindow or HoveredFlag.NoPopupHierarchy).i}\n" + + "IsWindowHovered(_ChildWindows|_AllowWhenBlockedByPopup) = ${isWindowHovered(HoveredFlag.ChildWindows or HoveredFlag.AllowWhenBlockedByPopup).i}\n" + + "isWindowHovered(_AnyWindow) = ${isWindowHovered(HoveredFlag.AnyWindow).i}\n" + + "IsWindowHovered(_Stationary) = ${isWindowHovered(HoveredFlag.Stationary).i}\n") beginChild("child", Vec2(0, 50), true) text("This is another child window for testing the _ChildWindows flag.") @@ -1970,7 +2035,7 @@ object ShowDemoWindowWidgets { } text( "IsItemHovered() after begin = ${isItemHovered()} (== is title bar hovered)\n" + - "IsItemActive() after begin = $isItemActive (== is window being clicked/moved)") + "IsItemActive() after begin = $isItemActive (== is window being clicked/moved)") end() } } @@ -1990,7 +2055,7 @@ object ShowDemoWindowWidgets { | "" display all lines | "xxx" display lines containing "xxx" | "xxx,yyy" display lines containing "xxx" or "yyy" - | "-xxx" hide lines containing "xxx"""") + | "-xxx" hide lines containing "xxx"""".trimMargin()) filter.draw() val lines = listOf("aaa1.c", "bbb1.c", "ccc1.c", "aaa2.cpp", "bbb2.cpp", "ccc2.cpp", "abc.h", "hello, world") for (line in lines) diff --git a/core/src/main/kotlin/imgui/demo/showExampleApp/AutoResize.kt b/core/src/main/kotlin/imgui/demo/showExampleApp/AutoResize.kt index afcb3df4d..4aef49abf 100644 --- a/core/src/main/kotlin/imgui/demo/showExampleApp/AutoResize.kt +++ b/core/src/main/kotlin/imgui/demo/showExampleApp/AutoResize.kt @@ -2,8 +2,9 @@ package imgui.demo.showExampleApp import imgui.ImGui.begin import imgui.ImGui.end -import imgui.ImGui.sliderInt import imgui.ImGui.text +import imgui.api.slider +import imgui.mutablePropertyAt import kotlin.reflect.KMutableProperty0 import imgui.WindowFlag as Wf @@ -14,7 +15,7 @@ object AutoResize { /** Demonstrate creating a window which gets auto-resized according to its content. */ operator fun invoke(open: KMutableProperty0) { - if (!begin("Example: Auto-resizing window", open, Wf.AlwaysAutoResize.i)) { + if (!begin("Example: Auto-resizing window", open, Wf.AlwaysAutoResize)) { end() return } @@ -23,7 +24,7 @@ object AutoResize { Window will resize every-frame to the size of its content. Note that you probably don't want to query the window size to output your content because that would create a feedback loop.""".trimIndent()) - sliderInt("Number of lines", lines, 0, 1, 20) + slider("Number of lines", lines mutablePropertyAt 0, 1, 20) for (i in 0 until lines[0]) text(" ".repeat(i * 4) + "This is line $i") // Pad with space to extend size horizontally end() diff --git a/core/src/main/kotlin/imgui/demo/showExampleApp/Console.kt b/core/src/main/kotlin/imgui/demo/showExampleApp/Console.kt index 3a775ba5c..1eae4f0d3 100644 --- a/core/src/main/kotlin/imgui/demo/showExampleApp/Console.kt +++ b/core/src/main/kotlin/imgui/demo/showExampleApp/Console.kt @@ -41,7 +41,6 @@ import imgui.classes.TextFilter import imgui.dsl.popupContextItem import uno.kotlin.getValue import uno.kotlin.setValue -import java.util.* import kotlin.reflect.KMutableProperty0 import imgui.InputTextFlag as Itf import imgui.WindowFlag as Wf @@ -93,14 +92,13 @@ object Console { // Here we create a context menu only available from the title bar. popupContextItem { if (menuItem("Close Console")) open = false } - textWrapped( - "This example implements a console with basic coloring, completion (TAB key) and history (Up/Down keys). A more elaborate " + textWrapped("This example implements a console with basic coloring, completion (TAB key) and history (Up/Down keys). A more elaborate " + "implementation may want to store entries along with extra data such as timestamp, emitter, etc.") textWrapped("Enter 'HELP' for help.") if (smallButton("Add Debug Text")) { addLog("%d some text", - items.size); addLog("some more text"); addLog("display very important message here!"); } + items.size); addLog("some more text"); addLog("display very important message here!"); } sameLine() if (smallButton("Add Debug Error")) addLog("[error] something went wrong") sameLine() @@ -125,7 +123,7 @@ object Console { // Reserve enough left-over height for 1 separator + 1 input text val footerHeightToReserve = style.itemSpacing.y + frameHeightWithSpacing - if(beginChild("ScrollingRegion", Vec2(0, -footerHeightToReserve), false, Wf.HorizontalScrollbar.i)) { + if (beginChild("ScrollingRegion", Vec2(0, -footerHeightToReserve), false, Wf.HorizontalScrollbar)) { if (beginPopupContextWindow()) { if (selectable("Clear")) clearLog() endPopup() @@ -232,7 +230,7 @@ object Console { fun inputTextCallback(data: InputTextCallbackData): Boolean { when (data.eventFlag) { - imgui.InputTextFlag.CallbackCompletion.i -> { + Itf.CallbackCompletion -> { val wordEnd = data.cursorPos var wordStart = wordEnd while (wordStart > 0) { @@ -245,8 +243,8 @@ object Console { val candidates = ArrayList() for (c in commands) if (c.startsWith(word)) candidates += c when { // No match - candidates.isEmpty() -> addLog("No match for \"%s\"!\n", - word) // Single match. Delete the beginning of the word and replace it entirely so we've got nice casing. + candidates.isEmpty() -> addLog("No match for \"%s\"!\n", word) + // Single match. Delete the beginning of the word and replace it entirely so we've got nice casing. candidates.size == 1 -> { // Single match. Delete the beginning of the word and replace it entirely so we've got nice casing. data.deleteChars(wordStart, wordEnd) data.insertChars(data.cursorPos, candidates[0]) @@ -278,7 +276,8 @@ object Console { } } } - Itf.CallbackHistory.i -> { + + Itf.CallbackHistory -> { val prevHistoryPos = historyPos if (data.eventKey == Key.UpArrow) { if (historyPos == -1) historyPos = history.size - 1 diff --git a/core/src/main/kotlin/imgui/demo/showExampleApp/ConstrainedResize.kt b/core/src/main/kotlin/imgui/demo/showExampleApp/ConstrainedResize.kt index a5b851bed..07c8bc9bb 100644 --- a/core/src/main/kotlin/imgui/demo/showExampleApp/ConstrainedResize.kt +++ b/core/src/main/kotlin/imgui/demo/showExampleApp/ConstrainedResize.kt @@ -11,7 +11,6 @@ import imgui.ImGui.button import imgui.ImGui.checkbox import imgui.ImGui.colorButton import imgui.ImGui.combo -import imgui.ImGui.dragInt import imgui.ImGui.end import imgui.ImGui.io import imgui.ImGui.popStyleVar @@ -21,6 +20,7 @@ import imgui.ImGui.setNextItemWidth import imgui.ImGui.setNextWindowSizeConstraints import imgui.ImGui.setWindowSize import imgui.ImGui.text +import imgui.api.drag import imgui.classes.SizeCallbackData import kotlin.reflect.KMutableProperty0 @@ -38,14 +38,13 @@ object ConstrainedResize { var windowPadding = true var type = 5 // Aspect Ratio var displayLines = 10 - val testDesc = listOf( - "Resize vertical only", - "Resize horizontal only", - "Width > 100, Height > 100", - "Width 400-500", - "Height 400-500", - "Custom: Always Square", - "Custom: Fixed Steps (100)") + val testDesc = listOf("Resize vertical only", + "Resize horizontal only", + "Width > 100, Height > 100", + "Width 400-500", + "Height 400-500", + "Custom: Always Square", + "Custom: Fixed Steps (100)") object CustomConstraints { // Helper functions to demonstrate programmatic constraints @@ -90,8 +89,8 @@ object ConstrainedResize { // Submit window if (!windowPadding) pushStyleVar(StyleVar.WindowPadding, Vec2()) - val windowFlags = if (autoResize) WindowFlag.AlwaysAutoResize else WindowFlag.None - val windowOpen = begin("Example: Constrained Resize", pOpen, windowFlags.i) + val windowFlags = if (autoResize) WindowFlag.AlwaysAutoResize else none + val windowOpen = begin("Example: Constrained Resize", pOpen, windowFlags) if (!windowPadding) popStyleVar() if (windowOpen) { @@ -110,7 +109,7 @@ object ConstrainedResize { setNextItemWidth(ImGui.fontSize * 20) combo("Constraint", ::type, testDesc) setNextItemWidth(ImGui.fontSize * 20) - dragInt("Lines", ::displayLines, 0.2f, 1, 100) + drag("Lines", ::displayLines, 0.2f, 1, 100) checkbox("Auto-resize", ::autoResize) checkbox("Window padding", ::windowPadding) for (i in 0 until displayLines) diff --git a/core/src/main/kotlin/imgui/demo/showExampleApp/CustomRendering.kt b/core/src/main/kotlin/imgui/demo/showExampleApp/CustomRendering.kt index c493379f5..4228baf19 100644 --- a/core/src/main/kotlin/imgui/demo/showExampleApp/CustomRendering.kt +++ b/core/src/main/kotlin/imgui/demo/showExampleApp/CustomRendering.kt @@ -11,7 +11,6 @@ import imgui.ImGui.calcItemWidth import imgui.ImGui.checkbox import imgui.ImGui.colorEdit4 import imgui.ImGui.cursorScreenPos -import imgui.ImGui.dragFloat import imgui.ImGui.dummy import imgui.ImGui.end import imgui.ImGui.endTabBar @@ -23,18 +22,21 @@ import imgui.ImGui.getColorU32 import imgui.ImGui.getMouseDragDelta import imgui.ImGui.invisibleButton import imgui.ImGui.io -import imgui.ImGui.isMouseReleased +import imgui.ImGui.isClicked +import imgui.ImGui.isDown +import imgui.ImGui.isDragging import imgui.ImGui.openPopupOnItemClick import imgui.ImGui.popItemWidth import imgui.ImGui.pushItemWidth import imgui.ImGui.sameLine -import imgui.ImGui.sliderInt import imgui.ImGui.style import imgui.ImGui.text import imgui.ImGui.windowDrawList import imgui.ImGui.windowPos import imgui.ImGui.windowSize import imgui.api.demoDebugInformations.Companion.helpMarker +import imgui.api.drag +import imgui.api.slider import imgui.dsl.menuItem import imgui.internal.sections.ButtonFlag import imgui.internal.sections.DrawFlag @@ -104,15 +106,15 @@ object CustomRendering { // Draw a bunch of primitives text("All primitives") - dragFloat("Size", ::sz, 0.2f, 2f, 100f, "%.0f") - dragFloat("Thickness", ::thickness, 0.05f, 1f, 8f, "%.02f") - sliderInt("N-gon sides", ::ngonSides, 3, 12) + drag("Size", ::sz, 0.2f, 2f, 100f, "%.0f") + drag("Thickness", ::thickness, 0.05f, 1f, 8f, "%.02f") + slider("N-gon sides", ::ngonSides, 3, 12) checkbox("##circlesegmentoverride", ::circleSegmentsOverride) sameLine(0f, style.itemInnerSpacing.x) - circleSegmentsOverride = sliderInt("Circle segments override", ::circleSegmentsOverrideV, 3, 40) or circleSegmentsOverride + circleSegmentsOverride = slider("Circle segments override", ::circleSegmentsOverrideV, 3, 40) or circleSegmentsOverride checkbox("##curvessegmentoverride", ::curveSegmentsOverride) sameLine(0f, style.itemInnerSpacing.x) - curveSegmentsOverride = sliderInt("Curves segments override", ::curveSegmentsOverrideV, 3, 40) or curveSegmentsOverride + curveSegmentsOverride = slider("Curves segments override", ::curveSegmentsOverrideV, 3, 40) or curveSegmentsOverride colorEdit4("Color", colf) val p = cursorScreenPos @@ -129,8 +131,8 @@ object CustomRendering { drawList.apply { addNgon(Vec2(x + sz * 0.5f, y + sz * 0.5f), sz * 0.5f, col, ngonSides, th); x += sz + spacing // N-gon addCircle(Vec2(x + sz * 0.5f, y + sz * 0.5f), sz * 0.5f, col, circleSegments, th); x += sz + spacing // Circle - addRect(Vec2(x, y), Vec2(x + sz, y + sz), col, 0f, DrawFlag.None.i, th); x += sz + spacing // Square - addRect(Vec2(x, y), Vec2(x + sz, y + sz), col, rounding, DrawFlag.None.i, th); x += sz + spacing // Square with all rounded corners + addRect(Vec2(x, y), Vec2(x + sz, y + sz), col, 0f, thickness = th); x += sz + spacing // Square + addRect(Vec2(x, y), Vec2(x + sz, y + sz), col, rounding, thickness = th); x += sz + spacing // Square with all rounded corners addRect(Vec2(x, y), Vec2(x + sz, y + sz), col, rounding, cornersTlBr, th); x += sz + spacing // Square with two rounded corners addTriangle(Vec2(x + sz * 0.5f, y), Vec2(x + sz, y + sz - 0.5f), Vec2(x, y + sz - 0.5f), col, th); x += sz + spacing // Triangle // addTriangle(Vec2(x + sz * 0.2f, y), Vec2(x, y + sz - 0.5f), Vec2(x + sz * 0.4f, y + sz - 0.5f), col, th); x += sz * 0.4f + spacing // Thin triangle @@ -204,14 +206,14 @@ object CustomRendering { val mousePosInCanvas = io.mousePos - origin // Add first and second point - if (isHovered && !addingLine && ImGui.isMouseClicked(MouseButton.Left)) { + if (isHovered && !addingLine && MouseButton.Left.isClicked) { points += mousePosInCanvas // TODO problems with same instance? points += mousePosInCanvas addingLine = true } if (addingLine) { points.last() put mousePosInCanvas - if (!ImGui.isMouseDown(MouseButton.Left)) + if (!MouseButton.Left.isDown) addingLine = false } @@ -219,13 +221,13 @@ object CustomRendering { // Pan (we use a zero mouse threshold when there's no context menu) // You may decide to make that threshold dynamic based on whether the mouse is hovering something etc. val mouseThresholdForPan = if (optEnableContextMenu) -1f else 0f - if (isActive && ImGui.isMouseDragging(MouseButton.Right, mouseThresholdForPan)) + if (isActive && MouseButton.Right.isDragging(mouseThresholdForPan)) scrolling += io.mouseDelta // Context menu (under default mouse threshold) val dragDelta = getMouseDragDelta(MouseButton.Right) if (optEnableContextMenu && dragDelta.x == 0f && dragDelta.y == 0f) // TODO glm - openPopupOnItemClick("context", PopupFlag.MouseButtonRight.i) + openPopupOnItemClick("context", PopupFlag.MouseButtonRight) dsl.popup("context") { if (addingLine) { points.pop() diff --git a/core/src/main/kotlin/imgui/demo/showExampleApp/Documents.kt b/core/src/main/kotlin/imgui/demo/showExampleApp/Documents.kt index e79aff15d..d70d1f6f8 100644 --- a/core/src/main/kotlin/imgui/demo/showExampleApp/Documents.kt +++ b/core/src/main/kotlin/imgui/demo/showExampleApp/Documents.kt @@ -37,6 +37,7 @@ import imgui.ImGui.setTabItemClosed import imgui.ImGui.text import imgui.ImGui.textLineHeightWithSpacing import imgui.ImGui.textWrapped +import imgui.dsl.childFrame import kotlin.reflect.KMutableProperty0 //----------------------------------------------------------------------------- @@ -45,18 +46,20 @@ import kotlin.reflect.KMutableProperty0 // Simplified structure to mimic a Document model class MyDocument( - /** Document title */ - val name: String, - /** Set when open (we keep an array of all available documents to simplify demo code!) */ - var open: Boolean = true, - /** An arbitrary variable associated to the document */ - val color: Vec4 = Vec4(1f) -) { + /** Document title */ + val name: String, + /** Set when open (we keep an array of all available documents to simplify demo code!) */ + var open: Boolean = true, + /** An arbitrary variable associated to the document */ + val color: Vec4 = Vec4(1f) + ) { /** Copy of Open from last update. */ var openPrev = open + /** Set when the document has been modified */ var dirty = false + /** Set when the document */ var wantClose = false @@ -109,13 +112,12 @@ class MyDocument( object Documents { val documents = arrayOf( - MyDocument("Lettuce", true, Vec4(0.4f, 0.8f, 0.4f, 1.0f)), - MyDocument("Eggplant", true, Vec4(0.8f, 0.5f, 1.0f, 1.0f)), - MyDocument("Carrot", true, Vec4(1.0f, 0.8f, 0.5f, 1.0f)), - MyDocument("Tomato", false, Vec4(1.0f, 0.3f, 0.4f, 1.0f)), - MyDocument("A Rather Long Title", false), - MyDocument("Some Document", false) - ) + MyDocument("Lettuce", true, Vec4(0.4f, 0.8f, 0.4f, 1.0f)), + MyDocument("Eggplant", true, Vec4(0.8f, 0.5f, 1.0f, 1.0f)), + MyDocument("Carrot", true, Vec4(1.0f, 0.8f, 0.5f, 1.0f)), + MyDocument("Tomato", false, Vec4(1.0f, 0.3f, 0.4f, 1.0f)), + MyDocument("A Rather Long Title", false), + MyDocument("Some Document", false)) // [Optional] Notify the system of Tabs/Windows closure that happened outside the regular tab interface. // If a tab has been closed programmatically (aka closed from another source such as the Checkbox() in the demo, @@ -135,13 +137,13 @@ object Documents { // Options var optReorderable = true - var optFittingFlags: TabBarFlags = TabBarFlag.FittingPolicyDefault_.i + var optFittingFlags: TabBarFlags = TabBarFlag.FittingPolicyDefault val closeQueue = ArrayList() operator fun invoke(pOpen: KMutableProperty0?) { - val windowContentsVisible = begin("Example: Documents", pOpen, WindowFlag.MenuBar.i) + val windowContentsVisible = begin("Example: Documents", pOpen, WindowFlag.MenuBar) if (!windowContentsVisible) { end() return @@ -197,7 +199,7 @@ object Documents { // Submit Tab Bar and Tabs run { - val tabBarFlags: TabBarFlags = optFittingFlags or if (optReorderable) TabBarFlag.Reorderable else TabBarFlag.None + val tabBarFlags: TabBarFlags = optFittingFlags or if (optReorderable) TabBarFlag.Reorderable else none if (beginTabBar("##tabs", tabBarFlags)) { if (optReorderable) notifyOfDocumentsClosedElsewhere() @@ -211,7 +213,7 @@ object Documents { if (!doc.open) continue - val tabFlags: TabItemFlags = if (doc.dirty) TabItemFlag.UnsavedDocument.i else TabItemFlag.None.i + val tabFlags: TabItemOnlyFlags = if (doc.dirty) TabItemFlag.UnsavedDocument else none val visible = beginTabItem(doc.name, doc::open, tabFlags) // Cancel attempt to close when unsaved add to save queue so we can display a popup. @@ -242,6 +244,7 @@ object Documents { // Display closing confirmation UI if (closeQueue.isNotEmpty()) { + val closeQueueUnsavedDocuments = closeQueue.count { it.dirty } if (closeQueueUnsavedDocuments == 0) { @@ -252,12 +255,14 @@ object Documents { if (!isPopupOpen("Save?")) openPopup("Save?") - if (beginPopupModal("Save?", null, WindowFlag.AlwaysAutoResize.i)) { + if (beginPopupModal("Save?", null, WindowFlag.AlwaysAutoResize)) { text("Save change to the following items?") val itemHeight = textLineHeightWithSpacing - if (beginChildFrame(getID("frame"), Vec2(-Float.MIN_VALUE, 6.25f * itemHeight))) { - closeQueue.forEach { if (it.dirty) text(it.name) } - endChildFrame() + childFrame(getID("frame"), Vec2(-Float.MIN_VALUE, 6.25f * itemHeight)) { + closeQueue.forEach { + if (it.dirty) + text(it.name) + } } val buttonSize = Vec2(fontSize * 7f, 0f) diff --git a/core/src/main/kotlin/imgui/demo/showExampleApp/Fullscreen.kt b/core/src/main/kotlin/imgui/demo/showExampleApp/Fullscreen.kt index 0fccdef48..77c443585 100644 --- a/core/src/main/kotlin/imgui/demo/showExampleApp/Fullscreen.kt +++ b/core/src/main/kotlin/imgui/demo/showExampleApp/Fullscreen.kt @@ -1,10 +1,8 @@ package imgui.demo.showExampleApp -import glm_.vec2.Vec2 import imgui.ImGui.button import imgui.ImGui.checkbox import imgui.ImGui.checkboxFlags -import imgui.ImGui.io import imgui.ImGui.mainViewport import imgui.ImGui.sameLine import imgui.ImGui.setNextWindowPos @@ -28,7 +26,7 @@ object Fullscreen { operator fun invoke(pOpen: KMutableProperty0?) { // We demonstrate using the full viewport area or the work area (without menu-bars, task-bars etc.) - // Based on your use case you may want one of the other. + // Based on your use case you may want one or the other. val viewport = mainViewport setNextWindowPos(if(useWorkArea) viewport.workPos else viewport.pos) setNextWindowSize(if(useWorkArea) viewport.workSize else viewport.size) @@ -39,12 +37,12 @@ object Fullscreen { sameLine() helpMarker("Main Area = entire viewport,\nWork Area = entire viewport minus sections used by the main menu bars, task bars etc.\n\nEnable the main-menu bar in Examples menu to see the difference.") - checkboxFlags("ImGuiWindowFlags_NoBackground", ::flags, Wf.NoBackground.i) - checkboxFlags("ImGuiWindowFlags_NoDecoration", ::flags, Wf.NoDecoration.i) + checkboxFlags("ImGuiWindowFlags_NoBackground", ::flags, Wf.NoBackground) + checkboxFlags("ImGuiWindowFlags_NoDecoration", ::flags, Wf.NoDecoration) indent { - checkboxFlags("ImGuiWindowFlags_NoTitleBar", ::flags, Wf.NoTitleBar.i) - checkboxFlags("ImGuiWindowFlags_NoCollapse", ::flags, Wf.NoCollapse.i) - checkboxFlags("ImGuiWindowFlags_NoScrollbar", ::flags, Wf.NoScrollbar.i) + checkboxFlags("ImGuiWindowFlags_NoTitleBar", ::flags, Wf.NoTitleBar) + checkboxFlags("ImGuiWindowFlags_NoCollapse", ::flags, Wf.NoCollapse) + checkboxFlags("ImGuiWindowFlags_NoScrollbar", ::flags, Wf.NoScrollbar) } if (pOpen != null && button("Close this window")) diff --git a/core/src/main/kotlin/imgui/demo/showExampleApp/Layout.kt b/core/src/main/kotlin/imgui/demo/showExampleApp/Layout.kt index da031ba06..9ee5bb7b0 100644 --- a/core/src/main/kotlin/imgui/demo/showExampleApp/Layout.kt +++ b/core/src/main/kotlin/imgui/demo/showExampleApp/Layout.kt @@ -17,7 +17,6 @@ import imgui.ImGui.separator import imgui.ImGui.setNextWindowSize import imgui.ImGui.text import imgui.ImGui.textWrapped -import imgui.TabBarFlag import imgui.dsl.button import imgui.dsl.child import imgui.dsl.group @@ -39,7 +38,7 @@ object Layout { var open by pOpen setNextWindowSize(Vec2(500, 440), Cond.FirstUseEver) - if (begin("Example: Simple layout", pOpen, Wf.MenuBar.i)) { + if (begin("Example: Simple layout", pOpen, Wf.MenuBar)) { menuBar { menu("File") { menuItem("Close", "Ctrl+W") { open = false } @@ -62,7 +61,7 @@ object Layout { beginChild("item view", Vec2(0, -frameHeightWithSpacing)) // Leave room for 1 line below us text("MyObject: $selected") separator() - if (beginTabBar("##Tabs", TabBarFlag.None.i)) { + if (beginTabBar("##Tabs")) { if (beginTabItem("Description")) { textWrapped("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ") endTabItem() diff --git a/core/src/main/kotlin/imgui/demo/showExampleApp/Log.kt b/core/src/main/kotlin/imgui/demo/showExampleApp/Log.kt index 89794026c..0564b8c59 100644 --- a/core/src/main/kotlin/imgui/demo/showExampleApp/Log.kt +++ b/core/src/main/kotlin/imgui/demo/showExampleApp/Log.kt @@ -26,8 +26,8 @@ import imgui.StyleVar import imgui.api.g import imgui.classes.ListClipper import imgui.classes.TextFilter +import imgui.classes.listClipper import java.util.* -import kotlin.collections.ArrayList import kotlin.math.abs import kotlin.reflect.KMutableProperty0 import imgui.WindowFlag as Wf @@ -116,7 +116,7 @@ object Log { filter.draw("Filter", -100f) separator() - if(beginChild("scrolling", Vec2(0, 0), false, Wf.HorizontalScrollbar.i)) { + if (beginChild("scrolling", Vec2(0, 0), false, Wf.HorizontalScrollbar)) { if (clear) clear() if (copy) logToClipboard() @@ -127,8 +127,8 @@ object Log { // This is because we don't have random access to the result of our filter. // A real application processing logs with ten of thousands of entries may want to store the result of // search/filter.. especially if the filtering function is not trivial (e.g. reg-exp). - for (line_no in 0 until lineOffsets.size) { - val line = buf.subSequence(lineOffsets[line_no], if (line_no + 1 < lineOffsets.size) lineOffsets[line_no + 1] - 1 else buf.length).toString() + for (lineNo in 0 until lineOffsets.size) { + val line = buf.subSequence(lineOffsets[lineNo], if (lineNo + 1 < lineOffsets.size) lineOffsets[lineNo + 1] - 1 else buf.length).toString() if (filter.passFilter(line)) textEx(line) } @@ -146,14 +146,13 @@ object Log { // When using the filter (in the block of code above) we don't have random access into the data to display // anymore, which is why we don't use the clipper. Storing or skimming through the search result would make // it possible (and would be recommended if you want to search through tens of thousands of entries). - val clipper = ListClipper() - while (clipper.step()) - for (lineNo in clipper.display) { + listClipper(lineOffsets.size) { + for (lineNo in it.display) { val lineStart = lineOffsets[lineNo] val lineEnd = if (lineNo + 1 < lineOffsets.size) lineOffsets[lineNo + 1] - 1 else buf.length textEx(buf.subSequence(lineStart, lineEnd).toString()) } - clipper.end() + } } popStyleVar() diff --git a/core/src/main/kotlin/imgui/demo/showExampleApp/LongText.kt b/core/src/main/kotlin/imgui/demo/showExampleApp/LongText.kt index ced4f5281..c6a9a7cb8 100644 --- a/core/src/main/kotlin/imgui/demo/showExampleApp/LongText.kt +++ b/core/src/main/kotlin/imgui/demo/showExampleApp/LongText.kt @@ -16,6 +16,7 @@ import imgui.ImGui.text import imgui.ImGui.textEx import imgui.StyleVar import imgui.classes.ListClipper +import imgui.classes.listClipper import uno.kotlin.NUL import kotlin.reflect.KMutableProperty0 @@ -35,11 +36,10 @@ object LongText { } text("Printing unusually long amount of text.") - combo( - "Test type", ::testType, - "Single call to TextUnformatted()" + NUL + - "Multiple calls to Text(), clipped" + NUL + - "Multiple calls to Text(), not clipped (slow)" + NUL) + combo("Test type", ::testType, + "Single call to TextUnformatted()" + NUL + + "Multiple calls to Text(), clipped" + NUL + + "Multiple calls to Text(), not clipped (slow)" + NUL) text("Buffer contents: %d lines, %d bytes", lines, log.length) if (button("Clear")) log.clear().also { lines = 0 } sameLine() @@ -56,13 +56,11 @@ object LongText { // Multiple calls to Text(), manually coarsely clipped - demonstrate how to use the ImGuiListClipper helper. 1 -> { pushStyleVar(StyleVar.ItemSpacing, Vec2(0)) - val clipper = ListClipper() - clipper.begin(lines) - while (clipper.step()) - for (i in clipper.display) + listClipper(lines) { + for (i in it.display) text("$i The quick brown fox jumps over the lazy dog") + } popStyleVar() - clipper.end() } 2 -> { pushStyleVar(StyleVar.ItemSpacing, Vec2(0, 1)) diff --git a/core/src/main/kotlin/imgui/demo/showExampleApp/MenuFile.kt b/core/src/main/kotlin/imgui/demo/showExampleApp/MenuFile.kt index c9528011c..202cb14e5 100644 --- a/core/src/main/kotlin/imgui/demo/showExampleApp/MenuFile.kt +++ b/core/src/main/kotlin/imgui/demo/showExampleApp/MenuFile.kt @@ -8,14 +8,14 @@ import imgui.ImGui.combo import imgui.ImGui.cursorScreenPos import imgui.ImGui.dummy import imgui.ImGui.endChild -import imgui.ImGui.inputFloat +import imgui.ImGui.input import imgui.ImGui.menuItem import imgui.ImGui.sameLine import imgui.ImGui.separator -import imgui.ImGui.sliderFloat import imgui.ImGui.text import imgui.ImGui.textLineHeight import imgui.ImGui.windowDrawList +import imgui.api.slider import imgui.dsl.menu object MenuFile { @@ -49,11 +49,10 @@ object MenuFile { menu("Options") { menuItem("Enabled", "", ::enabled) beginChild("child", Vec2(0, 60), true) - for (i in 0 until 10) - text("Scrolling Text $i") + for (i in 0 until 10) text("Scrolling Text $i") endChild() - sliderFloat("Value", ::float, 0f, 1f) - inputFloat("Input", ::float, 0.1f) + slider("Value", ::float, 0f, 1f) + input("Input", ::float, 0.1f) combo("Combo", ::combo, "Yes\u0000No\u0000Maybe\u0000\u0000") } @@ -78,6 +77,7 @@ object MenuFile { menu("Disabled", false) { assert(false) { "Disabled" } } menuItem("Checked", selected = true) + separator() menuItem("Quit", "Alt+F4") } } \ No newline at end of file diff --git a/core/src/main/kotlin/imgui/demo/showExampleApp/PropertyEditor.kt b/core/src/main/kotlin/imgui/demo/showExampleApp/PropertyEditor.kt index cf47a0020..8ddf91b64 100644 --- a/core/src/main/kotlin/imgui/demo/showExampleApp/PropertyEditor.kt +++ b/core/src/main/kotlin/imgui/demo/showExampleApp/PropertyEditor.kt @@ -4,10 +4,9 @@ import glm_.vec2.Vec2 import imgui.* import imgui.ImGui.alignTextToFramePadding import imgui.ImGui.begin -import imgui.ImGui.columns -import imgui.ImGui.dragFloat +import imgui.ImGui.drag import imgui.ImGui.end -import imgui.ImGui.inputFloat +import imgui.ImGui.input import imgui.ImGui.nextColumn import imgui.ImGui.popID import imgui.ImGui.popItemWidth @@ -15,7 +14,6 @@ import imgui.ImGui.popStyleVar import imgui.ImGui.pushID import imgui.ImGui.pushItemWidth import imgui.ImGui.pushStyleVar -import imgui.ImGui.separator import imgui.ImGui.setNextWindowSize import imgui.ImGui.tableNextRow import imgui.ImGui.tableSetColumnIndex @@ -24,6 +22,7 @@ import imgui.ImGui.treeNode import imgui.ImGui.treeNodeEx import imgui.ImGui.treePop import imgui.api.demoDebugInformations.Companion.helpMarker +import imgui.api.drag import imgui.dsl.table import kotlin.reflect.KMutableProperty0 import imgui.TreeNodeFlag as Tnf @@ -85,9 +84,9 @@ object PropertyEditor { tableSetColumnIndex(1) pushItemWidth(-Float.MIN_VALUE) if (i >= 5) - inputFloat("##value", placeholderMembers, i, 1f) + input("##value", placeholderMembers mutablePropertyAt i, 1f) else - dragFloat("##value", placeholderMembers, i, 0.01f) + drag("##value", placeholderMembers mutablePropertyAt i, 0.01f) popItemWidth() nextColumn() } diff --git a/core/src/main/kotlin/imgui/demo/showExampleApp/SimpleOverlay.kt b/core/src/main/kotlin/imgui/demo/showExampleApp/SimpleOverlay.kt index a7d512969..4839bdbfa 100644 --- a/core/src/main/kotlin/imgui/demo/showExampleApp/SimpleOverlay.kt +++ b/core/src/main/kotlin/imgui/demo/showExampleApp/SimpleOverlay.kt @@ -1,6 +1,6 @@ package imgui.demo.showExampleApp -import gli_.has +import glm_.has import glm_.vec2.Vec2 import imgui.* import imgui.ImGui.io diff --git a/core/src/main/kotlin/imgui/demo/showExampleApp/StyleEditor.kt b/core/src/main/kotlin/imgui/demo/showExampleApp/StyleEditor.kt index 2cd846fa1..d1b9052ee 100644 --- a/core/src/main/kotlin/imgui/demo/showExampleApp/StyleEditor.kt +++ b/core/src/main/kotlin/imgui/demo/showExampleApp/StyleEditor.kt @@ -1,6 +1,8 @@ +@file:OptIn(ExperimentalStdlibApi::class) + package imgui.demo.showExampleApp -import gli_.hasnt +import glm_.hasnt import glm_.c import glm_.f import glm_.i @@ -13,10 +15,10 @@ import imgui.ImGui.beginTabItem import imgui.ImGui.bulletText import imgui.ImGui.button import imgui.ImGui.checkbox -import imgui.ImGui.colorEditVec4 +import imgui.ImGui.checkboxFlags +import imgui.ImGui.colorEdit4 import imgui.ImGui.combo import imgui.ImGui.cursorScreenPos -import imgui.ImGui.dragFloat import imgui.ImGui.dummy import imgui.ImGui.endTabBar import imgui.ImGui.endTabItem @@ -33,25 +35,29 @@ import imgui.ImGui.pushFont import imgui.ImGui.pushItemWidth import imgui.ImGui.sameLine import imgui.ImGui.separator +import imgui.ImGui.separatorText import imgui.ImGui.setNextItemWidth import imgui.ImGui.setNextWindowPos import imgui.ImGui.setWindowFontScale import imgui.ImGui.showFontAtlas import imgui.ImGui.showFontSelector -import imgui.ImGui.sliderFloat -import imgui.ImGui.sliderVec2 +import imgui.ImGui.slider2 +import imgui.ImGui.smallButton import imgui.ImGui.spacing import imgui.ImGui.style import imgui.ImGui.text import imgui.ImGui.textEx import imgui.ImGui.textUnformatted import imgui.ImGui.treeNode +import imgui.ImGui.treeNodeEx import imgui.ImGui.treePop import imgui.ImGui.windowDrawList import imgui.ImGui.windowWidth import imgui.api.demoDebugInformations.Companion.helpMarker import imgui.api.demoDebugInformations.ShowStyleSelector +import imgui.api.drag import imgui.api.g +import imgui.api.slider import imgui.classes.Style import imgui.classes.TextFilter import imgui.dsl.button @@ -85,7 +91,7 @@ object StyleEditor { var outputDest = 0 var outputOnlyModified = true - var alphaFlags: ColorEditFlags = 0 + var alphaFlags: ColorEditFlags = none val filter = TextFilter() var windowScale = 1f @@ -105,8 +111,7 @@ object StyleEditor { showFontSelector("Fonts##Selector") // Simplified Settings (expose floating-pointer border sizes as boolean representing 0.0f or 1.0f) - if (sliderFloat("FrameRounding", style::frameRounding, 0f, 12f, "%.0f")) style.grabRounding = - style.frameRounding // Make GrabRounding always the same value as FrameRounding + if (slider("FrameRounding", style::frameRounding, 0f, 12f, "%.0f")) style.grabRounding = style.frameRounding // Make GrabRounding always the same value as FrameRounding run { border = style.windowBorderSize > 0f if (checkbox("WindowBorder", ::border)) style.windowBorderSize = border.f @@ -130,60 +135,80 @@ object StyleEditor { sameLine() if (button("Revert Ref")) g.style = ref!! sameLine() - helpMarker( - "Save/Revert in local non-persistent storage. Default Colors definition are not affected. " + "Use \"Export\" below to save them somewhere.") + helpMarker("Save/Revert in local non-persistent storage. Default Colors definition are not affected. " + "Use \"Export\" below to save them somewhere.") separator() - if (beginTabBar("##tabs", TabBarFlag.None.i)) { + if (beginTabBar("##tabs")) { if (beginTabItem("Sizes")) { - text("Main") - sliderVec2("WindowPadding", style.windowPadding, 0f, 20f, "%.0f") - sliderVec2("FramePadding", style.framePadding, 0f, 20f, "%.0f") - sliderVec2("CellPadding", style.cellPadding, 0f, 20f, "%.0f") - sliderVec2("ItemSpacing", style.itemSpacing, 0f, 20f, "%.0f") - sliderVec2("ItemInnerSpacing", style.itemInnerSpacing, 0f, 20f, "%.0f") - sliderVec2("TouchExtraPadding", style.touchExtraPadding, 0f, 10f, "%.0f") - sliderFloat("IndentSpacing", style::indentSpacing, 0f, 30f, "%.0f") - sliderFloat("ScrollbarSize", style::scrollbarSize, 1f, 20f, "%.0f") - sliderFloat("GrabMinSize", style::grabMinSize, 1f, 20f, "%.0f") - text("Borders") - sliderFloat("WindowBorderSize", style::windowBorderSize, 0f, 1f, "%.0f") - sliderFloat("ChildBorderSize", style::childBorderSize, 0f, 1f, "%.0f") - sliderFloat("PopupBorderSize", style::popupBorderSize, 0f, 1f, "%.0f") - sliderFloat("FrameBorderSize", style::frameBorderSize, 0f, 1f, "%.0f") - sliderFloat("TabBorderSize", style::tabBorderSize, 0f, 1f, "%.0f") - text("Rounding") - sliderFloat("WindowRounding", style::windowRounding, 0f, 12f, "%.0f") - sliderFloat("ChildRounding", style::childRounding, 0f, 12f, "%.0f") - sliderFloat("FrameRounding", style::frameRounding, 0f, 12f, "%.0f") - sliderFloat("PopupRounding", style::popupRounding, 0f, 16f, "%.0f") - sliderFloat("ScrollbarRounding", style::scrollbarRounding, 0f, 12f, "%.0f") - sliderFloat("GrabRounding", style::grabRounding, 0f, 12f, "%.0f") - sliderFloat("LogSliderDeadzone", style::logSliderDeadzone, 0f, 12f, "%.0f") - sliderFloat("TabRounding", style::tabRounding, 0f, 12f, "%.0f") - text("Alignment") - sliderVec2("WindowTitleAlign", style.windowTitleAlign, 0f, 1f, "%.2f") + + separatorText("Main") + slider2("WindowPadding", style.windowPadding, 0f, 20f, "%.0f") + slider2("FramePadding", style.framePadding, 0f, 20f, "%.0f") + slider2("CellPadding", style.cellPadding, 0f, 20f, "%.0f") + slider2("ItemSpacing", style.itemSpacing, 0f, 20f, "%.0f") + slider2("ItemInnerSpacing", style.itemInnerSpacing, 0f, 20f, "%.0f") + slider2("TouchExtraPadding", style.touchExtraPadding, 0f, 10f, "%.0f") + slider("IndentSpacing", style::indentSpacing, 0f, 30f, "%.0f") + slider("ScrollbarSize", style::scrollbarSize, 1f, 20f, "%.0f") + slider("GrabMinSize", style::grabMinSize, 1f, 20f, "%.0f") + + separatorText("Borders") + slider("WindowBorderSize", style::windowBorderSize, 0f, 1f, "%.0f") + slider("ChildBorderSize", style::childBorderSize, 0f, 1f, "%.0f") + slider("PopupBorderSize", style::popupBorderSize, 0f, 1f, "%.0f") + slider("FrameBorderSize", style::frameBorderSize, 0f, 1f, "%.0f") + slider("TabBorderSize", style::tabBorderSize, 0f, 1f, "%.0f") + + separatorText("Rounding") + slider("WindowRounding", style::windowRounding, 0f, 12f, "%.0f") + slider("ChildRounding", style::childRounding, 0f, 12f, "%.0f") + slider("FrameRounding", style::frameRounding, 0f, 12f, "%.0f") + slider("PopupRounding", style::popupRounding, 0f, 16f, "%.0f") + slider("ScrollbarRounding", style::scrollbarRounding, 0f, 12f, "%.0f") + slider("GrabRounding", style::grabRounding, 0f, 12f, "%.0f") + slider("TabRounding", style::tabRounding, 0f, 12f, "%.0f") + + separatorText("Widgets") + slider2("WindowTitleAlign", style.windowTitleAlign, 0f, 1f, "%.2f") run { - _i32 = style.windowMenuButtonPosition.i + 1 - if (combo("WindowMenuButtonPosition", ::_i32, - "None${NUL}Left${NUL}Right${NUL}") - ) style.windowMenuButtonPosition = Dir.values().first { it.i == _i32 - 1 } + val sideRef = (style.windowMenuButtonPosition.i + 1).mutableReference + val side by sideRef + if (combo("WindowMenuButtonPosition", sideRef, "None${NUL}Left${NUL}Right${NUL}")) { + style.windowMenuButtonPosition = Dir.values().first { it.i == side - 1 } + } } run { - _i32 = style.colorButtonPosition.i - combo("ColorButtonPosition", ::_i32, "Left\u0000Right\u0000") - style.colorButtonPosition = Dir.values().first { it.i == _i32 } + val sideRef = style.colorButtonPosition.i.mutableReference + val side by sideRef + combo("ColorButtonPosition", sideRef, "Left\u0000Right\u0000") + style.colorButtonPosition = Dir.values().first { it.i == side } } - sliderVec2("ButtonTextAlign", style.buttonTextAlign, 0f, 1f, "%.2f") + slider2("ButtonTextAlign", style.buttonTextAlign, 0f, 1f, "%.2f") sameLine(); helpMarker("Alignment applies when a button is larger than its text content.") - sliderVec2("SelectableTextAlign", style.selectableTextAlign, 0f, 1f, "%.2f") + slider2("SelectableTextAlign", style.selectableTextAlign, 0f, 1f, "%.2f") sameLine(); helpMarker("Alignment applies when a selectable is larger than its text content.") - text("Safe Area Padding") - sameLine(); helpMarker( - "Adjust if you cannot see the edges of your screen (e.g. on a TV where scaling has not been configured).") - sliderVec2("DisplaySafeAreaPadding", style.displaySafeAreaPadding, 0f, 30f, "%.0f") + slider("SeparatorTextBorderSize", style::separatorTextBorderSize, 0f, 10f, "%.0f") + slider2("SeparatorTextAlign", style.separatorTextAlign, 0f, 1f, "%.2f") + slider2("SeparatorTextPadding", style.separatorTextPadding, 0f, 40f, "%.0f") + slider("LogSliderDeadzone", style::logSliderDeadzone, 0f, 12f, "%.0f") + + separatorText("Tooltips") + for (n in 0..<2) + if (treeNodeEx(if (n == 0) "HoverFlagsForTooltipMouse" else "HoverFlagsForTooltipNav")) { + val p = if (n == 0) style::hoverFlagsForTooltipMouse else style::hoverFlagsForTooltipNav + checkboxFlags("ImGuiHoveredFlags_DelayNone", p, HoveredFlag.DelayNone) + checkboxFlags("ImGuiHoveredFlags_DelayShort", p, HoveredFlag.DelayShort) + checkboxFlags("ImGuiHoveredFlags_DelayNormal", p, HoveredFlag.DelayNormal) + checkboxFlags("ImGuiHoveredFlags_Stationary", p, HoveredFlag.Stationary) + checkboxFlags("ImGuiHoveredFlags_NoSharedDelay", p, HoveredFlag.NoSharedDelay) + treePop() + } + + separatorText("Misc") + slider2("DisplaySafeAreaPadding", style.displaySafeAreaPadding, 0f, 30f, "%.0f"); sameLine(); helpMarker("Adjust if you cannot see the edges of your screen (e.g. on a TV where scaling has not been configured).") + endTabItem() } @@ -196,9 +221,8 @@ object StyleEditor { for (i in Col.values()) { val col = style.colors[i] val name = i.name - if (!outputOnlyModified || col != ref!!.colors[i]) logText( - "colors[Col_$name]%s = Vec4(%.2f, %.2f, %.2f, %.2f)\n", " ".repeat(23 - name.length), col.x, - col.y, col.z, col.w) + if (!outputOnlyModified || col != ref!!.colors[i]) + logText("colors[Col_$name]%s = Vec4(%.2f, %.2f, %.2f, %.2f)\n", " ".repeat(23 - name.length), col.x, col.y, col.z, col.w) } logFinish() } @@ -211,24 +235,21 @@ object StyleEditor { filter.draw("Filter colors", fontSize * 16) - radioButton("Opaque", alphaFlags == Cef.None.i) { alphaFlags = Cef.None.i }; sameLine() - radioButton("Alpha", alphaFlags == Cef.AlphaPreview.i) { alphaFlags = Cef.AlphaPreview.i }; sameLine() - radioButton("Both", alphaFlags == Cef.AlphaPreviewHalf.i) { - alphaFlags = Cef.AlphaPreviewHalf.i - }; sameLine() + radioButton("Opaque", alphaFlags.isEmpty) { alphaFlags = none }; sameLine() + radioButton("Alpha", alphaFlags == Cef.AlphaPreview) { alphaFlags = Cef.AlphaPreview }; sameLine() + radioButton("Both", alphaFlags == Cef.AlphaPreviewHalf) { alphaFlags = Cef.AlphaPreviewHalf }; sameLine() helpMarker(""" In the color list: Left-click on color square to open color picker, Right-click to open edit options menu.""".trimIndent()) - child("#colors", Vec2(), true, - Wf.AlwaysVerticalScrollbar or Wf.AlwaysHorizontalScrollbar or Wf._NavFlattened) { + child("#colors", Vec2(), true, Wf.AlwaysVerticalScrollbar or Wf.AlwaysHorizontalScrollbar or Wf._NavFlattened) { withItemWidth(-160) { for (i in 0 until Col.COUNT) { val name = Col.values()[i].name if (!filter.passFilter(name)) continue withID(i) { - colorEditVec4("##color", style.colors[i], Cef.AlphaBar or alphaFlags) + colorEdit4("##color", style.colors[i], Cef.AlphaBar or alphaFlags) if (style.colors[i] != ref!!.colors[i]) { // Tips: in a real user application, you may want to merge and use an icon font into the main font, // so instead of "Save"/"Revert" you'd use icons! // Read the FAQ and docs/FONTS.txt about using icon fonts. It's really easy and super convenient! @@ -261,9 +282,9 @@ object StyleEditor { However, the _correct_ way of scaling your UI is currently to reload your font at the designed size, rebuild the font atlas, and call style.ScaleAllSizes() on a reference ImGuiStyle structure. Using those settings here will give you poor quality results.""".trimIndent()) pushItemWidth(fontSize * 8) - if (dragFloat("window scale", ::windowScale, 0.005f, MIN_SCALE, MAX_SCALE, "%.2f", SliderFlag.AlwaysClamp.i)) // Scale only this window + if (drag("window scale", ::windowScale, 0.005f, MIN_SCALE, MAX_SCALE, "%.2f", SliderFlag.AlwaysClamp)) // Scale only this window setWindowFontScale(windowScale) - dragFloat("global scale", io::fontGlobalScale, 0.005f, MIN_SCALE, MAX_SCALE, "%.2f", SliderFlag.AlwaysClamp.i) // Scale everything + drag("global scale", io::fontGlobalScale, 0.005f, MIN_SCALE, MAX_SCALE, "%.2f", SliderFlag.AlwaysClamp) // Scale everything popItemWidth() endTabItem() @@ -280,13 +301,15 @@ object StyleEditor { checkbox("Anti-aliased fill", style::antiAliasedFill) pushItemWidth(fontSize * 8) - dragFloat("Curve Tessellation Tolerance", style::curveTessellationTol, 0.02f, 0.1f, 10f, "%.2f") + drag("Curve Tessellation Tolerance", style::curveTessellationTol, 0.02f, 0.1f, 10f, "%.2f") if (style.curveTessellationTol < 10f) style.curveTessellationTol = 0.1f // When editing the "Circle Segment Max Error" value, draw a preview of its effect on auto-tessellated circles. - dragFloat("Circle Tessellation Max Error", style::circleTessellationMaxError, 0.005f, 0.1f, 5f, "%.2f", SliderFlag.AlwaysClamp.i) - if (ImGui.isItemActive) { + drag("Circle Tessellation Max Error", style::circleTessellationMaxError, 0.005f, 0.1f, 5f, "%.2f", SliderFlag.AlwaysClamp) + val showSamples = ImGui.isItemActive + if (showSamples) setNextWindowPos(ImGui.cursorScreenPos) + if (showSamples) tooltip { textUnformatted("(R = radius, N = number of segments)") spacing() @@ -300,7 +323,7 @@ object StyleEditor { val segmentCount = drawList._calcCircleAutoSegmentCount(rad) group { - text("R: %.f\nN: ${drawList._calcCircleAutoSegmentCount(rad)}", rad) + text("R: %.0f\nN: ${drawList._calcCircleAutoSegmentCount(rad)}", rad) val canvasWidth = minWidgetWidth max (rad * 2f) val offsetX = floor(canvasWidth * 0.5f) @@ -318,15 +341,13 @@ object StyleEditor { sameLine() } } - } ImGui.sameLine() - helpMarker( - "When drawing circle primitives with \"num_segments == 0\" tesselation will be calculated automatically.") + helpMarker("When drawing circle primitives with \"num_segments == 0\" tesselation will be calculated automatically.") /* Not exposing zero here so user doesn't "lose" the UI (zero alpha clips all widgets). But application code could have a toggle to switch between zero and non-zero. */ - dragFloat("Global Alpha", style::alpha, 0.005f, 0.2f, 1f, "%.2f") - dragFloat("Disabled Alpha", style::disabledAlpha, 0.005f, 0f, 1f, "%.2f"); sameLine(); helpMarker("Additional alpha multiplier for disabled items (multiply over current value of Alpha).") + drag("Global Alpha", style::alpha, 0.005f, 0.2f, 1f, "%.2f") + drag("Disabled Alpha", style::disabledAlpha, 0.005f, 0f, 1f, "%.2f"); sameLine(); helpMarker("Additional alpha multiplier for disabled items (multiply over current value of Alpha).") popItemWidth() endTabItem() @@ -336,12 +357,12 @@ object StyleEditor { popItemWidth() } - fun showFont(font: Font) { + fun debugNodeFont(font: Font) { val name = font.configData.getOrNull(0)?.name ?: "" - val fontDetailsOpened = treeNode(font, - "Font \\\"$name\\\"\\n%.2f px, %.2f px, ${font.glyphs.size} glyphs, ${font.configDataCount} file(s)", - font.fontSize) - sameLine(); smallButton("Set as default") { io.fontDefault = font } + val fontDetailsOpened = treeNode(font, "Font \"$name\"\n%.2f px, ${font.glyphs.size} glyphs, ${font.configDataCount} file(s)", font.fontSize) + sameLine() + if (smallButton("Set as default")) + io.fontDefault = font if (!fontDetailsOpened) return @@ -352,25 +373,22 @@ object StyleEditor { // Display details setNextItemWidth(fontSize * 8) - dragFloat("Font scale", font::scale, 0.005f, 0.3f, 2f, "%.1f") + drag("Font scale", font::scale, 0.005f, 0.3f, 2f, "%.1f") sameLine() - helpMarker(""" - |Note than the default embedded font is NOT meant to be scaled. - | - |Font are currently rendered into bitmaps at a given size at the time of building the atlas. You may oversample them to get some flexibility with scaling. You can also render at multiple sizes and select which one to use at runtime. - | - |(Glimmer of hope: the atlas system should hopefully be rewritten in the future to make scaling more natural and automatic.)""".trimMargin()) + helpMarker("""|Note than the default embedded font is NOT meant to be scaled. + | + |Font are currently rendered into bitmaps at a given size at the time of building the atlas. You may oversample them to get some flexibility with scaling. You can also render at multiple sizes and select which one to use at runtime. + | + |(Glimmer of hope: the atlas system should hopefully be rewritten in the future to make scaling more natural and automatic.)""".trimMargin()) text("Ascent: ${font.ascent}, Descent: ${font.descent}, Height: ${font.ascent - font.descent}") val cStr = ByteArray(5) - text("Fallback character: '${textCharToUtf8(cStr, font.fallbackChar.code)}' (U+%04X)", font.fallbackChar) - text("Ellipsis character: '${textCharToUtf8(cStr, font.ellipsisChar.code)}' (U+%04X)", font.ellipsisChar) + text("Fallback character: '${textCharToUtf8(cStr, font.fallbackChar)}' (U+%04X)", font.fallbackChar.code) + text("Ellipsis character: '${textCharToUtf8(cStr, font.ellipsisChar)}' (U+%04X)", font.ellipsisChar.code) val side = sqrt(font.metricsTotalSurface.f).i text("Texture Area: about ${font.metricsTotalSurface} px ~${side}x$side px") for (c in 0 until font.configDataCount) font.configData.getOrNull(c)?.let { - bulletText( - "Input $c: '${it.name}', Oversample: ${it.oversample}, PixelSnapH: ${it.pixelSnapH}, Offset: (%.1f,%.1f)", - it.glyphOffset.x, it.glyphOffset.y) + bulletText("Input $c: '${it.name}', Oversample: ${it.oversample}, PixelSnapH: ${it.pixelSnapH}, Offset: (%.1f,%.1f)", it.glyphOffset.x, it.glyphOffset.y) } // Display all glyphs of the fonts in separate pages of 256 characters @@ -400,8 +418,7 @@ object StyleEditor { basePos.y + (n / 16) * (cellSize + cellSpacing)) val cellP2 = Vec2(cellP1.x + cellSize, cellP1.y + cellSize) val glyph = font.findGlyphNoFallback((base + n).c) - drawList.addRect(cellP1, cellP2, COL32(255, 255, 255, - if (glyph != null) 100 else 50)) // We use ImFont::RenderChar as a shortcut because we don't have UTF-8 conversion functions + drawList.addRect(cellP1, cellP2, COL32(255, 255, 255, if (glyph != null) 100 else 50)) // We use ImFont::RenderChar as a shortcut because we don't have UTF-8 conversion functions // available here and thus cannot easily generate a zero-terminated UTF-8 encoded string. if (glyph != null) { font.renderChar(drawList, cellSize, cellP1, Col.Text.u32, (base + n).c) @@ -421,7 +438,7 @@ object StyleEditor { } base += 256 } - treePop() } + treePop() } } \ No newline at end of file diff --git a/core/src/main/kotlin/imgui/dsl.kt b/core/src/main/kotlin/imgui/dsl.kt index efffe0470..fd5bdd3fb 100644 --- a/core/src/main/kotlin/imgui/dsl.kt +++ b/core/src/main/kotlin/imgui/dsl.kt @@ -12,6 +12,7 @@ import imgui.ImGui.beginCombo import imgui.ImGui.beginDragDropSource import imgui.ImGui.beginDragDropTarget import imgui.ImGui.beginGroup +import imgui.ImGui.beginListBox import imgui.ImGui.beginMainMenuBar import imgui.ImGui.beginMenu import imgui.ImGui.beginMenuBar @@ -37,6 +38,7 @@ import imgui.ImGui.endCombo import imgui.ImGui.endDragDropSource import imgui.ImGui.endDragDropTarget import imgui.ImGui.endGroup +import imgui.ImGui.endListBox import imgui.ImGui.endMainMenuBar import imgui.ImGui.endMenu import imgui.ImGui.endMenuBar @@ -48,10 +50,8 @@ import imgui.ImGui.endTooltip import imgui.ImGui.imageButton import imgui.ImGui.indent import imgui.ImGui.invisibleButton -import imgui.ImGui.endListBox -import imgui.ImGui.beginListBox import imgui.ImGui.menuItem -import imgui.ImGui.popAllowKeyboardFocus +import imgui.ImGui.popTabStop import imgui.ImGui.popButtonRepeat import imgui.ImGui.popClipRect import imgui.ImGui.popFont @@ -60,7 +60,7 @@ import imgui.ImGui.popItemWidth import imgui.ImGui.popStyleColor import imgui.ImGui.popStyleVar import imgui.ImGui.popTextWrapPos -import imgui.ImGui.pushAllowKeyboardFocus +import imgui.ImGui.pushTabStop import imgui.ImGui.pushButtonRepeat import imgui.ImGui.pushClipRect import imgui.ImGui.pushFont @@ -77,8 +77,7 @@ import imgui.ImGui.treeNodeEx import imgui.ImGui.treePop import imgui.ImGui.unindent import imgui.font.Font -import imgui.internal.sections.OldColumnsFlag -import imgui.internal.sections.OldColumnsFlags +import imgui.internal.sections.OldColumnFlags import kotlin.reflect.KMutableProperty0 /** twin brother of Jdsl */ @@ -86,7 +85,7 @@ object dsl { // Tables - inline fun table(strId: String, columns: Int, flags: TableFlags = TableFlag.None.i, + inline fun table(strId: String, columns: Int, flags: TableFlags = none, outerSize: Vec2 = Vec2(), innerWidth: Float = 0f, block: () -> Unit) { if (beginTable(strId, columns, flags, outerSize, innerWidth)) { // ~open block() @@ -96,7 +95,7 @@ object dsl { // Windows - inline fun window(name: String, open: KMutableProperty0? = null, flags: WindowFlags = 0, block: () -> Unit) { + inline fun window(name: String, open: KMutableProperty0? = null, flags: WindowFlags = none, block: () -> Unit) { if (begin(name, open, flags)) // ~open block() end() @@ -104,7 +103,7 @@ object dsl { // Child Windows - inline fun child(strId: String, size: Vec2 = Vec2(), border: Boolean = false, extraFlags: WindowFlags = 0, block: () -> Unit) { + inline fun child(strId: String, size: Vec2 = Vec2(), border: Boolean = false, extraFlags: WindowFlags = none, block: () -> Unit) { if (beginChild(strId, size, border, extraFlags)) // ~open block() endChild() @@ -199,9 +198,9 @@ object dsl { } inline fun withAllowKeyboardFocus(allowKeyboardFocus: Boolean, block: () -> Unit) { - pushAllowKeyboardFocus(allowKeyboardFocus) + pushTabStop(allowKeyboardFocus) block() - popAllowKeyboardFocus() + popTabStop() } inline fun withButtonRepeat(repeat: Boolean, block: () -> R): R { @@ -279,7 +278,7 @@ object dsl { block() } - inline fun checkboxFlags(label: String, vPtr: KMutableProperty0, flagsValue: Int, block: () -> Unit) { + inline fun > checkboxFlags(label: String, vPtr: KMutableProperty0>, flagsValue: Flag, block: () -> Unit) { if (checkboxFlags(label, vPtr, flagsValue)) block() } @@ -298,7 +297,7 @@ object dsl { // Widgets: Combo Box - inline fun useCombo(label: String, previewValue: String?, flags: ComboFlags = 0, block: () -> Unit) { + inline fun useCombo(label: String, previewValue: String?, flags: ComboFlags = none, block: () -> Unit) { if (beginCombo(label, previewValue, flags)) { block() endCombo() @@ -335,7 +334,7 @@ object dsl { } } - inline fun treeNodeEx(strID: String, flags: TreeNodeFlags = 0, block: () -> Unit) { + inline fun treeNodeEx(strID: String, flags: TreeNodeFlags = none, block: () -> Unit) { if (treeNodeEx(strID, flags)) { block() treePop() @@ -368,12 +367,12 @@ object dsl { // try { block() } finally { treePop() } // } - inline fun collapsingHeader(label: String, flags: TreeNodeFlags = 0, block: () -> Unit) { + inline fun collapsingHeader(label: String, flags: TreeNodeFlags = none, block: () -> Unit) { if (collapsingHeader(label, flags)) block() } - inline fun collapsingHeader(label: String, open: KMutableProperty0, flags: TreeNodeFlags = 0, block: () -> Unit) { + inline fun collapsingHeader(label: String, open: KMutableProperty0, flags: TreeNodeFlags = none, block: () -> Unit) { if (collapsingHeader(label, open, flags)) block() } @@ -381,7 +380,7 @@ object dsl { // Widgets: Selectables - inline fun selectable(label: String, selected: Boolean = false, flags: Int = 0, sizeArg: Vec2 = Vec2(), block: () -> Unit) { + inline fun selectable(label: String, selected: Boolean = false, flags: SelectableFlags = none, sizeArg: Vec2 = Vec2(), block: () -> Unit) { if (selectable(label, selected, flags, sizeArg)) block() } @@ -419,43 +418,44 @@ object dsl { // Tooltips inline fun tooltip(block: () -> Unit) { - beginTooltip() - block() - endTooltip() + if (beginTooltip()) { + block() + endTooltip() + } } // Popups, Modals - inline fun popup(strId: String, flags: WindowFlags = 0, block: () -> Unit) { + inline fun popup(strId: String, flags: WindowFlags = none, block: () -> Unit) { if (beginPopup(strId, flags)) { block() endPopup() } } - inline fun popupContextItem(strId: String = "", popupFlags: PopupFlags = PopupFlag.MouseButtonRight.i, block: () -> Unit) { + inline fun popupContextItem(strId: String = "", popupFlags: PopupFlags = PopupFlag.MouseButtonRight, block: () -> Unit) { if (beginPopupContextItem(strId, popupFlags)) { block() endPopup() } } - inline fun popupContextWindow(strId: String = "", popupFlags: PopupFlags = PopupFlag.MouseButtonRight.i, block: () -> Unit) { + inline fun popupContextWindow(strId: String = "", popupFlags: PopupFlags = PopupFlag.MouseButtonRight, block: () -> Unit) { if (beginPopupContextWindow(strId, popupFlags)) { block() endPopup() } } - inline fun popupContextVoid(strId: String = "", popupFlags: PopupFlags = PopupFlag.MouseButtonRight.i, block: () -> Unit) { + inline fun popupContextVoid(strId: String = "", popupFlags: PopupFlags = PopupFlag.MouseButtonRight, block: () -> Unit) { if (beginPopupContextVoid(strId, popupFlags)) { block() endPopup() } } - inline fun popupModal(name: String, pOpen: KMutableProperty0? = null, extraFlags: WindowFlags = 0, block: () -> Unit) { + inline fun popupModal(name: String, pOpen: KMutableProperty0? = null, extraFlags: WindowFlags = none, block: () -> Unit) { if (beginPopupModal(name, pOpen, extraFlags)) { block() endPopup() @@ -465,31 +465,24 @@ object dsl { // Tab Bars, Tabs - inline fun tabBar(strId: String, flags: TabBarFlags = 0, block: () -> Unit) { + inline fun tabBar(strId: String, flags: TabBarFlags = none, block: () -> Unit) { if (beginTabBar(strId, flags)) { block() endTabBar() } } - inline fun tabItem(label: String, pOpen: KMutableProperty0? = null, flags: TabItemFlags = 0, block: () -> Unit) { + inline fun tabItem(label: String, pOpen: KMutableProperty0? = null, flags: TabItemOnlyFlags = none, block: () -> Unit) { if (beginTabItem(label, pOpen, flags)) { block() endTabItem() } } - inline fun tabItem(label: String, pOpen: BooleanArray, ptr: Int, flags: TabItemFlags = 0, block: () -> Unit) { - if (beginTabItem(label, pOpen, ptr, flags)) { - block() - endTabItem() - } - } - // Drag and Drop - inline fun dragDropSource(flags: DragDropFlags = 0, block: () -> Unit) { + inline fun dragDropSource(flags: DragDropFlags = none, block: () -> Unit) { if (beginDragDropSource(flags)) { block() endDragDropSource() @@ -515,16 +508,15 @@ object dsl { // Miscellaneous Utilities - inline fun childFrame(id: ID, size: Vec2, extraFlags: WindowFlags = 0, block: () -> Unit) { - beginChildFrame(id, size, extraFlags) - block() + inline fun childFrame(id: ID, size: Vec2, extraFlags: WindowFlags = none, block: () -> Unit) { + if (beginChildFrame(id, size, extraFlags)) + block() endChildFrame() } // Columns - inline fun columns(strId: String = "", columnsCount: Int, - flags: OldColumnsFlags = OldColumnsFlag.None.i, block: () -> Unit) { + inline fun columns(strId: String = "", columnsCount: Int, flags: OldColumnFlags = none, block: () -> Unit) { beginColumns(strId, columnsCount, flags) block() endColumns() diff --git a/core/src/main/kotlin/imgui/flags & enumerations.kt b/core/src/main/kotlin/imgui/flags & enumerations.kt index 8be61ea59..050c05d75 100644 --- a/core/src/main/kotlin/imgui/flags & enumerations.kt +++ b/core/src/main/kotlin/imgui/flags & enumerations.kt @@ -1,287 +1,394 @@ package imgui +import com.livefront.sealedenum.GenSealedEnum import glm_.vec4.Vec4 +import imgui.DragDropFlag.* import imgui.ImGui.getColorU32 +import imgui.internal.isPowerOfTwo +import unsigned.* +import kotlin.internal.NoInfer //----------------------------------------------------------------------------- // [SECTION] Flags & Enumerations //----------------------------------------------------------------------------- +@JvmInline +private value class Flags>(override val i: Int) : Flag { + constructor() : this(0) + constructor(flag: Flag) : this(flag.i) -interface Flag> { - val i: Int + @Suppress("RESERVED_MEMBER_INSIDE_VALUE_CLASS") + override fun equals(other: Any?): Boolean = other is Flag<*> && i == other.i - infix fun and(b: Self): Int = i and b.i - infix fun and(b: Int): Int = i and b - infix fun or(b: Self): Int = i or b.i - infix fun or(b: Int): Int = i or b - infix fun xor(b: Self): Int = i xor b.i - infix fun xor(b: Int): Int = i xor b - infix fun wo(b: Self): Int = and(b.i.inv()) - infix fun wo(b: Int): Int = and(b.inv()) - infix fun has(b: Self): Boolean = and(b.i) != 0 - infix fun hasnt(b: Self): Boolean = and(b.i) == 0 + @Suppress("RESERVED_MEMBER_INSIDE_VALUE_CLASS") + override fun hashCode(): Int = i + + infix fun and(b: Flags): Flags = Flags(i and b.i) + infix fun or(b: Flags): Flags = Flags(i or b.i) + infix fun xor(b: Flags): Flags = Flags(i xor b.i) + infix fun wo(b: Flags): Flags = Flags(i and b.i.inv()) +} + +@JvmInline +value class FlagArray> private constructor(private val array: IntArray) { + constructor(flag: Flag) : this(IntArray(1) { + flag.i + }) + + constructor(flags: Array>) : this(IntArray(flags.size) { + flags[it].i + }) + + // constructor from size + constructor(size: Int) : this(IntArray(size)) + + // Provides Java-style constructors + companion object { + @JvmName("of") + @JvmStatic + fun > of(flag: Flag): FlagArray = FlagArray(flag) + + @JvmName("of") + @JvmStatic + fun > of(vararg flags: Flag): FlagArray = FlagArray(flags) + + @JvmName("of") + @JvmStatic + fun > of(size: Int): FlagArray = FlagArray(size) + + @JvmName("get") + @JvmStatic + fun > get(flagArray: FlagArray, index: Int): Flag = flagArray[index] + + @JvmName("set") + @JvmStatic + fun > set(flagArray: FlagArray, index: Int, value: Flag) { + flagArray[index] = value + } + + @JvmName("iterator") + @JvmStatic + fun > iterator(flagArray: FlagArray): Iterator> = flagArray.iterator() + } + + operator fun get(index: Int): Flag = Flags(array[index]) + operator fun set(index: Int, value: Flag) { + array[index] = value.i + } + + val size: Int get() = array.size + operator fun iterator(): Iterator> = array.iterator().asSequence().map { Flags(it) }.iterator() + + /** + * Returns the range of valid indices for the array. + */ + val indices: IntRange + get() = IntRange(0, size - 1) +} + +/** + * Creates a new array of the specified [size], where each element is calculated by calling the specified + * [init] function. + * + * The function [init] is called for each array element sequentially starting from the first one. + * It should return the value for an array element given its index. + */ +inline fun > FlagArray(size: Int, init: (Int) -> Flag) = FlagArray(size).apply { + for (i in indices) + this[i] = init(i) } -infix fun Int.and(b: Flag<*>): Int = and(b.i) -infix fun Int.or(b: Flag<*>): Int = or(b.i) -infix fun Int.xor(b: Flag<*>): Int = xor(b.i) -infix fun Int.has(b: Flag<*>): Boolean = and(b.i) != 0 -infix fun Int.hasnt(b: Flag<*>): Boolean = and(b.i) == 0 -infix fun Int.wo(b: Flag<*>): Int = and(b.i.inv()) -operator fun Int.minus(flag: Flag<*>): Int = wo(flag) -operator fun Int.div(flag: Flag<*>): Int = or(flag) +val none: Flag = Flags() +fun > emptyFlags(): Flag = none +fun > flagArrayOf(flag: Flag): FlagArray = FlagArray(flag) +fun > flagArrayOf(vararg flags: Flag): FlagArray = FlagArray(flags) +interface Flag> { + val i: Int + + companion object { + @JvmStatic + fun > none(): Flag = none + } + + val isEmpty get() = this == none + val isNotEmpty get() = !isEmpty + val isPowerOfTwo get() = i.isPowerOfTwo +} + +infix fun > Flag.and(b: Flag): Flag = Flags(this) and Flags(b) +infix fun > Flag.or(b: Flag): Flag = Flags(this) or Flags(b) +infix fun > Flag.xor(b: Flag): Flag = Flags(this) xor Flags(b) +infix fun > Flag.wo(b: Flag): Flag = Flags(this) wo Flags(b) +infix fun > Flag.has(b: Flag<@NoInfer Self>): Boolean = and(b).isNotEmpty +infix fun > Flag.hasnt(b: Flag<@NoInfer Self>): Boolean = and(b).isEmpty +operator fun > Flag.minus(flag: Flag): Flag = wo(flag) +operator fun > Flag.div(flag: Flag): Flag = or(flag) +operator fun > Flag.contains(flag: Flag<@NoInfer Self>) = and(flag) == flag + +abstract class FlagBase> : Flag { + override fun equals(other: Any?): Boolean = this === other || (other is Flag<*> && i == other.i) + override fun hashCode(): Int = i +} /** Flags for ImGui::Begin() * (Those are per-window flags. There are shared flags in ImGuiIO: io.ConfigWindowsResizeFromEdges and io.ConfigWindowsMoveFromTitleBarOnly) */ -typealias WindowFlags = Int +typealias WindowFlags = Flag /** Flags: for Begin(), BeginChild() */ -enum class WindowFlag(override val i: WindowFlags) : Flag { - - None(0), +sealed class WindowFlag(override val i: Int) : FlagBase() { + /** Disable title-bar */ + object NoTitleBar : WindowFlag(1 shl 0) - /** Disable title-bar */ - NoTitleBar(1 shl 0), + /** Disable user resizing with the lower-right grip */ + object NoResize : WindowFlag(1 shl 1) - /** Disable user resizing with the lower-right grip */ - NoResize(1 shl 1), - - /** Disable user moving the window */ - NoMove(1 shl 2), + /** Disable user moving the window */ + object NoMove : WindowFlag(1 shl 2) /** Disable scrollbars (window can still scroll with mouse or programmatically) */ - NoScrollbar(1 shl 3), + object NoScrollbar : WindowFlag(1 shl 3) /** Disable user vertically scrolling with mouse wheel. On child window, mouse wheel will be forwarded to the parent * unless noScrollbar is also set. */ - NoScrollWithMouse(1 shl 4), + object NoScrollWithMouse : WindowFlag(1 shl 4) /** Disable user collapsing window by double-clicking on it. Also referred to as Window Menu Button (e.g. within a docking node). */ - NoCollapse(1 shl 5), + object NoCollapse : WindowFlag(1 shl 5) /** Resize every window to its content every frame */ - AlwaysAutoResize(1 shl 6), + object AlwaysAutoResize : WindowFlag(1 shl 6) /** Disable drawing background color (WindowBg, etc.) and outside border. Similar as using SetNextWindowBgAlpha(0.0f).(1 shl 7) */ - NoBackground(1 shl 7), + object NoBackground : WindowFlag(1 shl 7) /** Never load/save settings in .ini file */ - NoSavedSettings(1 shl 8), + object NoSavedSettings : WindowFlag(1 shl 8) /** Disable catching mouse or keyboard inputs */ - NoMouseInputs(1 shl 9), + object NoMouseInputs : WindowFlag(1 shl 9) /** Has a menu-bar */ - MenuBar(1 shl 10), + object MenuBar : WindowFlag(1 shl 10) /** Allow horizontal scrollbar to appear (off by default). You may use SetNextWindowContentSize(ImVec2(width),0.0f)); * prior to calling Begin() to specify width. Read code in imgui_demo in the "Horizontal Scrolling" section. */ - HorizontalScrollbar(1 shl 11), + object HorizontalScrollbar : WindowFlag(1 shl 11) /** Disable taking focus when transitioning from hidden to visible state */ - NoFocusOnAppearing(1 shl 12), + object NoFocusOnAppearing : WindowFlag(1 shl 12) /** Disable bringing window to front when taking focus (e.g. clicking on it or programmatically giving it focus) */ - NoBringToFrontOnFocus(1 shl 13), + object NoBringToFrontOnFocus : WindowFlag(1 shl 13) /** Always show vertical scrollbar (even if ContentSize.y < Size.y) */ - AlwaysVerticalScrollbar(1 shl 14), + object AlwaysVerticalScrollbar : WindowFlag(1 shl 14) /** Always show horizontal scrollbar (even if ContentSize.x < Size.x) */ - AlwaysHorizontalScrollbar(1 shl 15), + object AlwaysHorizontalScrollbar : WindowFlag(1 shl 15) /** Ensure child windows without border uses style.WindowPadding (ignored by default for non-bordered child windows), * because more convenient) */ - AlwaysUseWindowPadding(1 shl 16), + object AlwaysUseWindowPadding : WindowFlag(1 shl 16) /** No gamepad/keyboard navigation within the window */ - NoNavInputs(1 shl 18), + object NoNavInputs : WindowFlag(1 shl 18) /** No focusing toward this window with gamepad/keyboard navigation (e.g. skipped by CTRL+TAB) */ - NoNavFocus(1 shl 19), + object NoNavFocus : WindowFlag(1 shl 19) /** Display a dot next to the title. When used in a tab/docking context, tab is selected when clicking the X + closure is not assumed (will wait for user to stop submitting the tab). Otherwise closure is assumed when pressing the X, so if you keep submitting the tab may reappear at end of tab bar. */ - UnsavedDocument(1 shl 20), - - NoNav(NoNavInputs or NoNavFocus), - - NoDecoration(NoTitleBar or NoResize or NoScrollbar or NoCollapse), - - NoInputs(NoMouseInputs or NoNavInputs or NoNavFocus), + object UnsavedDocument : WindowFlag(1 shl 20) // [Internal] /** [BETA] On child window: allow gamepad/keyboard navigation to cross over parent border to this child or between sibling child windows. */ - _NavFlattened(1 shl 23), + object _NavFlattened : WindowFlag(1 shl 23) /** Don't use! For internal use by BeginChild() */ - _ChildWindow(1 shl 24), + object _ChildWindow : WindowFlag(1 shl 24) /** Don't use! For internal use by BeginTooltip() */ - _Tooltip(1 shl 25), + object _Tooltip : WindowFlag(1 shl 25) /** Don't use! For internal use by BeginPopup() */ - _Popup(1 shl 26), + object _Popup : WindowFlag(1 shl 26) /** Don't use! For internal use by BeginPopupModal() */ - _Modal(1 shl 27), + object _Modal : WindowFlag(1 shl 27) + + /** Don't use! For internal use by BeginMenu() */ + object _ChildMenu : WindowFlag(1 shl 28) + + @GenSealedEnum + companion object { + val NoNav: WindowFlags get() = NoNavInputs / NoNavFocus - /** Don't use! For internal use by BeginMenu() */ - _ChildMenu(1 shl 28) + val NoDecoration: WindowFlags get() = NoTitleBar / NoResize / NoScrollbar / NoCollapse + + val NoInputs: WindowFlags get() = NoMouseInputs / NoNavInputs / NoNavFocus + } } /** Flags for ImGui::InputText(), InputTextMultiline() * (Those are per-item flags. There are shared flags in ImGuiIO: io.ConfigInputTextCursorBlink and io.ConfigInputTextEnterKeepActive) */ -typealias InputTextFlags = Int +typealias InputTextFlags = Flag> +typealias InputTextSingleFlags = Flag -enum class InputTextFlag(override val i: InputTextFlags) : Flag { +sealed class InputTextFlag>(override val i: Int) : FlagBase() { + sealed class Single(i: Int) : InputTextFlag(i) - None(0), + sealed class Multiline(i: Int) : InputTextFlag(i) - /** Allow 0123456789 . + - * / */ - CharsDecimal(1 shl 0), + /** Allow 0123456789 . + - * / */ + object CharsDecimal : Single(1 shl 0) - /** Allow 0123456789ABCDEFabcdef */ - CharsHexadecimal(1 shl 1), + /** Allow 0123456789ABCDEFabcdef */ + object CharsHexadecimal : Single(1 shl 1) - /** Turn a..z into A..Z */ - CharsUppercase(1 shl 2), + /** Turn a..z into A..Z */ + object CharsUppercase : Single(1 shl 2) /** Filter out spaces), tabs */ - CharsNoBlank(1 shl 3), + object CharsNoBlank : Single(1 shl 3) /** Select entire text when first taking mouse focus */ - AutoSelectAll(1 shl 4), + object AutoSelectAll : Single(1 shl 4) /** Return 'true' when Enter is pressed (as opposed to every time the value was modified). Consider looking at the IsItemDeactivatedAfterEdit() function. */ - EnterReturnsTrue(1 shl 5), + object EnterReturnsTrue : Single(1 shl 5) /** Callback on pressing TAB (for completion handling) */ - CallbackCompletion(1 shl 6), + object CallbackCompletion : Single(1 shl 6) /** Callback on pressing Up/Down arrows (for history handling) */ - CallbackHistory(1 shl 7), + object CallbackHistory : Single(1 shl 7) /** Callback on each iteration. User code may query cursor position), modify text buffer. */ - CallbackAlways(1 shl 8), + object CallbackAlways : Single(1 shl 8) /** Callback on character inputs to replace or discard them. Modify 'EventChar' to replace or discard, * or return 1 in callback to discard. */ - CallbackCharFilter(1 shl 9), + object CallbackCharFilter : Single(1 shl 9) /** Pressing TAB input a '\t' character into the text field */ - AllowTabInput(1 shl 10), + object AllowTabInput : Single(1 shl 10) /** In multi-line mode), unfocus with Enter), add new line with Ctrl+Enter (default is opposite: unfocus with * Ctrl+Enter), add line with Enter). */ - CtrlEnterForNewLine(1 shl 11), + object CtrlEnterForNewLine : Single(1 shl 11) /** Disable following the cursor horizontally */ - NoHorizontalScroll(1 shl 12), + object NoHorizontalScroll : Single(1 shl 12) /** Overwrite mode */ - AlwaysOverwrite(1 shl 13), + object AlwaysOverwrite : Single(1 shl 13) /** Read-only mode */ - ReadOnly(1 shl 14), + object ReadOnly : Single(1 shl 14) /** Password mode), display all characters as '*' */ - Password(1 shl 15), + object Password : Single(1 shl 15) /** Disable undo/redo. Note that input text owns the text data while active, if you want to provide your own * undo/redo stack you need e.g. to call clearActiveID(). */ - NoUndoRedo(1 shl 16), + object NoUndoRedo : Single(1 shl 16) /** Allow 0123456789.+-* /eE (Scientific notation input) */ - CharsScientific(1 shl 17), + object CharsScientific : Single(1 shl 17) /** Callback on buffer capacity changes request (beyond 'buf_size' parameter value), allowing the string to grow. * Notify when the string wants to be resized (for string types which hold a cache of their Size). * You will be provided a new BufSize in the callback and NEED to honor it. (see misc/cpp/imgui_stl.h for an example of using this) */ - CallbackResize(1 shl 18), + object CallbackResize : Single(1 shl 18) // [JVM] be sure to modify the upstream source as well on resize! /** Callback on any edit (note that InputText() already returns true on edit, the callback is useful mainly to * manipulate the underlying buffer while focus is active) */ - CallbackEdit(1 shl 19), + object CallbackEdit : Single(1 shl 19) /** Escape key clears content if not empty, and deactivate otherwise (contrast to default behavior of Escape to revert) */ - EscapeClearsAll(1 shl 20), + object EscapeClearsAll : Single(1 shl 20) // [Internal] /** For internal use by InputTextMultiline() */ - _Multiline(1 shl 26), + object _Multiline : Multiline(1 shl 26) /** For internal use by functions using InputText() before reformatting data */ - _NoMarkEdited(1 shl 27), + object _NoMarkEdited : Single(1 shl 27) + + /** For internal use by TempInputText(), will skip calling ItemAdd(). Require bounding-box to strictly match. */ + object _MergedItem : Single(1 shl 28) - /** For internal use by TempInputText(), will skip calling ItemAdd(). Require bounding-box to strictly match. */ - _MergedItem(1 shl 28) + @GenSealedEnum + companion object } -typealias TreeNodeFlags = Int +typealias TreeNodeFlags = Flag /** Flags: for TreeNode(), TreeNodeEx(), CollapsingHeader() */ -enum class TreeNodeFlag(override val i: TreeNodeFlags) : Flag { - - None(0), - - /** Draw as selected */ - Selected(1 shl 0), +sealed class TreeNodeFlag(override val i: Int) : FlagBase() { + /** Draw as selected */ + object Selected : TreeNodeFlag(1 shl 0) - /** Draw frame with background (e.g. for CollapsingHeader) */ - Framed(1 shl 1), + /** Draw frame with background (e.g. for CollapsingHeader) */ + object Framed : TreeNodeFlag(1 shl 1) - /** Hit testing to allow subsequent widgets to overlap this one */ - AllowItemOverlap(1 shl 2), + /** Hit testing to allow subsequent widgets to overlap this one */ + object AllowOverlap : TreeNodeFlag(1 shl 2) /** Don't do a TreePush() when open (e.g. for CollapsingHeader) ( no extra indent nor pushing on ID stack */ - NoTreePushOnOpen(1 shl 3), + object NoTreePushOnOpen : TreeNodeFlag(1 shl 3) /** Don't automatically and temporarily open node when Logging is active (by default logging will automatically open * tree nodes) */ - NoAutoOpenOnLog(1 shl 4), + object NoAutoOpenOnLog : TreeNodeFlag(1 shl 4) /** Default node to be open */ - DefaultOpen(1 shl 5), + object DefaultOpen : TreeNodeFlag(1 shl 5) /** Need double-click to open node */ - OpenOnDoubleClick(1 shl 6), + object OpenOnDoubleClick : TreeNodeFlag(1 shl 6) /** Only open when clicking on the arrow part. If OpenOnDoubleClick is also set), single-click arrow or double-click * all box to open. */ - OpenOnArrow(1 shl 7), + object OpenOnArrow : TreeNodeFlag(1 shl 7) /** No collapsing), no arrow (use as a convenience for leaf nodes). */ - Leaf(1 shl 8), + object Leaf : TreeNodeFlag(1 shl 8) /** Display a bullet instead of arrow */ - Bullet(1 shl 9), + object Bullet : TreeNodeFlag(1 shl 9) /** Use FramePadding (even for an unframed text node) to vertically align text baseline to regular widget height. * Equivalent to calling alignTextToFramePadding(). */ - FramePadding(1 shl 10), + object FramePadding : TreeNodeFlag(1 shl 10) /** Extend hit box to the right-most edge, even if not framed. This is not the default in order to allow adding other items on the same line. In the future we may refactor the hit system to be front-to-back, allowing natural overlaps and then this can become the default. */ - SpanAvailWidth(1 shl 11), + object SpanAvailWidth : TreeNodeFlag(1 shl 11) /** Extend hit box to the left-most and right-most edges (bypass the indented area). */ - SpanFullWidth(1 shl 12), + object SpanFullWidth : TreeNodeFlag(1 shl 12) /** (WIP) Nav: left direction may move to this TreeNode() from any of its child (items submitted between TreeNode and TreePop) */ - NavLeftJumpsBackHere(1 shl 13), - CollapsingHeader(Framed or NoTreePushOnOpen or NoAutoOpenOnLog), + object NavLeftJumpsBackHere : TreeNodeFlag(1 shl 13) // [Internal] - _ClipLabelForTrailingButton(1 shl 20) + internal object ClipLabelForTrailingButton : TreeNodeFlag(1 shl 20) + + /** (FIXME-WIP) Turn Down arrow into an Up arrow, but reversed trees (#6517) */ + internal object UpsideDownArrow : TreeNodeFlag(1 shl 21) + + @GenSealedEnum + companion object { + val CollapsingHeader: TreeNodeFlags get() = Framed or NoTreePushOnOpen or NoAutoOpenOnLog + } } -typealias PopupFlags = Int +typealias PopupFlags = Flag /** Flags for OpenPopup*(), BeginPopupContext*(), IsPopupOpen() functions. * - To be backward compatible with older API which took an 'int mouse_button = 1' argument, we need to treat @@ -291,206 +398,230 @@ typealias PopupFlags = Int * IMPORTANT: because the default parameter is 1 (==ImGuiPopupFlags_MouseButtonRight), if you rely on the default parameter * and want to use another flag, you need to pass in the ImGuiPopupFlags_MouseButtonRight flag explicitly. * - Multiple buttons currently cannot be combined/or-ed in those functions (we could allow it later). */ -enum class PopupFlag(override val i: PopupFlags) : Flag { +sealed class PopupFlag(override val i: Int) : FlagBase() { + /** For BeginPopupContext*(): open on Left Mouse release. */ + object MouseButtonLeft : PopupFlag(0) - None(0), + /** For BeginPopupContext*(): open on Right Mouse release. */ + object MouseButtonRight : PopupFlag(1) - /** For BeginPopupContext*(): open on Left Mouse release. Guaranteed to always be == 0 (same as ImGuiMouseButton_Left) */ - MouseButtonLeft(0), - - /** For BeginPopupContext*(): open on Right Mouse release. Guaranteed to always be == 1 (same as ImGuiMouseButton_Right) */ - MouseButtonRight(1), - - /** For BeginPopupContext*(): open on Middle Mouse release. Guaranteed to always be == 2 (same as ImGuiMouseButton_Middle) */ - MouseButtonMiddle(2), - MouseButtonMask_(0x1F), - MouseButtonDefault_(1), + /** For BeginPopupContext*(): open on Middle Mouse release. */ + object MouseButtonMiddle : PopupFlag(2) /** For OpenPopup*(), BeginPopupContext*(): don't open if there's already a popup at the same level of the popup stack */ - NoOpenOverExistingPopup(1 shl 5), + object NoOpenOverExistingPopup : PopupFlag(1 shl 5) /** For BeginPopupContextWindow(): don't return true when hovering items, only when hovering empty space */ - NoOpenOverItems(1 shl 6), + object NoOpenOverItems : PopupFlag(1 shl 6) /** For IsPopupOpen(): ignore the ImGuiID parameter and test for any popup. */ - AnyPopupId(1 shl 7), + object AnyPopupId : PopupFlag(1 shl 7) /** For IsPopupOpen(): search/test at any level of the popup stack (default test in the current level) */ - AnyPopupLevel(1 shl 8), - AnyPopup(AnyPopupId or AnyPopupLevel); + object AnyPopupLevel : PopupFlag(1 shl 8) + + @GenSealedEnum + companion object { + val AnyPopup: PopupFlags get() = AnyPopupId or AnyPopupLevel + val Default: PopupFlags get() = MouseButtonRight + + infix fun of(mouseButton: MouseButton): PopupFlags = when (mouseButton) { + MouseButton.Left -> MouseButtonLeft + MouseButton.Right -> MouseButtonRight + MouseButton.Middle -> MouseButtonMiddle + else -> none + } + } } +val PopupFlags.mouseButton: MouseButton + get() = when (this) { + PopupFlag.MouseButtonLeft -> MouseButton.Left + PopupFlag.MouseButtonRight -> MouseButton.Right + PopupFlag.MouseButtonMiddle -> MouseButton.Middle + else -> MouseButton.None + } -typealias SelectableFlags = Int +typealias SelectableFlags = Flag /** Flags for ImGui::Selectable() */ -enum class SelectableFlag(override val i: SelectableFlags) : Flag { - - None(0), - - /** Clicking this doesn't close parent popup window */ - DontClosePopups(1 shl 0), +sealed class SelectableFlag(override val i: Int) : FlagBase() { + /** Clicking this doesn't close parent popup window */ + object DontClosePopups : SelectableFlag(1 shl 0) - /** Selectable frame can span all columns (text will still fit in current column) */ - SpanAllColumns(1 shl 1), + /** Selectable frame can span all columns (text will still fit in current column) */ + object SpanAllColumns : SelectableFlag(1 shl 1) - /** Generate press events on double clicks too */ - AllowDoubleClick(1 shl 2), + /** Generate press events on double clicks too */ + object AllowDoubleClick : SelectableFlag(1 shl 2) /** Cannot be selected, display grayed out text */ - Disabled(1 shl 3), + object Disabled : SelectableFlag(1 shl 3) /** (WIP) Hit testing to allow subsequent widgets to overlap this one */ - AllowItemOverlap(1 shl 4), + object AllowOverlap : SelectableFlag(1 shl 4) // [Internal] NB: need to be in sync with last value of ImGuiSelectableFlags_ /** private */ - _NoHoldingActiveID(1 shl 20), + object _NoHoldingActiveID : SelectableFlag(1 shl 20) /** (WIP) Auto-select when moved into. This is not exposed in public API as to handle multi-select and modifiers we will need user to explicitly control focus scope. May be replaced with a BeginSelection() API. */ - _SelectOnNav(1 shl 21), + object _SelectOnNav : SelectableFlag(1 shl 21) /** Override button behavior to react on Click (default is Click+Release) */ - _SelectOnClick(1 shl 22), + object _SelectOnClick : SelectableFlag(1 shl 22) /** Override button behavior to react on Release (default is Click+Release) */ - _SelectOnRelease(1 shl 23), + object _SelectOnRelease : SelectableFlag(1 shl 23) /** Span all avail width even if we declared less for layout purpose. FIXME: We may be able to remove this (added in 6251d379, 2bcafc86 for menus) */ - _SpanAvailWidth(1 shl 24), + object _SpanAvailWidth : SelectableFlag(1 shl 24) /** Set Nav/Focus ID on mouse hover (used by MenuItem) */ - _SetNavIdOnHover(1 shl 25), + object _SetNavIdOnHover : SelectableFlag(1 shl 25) /** Disable padding each side with ItemSpacing * 0.5f */ - _NoPadWithHalfSpacing(1 shl 26), + object _NoPadWithHalfSpacing : SelectableFlag(1 shl 26) /** Don't set key/input owner on the initial click (note: mouse buttons are keys! often, the key in question will be ImGuiKey_MouseLeft!) */ - _NoSetKeyOwner(1 shl 27); + object _NoSetKeyOwner : SelectableFlag(1 shl 27) + + @GenSealedEnum + companion object } -typealias ComboFlags = Int +typealias ComboFlags = Flag /** Flags: for BeginCombo() */ -enum class ComboFlag(override val i: ComboFlags) : Flag { - None(0), - - /** Align the popup toward the left by default */ - PopupAlignLeft(1 shl 0), +sealed class ComboFlag(override val i: Int) : FlagBase() { + /** Align the popup toward the left by default */ + object PopupAlignLeft : ComboFlag(1 shl 0) - /** Max ~4 items visible */ - HeightSmall(1 shl 1), + /** Max ~4 items visible */ + object HeightSmall : ComboFlag(1 shl 1) - /** Max ~8 items visible (default) */ - HeightRegular(1 shl 2), + /** Max ~8 items visible (default) */ + object HeightRegular : ComboFlag(1 shl 2) /** Max ~20 items visible */ - HeightLarge(1 shl 3), + object HeightLarge : ComboFlag(1 shl 3) /** As many fitting items as possible */ - HeightLargest(1 shl 4), + object HeightLargest : ComboFlag(1 shl 4) /** Display on the preview box without the square arrow button */ - NoArrowButton(1 shl 5), + object NoArrowButton : ComboFlag(1 shl 5) /** Display only a square arrow button */ - NoPreview(1 shl 6), - HeightMask_(HeightSmall or HeightRegular or HeightLarge or HeightLargest), + object NoPreview : ComboFlag(1 shl 6) // private - /** enable BeginComboPreview() */ - _CustomPreview(1 shl 20) + /** enable BeginComboPreview() */ + object _CustomPreview : ComboFlag(1 shl 20) + + @GenSealedEnum + companion object { + val HeightMask: ComboFlags get() = HeightSmall or HeightRegular or HeightLarge or HeightLargest + } } -typealias TabBarFlags = Int +typealias TabBarFlags = Flag /** Flags for ImGui::BeginTabBar() */ -enum class TabBarFlag(override val i: TabBarFlags) : Flag { - None(0), +sealed class TabBarFlag(override val i: Int) : FlagBase() { + /** Allow manually dragging tabs to re-order them + New tabs are appended at the end of list */ + object Reorderable : TabBarFlag(1 shl 0) - /** Allow manually dragging tabs to re-order them + New tabs are appended at the end of list */ - Reorderable(1 shl 0), + /** Automatically select new tabs when they appear */ + object AutoSelectNewTabs : TabBarFlag(1 shl 1) - /** Automatically select new tabs when they appear */ - AutoSelectNewTabs(1 shl 1), - - /** Disable buttons to open the tab list popup */ - TabListPopupButton(1 shl 2), + /** Disable buttons to open the tab list popup */ + object TabListPopupButton : TabBarFlag(1 shl 2) /** Disable behavior of closing tabs (that are submitted with p_open != NULL) with middle mouse button. * You can still repro this behavior on user's side with if (IsItemHovered() && IsMouseClicked(2)) *p_open = false. */ - NoCloseWithMiddleMouseButton(1 shl 3), + object NoCloseWithMiddleMouseButton : TabBarFlag(1 shl 3) /** Disable scrolling buttons (apply when fitting policy is ImGuiTabBarFlags_FittingPolicyScroll) */ - NoTabListScrollingButtons(1 shl 4), + object NoTabListScrollingButtons : TabBarFlag(1 shl 4) /** Disable tooltips when hovering a tab */ - NoTooltip(1 shl 5), + object NoTooltip : TabBarFlag(1 shl 5) /** Resize tabs when they don't fit */ - FittingPolicyResizeDown(1 shl 6), + object FittingPolicyResizeDown : TabBarFlag(1 shl 6) /** Add scroll buttons when tabs don't fit */ - FittingPolicyScroll(1 shl 7), - FittingPolicyMask_(FittingPolicyResizeDown or FittingPolicyScroll), - FittingPolicyDefault_(FittingPolicyResizeDown.i), + object FittingPolicyScroll : TabBarFlag(1 shl 7) // Private /** Part of a dock node [we don't use this in the master branch but it facilitate branch syncing to keep this around] */ - _DockNode(1 shl 20), - _IsFocused(1 shl 21), + object _DockNode : TabBarFlag(1 shl 20) + + object _IsFocused : TabBarFlag(1 shl 21) /** FIXME: Settings are handled by the docking system, this only request the tab bar to mark settings dirty when reordering tabs, */ - _SaveSettings(1 shl 22); + object _SaveSettings : TabBarFlag(1 shl 22) + + @GenSealedEnum + companion object { + val FittingPolicyMask: TabBarFlags get() = FittingPolicyResizeDown or FittingPolicyScroll + val FittingPolicyDefault: TabBarFlags get() = FittingPolicyResizeDown + } } -typealias TabItemFlags = Int +// Represents flags that work for TabButton (which includes TabItem flags) +typealias TabItemFlags = Flag> +// Represents flags for _only_ items +typealias TabItemOnlyFlags = Flag /** Flags for ImGui::BeginTabItem() */ -enum class TabItemFlag(override val i: TabItemFlags) : Flag { - None(0), +sealed class TabItemFlag>(override val i: Int) : FlagBase() { + sealed class Button(i: Int) : TabItemFlag