├── .editorconfig ├── .git-blame-ignore-revs ├── .gitattributes ├── .github ├── CONTRIBUTING.md ├── CONTRIBUTORS.md ├── FUNDING.yml ├── ktx-logo.png ├── ktx-logo.svg ├── repository-preview.png └── workflows │ ├── build.yml │ ├── publish-documentation.yml │ ├── publish-project-samples.yml │ └── upload-snapshot.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE.txt ├── README.md ├── actors ├── README.md ├── build.gradle.kts ├── gradle.properties └── src │ ├── main │ └── kotlin │ │ └── ktx │ │ └── actors │ │ ├── actions.kt │ │ ├── actors.kt │ │ ├── events.kt │ │ ├── stages.kt │ │ └── widgets.kt │ └── test │ └── kotlin │ └── ktx │ └── actors │ ├── ActionsTest.kt │ ├── ActorsTest.kt │ ├── EventsTest.kt │ ├── StagesTest.kt │ ├── WidgetsTest.kt │ └── utils.kt ├── ai ├── README.md ├── build.gradle.kts ├── gradle.properties └── src │ ├── main │ └── kotlin │ │ └── ktx │ │ └── ai │ │ └── behaviorTree.kt │ └── test │ ├── kotlin │ └── ktx │ │ └── ai │ │ ├── BehaviorTreeTest.kt │ │ ├── BranchTasksTest.kt │ │ ├── Cat.kt │ │ ├── DecoratorsTest.kt │ │ ├── EatTask.kt │ │ └── LeafTasksTest.kt │ └── resources │ └── mockito-extensions │ └── org.mockito.plugins.MockMaker ├── app ├── README.md ├── build.gradle.kts ├── gradle.properties └── src │ ├── main │ └── kotlin │ │ └── ktx │ │ └── app │ │ ├── Platform.kt │ │ ├── application.kt │ │ ├── exceptions.kt │ │ ├── game.kt │ │ ├── graphics.kt │ │ └── profiling.kt │ └── test │ └── kotlin │ └── ktx │ └── app │ ├── ExceptionsTest.kt │ ├── GraphicsTest.kt │ ├── PlatformTest.kt │ ├── ProfilingTest.kt │ ├── applicationTest.kt │ └── gameTest.kt ├── artemis ├── README.md ├── build.gradle.kts ├── gradle.properties └── src │ ├── main │ └── kotlin │ │ └── ktx │ │ └── artemis │ │ ├── archetype.kt │ │ ├── arrays.kt │ │ ├── aspect.kt │ │ ├── entity.kt │ │ ├── mapper.kt │ │ ├── transmuter.kt │ │ └── world.kt │ └── test │ └── kotlin │ └── ktx │ └── artemis │ ├── ArchetypeSpec.kt │ ├── AspectSpec.kt │ ├── Dead.kt │ ├── EntitySpec.kt │ ├── MapperSpec.kt │ ├── Position.kt │ ├── Remove.kt │ ├── RigidBody.kt │ ├── TestSystem.kt │ ├── Texture.kt │ ├── Transform.kt │ ├── TransmuterSpec.kt │ └── WorldSpec.kt ├── ashley ├── README.md ├── build.gradle.kts ├── gradle.properties └── src │ ├── main │ └── kotlin │ │ └── ktx │ │ └── ashley │ │ ├── AshleyDsl.kt │ │ ├── engines.kt │ │ ├── entities.kt │ │ ├── families.kt │ │ ├── listeners.kt │ │ ├── mappers.kt │ │ └── properties.kt │ └── test │ └── kotlin │ └── ktx │ └── ashley │ ├── ComponentMappersSpec.kt │ ├── ComponentPropertiesSpec.kt │ ├── ComponentTagSpec.kt │ ├── EnginesSpec.kt │ ├── EntitiesSpec.kt │ ├── FamiliesSpec.kt │ ├── ListenersSpec.kt │ ├── RigidBody.kt │ ├── TestSystem.kt │ ├── Texture.kt │ ├── Transform.kt │ └── listenerTest.kt ├── assets-async ├── README.md ├── build.gradle.kts ├── gradle.properties └── src │ ├── main │ └── kotlin │ │ └── ktx │ │ └── assets │ │ └── async │ │ ├── AssetManager.kt │ │ ├── AssetManagerWrapper.kt │ │ ├── LoadingProgress.kt │ │ ├── errors.kt │ │ ├── loaders.kt │ │ ├── manager.kt │ │ └── storage.kt │ └── test │ ├── kotlin │ └── ktx │ │ └── assets │ │ └── async │ │ ├── AssetStorageTest.kt │ │ ├── AsyncAssetManagerTest.kt │ │ ├── LoadingProgressTest.kt │ │ ├── assetStorageLoadingTest.kt │ │ └── loadersTest.kt │ └── resources │ ├── ktx │ └── assets │ │ └── async │ │ ├── corrupted.atlas │ │ ├── cubemap.zktx │ │ ├── i18n.properties │ │ ├── model.g3db │ │ ├── model.g3dj │ │ ├── model.obj │ │ ├── particle.p2d │ │ ├── particle.p3d │ │ ├── polygon.png │ │ ├── polygon.psh │ │ ├── shader.frag │ │ ├── shader.vert │ │ ├── skin.atlas │ │ ├── skin.json │ │ ├── sound.ogg │ │ ├── string.txt │ │ └── texture.png │ └── mockito-extensions │ └── org.mockito.plugins.MockMaker ├── assets ├── README.md ├── gradle.properties └── src │ ├── main │ └── kotlin │ │ └── ktx │ │ └── assets │ │ ├── TextAssetLoader.kt │ │ ├── assets.kt │ │ ├── disposables.kt │ │ ├── files.kt │ │ ├── pools.kt │ │ └── resolvers.kt │ └── test │ └── kotlin │ └── ktx │ └── assets │ ├── AssetsTest.kt │ ├── DisposablesTest.kt │ ├── FilesTest.kt │ ├── PoolsTest.kt │ ├── ResolversTest.kt │ ├── TextAssetLoaderTest.kt │ └── utils.kt ├── async ├── README.md ├── build.gradle.kts ├── gradle.properties └── src │ ├── main │ └── kotlin │ │ └── ktx │ │ └── async │ │ ├── async.kt │ │ ├── dispatchers.kt │ │ ├── http.kt │ │ └── timer.kt │ └── test │ ├── kotlin │ └── ktx │ │ └── async │ │ ├── AsyncTest.kt │ │ ├── KtxAsyncTest.kt │ │ ├── TimerTest.kt │ │ ├── dispatchersTest.kt │ │ └── httpTest.kt │ └── resources │ └── mockito-extensions │ └── org.mockito.plugins.MockMaker ├── box2d ├── README.md ├── build.gradle.kts ├── gradle.properties ├── img │ ├── box.png │ ├── chain.png │ ├── circle-custom.png │ ├── circle.png │ ├── edge.png │ ├── joint.png │ ├── loop.png │ ├── multiple.png │ └── polygon.png └── src │ ├── main │ └── kotlin │ │ └── ktx │ │ └── box2d │ │ ├── Box2DDsl.kt │ │ ├── bodies.kt │ │ ├── fixtures.kt │ │ ├── joints.kt │ │ └── worlds.kt │ └── test │ └── kotlin │ └── ktx │ └── box2d │ ├── BodiesTest.kt │ ├── Box2DTest.kt │ ├── FixturesTest.kt │ ├── JointsTest.kt │ └── WorldsTest.kt ├── build.gradle.kts ├── buildSrc ├── build.gradle.kts └── src │ └── main │ └── kotlin │ └── ktx │ └── Versions.kt ├── collections ├── README.md ├── gradle.properties └── src │ ├── main │ └── kotlin │ │ └── ktx │ │ └── collections │ │ ├── arrays.kt │ │ ├── maps.kt │ │ └── sets.kt │ └── test │ └── kotlin │ └── ktx │ └── collections │ ├── ArraysTest.kt │ ├── MapsTest.kt │ └── SetsTest.kt ├── freetype-async ├── README.md ├── build.gradle.kts ├── gradle.properties └── src │ ├── main │ └── kotlin │ │ └── ktx │ │ └── freetype │ │ └── async │ │ └── freetypeAsync.kt │ └── test │ ├── kotlin │ └── ktx │ │ └── freetype │ │ └── async │ │ └── freeTypeAsyncTest.kt │ └── resources │ └── ktx │ └── freetype │ └── async │ ├── hack.otf │ └── hack.ttf ├── freetype ├── README.md ├── build.gradle.kts ├── gradle.properties └── src │ ├── main │ └── kotlin │ │ └── ktx │ │ └── freetype │ │ └── freetype.kt │ └── test │ ├── kotlin │ └── ktx │ │ └── freetype │ │ └── FreeTypeTest.kt │ └── resources │ └── ktx │ └── freetype │ ├── hack.otf │ └── hack.ttf ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── graphics ├── README.md ├── build.gradle.kts ├── gradle.properties └── src │ ├── main │ └── kotlin │ │ └── ktx │ │ └── graphics │ │ ├── camera.kt │ │ ├── color.kt │ │ ├── graphics.kt │ │ ├── shapeRenderer.kt │ │ └── text.kt │ └── test │ └── kotlin │ └── ktx │ └── graphics │ ├── ColorTest.kt │ ├── GraphicsTest.kt │ ├── ShapeRendererTest.kt │ ├── TextUtilitiesTest.kt │ └── cameraTest.kt ├── i18n ├── README.md ├── gradle.properties └── src │ ├── main │ └── kotlin │ │ └── ktx │ │ └── i18n │ │ └── i18n.kt │ └── test │ └── kotlin │ └── ktx │ └── i18n │ └── I18nTest.kt ├── inject ├── README.md ├── build.gradle.kts ├── gradle.properties └── src │ ├── main │ └── kotlin │ │ └── ktx │ │ └── inject │ │ └── inject.kt │ └── test │ └── kotlin │ └── ktx │ └── inject │ └── injectTest.kt ├── json ├── README.md ├── build.gradle.kts ├── gradle.properties └── src │ ├── main │ └── kotlin │ │ └── ktx │ │ └── json │ │ ├── json.kt │ │ └── serializers.kt │ └── test │ ├── kotlin │ └── ktx │ │ └── json │ │ ├── JsonTest.kt │ │ └── serializersTest.kt │ └── resources │ └── ktx │ └── json │ └── file.json ├── log ├── README.md ├── gradle.properties └── src │ ├── main │ └── kotlin │ │ └── ktx │ │ └── log │ │ └── log.kt │ └── test │ └── kotlin │ └── ktx │ └── log │ └── LogTest.kt ├── math ├── README.md ├── build.gradle.kts ├── gradle.properties └── src │ ├── main │ └── kotlin │ │ └── ktx │ │ └── math │ │ ├── ImmutableVector.kt │ │ ├── ImmutableVector2.kt │ │ ├── circle.kt │ │ ├── ellipse.kt │ │ ├── matrix3.kt │ │ ├── matrix4.kt │ │ ├── polygon.kt │ │ ├── polyline.kt │ │ ├── ranges.kt │ │ ├── rectangle.kt │ │ ├── vector2.kt │ │ ├── vector3.kt │ │ └── vector4.kt │ └── test │ └── kotlin │ └── ktx │ └── math │ ├── CircleTest.kt │ ├── EllipseTest.kt │ ├── ImmutableVector2Test.kt │ ├── Matrix3Test.kt │ ├── Matrix4Test.kt │ ├── PolygonTest.kt │ ├── PolylineTest.kt │ ├── RangesTest.kt │ ├── RectangleTest.kt │ ├── Vector2Test.kt │ ├── Vector3Test.kt │ └── Vector4Test.kt ├── preferences ├── README.md ├── gradle.properties └── src │ ├── main │ └── kotlin │ │ └── ktx │ │ └── preferences │ │ └── preferences.kt │ └── test │ └── kotlin │ └── ktx │ └── preferences │ └── preferencesTest.kt ├── reflect ├── README.md ├── gradle.properties └── src │ ├── main │ └── kotlin │ │ └── ktx │ │ └── reflect │ │ └── reflect.kt │ └── test │ └── kotlin │ └── ktx │ └── reflect │ └── reflectTest.kt ├── scene2d ├── README.md ├── build.gradle.kts ├── gradle.properties ├── img │ ├── 00.png │ ├── 01.png │ ├── 02.png │ ├── 03.png │ ├── 04.png │ └── README.md └── src │ ├── main │ └── kotlin │ │ └── ktx │ │ └── scene2d │ │ ├── Scene2dDsl.kt │ │ ├── factory.kt │ │ ├── skin.kt │ │ ├── stage.kt │ │ ├── tooltip.kt │ │ └── widget.kt │ └── test │ └── kotlin │ └── ktx │ └── scene2d │ ├── Scene2DSkinTest.kt │ ├── StageWidgetTest.kt │ ├── TooltipTest.kt │ ├── factoryTest.kt │ ├── testUtilities.kt │ └── widgetTest.kt ├── script ├── README.md ├── build.gradle.kts ├── gradle.properties └── src │ ├── main │ └── kotlin │ │ └── ktx │ │ └── script │ │ └── script.kt │ └── test │ ├── kotlin │ └── ktx │ │ └── script │ │ └── KotlinScriptEngineTest.kt │ └── resources │ └── ktx │ └── script │ ├── broken.script │ ├── receiver │ └── script.kts ├── settings.gradle.kts ├── style ├── README.md ├── gradle.properties └── src │ ├── main │ └── kotlin │ │ └── ktx │ │ └── style │ │ └── style.kt │ └── test │ └── kotlin │ └── ktx │ └── style │ └── StyleTest.kt ├── tiled ├── README.md ├── gradle.properties └── src │ ├── main │ └── kotlin │ │ └── ktx │ │ └── tiled │ │ ├── batchTiledMapRenderer.kt │ │ ├── exceptions.kt │ │ ├── mapLayers.kt │ │ ├── mapObjects.kt │ │ ├── mapProperties.kt │ │ ├── tiledMapRenderer.kt │ │ ├── tiledMapTileSets.kt │ │ ├── tiledMapTiles.kt │ │ └── tiledMaps.kt │ └── test │ └── kotlin │ └── ktx │ └── tiled │ ├── MapLayerTest.kt │ ├── MapObjectTest.kt │ ├── MapPropertiesTest.kt │ ├── TiledMapRendererTest.kt │ ├── TiledMapTest.kt │ ├── TiledMapTileSetTest.kt │ └── tiledMapTilesTest.kt ├── version.txt ├── vis-style ├── README.md ├── build.gradle.kts ├── gradle.properties └── src │ ├── main │ └── kotlin │ │ └── ktx │ │ └── style │ │ └── visStyle.kt │ └── test │ └── kotlin │ └── ktx │ └── style │ └── VisStyleTest.kt └── vis ├── README.md ├── build.gradle.kts ├── gradle.properties ├── img ├── README.md ├── form.png ├── list.png ├── menu.png ├── tabs.png └── tree.png └── src ├── main └── kotlin │ └── ktx │ └── scene2d │ └── vis │ ├── factory.kt │ ├── menu.kt │ ├── tooltip.kt │ ├── validation.kt │ └── widget.kt └── test └── kotlin └── ktx └── scene2d └── vis ├── MenusTest.kt ├── TooltipsTest.kt ├── ValidationTest.kt ├── factoryTest.kt └── widgetTest.kt /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | charset = utf-8 7 | 8 | [*.{kt,kts}] 9 | indent_style = space 10 | indent_size = 2 11 | ktlint_standard_filename = disabled 12 | ktlint_standard_property-naming = disabled 13 | ktlint_code_style = ktlint_official 14 | -------------------------------------------------------------------------------- /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | # .git-blame-ignore-revs 2 | # Ktlint 0.50.0 -> 1.5.0 update: 3 | dc3b14bee634a5e25fee71e2fef856e2024b5deb 4 | 5 | # Ktlint 0.42.1 -> 0.50.0 update: 6 | 2e91decb0f3cea8fd7ab953639cae74f01ce8a04 7 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | *.bat text=auto eol=crlf 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [czyzby] 2 | # patreon: # Replace with a single Patreon username 3 | open_collective: ktx 4 | # ko_fi: # Replace with a single Ko-fi username 5 | # tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 6 | # community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 7 | # liberapay: # Replace with a single Liberapay username 8 | # issuehunt: # Replace with a single IssueHunt username 9 | # otechie: # Replace with a single Otechie username 10 | # lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 11 | # custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 12 | -------------------------------------------------------------------------------- /.github/ktx-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libktx/ktx/0f28adef8191a15a3c4f453a97fe15524fb3c8fd/.github/ktx-logo.png -------------------------------------------------------------------------------- /.github/repository-preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libktx/ktx/0f28adef8191a15a3c4f453a97fe15524fb3c8fd/.github/repository-preview.png -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: [ master, develop ] 6 | pull_request: 7 | branches: [ master, develop ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | java-version: [11, 17] 15 | 16 | steps: 17 | - name: Repository checkout 18 | uses: actions/checkout@v3 19 | - name: JDK setup 20 | uses: actions/setup-java@v3 21 | with: 22 | java-version: ${{ matrix.java-version }} 23 | distribution: temurin 24 | - name: Gradle setup 25 | uses: gradle/gradle-build-action@v2 26 | - name: Gradle wrapper validation 27 | uses: gradle/wrapper-validation-action@v1 28 | - name: Build 29 | run: ./gradlew assemble install -x dokkaHtml -x dokkaZip 30 | - name: Tests 31 | run: ./gradlew check --continue 32 | env: 33 | TEST_PROFILE: ci 34 | -------------------------------------------------------------------------------- /.github/workflows/publish-documentation.yml: -------------------------------------------------------------------------------- 1 | name: publish-documentation 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | workflow_dispatch: 7 | 8 | jobs: 9 | publish: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Repository checkout 14 | uses: actions/checkout@v3 15 | - name: JDK setup 16 | uses: actions/setup-java@v3 17 | with: 18 | java-version: 11 19 | distribution: temurin 20 | - name: Save version 21 | shell: bash 22 | run: | 23 | echo "KTX_VERSION=$(cat version.txt)" >> $GITHUB_ENV 24 | - name: Gradle setup 25 | uses: gradle/gradle-build-action@v2 26 | - name: Dokka 27 | run: ./gradlew dokkaHtmlMultiModule 28 | - name: Publish GitHub pages 29 | uses: JamesIves/github-pages-deploy-action@releases/v4 30 | with: 31 | token: ${{ secrets.accessToken }} 32 | branch: gh-pages 33 | folder: build/dokka/htmlMultiModule 34 | repository-name: libktx/docs 35 | commit-message: 'Documentation of KTX ${{ env.KTX_VERSION }}.' 36 | -------------------------------------------------------------------------------- /.github/workflows/publish-project-samples.yml: -------------------------------------------------------------------------------- 1 | name: publish-project-samples 2 | on: 3 | push: 4 | tags: [ '*' ] 5 | workflow_dispatch: 6 | 7 | jobs: 8 | publish: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - name: Repository checkout 13 | uses: actions/checkout@v3 14 | with: 15 | repository: tommyettinger/gdx-liftoff 16 | - name: JDK setup 17 | uses: actions/setup-java@v3 18 | with: 19 | java-version: 17 20 | distribution: temurin 21 | - name: Save versions 22 | shell: bash 23 | run: | 24 | echo "LIFTOFF_VERSION=$(cat version.txt)" >> $GITHUB_ENV 25 | echo "KTX_VERSION=$(curl https://raw.githubusercontent.com/libktx/ktx/master/version.txt)" >> $GITHUB_ENV 26 | - name: Gradle setup 27 | uses: gradle/gradle-build-action@v2 28 | - name: Generate sample 29 | run: ./gradlew sample --args="ktx" 30 | - name: Prepare sample for release 31 | run: | 32 | find build/dist/sample/ -type f \( -iname \*.kt -o -iname \*.gradle -o -iname \*.json -o -iname \*.xml \) | xargs sed -i -e 's/\t/ /g' 33 | - name: Publish KTX sample 34 | uses: JamesIves/github-pages-deploy-action@v4 35 | with: 36 | token: ${{ secrets.accessToken }} 37 | branch: main 38 | folder: build/dist/sample 39 | repository-name: libktx/ktx-sample-project 40 | commit-message: 'Automatic deployment: KTX ${{ env.KTX_VERSION }}, gdx-liftoff ${{ env.LIFTOFF_VERSION }}.' 41 | - name: Clean basic sample 42 | run: rm -rf build/dist/sample 43 | - name: Generate web sample 44 | run: ./gradlew sample --args="ktx_web" 45 | - name: Prepare sample for release 46 | run: | 47 | find build/dist/sample/ -type f \( -iname \*.kt -o -iname \*.gradle -o -iname \*.json -o -iname \*.xml \) | xargs sed -i -e 's/\t/ /g' 48 | - name: Publish KTX web sample 49 | uses: JamesIves/github-pages-deploy-action@v4 50 | with: 51 | token: ${{ secrets.accessToken }} 52 | branch: main 53 | folder: build/dist/sample 54 | repository-name: libktx/ktx-sample-web-project 55 | commit-message: 'Automatic deployment: KTX ${{ env.KTX_VERSION }}, gdx-liftoff ${{ env.LIFTOFF_VERSION }}.' 56 | -------------------------------------------------------------------------------- /.github/workflows/upload-snapshot.yml: -------------------------------------------------------------------------------- 1 | name: upload-snapshot 2 | 3 | on: 4 | push: 5 | branches: [ develop ] 6 | 7 | jobs: 8 | upload: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - name: Repository checkout 13 | uses: actions/checkout@v3 14 | - name: JDK setup 15 | uses: actions/setup-java@v3 16 | with: 17 | java-version: 11 18 | distribution: temurin 19 | - name: Gradle setup 20 | uses: gradle/gradle-build-action@v2 21 | - name: Tests 22 | run: ./gradlew check 23 | env: 24 | TEST_PROFILE: ci 25 | - name: Upload snapshot to Maven Central 26 | run: ./gradlew build uploadSnapshot -x dokkaHtml 27 | env: 28 | ORG_GRADLE_PROJECT_ossrhUsername: ${{ secrets.ossrhUsername }} 29 | ORG_GRADLE_PROJECT_ossrhPassword: ${{ secrets.ossrhPassword }} 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Based on default libGDX project settings. 2 | 3 | ## Java 4 | *.class 5 | *.war 6 | *.ear 7 | hs_err_pid* 8 | 9 | ## Android Studio, IntelliJ, Android 10 | android/libs/armeabi/ 11 | android/libs/armeabi-v7a/ 12 | android/libs/x86/ 13 | android/gen/ 14 | .idea/ 15 | *.ipr 16 | *.iws 17 | *.iml 18 | *.so 19 | out/ 20 | com_crashlytics_export_strings.xml 21 | 22 | ## Eclipse 23 | .classpath 24 | .project 25 | .metadata 26 | **/bin/ 27 | tmp/ 28 | *.tmp 29 | *.bak 30 | *.swp 31 | *~.nib 32 | local.properties 33 | .settings/ 34 | .loadpath 35 | .externalToolBuilders/ 36 | *.launch 37 | 38 | ## Gradle 39 | .gradle 40 | gradle-app.setting 41 | build/ 42 | 43 | ## OS-specific, others 44 | .DS_Store 45 | TODO 46 | 47 | -------------------------------------------------------------------------------- /actors/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import ktx.* 2 | 3 | dependencies { 4 | testImplementation("com.badlogicgames.gdx:gdx-backend-lwjgl:$gdxVersion") 5 | testImplementation("com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-desktop") 6 | } 7 | -------------------------------------------------------------------------------- /actors/gradle.properties: -------------------------------------------------------------------------------- 1 | projectName=ktx-actors 2 | projectDesc=General Scene2D utilities for libGDX applications written with Kotlin. 3 | -------------------------------------------------------------------------------- /actors/src/main/kotlin/ktx/actors/stages.kt: -------------------------------------------------------------------------------- 1 | package ktx.actors 2 | 3 | import com.badlogic.gdx.Gdx 4 | import com.badlogic.gdx.graphics.OrthographicCamera 5 | import com.badlogic.gdx.graphics.g2d.Batch 6 | import com.badlogic.gdx.graphics.g2d.SpriteBatch 7 | import com.badlogic.gdx.scenes.scene2d.Stage 8 | import com.badlogic.gdx.utils.Scaling.stretch 9 | import com.badlogic.gdx.utils.viewport.ScalingViewport 10 | import com.badlogic.gdx.utils.viewport.Viewport 11 | 12 | /** 13 | * Allows to leverage named and default parameters to initiate a custom [Stage]. [batch] will be used to render 14 | * the [Stage], while [viewport]'s camera will affect how the [Stage] is rendered. If any of the parameters are 15 | * not given, the used default values match [Stage] no-arg constructor. 16 | */ 17 | fun stage( 18 | batch: Batch = SpriteBatch(), 19 | viewport: Viewport = getDefaultViewport(), 20 | ) = Stage(viewport, batch) 21 | 22 | /** 23 | * Returns an instance of [Viewport] compatible with the [Stage] default constructor. 24 | */ 25 | private fun getDefaultViewport() = 26 | ScalingViewport( 27 | stretch, 28 | Gdx.graphics.width.toFloat(), 29 | Gdx.graphics.height.toFloat(), 30 | OrthographicCamera(), 31 | ) 32 | -------------------------------------------------------------------------------- /actors/src/main/kotlin/ktx/actors/widgets.kt: -------------------------------------------------------------------------------- 1 | package ktx.actors 2 | 3 | import com.badlogic.gdx.scenes.scene2d.ui.Label 4 | import com.badlogic.gdx.scenes.scene2d.ui.TextButton 5 | 6 | /** 7 | * Allows to access and modify [Label] text with idiomatic Kotlin property syntax. 8 | */ 9 | inline var Label.txt: String 10 | get() = text.toString() 11 | set(value) = setText(value) 12 | 13 | /** 14 | * Allows to access and modify [TextButton] text with idiomatic Kotlin property syntax. 15 | */ 16 | inline var TextButton.txt: String 17 | get() = text.toString() 18 | set(value) = setText(value) 19 | -------------------------------------------------------------------------------- /actors/src/test/kotlin/ktx/actors/WidgetsTest.kt: -------------------------------------------------------------------------------- 1 | package ktx.actors 2 | 3 | import com.badlogic.gdx.Gdx 4 | import com.badlogic.gdx.backends.lwjgl.LwjglFiles 5 | import com.badlogic.gdx.backends.lwjgl.LwjglNativesLoader 6 | import com.badlogic.gdx.graphics.Color 7 | import com.badlogic.gdx.graphics.g2d.BitmapFont 8 | import com.badlogic.gdx.scenes.scene2d.ui.Label 9 | import com.badlogic.gdx.scenes.scene2d.ui.Label.LabelStyle 10 | import com.badlogic.gdx.scenes.scene2d.ui.TextButton 11 | import com.badlogic.gdx.scenes.scene2d.ui.TextButton.TextButtonStyle 12 | import org.junit.Assert.assertEquals 13 | import org.junit.Before 14 | import org.junit.Test 15 | import org.mockito.kotlin.mock 16 | 17 | class WidgetsTest { 18 | @Before 19 | fun `initiate libGDX`() { 20 | // Mocking BitmapFont is tedious, if not next to impossible, because of direct fields usage in related classes 21 | // constructors. Label will not successfully construct an instance without a BitmapFont. 22 | LwjglNativesLoader.load() 23 | 24 | Gdx.graphics = mock() 25 | Gdx.app = mock() 26 | Gdx.gl20 = mock() 27 | Gdx.files = LwjglFiles() 28 | Gdx.gl = Gdx.gl20 29 | } 30 | 31 | @Test 32 | fun `should read text property of a Label`() { 33 | val label = Label("text", LabelStyle(BitmapFont(), Color.BLACK)) 34 | 35 | assertEquals("text", label.txt) 36 | } 37 | 38 | @Test 39 | fun `should write text property of a Label`() { 40 | val label = Label("text", LabelStyle(BitmapFont(), Color.BLACK)) 41 | 42 | label.txt = "replaced" 43 | 44 | val text = label.text.toString() 45 | assertEquals("replaced", text) 46 | } 47 | 48 | @Test 49 | fun `should read text property of a TextButton`() { 50 | val button = TextButton("text", TextButtonStyle().apply { font = BitmapFont() }) 51 | 52 | assertEquals("text", button.txt) 53 | } 54 | 55 | @Test 56 | fun `should write text property of a TextButton`() { 57 | val button = TextButton("text", TextButtonStyle().apply { font = BitmapFont() }) 58 | 59 | button.txt = "replaced" 60 | 61 | val text = button.text.toString() 62 | assertEquals("replaced", text) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /actors/src/test/kotlin/ktx/actors/utils.kt: -------------------------------------------------------------------------------- 1 | package ktx.actors 2 | 3 | import com.badlogic.gdx.Gdx 4 | import com.badlogic.gdx.scenes.scene2d.Stage 5 | import com.badlogic.gdx.utils.viewport.Viewport 6 | import org.mockito.kotlin.doReturn 7 | import org.mockito.kotlin.mock 8 | 9 | /** @return [Stage] with mocked viewport and batch. */ 10 | internal fun getMockStage( 11 | viewportWidth: Float = 800f, 12 | viewportHeight: Float = 600f, 13 | ): Stage { 14 | Gdx.graphics = mock() // Referenced by Stage constructor. 15 | val viewport = 16 | mock { 17 | on(it.worldWidth) doReturn viewportWidth 18 | on(it.worldHeight) doReturn viewportHeight 19 | } 20 | return Stage(viewport, mock()) 21 | } 22 | -------------------------------------------------------------------------------- /ai/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import ktx.* 2 | 3 | dependencies { 4 | api("com.badlogicgames.gdx:gdx-ai:$gdxAiVersion") 5 | testImplementation("com.badlogicgames.gdx:gdx-backend-lwjgl:$gdxVersion") 6 | testImplementation("com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-desktop") 7 | } 8 | -------------------------------------------------------------------------------- /ai/gradle.properties: -------------------------------------------------------------------------------- 1 | projectName=ktx-ai 2 | projectDesc=gdxAI utilities for Kotlin applications. 3 | -------------------------------------------------------------------------------- /ai/src/test/kotlin/ktx/ai/BehaviorTreeTest.kt: -------------------------------------------------------------------------------- 1 | package ktx.ai 2 | 3 | import com.badlogic.gdx.ai.btree.BehaviorTree 4 | import com.badlogic.gdx.ai.btree.Task 5 | import io.kotlintest.mock.mock 6 | import org.junit.Assert.assertEquals 7 | import org.junit.Assert.assertNotNull 8 | import org.junit.Test 9 | 10 | class BehaviorTreeTest { 11 | @Test 12 | fun `behaviorTree function without arguments should return BehaviorTree object`() { 13 | val behaviorTree = behaviorTree() 14 | 15 | assertNotNull(behaviorTree) 16 | } 17 | 18 | @Test 19 | fun `behaviorTree function with non-null arguments should return BehaviorTree object with correct properties`() { 20 | val rootTask = mock>() 21 | val blackboard = mock() 22 | val tree = behaviorTree(rootTask, blackboard) 23 | 24 | assertNotNull(tree) 25 | assertEquals(rootTask, tree.getChild(0)) 26 | assertEquals(blackboard, tree.`object`) 27 | } 28 | 29 | @Test 30 | fun `behaviorTree function init block should configure BehaviorTree exactly once`() { 31 | val variable: Int 32 | 33 | behaviorTree { 34 | variable = 42 35 | } 36 | 37 | assertEquals(42, variable) 38 | } 39 | 40 | @Test 41 | fun `add function should add task to BehaviorTree`() { 42 | val tree = BehaviorTree() 43 | val initialChildCount = tree.childCount 44 | 45 | val task = mock>() 46 | tree.add(task) 47 | 48 | assertEquals(initialChildCount + 1, tree.childCount) 49 | } 50 | 51 | @Test 52 | fun `add function should return index where task is added`() { 53 | val selector = GdxAiSelector() 54 | val index1 = selector.add(mock>()) 55 | val index2 = selector.add(mock>()) 56 | 57 | assertEquals(0, index1) 58 | assertEquals(1, index2) 59 | } 60 | 61 | @Test 62 | fun `add function inside of behaviorTree function's init block should add task to BehaviorTree`() { 63 | val initialChildCount: Int 64 | 65 | val tree = 66 | behaviorTree { 67 | initialChildCount = childCount 68 | add(mock>()) 69 | } 70 | 71 | assertEquals(initialChildCount + 1, tree.childCount) 72 | } 73 | 74 | @Test 75 | fun `add function's init block allows editing properties of Task subclass`() { 76 | val receiverTask = mock>() 77 | 78 | val eatTask = EatTask(hunger = 1) 79 | val initialHunger = eatTask.hunger 80 | 81 | receiverTask.add(eatTask) { 82 | hunger -= 1 83 | } 84 | 85 | assertEquals(initialHunger - 1, eatTask.hunger) 86 | } 87 | 88 | @Test 89 | fun `add function's init block should configure Task exactly once`() { 90 | val variable: Int 91 | val receiverTask = mock>() 92 | val task = mock>() 93 | 94 | receiverTask.add(task) { 95 | variable = 42 96 | } 97 | 98 | assertEquals(42, variable) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /ai/src/test/kotlin/ktx/ai/Cat.kt: -------------------------------------------------------------------------------- 1 | package ktx.ai 2 | 3 | class Cat 4 | -------------------------------------------------------------------------------- /ai/src/test/kotlin/ktx/ai/EatTask.kt: -------------------------------------------------------------------------------- 1 | package ktx.ai 2 | 3 | import com.badlogic.gdx.ai.btree.LeafTask 4 | import com.badlogic.gdx.ai.btree.Task 5 | import com.badlogic.gdx.ai.btree.annotation.TaskAttribute 6 | 7 | class EatTask( 8 | @JvmField 9 | @TaskAttribute(required = true) 10 | var hunger: Int, 11 | ) : LeafTask() { 12 | override fun copyTo(task: Task): Task = this 13 | 14 | override fun execute(): Status = Status.SUCCEEDED 15 | } 16 | -------------------------------------------------------------------------------- /ai/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker: -------------------------------------------------------------------------------- 1 | mock-maker-inline 2 | -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import ktx.* 2 | 3 | dependencies { 4 | testImplementation("com.badlogicgames.gdx:gdx-backend-lwjgl:$gdxVersion") 5 | testImplementation("com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-desktop") 6 | } 7 | -------------------------------------------------------------------------------- /app/gradle.properties: -------------------------------------------------------------------------------- 1 | projectName=ktx-app 2 | projectDesc=libGDX general utilities for Kotlin applications. 3 | -------------------------------------------------------------------------------- /app/src/main/kotlin/ktx/app/application.kt: -------------------------------------------------------------------------------- 1 | package ktx.app 2 | 3 | import com.badlogic.gdx.ApplicationListener 4 | import com.badlogic.gdx.InputProcessor 5 | import com.badlogic.gdx.utils.Disposable 6 | 7 | /** 8 | * Wrapping interface around [com.badlogic.gdx.ApplicationListener]. Provides no-op implementations of all methods, 9 | * making them optional to implement. 10 | * 11 | * Explicitly extends the [Disposable] interface, matching the [ApplicationListener.dispose] method, 12 | * which allows to leverage [Disposable] utilities. 13 | */ 14 | interface KtxApplicationAdapter : 15 | ApplicationListener, 16 | Disposable { 17 | override fun resize( 18 | width: Int, 19 | height: Int, 20 | ) = Unit 21 | 22 | override fun create() = Unit 23 | 24 | override fun render() = Unit 25 | 26 | override fun resume() = Unit 27 | 28 | override fun dispose() = Unit 29 | 30 | override fun pause() = Unit 31 | } 32 | 33 | /** 34 | * Wrapping interface around [com.badlogic.gdx.InputProcessor]. Provides empty implementations of all methods, 35 | * making them optional to implement. 36 | */ 37 | interface KtxInputAdapter : InputProcessor { 38 | override fun touchUp( 39 | screenX: Int, 40 | screenY: Int, 41 | pointer: Int, 42 | button: Int, 43 | ) = false 44 | 45 | override fun keyDown(keycode: Int) = false 46 | 47 | override fun keyTyped(character: Char) = false 48 | 49 | override fun keyUp(keycode: Int) = false 50 | 51 | override fun mouseMoved( 52 | screenX: Int, 53 | screenY: Int, 54 | ) = false 55 | 56 | override fun scrolled( 57 | amountX: Float, 58 | amountY: Float, 59 | ) = false 60 | 61 | override fun touchDown( 62 | screenX: Int, 63 | screenY: Int, 64 | pointer: Int, 65 | button: Int, 66 | ) = false 67 | 68 | override fun touchDragged( 69 | screenX: Int, 70 | screenY: Int, 71 | pointer: Int, 72 | ) = false 73 | 74 | /** Calls [InputProcessor.touchUp] for consistency with libGDX 1.11.0 and older. Override for custom handling. */ 75 | override fun touchCancelled( 76 | screenX: Int, 77 | screenY: Int, 78 | pointer: Int, 79 | button: Int, 80 | ) = touchUp(screenX, screenY, pointer, button) 81 | } 82 | -------------------------------------------------------------------------------- /app/src/main/kotlin/ktx/app/exceptions.kt: -------------------------------------------------------------------------------- 1 | package ktx.app 2 | 3 | import com.badlogic.gdx.utils.GdxRuntimeException 4 | 5 | /** 6 | * Throws a [GdxRuntimeException]. The [message] will be converted to string and passed as the exception message. 7 | * The [cause] is an optional exception cause. See also: [error]. 8 | */ 9 | @Suppress("NOTHING_TO_INLINE") 10 | inline fun gdxError( 11 | message: Any? = null, 12 | cause: Throwable? = null, 13 | ): Nothing = throw GdxRuntimeException(message.toString(), cause) 14 | -------------------------------------------------------------------------------- /app/src/main/kotlin/ktx/app/graphics.kt: -------------------------------------------------------------------------------- 1 | package ktx.app 2 | 3 | import com.badlogic.gdx.utils.ScreenUtils 4 | 5 | /** 6 | * Clears current screen with the selected color. Inlined to lower the total method count. Assumes alpha is 1f. 7 | * Clears depth by default. 8 | * @param red red color value. 9 | * @param green green color value. 10 | * @param blue blue color value. 11 | * @param alpha color alpha value. Optional, defaults to 1f (non-transparent). 12 | * @param clearDepth adds the GL_DEPTH_BUFFER_BIT mask if true. 13 | * @see ScreenUtils.clear 14 | */ 15 | @Suppress("NOTHING_TO_INLINE") 16 | inline fun clearScreen( 17 | red: Float, 18 | green: Float, 19 | blue: Float, 20 | alpha: Float = 1f, 21 | clearDepth: Boolean = true, 22 | ) { 23 | ScreenUtils.clear(red, green, blue, alpha, clearDepth) 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/kotlin/ktx/app/profiling.kt: -------------------------------------------------------------------------------- 1 | package ktx.app 2 | 3 | import com.badlogic.gdx.Gdx 4 | import com.badlogic.gdx.utils.PerformanceCounter 5 | 6 | /** 7 | * Profiles the given [operation] using a [PerformanceCounter]. 8 | * The operation will be repeated [repeats] times to gather the performance data. 9 | * [PerformanceCounter.tick] will be called after each operation. 10 | * [repeats] will be used to set the window size of the [PerformanceCounter]. 11 | * If [printResults] is set to true, a short summary will be printed by the application. 12 | * 13 | * [PerformanceCounter] used for the profiling will be returned, so that the profiling 14 | * data can be analyzed and further tests can be performed. Note that to perform further 15 | * profiling with this [PerformanceCounter] of a different operation, 16 | * [PerformanceCounter.reset] should be called. 17 | */ 18 | inline fun profile( 19 | name: String = "Profiler", 20 | repeats: Int = 10, 21 | printResults: Boolean = true, 22 | operation: () -> Unit, 23 | ): PerformanceCounter { 24 | val performanceCounter = PerformanceCounter(name, repeats) 25 | performanceCounter.profile(repeats, printResults, operation) 26 | return performanceCounter 27 | } 28 | 29 | /** 30 | * Profiles the given [operation] using this [PerformanceCounter]. 31 | * The operation will be repeated [repeats] times to gather the performance data. 32 | * [PerformanceCounter.tick] will be called after each operation. 33 | * By default, [repeats] is set to the window size passed to the [PerformanceCounter] 34 | * constructor or 10 if the window size is set to 1. 35 | * If [printResults] is set to true, a short summary will be printed by the application. 36 | * 37 | * Note that to perform further profiling with this [PerformanceCounter] of a different 38 | * operation, [PerformanceCounter.reset] should be called. 39 | */ 40 | inline fun PerformanceCounter.profile( 41 | repeats: Int = if (time.mean != null) time.mean.windowSize else 10, 42 | printResults: Boolean = true, 43 | operation: () -> Unit, 44 | ) { 45 | if (this.time.count == 0) tick() 46 | repeat(repeats) { 47 | this.start() 48 | operation() 49 | this.stop() 50 | this.tick() 51 | } 52 | if (printResults) { 53 | prettyPrint() 54 | } 55 | } 56 | 57 | /** 58 | * Logs profiling information of this [PerformanceCounter] as an organized block. 59 | * Uses passed [decimalFormat] to format floating point numbers. 60 | */ 61 | fun PerformanceCounter.prettyPrint(decimalFormat: String = "%.6fs") { 62 | Gdx.app.log(name, "--------------------------------------------") 63 | Gdx.app.log(name, "Number of repeats: ${time.count}") 64 | val mean = time.mean 65 | val minimum: Float 66 | val maximum: Float 67 | if (mean != null && mean.hasEnoughData()) { 68 | Gdx.app.log( 69 | name, 70 | "Average OP time: ${decimalFormat.format(mean.mean)} " + 71 | "± ${decimalFormat.format(mean.standardDeviation())}", 72 | ) 73 | minimum = mean.lowest 74 | maximum = mean.highest 75 | } else { 76 | Gdx.app.log(name, "Average OP time: ${decimalFormat.format(time.average)}") 77 | minimum = time.min 78 | maximum = time.max 79 | } 80 | Gdx.app.log(name, "Minimum OP time: ${decimalFormat.format(minimum)}") 81 | Gdx.app.log(name, "Maximum OP time: ${decimalFormat.format(maximum)}") 82 | Gdx.app.log(name, "--------------------------------------------") 83 | } 84 | -------------------------------------------------------------------------------- /app/src/test/kotlin/ktx/app/ExceptionsTest.kt: -------------------------------------------------------------------------------- 1 | package ktx.app 2 | 3 | import com.badlogic.gdx.utils.GdxRuntimeException 4 | import io.kotlintest.matchers.shouldThrow 5 | import org.junit.Assert.assertEquals 6 | import org.junit.Assert.assertSame 7 | import org.junit.Test 8 | 9 | class ExceptionsTest { 10 | @Test 11 | fun `should throw error`() { 12 | shouldThrow { 13 | gdxError() 14 | } 15 | } 16 | 17 | @Test 18 | fun `should throw error given a null message`() { 19 | val exception = 20 | shouldThrow { 21 | gdxError(null) 22 | } 23 | 24 | assertEquals("null", exception.message) 25 | } 26 | 27 | @Test 28 | fun `should throw error given a string message`() { 29 | val exception = 30 | shouldThrow { 31 | gdxError("Test") 32 | } 33 | 34 | assertEquals("Test", exception.message) 35 | } 36 | 37 | @Test 38 | fun `should throw error given an object message`() { 39 | val exception = 40 | shouldThrow { 41 | gdxError(42) 42 | } 43 | 44 | assertEquals("42", exception.message) 45 | } 46 | 47 | @Test 48 | fun `should throw error given a cause`() { 49 | val cause = Exception() 50 | 51 | val exception = 52 | shouldThrow { 53 | gdxError("Message", cause) 54 | } 55 | 56 | assertEquals("Message", exception.message) 57 | assertSame(cause, exception.cause) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app/src/test/kotlin/ktx/app/GraphicsTest.kt: -------------------------------------------------------------------------------- 1 | package ktx.app 2 | 3 | import com.badlogic.gdx.Gdx 4 | import com.badlogic.gdx.graphics.GL20 5 | import org.junit.Test 6 | import org.mockito.kotlin.mock 7 | import org.mockito.kotlin.verify 8 | 9 | /** 10 | * Tests general utilities related to libGDX graphics API. 11 | */ 12 | class GraphicsTest { 13 | @Test 14 | fun `should clear screen`() { 15 | Gdx.gl = mock() 16 | 17 | clearScreen(0.25f, 0.5f, 0.75f, 0.6f) 18 | 19 | verify(Gdx.gl).glClearColor(0.25f, 0.5f, 0.75f, 0.6f) 20 | verify(Gdx.gl).glClear(GL20.GL_COLOR_BUFFER_BIT or GL20.GL_DEPTH_BUFFER_BIT) 21 | } 22 | 23 | @Test 24 | fun `should clear screen with optional alpha`() { 25 | Gdx.gl = mock() 26 | 27 | clearScreen(0.25f, 0.5f, 0.75f) 28 | 29 | verify(Gdx.gl).glClearColor(0.25f, 0.5f, 0.75f, 1f) 30 | verify(Gdx.gl).glClear(GL20.GL_COLOR_BUFFER_BIT or GL20.GL_DEPTH_BUFFER_BIT) 31 | } 32 | 33 | @Test 34 | fun `should clear screen without the depth buffer`() { 35 | Gdx.gl = mock() 36 | 37 | clearScreen(0.25f, 0.5f, 0.75f, alpha = 0.5f, clearDepth = false) 38 | 39 | verify(Gdx.gl).glClearColor(0.25f, 0.5f, 0.75f, 0.5f) 40 | verify(Gdx.gl).glClear(GL20.GL_COLOR_BUFFER_BIT) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/src/test/kotlin/ktx/app/ProfilingTest.kt: -------------------------------------------------------------------------------- 1 | package ktx.app 2 | 3 | import com.badlogic.gdx.Gdx 4 | import com.badlogic.gdx.utils.PerformanceCounter 5 | import org.junit.After 6 | import org.junit.Assert.assertEquals 7 | import org.junit.Before 8 | import org.junit.Test 9 | import org.mockito.kotlin.mock 10 | 11 | class ProfilingTest { 12 | @Before 13 | fun `initiate libGDX`() { 14 | Gdx.app = mock() 15 | } 16 | 17 | @Test 18 | fun `should profile operation`() { 19 | var repeats = 0 20 | 21 | val performanceCounter = 22 | profile(name = "Thread.sleep", repeats = 10) { 23 | repeats++ 24 | Thread.sleep(10L) 25 | } 26 | 27 | assertEquals("Thread.sleep", performanceCounter.name) 28 | assertEquals(10, performanceCounter.time.mean.windowSize) 29 | assertEquals(10, performanceCounter.time.count) 30 | assertEquals(10, repeats) 31 | assertEquals(0.01f, performanceCounter.time.mean.mean, 0.002f) 32 | } 33 | 34 | @Test 35 | fun `should profile operation with existing PerformanceCounter`() { 36 | val performanceCounter = PerformanceCounter("Thread.sleep", 10) 37 | var repeats = 0 38 | 39 | performanceCounter.profile { 40 | repeats++ 41 | Thread.sleep(10L) 42 | } 43 | 44 | assertEquals("Thread.sleep", performanceCounter.name) 45 | assertEquals(10, performanceCounter.time.count) 46 | assertEquals(10, repeats) 47 | assertEquals(0.01f, performanceCounter.time.mean.mean, 0.002f) 48 | } 49 | 50 | @After 51 | fun `destroy libGDX`() { 52 | Gdx.app = null 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/src/test/kotlin/ktx/app/applicationTest.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("ktlint:standard:class-naming") 2 | 3 | package ktx.app 4 | 5 | @Suppress("unused") 6 | class `should implement KtxApplicationAdapter with no methods overridden` : KtxApplicationAdapter { 7 | // Guarantees all KtxApplicationAdapter methods are optional to implement. 8 | } 9 | 10 | @Suppress("unused") 11 | class `should implement KtxInputAdapter with no methods overridden` : KtxInputAdapter { 12 | // Guarantees all KtxInputAdapter methods are optional to implement. 13 | } 14 | -------------------------------------------------------------------------------- /artemis/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import ktx.* 2 | 3 | val junitPlatformVersion: String by project 4 | 5 | dependencies { 6 | api("net.onedaybeard.artemis:artemis-odb:$artemisOdbVersion") 7 | 8 | testImplementation("org.jetbrains.spek:spek-api:$spekVersion") 9 | testImplementation("org.assertj:assertj-core:$assertjVersion") 10 | 11 | testRuntimeOnly("org.junit.platform:junit-platform-launcher:$junitPlatformVersion") 12 | testRuntimeOnly("org.jetbrains.spek:spek-junit-platform-engine:$spekVersion") 13 | } 14 | 15 | tasks.withType { 16 | testLogging { 17 | showExceptions = true 18 | events("FAILED", "SKIPPED") 19 | } 20 | 21 | useJUnitPlatform { 22 | includeEngines("spek") 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /artemis/gradle.properties: -------------------------------------------------------------------------------- 1 | projectName=ktx-artemis 2 | projectDesc=Artemis-odb entity component system utilities for Kotlin libGDX applications. 3 | -------------------------------------------------------------------------------- /artemis/src/main/kotlin/ktx/artemis/archetype.kt: -------------------------------------------------------------------------------- 1 | package ktx.artemis 2 | 3 | import com.artemis.Archetype 4 | import com.artemis.ArchetypeBuilder 5 | import com.artemis.Component 6 | import kotlin.reflect.KClass 7 | 8 | /** 9 | * Adds a [Component] to an [ArchetypeBuilder]. 10 | * 11 | * @receiver the [ArchetypeBuilder] for creating an [Archetype]. 12 | * @param T the component to add to the [ArchetypeBuilder]. 13 | * @return the [ArchetypeBuilder]. 14 | */ 15 | inline fun ArchetypeBuilder.add(): ArchetypeBuilder = add(T::class.java) 16 | 17 | /** 18 | * Removes a [Component] from an [ArchetypeBuilder]. 19 | * 20 | * @receiver the [ArchetypeBuilder] for creating an [Archetype]. 21 | * @param T the component to remove from the [ArchetypeBuilder]. 22 | * @return the [ArchetypeBuilder]. 23 | */ 24 | inline fun ArchetypeBuilder.remove(): ArchetypeBuilder = remove(T::class.java) 25 | 26 | /** 27 | * Adds multiple components to an [ArchetypeBuilder]. 28 | * 29 | * @receiver the [ArchetypeBuilder] for creating an [Archetype]. 30 | * @param components the components to add to the [ArchetypeBuilder]. 31 | * @return the [ArchetypeBuilder]. 32 | */ 33 | fun ArchetypeBuilder.add(vararg components: KClass): ArchetypeBuilder = add(*components.asJavaClasses()) 34 | 35 | /** 36 | * Removes multiple components from an [ArchetypeBuilder]. 37 | * 38 | * @receiver the [ArchetypeBuilder] for creating an [Archetype]. 39 | * @param components - the components to remove from the [ArchetypeBuilder] 40 | * @return the [ArchetypeBuilder]. 41 | */ 42 | fun ArchetypeBuilder.remove(vararg components: KClass): ArchetypeBuilder = remove(*components.asJavaClasses()) 43 | -------------------------------------------------------------------------------- /artemis/src/main/kotlin/ktx/artemis/arrays.kt: -------------------------------------------------------------------------------- 1 | package ktx.artemis 2 | 3 | import kotlin.reflect.KClass 4 | 5 | internal fun Array>.asJavaClasses(): Array> = Array(size) { index -> this[index].java } 6 | -------------------------------------------------------------------------------- /artemis/src/main/kotlin/ktx/artemis/aspect.kt: -------------------------------------------------------------------------------- 1 | package ktx.artemis 2 | 3 | import com.artemis.Aspect 4 | import com.artemis.Component 5 | import kotlin.reflect.KClass 6 | 7 | /** 8 | * Includes entities to an [Aspect] if they have at least one of the specified components. 9 | * 10 | * @param components one of the components the entities must have. 11 | * @return an [Aspect.Builder] for the [Aspect]. 12 | */ 13 | fun oneOf(vararg components: KClass): Aspect.Builder = Aspect.one(*components.asJavaClasses()) 14 | 15 | /** 16 | * Includes entities to an [Aspect] if they have all the specified components. 17 | * 18 | * @param components all the components the entities must have. 19 | * @return an [Aspect.Builder] for the [Aspect]. 20 | */ 21 | fun allOf(vararg components: KClass): Aspect.Builder = Aspect.all(*components.asJavaClasses()) 22 | 23 | /** 24 | * Excludes entities from an [Aspect] if they have at least one of the specified components. 25 | * 26 | * @param components all the components the entities cannot have. 27 | * @return an [Aspect.Builder] for the [Aspect]. 28 | */ 29 | fun exclude(vararg components: KClass): Aspect.Builder = Aspect.exclude(*components.asJavaClasses()) 30 | 31 | /** 32 | * Includes entities to an [Aspect] if they have at least one of the specified components. 33 | * 34 | * @receiver the [Aspect.Builder] for creating an [Aspect]. 35 | * @param components one of the components the entities must have. 36 | * @return an [Aspect.Builder] for the [Aspect]. 37 | */ 38 | fun Aspect.Builder.oneOf(vararg components: KClass): Aspect.Builder = one(*components.asJavaClasses()) 39 | 40 | /** 41 | * Includes entities to an [Aspect] if they have all the specified components. 42 | * 43 | * @receiver the [Aspect.Builder] for creating an [Aspect]. 44 | * @param components all the components the entities must have. 45 | * @return an [Aspect.Builder] for the [Aspect]. 46 | */ 47 | fun Aspect.Builder.allOf(vararg components: KClass): Aspect.Builder = all(*components.asJavaClasses()) 48 | 49 | /** 50 | * Excludes entities from an [Aspect] if they have at least one of the specified components. 51 | * 52 | * @receiver the [Aspect.Builder] for creating an [Aspect]. 53 | * @param components all the components the entities cannot have. 54 | * @return an [Aspect.Builder] for the [Aspect]. 55 | */ 56 | fun Aspect.Builder.exclude(vararg components: KClass): Aspect.Builder = exclude(*components.asJavaClasses()) 57 | -------------------------------------------------------------------------------- /artemis/src/main/kotlin/ktx/artemis/entity.kt: -------------------------------------------------------------------------------- 1 | package ktx.artemis 2 | 3 | import com.artemis.Archetype 4 | import com.artemis.Component 5 | import com.artemis.EntityEdit 6 | import com.artemis.World 7 | import kotlin.contracts.ExperimentalContracts 8 | import kotlin.contracts.InvocationKind 9 | import kotlin.contracts.contract 10 | 11 | /** 12 | * Creates and adds an entity to the [World]. 13 | * 14 | * @receiver the [World] for creating the entity. 15 | * @param entityEdit the inlined function with [EntityEdit]. 16 | * @return the entity's ID as [Int]. 17 | */ 18 | @OptIn(ExperimentalContracts::class) 19 | inline fun World.entity(entityEdit: EntityEdit.() -> Unit = {}): Int { 20 | contract { callsInPlace(entityEdit, InvocationKind.EXACTLY_ONCE) } 21 | val entity = create() 22 | 23 | edit(entity).entityEdit() 24 | 25 | return entity 26 | } 27 | 28 | /** 29 | * Creates and adds an entity to the [World]. 30 | * 31 | * @receiver the [World] for creating the entity. 32 | * @param archetype the [Archetype] to add to the entity. 33 | * @param entityEdit the inlined function with the [EntityEdit]. 34 | * @return the entity's ID as [Int]. 35 | */ 36 | @OptIn(ExperimentalContracts::class) 37 | inline fun World.entity( 38 | archetype: Archetype, 39 | entityEdit: EntityEdit.() -> Unit = {}, 40 | ): Int { 41 | contract { callsInPlace(entityEdit, InvocationKind.EXACTLY_ONCE) } 42 | val entity = this.create(archetype) 43 | edit(entity).entityEdit() 44 | return entity 45 | } 46 | 47 | /** 48 | * Edits an entity. 49 | * 50 | * @receiver the [World] for editing the entity. 51 | * @param entityId the ID of the entity to edit. 52 | * @param entityEdit the inlined function with the [EntityEdit]. 53 | * @return the [EntityEdit]. 54 | */ 55 | @OptIn(ExperimentalContracts::class) 56 | inline fun World.edit( 57 | entityId: Int, 58 | entityEdit: EntityEdit.() -> Unit = {}, 59 | ): EntityEdit { 60 | contract { callsInPlace(entityEdit, InvocationKind.EXACTLY_ONCE) } 61 | return edit(entityId).apply(entityEdit) 62 | } 63 | 64 | /** 65 | * Adds or replaces a [Component] of the [EntityEdit]. 66 | * 67 | * @receiver the [EntityEdit] for creating a [Component]. 68 | * @param T the [Component] to create. 69 | * @param componentEdit the inlined function with the created [Component]. 70 | * @return this [EntityEdit]. 71 | */ 72 | @OptIn(ExperimentalContracts::class) 73 | inline fun EntityEdit.with(componentEdit: T.() -> Unit = {}): EntityEdit { 74 | contract { callsInPlace(componentEdit, InvocationKind.EXACTLY_ONCE) } 75 | val component = create(T::class.java) 76 | component.componentEdit() 77 | return this 78 | } 79 | 80 | /** 81 | * Adds a [Component] to the [EntityEdit]. 82 | * The component gets replaced if it already exists. 83 | * 84 | * @receiver the [EntityEdit] for adding the [Component]. 85 | * @param component the [Component] which will be added to the entity. 86 | */ 87 | operator fun EntityEdit.plusAssign(component: Component) { 88 | add(component) 89 | } 90 | 91 | /** 92 | * Removes a [Component] from the [EntityEdit]. 93 | * 94 | * @receiver the [EntityEdit] for removing a [Component]. 95 | * @param T the [Component] to remove from the entity. 96 | * @return this [EntityEdit]. 97 | */ 98 | inline fun EntityEdit.remove(): EntityEdit = remove(T::class.java) 99 | -------------------------------------------------------------------------------- /artemis/src/main/kotlin/ktx/artemis/mapper.kt: -------------------------------------------------------------------------------- 1 | package ktx.artemis 2 | 3 | import com.artemis.Component 4 | import com.artemis.ComponentMapper 5 | import com.artemis.World 6 | 7 | /** 8 | * Checks if the entity has this type of [Component]. 9 | * 10 | * @receiver the [ComponentMapper] for checking if the entity has the component. 11 | * @param entityId the ID of entity to check. 12 | * @return `true` if the entity has this component type, `false`` if it does not or is scheduled for removal. 13 | * @throws ArrayIndexOutOfBoundsException if the component was removed or never existed. 14 | */ 15 | operator fun ComponentMapper.contains(entityId: Int): Boolean = has(entityId) 16 | 17 | /** 18 | * Retrieves a ComponentMapper instance for a [Component]. 19 | * 20 | * @receiver the [World]. 21 | * @param T type of the [ComponentMapper] to retrieve. 22 | * @return the [ComponentMapper] of the given type. 23 | */ 24 | inline fun World.mapperFor(): ComponentMapper = getMapper(T::class.java) 25 | -------------------------------------------------------------------------------- /artemis/src/main/kotlin/ktx/artemis/transmuter.kt: -------------------------------------------------------------------------------- 1 | package ktx.artemis 2 | 3 | import com.artemis.Component 4 | import com.artemis.EntityTransmuter 5 | import com.artemis.EntityTransmuterFactory 6 | 7 | /** 8 | * Adds a [Component] to an [EntityTransmuterFactory]. 9 | * 10 | * @receiver the [EntityTransmuterFactory] for creating an [EntityTransmuter]. 11 | * @param T the component to add when transmuting an entity. 12 | * @return the [EntityTransmuterFactory]. 13 | */ 14 | inline fun EntityTransmuterFactory.add(): EntityTransmuterFactory = add(T::class.java) 15 | 16 | /** 17 | * Removes a [Component] from an [EntityTransmuterFactory]. 18 | * 19 | * @receiver the [EntityTransmuterFactory] for creating an [EntityTransmuter]. 20 | * @param T the component to remove when transmuting an entity. 21 | * @return the [EntityTransmuterFactory]. 22 | */ 23 | inline fun EntityTransmuterFactory.remove(): EntityTransmuterFactory = remove(T::class.java) 24 | -------------------------------------------------------------------------------- /artemis/src/main/kotlin/ktx/artemis/world.kt: -------------------------------------------------------------------------------- 1 | package ktx.artemis 2 | 3 | import com.artemis.BaseSystem 4 | import com.artemis.World 5 | import com.badlogic.gdx.utils.GdxRuntimeException 6 | import kotlin.reflect.KClass 7 | 8 | /** 9 | * Retrieves a system from the [World]. 10 | * 11 | * @receiver the [World]. 12 | * @param T type of the system to retrieve. 13 | * @return the [BaseSystem] of the given type. 14 | * @throws MissingBaseSystemException if no system under [T] type is registered. 15 | */ 16 | inline fun World.getSystem(): T = getSystem(T::class.java) ?: throw MissingBaseSystemException(T::class.java) 17 | 18 | /** 19 | * Retrieves a system from the [World]. 20 | * 21 | * @receiver the [World]. 22 | * @param type type of the system to retrieve. 23 | * @return the [BaseSystem] of the given type. May be null if it does not exist. 24 | */ 25 | operator fun World.get(type: KClass): T? = getSystem(type.java) 26 | 27 | /** 28 | * Thrown when accessing an [BaseSystem] via [getSystem] that does not exist in the [World]. 29 | */ 30 | class MissingBaseSystemException( 31 | type: Class, 32 | ) : GdxRuntimeException( 33 | "Could not access system of type ${type.simpleName} - is it added to the world?", 34 | ) 35 | -------------------------------------------------------------------------------- /artemis/src/test/kotlin/ktx/artemis/ArchetypeSpec.kt: -------------------------------------------------------------------------------- 1 | package ktx.artemis 2 | 3 | import com.artemis.ArchetypeBuilder 4 | import com.artemis.World 5 | import org.assertj.core.api.Assertions.assertThat 6 | import org.jetbrains.spek.api.Spek 7 | import org.jetbrains.spek.api.dsl.describe 8 | import org.jetbrains.spek.api.dsl.it 9 | 10 | object ArchetypeSpec : Spek({ 11 | describe("utilities for archetypes") { 12 | val world by memoized { World() } 13 | val archetypeBuilder by memoized { ArchetypeBuilder() } 14 | val transformMapper by memoized { world.getMapper(Transform::class.java) } 15 | val textureMapper by memoized { world.getMapper(Texture::class.java) } 16 | val positionMapper by memoized { world.getMapper(Position::class.java) } 17 | 18 | describe("add component function") { 19 | it("should add a component") { 20 | val archetype = 21 | archetypeBuilder 22 | .add() 23 | .build(world) 24 | 25 | val entity = world.entity(archetype) 26 | 27 | assertThat(transformMapper.has(entity)).isTrue() 28 | } 29 | it("should add multiple components") { 30 | val archetype = 31 | archetypeBuilder 32 | .add( 33 | Transform::class, 34 | Texture::class, 35 | ).build(world) 36 | 37 | val entity = world.entity(archetype) 38 | 39 | assertThat(transformMapper.has(entity)).isTrue() 40 | assertThat(textureMapper.has(entity)).isTrue() 41 | } 42 | } 43 | 44 | describe("remove component function") { 45 | it("should remove a component") { 46 | val archetype = 47 | archetypeBuilder 48 | .add( 49 | Transform::class, 50 | Texture::class, 51 | ).remove() 52 | .build(world) 53 | 54 | val entity = world.entity(archetype) 55 | 56 | assertThat(transformMapper.has(entity)).isTrue() 57 | assertThat(!textureMapper.has(entity)).isTrue() 58 | } 59 | it("should remove multiple components") { 60 | val archetype = 61 | archetypeBuilder 62 | .add( 63 | Position::class, 64 | Transform::class, 65 | Texture::class, 66 | ).remove( 67 | Transform::class, 68 | Texture::class, 69 | ).build(world) 70 | 71 | val entity = world.entity(archetype) 72 | assertThat(positionMapper.has(entity)).isTrue() 73 | assertThat(!transformMapper.has(entity)).isTrue() 74 | assertThat(!textureMapper.has(entity)).isTrue() 75 | } 76 | } 77 | } 78 | }) 79 | -------------------------------------------------------------------------------- /artemis/src/test/kotlin/ktx/artemis/AspectSpec.kt: -------------------------------------------------------------------------------- 1 | package ktx.artemis 2 | 3 | import com.artemis.World 4 | import org.assertj.core.api.Assertions.assertThat 5 | import org.jetbrains.spek.api.Spek 6 | import org.jetbrains.spek.api.dsl.describe 7 | import org.jetbrains.spek.api.dsl.it 8 | 9 | object AspectSpec : Spek({ 10 | describe("utilities for aspects") { 11 | val world by memoized { World() } 12 | val textureEntity by memoized { 13 | world.createEntity().apply { 14 | edit().add(Texture()) 15 | } 16 | } 17 | val rigidBodyEntity by memoized { 18 | world.createEntity().apply { 19 | edit().add(RigidBody()) 20 | } 21 | } 22 | 23 | val textureAndTransformEntity by memoized { 24 | world.createEntity().apply { 25 | edit() 26 | .add(Texture()) 27 | .add(Transform()) 28 | } 29 | } 30 | 31 | val threeComponentsEntity by memoized { 32 | world.createEntity().apply { 33 | edit() 34 | .add(Texture()) 35 | .add(Transform()) 36 | .add(RigidBody()) 37 | } 38 | } 39 | 40 | it("should create an aspect that matches one of the components") { 41 | val aspect = oneOf(Texture::class, Transform::class).build(world) 42 | 43 | assertThat(aspect.isInterested(textureEntity)).isTrue() 44 | assertThat(aspect.isInterested(textureAndTransformEntity)).isTrue() 45 | assertThat(aspect.isInterested(rigidBodyEntity)).isFalse() 46 | } 47 | it("should create an aspect that matches all the components") { 48 | val aspect = allOf(Texture::class, Transform::class).build(world) 49 | 50 | assertThat(aspect.isInterested(textureEntity)).isFalse() 51 | assertThat(aspect.isInterested(textureAndTransformEntity)).isTrue() 52 | } 53 | it("should create an aspect that excludes components") { 54 | assertThat(exclude(Transform::class).build(world).isInterested(textureEntity)).isTrue() 55 | assertThat(exclude(Texture::class).build(world).isInterested(textureEntity)).isFalse() 56 | } 57 | 58 | it("should create an aspect that matches all, one of and excludes components") { 59 | val aspect = 60 | exclude(Remove::class) 61 | .oneOf(Position::class, RigidBody::class) 62 | .allOf(Texture::class, Transform::class) 63 | .exclude(Dead::class) 64 | .build(world) 65 | 66 | assertThat(aspect.isInterested(rigidBodyEntity)).isFalse() 67 | assertThat(aspect.isInterested(threeComponentsEntity)).isTrue() 68 | 69 | threeComponentsEntity.edit().add(Dead()) 70 | 71 | assertThat(aspect.isInterested(threeComponentsEntity)).isFalse() 72 | } 73 | } 74 | }) 75 | -------------------------------------------------------------------------------- /artemis/src/test/kotlin/ktx/artemis/Dead.kt: -------------------------------------------------------------------------------- 1 | package ktx.artemis 2 | 3 | import com.artemis.Component 4 | 5 | class Dead : Component() 6 | -------------------------------------------------------------------------------- /artemis/src/test/kotlin/ktx/artemis/MapperSpec.kt: -------------------------------------------------------------------------------- 1 | package ktx.artemis 2 | 3 | import com.artemis.ComponentMapper 4 | import com.artemis.World 5 | import org.assertj.core.api.Assertions.assertThat 6 | import org.jetbrains.spek.api.Spek 7 | import org.jetbrains.spek.api.dsl.describe 8 | import org.jetbrains.spek.api.dsl.it 9 | 10 | object MapperSpec : Spek({ 11 | describe("utilities for mappers") { 12 | val world by memoized { World() } 13 | val transform by memoized { Transform() } 14 | val entity by memoized { 15 | world.entity { 16 | add(transform) 17 | } 18 | } 19 | val transformMapper by memoized { world.getMapper(Transform::class.java) } 20 | val textureMapper by memoized { world.getMapper(Texture::class.java) } 21 | 22 | describe("contains component function") { 23 | it("should return true if component exists") { 24 | assertThat(transformMapper.contains(entity)).isTrue() 25 | } 26 | it("should return false if component does not exists") { 27 | assertThat(textureMapper.contains(entity)).isFalse() 28 | } 29 | it("should return true with operator if component exists") { 30 | assertThat(entity in transformMapper).isTrue() 31 | } 32 | it("should return false with operator if component does not exists") { 33 | assertThat(entity !in textureMapper).isTrue() 34 | } 35 | } 36 | describe("getMapper function") { 37 | it("should return a mapper") { 38 | val positionMapper = world.mapperFor() 39 | positionMapper.create(entity) 40 | 41 | assertThat(positionMapper.javaClass == ComponentMapper::class.java).isTrue() 42 | assertThat(positionMapper.has(entity)).isTrue() 43 | } 44 | } 45 | } 46 | }) 47 | -------------------------------------------------------------------------------- /artemis/src/test/kotlin/ktx/artemis/Position.kt: -------------------------------------------------------------------------------- 1 | package ktx.artemis 2 | 3 | import com.artemis.Component 4 | 5 | class Position : Component() 6 | -------------------------------------------------------------------------------- /artemis/src/test/kotlin/ktx/artemis/Remove.kt: -------------------------------------------------------------------------------- 1 | package ktx.artemis 2 | 3 | import com.artemis.Component 4 | 5 | class Remove : Component() 6 | -------------------------------------------------------------------------------- /artemis/src/test/kotlin/ktx/artemis/RigidBody.kt: -------------------------------------------------------------------------------- 1 | package ktx.artemis 2 | 3 | import com.artemis.Component 4 | 5 | class RigidBody : Component() 6 | -------------------------------------------------------------------------------- /artemis/src/test/kotlin/ktx/artemis/TestSystem.kt: -------------------------------------------------------------------------------- 1 | package ktx.artemis 2 | 3 | import com.artemis.BaseSystem 4 | 5 | class TestSystem : BaseSystem() { 6 | override fun processSystem() = Unit 7 | } 8 | -------------------------------------------------------------------------------- /artemis/src/test/kotlin/ktx/artemis/Texture.kt: -------------------------------------------------------------------------------- 1 | package ktx.artemis 2 | 3 | import com.artemis.Component 4 | 5 | class Texture : Component() 6 | -------------------------------------------------------------------------------- /artemis/src/test/kotlin/ktx/artemis/Transform.kt: -------------------------------------------------------------------------------- 1 | package ktx.artemis 2 | 3 | import com.artemis.Component 4 | 5 | data class Transform( 6 | var x: Float = 0f, 7 | var y: Float = 0f, 8 | ) : Component() 9 | -------------------------------------------------------------------------------- /artemis/src/test/kotlin/ktx/artemis/TransmuterSpec.kt: -------------------------------------------------------------------------------- 1 | package ktx.artemis 2 | 3 | import com.artemis.EntityTransmuterFactory 4 | import com.artemis.World 5 | import org.assertj.core.api.Assertions.assertThat 6 | import org.jetbrains.spek.api.Spek 7 | import org.jetbrains.spek.api.dsl.describe 8 | import org.jetbrains.spek.api.dsl.it 9 | 10 | object TransmuterSpec : Spek({ 11 | describe("utilities for transmuters") { 12 | val world by memoized { World() } 13 | val transmuterFactory by memoized { EntityTransmuterFactory(world) } 14 | val transformMapper by memoized { world.getMapper(Transform::class.java) } 15 | val textureMapper by memoized { world.getMapper(Texture::class.java) } 16 | val entity by memoized { 17 | world.entity { 18 | add(Transform()) 19 | } 20 | } 21 | 22 | describe("add component function") { 23 | it("should add a component to an entity") { 24 | val transmuter = 25 | transmuterFactory 26 | .add() 27 | .build() 28 | 29 | transmuter.transmute(entity) 30 | 31 | assertThat(textureMapper.has(entity)).isTrue() 32 | } 33 | } 34 | describe("remove component function") { 35 | it("should remove a component from an entity") { 36 | val transmuter = 37 | transmuterFactory 38 | .add() 39 | .remove() 40 | .build() 41 | 42 | transmuter.transmute(entity) 43 | 44 | assertThat(textureMapper.has(entity)).isTrue() 45 | assertThat(!transformMapper.has(entity)).isTrue() 46 | } 47 | } 48 | } 49 | }) 50 | -------------------------------------------------------------------------------- /artemis/src/test/kotlin/ktx/artemis/WorldSpec.kt: -------------------------------------------------------------------------------- 1 | package ktx.artemis 2 | 3 | import com.artemis.World 4 | import com.artemis.WorldConfigurationBuilder 5 | import org.assertj.core.api.Assertions.assertThat 6 | import org.assertj.core.api.Assertions.assertThatExceptionOfType 7 | import org.jetbrains.spek.api.Spek 8 | import org.jetbrains.spek.api.dsl.describe 9 | import org.jetbrains.spek.api.dsl.it 10 | 11 | object WorldSpec : Spek({ 12 | describe("utilities for world") { 13 | val worldConfig by memoized { 14 | WorldConfigurationBuilder().build() 15 | } 16 | 17 | describe("getSystem function") { 18 | it("should add a system and return it") { 19 | val system = TestSystem() 20 | worldConfig.setSystem(system) 21 | val world = World(worldConfig) 22 | 23 | assertThat(world.getSystem()).isEqualTo(system) 24 | } 25 | it("should add a system and return it with operator function") { 26 | val system = TestSystem() 27 | worldConfig.setSystem(system) 28 | val world = World(worldConfig) 29 | 30 | assertThat(world[TestSystem::class]).isEqualTo(system) 31 | } 32 | it("should throw an exception if the system doesn't exist in the world") { 33 | assertThatExceptionOfType(MissingBaseSystemException::class.java).isThrownBy { 34 | val world = World(worldConfig) 35 | 36 | world.getSystem() 37 | } 38 | } 39 | it("should return null if the system doesn't exist in the world") { 40 | val world = World(worldConfig) 41 | 42 | assertThat(world[TestSystem::class]).isNull() 43 | } 44 | } 45 | } 46 | }) 47 | -------------------------------------------------------------------------------- /ashley/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import ktx.* 2 | 3 | val junitPlatformVersion: String by project 4 | 5 | dependencies { 6 | api("com.badlogicgames.ashley:ashley:$ashleyVersion") 7 | 8 | testImplementation("org.jetbrains.spek:spek-api:$spekVersion") 9 | testImplementation("org.assertj:assertj-core:$assertjVersion") 10 | 11 | testRuntimeOnly("org.junit.platform:junit-platform-launcher:$junitPlatformVersion") 12 | testRuntimeOnly("org.jetbrains.spek:spek-junit-platform-engine:$spekVersion") 13 | } 14 | 15 | tasks.withType { 16 | testLogging { 17 | showExceptions = true 18 | events("FAILED", "SKIPPED") 19 | } 20 | 21 | useJUnitPlatform { 22 | includeEngines("spek") 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /ashley/gradle.properties: -------------------------------------------------------------------------------- 1 | projectName=ktx-ashley 2 | projectDesc=Ashley entity component system utilities for Kotlin libGDX applications. 3 | -------------------------------------------------------------------------------- /ashley/src/main/kotlin/ktx/ashley/AshleyDsl.kt: -------------------------------------------------------------------------------- 1 | package ktx.ashley 2 | 3 | /** 4 | * Marks KTX Ashley type-safe builders. 5 | */ 6 | @DslMarker 7 | @Target(AnnotationTarget.CLASS, AnnotationTarget.TYPE) 8 | annotation class AshleyDsl 9 | -------------------------------------------------------------------------------- /ashley/src/main/kotlin/ktx/ashley/families.kt: -------------------------------------------------------------------------------- 1 | package ktx.ashley 2 | 3 | import com.badlogic.ashley.core.Component 4 | import com.badlogic.ashley.core.Family 5 | import com.badlogic.ashley.core.Family.Builder 6 | import kotlin.reflect.KClass 7 | 8 | /** 9 | * @param components matches [entities][com.badlogic.ashley.core.Entity] with at least one of the specified components. 10 | * @return a new [Builder] for a [Family]. 11 | */ 12 | fun oneOf(vararg components: KClass): Builder = Family.one(*toJavaClassArray(components)) 13 | 14 | /** 15 | * @param components matches [entities][com.badlogic.ashley.core.Entity] with all of the specified components. 16 | * @return a new [Builder] for a [Family]. 17 | */ 18 | fun allOf(vararg components: KClass): Builder = Family.all(*toJavaClassArray(components)) 19 | 20 | /** 21 | * @param components does not match [entities][com.badlogic.ashley.core.Entity] with any of the specified components. 22 | * @return a new [Builder] for a [Family]. 23 | */ 24 | fun exclude(vararg components: KClass): Builder = Family.exclude(*toJavaClassArray(components)) 25 | 26 | /** 27 | * @receiver the [Builder] for creating a [Family]. 28 | * @param components matches [entities][com.badlogic.ashley.core.Entity] with at least one of the specified components. 29 | * @return the received [Builder] for the [Family]. 30 | */ 31 | fun Builder.oneOf(vararg components: KClass): Builder = one(*toJavaClassArray(components)) 32 | 33 | /** 34 | * @receiver the [Builder] for creating a [Family]. 35 | * @param components matches [entities][com.badlogic.ashley.core.Entity] with all of the specified components. 36 | * @return the received [Builder] for the [Family]. 37 | */ 38 | fun Builder.allOf(vararg components: KClass): Builder = all(*toJavaClassArray(components)) 39 | 40 | /** 41 | * @receiver the [Builder] for creating a [Family]. 42 | * @param components does not match [entities][com.badlogic.ashley.core.Entity] with any of the specified components. 43 | * @return the received [Builder] for the [Family]. 44 | */ 45 | fun Builder.exclude(vararg components: KClass): Builder = exclude(*toJavaClassArray(components)) 46 | 47 | private fun toJavaClassArray(components: Array>): Array> = 48 | Array(components.size) { index -> components[index].java } 49 | -------------------------------------------------------------------------------- /ashley/src/main/kotlin/ktx/ashley/mappers.kt: -------------------------------------------------------------------------------- 1 | package ktx.ashley 2 | 3 | import com.badlogic.ashley.core.Component 4 | import com.badlogic.ashley.core.ComponentMapper 5 | import com.badlogic.gdx.utils.GdxRuntimeException 6 | import com.badlogic.gdx.utils.reflect.ClassReflection 7 | 8 | /** 9 | * Creates a [ComponentMapper] for the specified [Component] type. 10 | * 11 | * Provides `O(1)` retrieval of [Component]s for an [com.badlogic.ashley.core.Entity]. 12 | * 13 | * @param T the [Component] type to create a [ComponentMapper] for. 14 | * @return a [ComponentMapper] matching the selected component type. 15 | * @see ComponentMapper 16 | * @see Component 17 | */ 18 | inline fun mapperFor(): ComponentMapper = ComponentMapper.getFor(T::class.java) 19 | 20 | /** 21 | * A utility abstract class for companion objects of [Component]s. 22 | * Defines a static [mapper] available of a chosen [Component] type. 23 | * 24 | * Extending this class outside nested objects in [Component] classes 25 | * will result in a [GdxRuntimeException], or alternatively a 26 | * [java.lang.ExceptionInInitializerError] during object initiation. 27 | * Using wrong generic type for [T] will result in runtime exceptions 28 | * due to type mismatch when using the [mapper]. 29 | * 30 | * @see ComponentMapper 31 | */ 32 | abstract class Mapper { 33 | /** [ComponentMapper] for the [T] [Component]. */ 34 | @Suppress("UNCHECKED_CAST") 35 | val mapper: ComponentMapper = ComponentMapper.getFor(getComponentType()) as ComponentMapper 36 | 37 | private fun getComponentType(): Class { 38 | val enclosingClass = 39 | javaClass.enclosingClass 40 | ?: throw GdxRuntimeException( 41 | "Classes extending ktx.ashley.Mapper must be nested objects inside component classes. ${javaClass.name} " + 42 | "is a top-level class defined outside of the corresponding com.badlogic.ashley.core.Component.", 43 | ) 44 | if (!ClassReflection.isAssignableFrom(Component::class.java, enclosingClass)) { 45 | throw GdxRuntimeException( 46 | "Classes extending ktx.ashley.Mapper must be nested objects inside component classes. ${javaClass.name} " + 47 | "is defined in ${enclosingClass.name}, which does not implement com.badlogic.ashley.core.Component.", 48 | ) 49 | } 50 | @Suppress("UNCHECKED_CAST") 51 | return enclosingClass as Class 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /ashley/src/test/kotlin/ktx/ashley/ComponentMappersSpec.kt: -------------------------------------------------------------------------------- 1 | package ktx.ashley 2 | 3 | import com.badlogic.ashley.core.Component 4 | import com.badlogic.ashley.core.Entity 5 | import com.badlogic.gdx.utils.GdxRuntimeException 6 | import org.assertj.core.api.Assertions.assertThat 7 | import org.assertj.core.api.Assertions.assertThatThrownBy 8 | import org.jetbrains.spek.api.Spek 9 | import org.jetbrains.spek.api.dsl.describe 10 | import org.jetbrains.spek.api.dsl.it 11 | 12 | /** For [Mapper] testing. Must be a top-level class due to the companion object usage. */ 13 | class CustomComponent : Component { 14 | companion object : Mapper() 15 | } 16 | 17 | /** For [Mapper] testing. Must not be enclosed by another class for test purposes. */ 18 | class TopLevelMapper : Mapper() 19 | 20 | object ComponentMappersSpec : Spek({ 21 | describe("utilities for component mappers") { 22 | val entity = 23 | Entity().apply { 24 | add(Texture()) 25 | } 26 | it("should return a component mapper for the provided a reified type") { 27 | val mapper = mapperFor() 28 | assertThat(mapper.has(entity)).isTrue() 29 | } 30 | } 31 | 32 | describe("Mapper abstract class") { 33 | val entity = 34 | Entity().apply { 35 | add(CustomComponent()) 36 | } 37 | it("should create a component mapper of the enclosing component class") { 38 | assertThat(CustomComponent.mapper.has(entity)).isTrue() 39 | } 40 | it("should fail to create a component mapper if there is no enclosing class") { 41 | assertThatThrownBy { TopLevelMapper() } 42 | .isInstanceOf(GdxRuntimeException::class.java) 43 | .hasMessageEndingWith("defined outside of the corresponding com.badlogic.ashley.core.Component.") 44 | } 45 | it("should fail to create a component mapper if the enclosing class is not a component") { 46 | class BrokenMapper : Mapper() 47 | assertThatThrownBy { BrokenMapper() } 48 | .isInstanceOf(GdxRuntimeException::class.java) 49 | .hasMessageEndingWith("does not implement com.badlogic.ashley.core.Component.") 50 | } 51 | } 52 | }) 53 | -------------------------------------------------------------------------------- /ashley/src/test/kotlin/ktx/ashley/ComponentPropertiesSpec.kt: -------------------------------------------------------------------------------- 1 | package ktx.ashley 2 | 3 | import com.badlogic.ashley.core.Component 4 | import com.badlogic.ashley.core.Entity 5 | import org.assertj.core.api.Assertions.assertThat 6 | import org.jetbrains.spek.api.Spek 7 | import org.jetbrains.spek.api.dsl.describe 8 | import org.jetbrains.spek.api.dsl.it 9 | 10 | class ComponentWithString( 11 | var string: String = "", 12 | ) : Component 13 | 14 | class ComponentWithInt( 15 | var integer: Int = 0, 16 | ) : Component 17 | 18 | var Entity.mandatoryComponent by propertyFor() 19 | var Entity.optionalComponent by optionalPropertyFor() 20 | 21 | object ComponentPropertiesSpec : Spek({ 22 | describe("utilities for component properties") { 23 | val mandatory = ComponentWithString() 24 | val optional = ComponentWithInt() 25 | val entity = 26 | Entity().apply { 27 | add(mandatory) 28 | add(optional) 29 | } 30 | it("should return a mandatory component value via property") { 31 | assertThat(entity.mandatoryComponent).isNotNull() 32 | } 33 | it("should return an optional component value via property") { 34 | assertThat(entity.optionalComponent).isNotNull() 35 | } 36 | it("should modify mandatory component variables via property") { 37 | entity.mandatoryComponent.string = "test" 38 | assertThat(mandatory.string).isEqualTo("test") 39 | } 40 | it("should modify optional component variables via property") { 41 | entity.optionalComponent?.integer = 42 42 | assertThat(optional.integer).isEqualTo(42) 43 | } 44 | it("should replace mandatory component instance via property") { 45 | entity.mandatoryComponent = ComponentWithString("new") 46 | assertThat(entity.mandatoryComponent.string).isEqualTo("new") 47 | assertThat(mandatory.string).isNotEqualTo(entity.mandatoryComponent.string) 48 | assertThat(entity.getComponent(ComponentWithString::class.java)).isNotSameAs(mandatory) 49 | } 50 | it("should replace optional component instance via property") { 51 | entity.optionalComponent = ComponentWithInt(11) 52 | assertThat(entity.optionalComponent?.integer).isEqualTo(11) 53 | assertThat(optional.integer).isNotEqualTo(entity.optionalComponent!!.integer) 54 | assertThat(entity.getComponent(ComponentWithInt::class.java)).isNotSameAs(optional) 55 | } 56 | it("should remove optional component via property") { 57 | entity.optionalComponent = null 58 | assertThat(entity.optionalComponent).isNull() 59 | assertThat(entity.getComponent(ComponentWithInt::class.java)).isNull() 60 | } 61 | } 62 | }) 63 | -------------------------------------------------------------------------------- /ashley/src/test/kotlin/ktx/ashley/FamiliesSpec.kt: -------------------------------------------------------------------------------- 1 | package ktx.ashley 2 | 3 | import com.badlogic.ashley.core.Entity 4 | import org.assertj.core.api.Assertions.assertThat 5 | import org.jetbrains.spek.api.Spek 6 | import org.jetbrains.spek.api.dsl.describe 7 | import org.jetbrains.spek.api.dsl.it 8 | 9 | object FamiliesSpec : Spek({ 10 | describe("utilities for component families") { 11 | val textureEntity = 12 | Entity().apply { 13 | add(Texture()) 14 | } 15 | 16 | val rigidBodyEntity = 17 | Entity().apply { 18 | add(RigidBody()) 19 | } 20 | 21 | val textureAndTransformEntity = 22 | Entity().apply { 23 | add(Texture()) 24 | add(Transform()) 25 | } 26 | 27 | val allComponentsEntity = 28 | Entity().apply { 29 | add(Texture()) 30 | add(Transform()) 31 | add(RigidBody()) 32 | } 33 | 34 | it("should create a family that matches one of component") { 35 | val family = oneOf(Texture::class, Transform::class).get() 36 | assertThat(family.matches(textureEntity)).isTrue() 37 | assertThat(family.matches(rigidBodyEntity)).isFalse() 38 | } 39 | 40 | it("should create a family that matches all components") { 41 | val family = allOf(Texture::class, Transform::class).get() 42 | assertThat(family.matches(textureEntity)).isFalse() 43 | assertThat(family.matches(textureAndTransformEntity)).isTrue() 44 | } 45 | 46 | it("should create a family that matches any excluded components") { 47 | assertThat(exclude(Transform::class).get().matches(textureEntity)).isTrue() 48 | assertThat(exclude(Texture::class).get().matches(textureEntity)).isFalse() 49 | } 50 | 51 | describe("composite families") { 52 | it("should build a family chained with matching any of one component") { 53 | val family = exclude(Transform::class).oneOf(Texture::class, RigidBody::class) 54 | assertThat(family.get().matches(textureEntity)).isTrue() 55 | assertThat(family.get().matches(textureAndTransformEntity)).isFalse() 56 | } 57 | 58 | it("should build a family chained with matching all components") { 59 | val family = exclude(RigidBody::class).allOf(Texture::class, Transform::class) 60 | assertThat(family.get().matches(allComponentsEntity)).isFalse() 61 | assertThat(family.get().matches(textureAndTransformEntity)).isTrue() 62 | } 63 | 64 | it("should build a family chained with excluding components") { 65 | val family = oneOf(RigidBody::class).exclude(Texture::class, Transform::class) 66 | assertThat(family.get().matches(allComponentsEntity)).isFalse() 67 | assertThat(family.get().matches(rigidBodyEntity)).isTrue() 68 | } 69 | } 70 | } 71 | }) 72 | -------------------------------------------------------------------------------- /ashley/src/test/kotlin/ktx/ashley/RigidBody.kt: -------------------------------------------------------------------------------- 1 | package ktx.ashley 2 | 3 | import com.badlogic.ashley.core.Component 4 | 5 | class RigidBody : Component 6 | -------------------------------------------------------------------------------- /ashley/src/test/kotlin/ktx/ashley/TestSystem.kt: -------------------------------------------------------------------------------- 1 | package ktx.ashley 2 | 3 | import com.badlogic.ashley.core.EntitySystem 4 | 5 | class TestSystem : EntitySystem() 6 | -------------------------------------------------------------------------------- /ashley/src/test/kotlin/ktx/ashley/Texture.kt: -------------------------------------------------------------------------------- 1 | package ktx.ashley 2 | 3 | import com.badlogic.ashley.core.Component 4 | import com.badlogic.ashley.core.ComponentMapper 5 | 6 | class Texture : Component { 7 | companion object { 8 | val mapper = ComponentMapper.getFor(Texture::class.java)!! 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /ashley/src/test/kotlin/ktx/ashley/Transform.kt: -------------------------------------------------------------------------------- 1 | package ktx.ashley 2 | 3 | import com.badlogic.ashley.core.Component 4 | import com.badlogic.ashley.core.ComponentMapper 5 | 6 | class Transform( 7 | var x: Float = 0f, 8 | var y: Float = 0f, 9 | ) : Component { 10 | companion object { 11 | val mapper = ComponentMapper.getFor(Transform::class.java)!! 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ashley/src/test/kotlin/ktx/ashley/listenerTest.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("ClassName") 2 | 3 | package ktx.ashley 4 | 5 | import com.badlogic.ashley.core.Entity 6 | 7 | @Suppress("unused") 8 | class `should implement EntityAdditionListener with only one method implemented` : EntityAdditionListener { 9 | // Guarantees EntityAdditionListener's entityRemoved method is always optional to implement 10 | 11 | override fun entityAdded(entity: Entity) = Unit 12 | } 13 | 14 | @Suppress("unused") 15 | class `should implement EntityRemovalListener with only one method implemented` : EntityRemovalListener { 16 | // Guarantees EntityRemovalListener's entityAdded method is always optional to implement 17 | 18 | override fun entityRemoved(entity: Entity) = Unit 19 | } 20 | -------------------------------------------------------------------------------- /assets-async/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import ktx.* 2 | 3 | val async = project(":async") 4 | 5 | dependencies { 6 | api(project(":assets")) 7 | api(async) 8 | api("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinCoroutinesVersion") 9 | 10 | testImplementation(async.sourceSets.test.get().output) 11 | testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:$kotlinCoroutinesVersion") 12 | testImplementation("com.badlogicgames.gdx:gdx-backend-headless:$gdxVersion") 13 | testImplementation("com.badlogicgames.gdx:gdx-backend-lwjgl3:$gdxVersion") 14 | testImplementation("com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-desktop") 15 | } 16 | -------------------------------------------------------------------------------- /assets-async/gradle.properties: -------------------------------------------------------------------------------- 1 | projectName=ktx-assets-async 2 | projectDesc=Asynchronous coroutines-based asset loader for libGDX. 3 | -------------------------------------------------------------------------------- /assets-async/src/main/kotlin/ktx/assets/async/AssetManager.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("PackageDirectoryMismatch") 2 | 3 | // This extension methods have to appear in the libGDX package in order to access package-private fields. 4 | 5 | package com.badlogic.gdx.assets 6 | 7 | /** Attempts to cancel loading of asset identified by [fileName]. For internal use. */ 8 | fun AssetManager.cancelLoading(fileName: String) { 9 | loadQueue.removeAll { it.fileName == fileName } 10 | tasks.forEach { if (it.assetDesc.fileName == fileName) it.cancel = true } 11 | assetDependencies.remove(fileName) 12 | } 13 | 14 | /** Attempts to cancel loading of assets identified by [fileNames]. For internal use. */ 15 | fun AssetManager.cancelLoading(fileNames: Set) { 16 | loadQueue.removeAll { it.fileName in fileNames } 17 | tasks.forEach { if (it.assetDesc.fileName in fileNames) it.cancel = true } 18 | fileNames.forEach(assetDependencies::remove) 19 | } 20 | -------------------------------------------------------------------------------- /assets-async/src/test/resources/ktx/assets/async/corrupted.atlas: -------------------------------------------------------------------------------- 1 | 2 | missing.png 3 | size: 32,32 4 | format: RGBA8888 5 | filter: Linear,Linear 6 | repeat: none 7 | -------------------------------------------------------------------------------- /assets-async/src/test/resources/ktx/assets/async/cubemap.zktx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libktx/ktx/0f28adef8191a15a3c4f453a97fe15524fb3c8fd/assets-async/src/test/resources/ktx/assets/async/cubemap.zktx -------------------------------------------------------------------------------- /assets-async/src/test/resources/ktx/assets/async/i18n.properties: -------------------------------------------------------------------------------- 1 | key=Value. 2 | -------------------------------------------------------------------------------- /assets-async/src/test/resources/ktx/assets/async/model.g3db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libktx/ktx/0f28adef8191a15a3c4f453a97fe15524fb3c8fd/assets-async/src/test/resources/ktx/assets/async/model.g3db -------------------------------------------------------------------------------- /assets-async/src/test/resources/ktx/assets/async/model.g3dj: -------------------------------------------------------------------------------- 1 | { 2 | "version": [ 0, 1], 3 | "id": "", 4 | "meshes": [ 5 | { 6 | "attributes": ["POSITION", "NORMAL"], 7 | "vertices": [ 8 | -1.000000, -1.000000, -1.000000, 0.000000, 0.000000, -1.000000, 9 | 1.000000, 1.000000, -1.000000, 0.000000, 0.000000, -1.000000, 10 | 1.000000, -1.000000, -1.000000, 0.000000, 0.000000, -1.000000, 11 | -1.000000, 1.000000, -1.000000, 0.000000, 0.000000, -1.000000, 12 | -1.000000, 1.000000, 1.000000, 0.000000, 0.000000, 1.000000, 13 | 0.999999, -1.000001, 1.000000, 0.000000, 0.000000, 1.000000, 14 | 1.000000, 0.999999, 1.000000, 0.000000, 0.000000, 1.000000, 15 | -1.000000, -1.000000, 1.000000, 0.000000, 0.000000, 1.000000, 16 | 1.000000, 0.999999, 1.000000, 1.000000, -0.000000, 0.000000, 17 | 1.000000, -1.000000, -1.000000, 1.000000, -0.000000, 0.000000, 18 | 1.000000, 1.000000, -1.000000, 1.000000, -0.000000, 0.000000, 19 | 0.999999, -1.000001, 1.000000, 1.000000, -0.000000, 0.000000, 20 | 0.999999, -1.000001, 1.000000, -0.000000, -1.000000, -0.000000, 21 | -1.000000, -1.000000, -1.000000, -0.000000, -1.000000, -0.000000, 22 | 1.000000, -1.000000, -1.000000, -0.000000, -1.000000, -0.000000, 23 | -1.000000, -1.000000, 1.000000, -0.000000, -1.000000, -0.000000, 24 | -1.000000, 1.000000, 1.000000, -1.000000, 0.000000, -0.000000, 25 | -1.000000, -1.000000, -1.000000, -1.000000, 0.000000, -0.000000, 26 | -1.000000, -1.000000, 1.000000, -1.000000, 0.000000, -0.000000, 27 | -1.000000, 1.000000, -1.000000, -1.000000, 0.000000, -0.000000, 28 | -1.000000, 1.000000, 1.000000, 0.000000, 1.000000, 0.000000, 29 | 1.000000, 1.000000, -1.000000, 0.000000, 1.000000, 0.000000, 30 | -1.000000, 1.000000, -1.000000, 0.000000, 1.000000, 0.000000, 31 | 1.000000, 0.999999, 1.000000, 0.000000, 1.000000, 0.000000 32 | ], 33 | "parts": [ 34 | { 35 | "id": "Cube_part1", 36 | "type": "TRIANGLES", 37 | "indices": [ 38 | 0, 1, 2, 1, 0, 3, 4, 5, 6, 5, 4, 7, 39 | 8, 9, 10, 9, 8, 11, 12, 13, 14, 13, 12, 15, 40 | 16, 17, 18, 17, 16, 19, 20, 21, 22, 21, 20, 23 41 | ] 42 | } 43 | ] 44 | } 45 | ], 46 | "materials": [ 47 | { 48 | "id": "Material", 49 | "ambient": [ 0.000000, 0.000000, 0.000000], 50 | "diffuse": [ 0.800000, 0.800000, 0.800000], 51 | "emissive": [ 0.800000, 0.800000, 0.800000], 52 | "opacity": 1.000000, 53 | "specular": [ 1.000000, 1.000000, 1.000000], 54 | "shininess": 9.607843 55 | } 56 | ], 57 | "nodes": [ 58 | { 59 | "id": "Cube", 60 | "rotation": [-0.707107, 0.000000, 0.000000, 0.707107], 61 | "scale": [ 100.000000, 100.000000, 100.000000], 62 | "parts": [ 63 | { 64 | "meshpartid": "Cube_part1", 65 | "materialid": "Material" 66 | } 67 | ] 68 | }, 69 | { 70 | "id": "Lamp", 71 | "rotation": [ 0.169076, 0.755880, -0.272171, 0.570948], 72 | "scale": [ 100.000008, 100.000000, 100.000000], 73 | "translation": [ 407.624542, 590.386230, -100.545395] 74 | }, 75 | { 76 | "id": "Camera", 77 | "rotation": [-0.212557, 0.904797, -0.084389, 0.359222], 78 | "scale": [ 100.000000, 100.000008, 99.999992], 79 | "translation": [ 748.113159, 534.366516, 650.763977] 80 | } 81 | ], 82 | "animations": [] 83 | } 84 | -------------------------------------------------------------------------------- /assets-async/src/test/resources/ktx/assets/async/model.obj: -------------------------------------------------------------------------------- 1 | o Cube 2 | v 1.000000 -1.000000 -1.000000 3 | v 1.000000 -1.000000 1.000000 4 | v -1.000000 -1.000000 1.000000 5 | v -1.000000 -1.000000 -1.000000 6 | v 1.000000 1.000000 -0.999999 7 | v 0.999999 1.000000 1.000001 8 | v -1.000000 1.000000 1.000000 9 | v -1.000000 1.000000 -1.000000 10 | vn 0.0000 -1.0000 0.0000 11 | vn 0.0000 1.0000 0.0000 12 | vn 1.0000 0.0000 0.0000 13 | vn -0.0000 -0.0000 1.0000 14 | vn -1.0000 -0.0000 -0.0000 15 | vn 0.0000 0.0000 -1.0000 16 | s off 17 | f 1//1 2//1 3//1 4//1 18 | f 5//2 8//2 7//2 6//2 19 | f 1//3 5//3 6//3 2//3 20 | f 2//4 6//4 7//4 3//4 21 | f 3//5 7//5 8//5 4//5 22 | f 5//6 1//6 4//6 8//6 23 | -------------------------------------------------------------------------------- /assets-async/src/test/resources/ktx/assets/async/particle.p2d: -------------------------------------------------------------------------------- 1 | Untitled 2 | - Delay - 3 | active: false 4 | - Duration - 5 | lowMin: 3000.0 6 | lowMax: 3000.0 7 | - Count - 8 | min: 0 9 | max: 200 10 | - Emission - 11 | lowMin: 0.0 12 | lowMax: 0.0 13 | highMin: 250.0 14 | highMax: 250.0 15 | relative: false 16 | scalingCount: 1 17 | scaling0: 1.0 18 | timelineCount: 1 19 | timeline0: 0.0 20 | - Life - 21 | lowMin: 0.0 22 | lowMax: 0.0 23 | highMin: 500.0 24 | highMax: 1000.0 25 | relative: false 26 | scalingCount: 3 27 | scaling0: 1.0 28 | scaling1: 1.0 29 | scaling2: 0.3 30 | timelineCount: 3 31 | timeline0: 0.0 32 | timeline1: 0.66 33 | timeline2: 1.0 34 | - Life Offset - 35 | active: false 36 | - X Offset - 37 | active: false 38 | - Y Offset - 39 | active: false 40 | - Spawn Shape - 41 | shape: point 42 | - Spawn Width - 43 | lowMin: 0.0 44 | lowMax: 0.0 45 | highMin: 0.0 46 | highMax: 0.0 47 | relative: false 48 | scalingCount: 1 49 | scaling0: 1.0 50 | timelineCount: 1 51 | timeline0: 0.0 52 | - Spawn Height - 53 | lowMin: 0.0 54 | lowMax: 0.0 55 | highMin: 0.0 56 | highMax: 0.0 57 | relative: false 58 | scalingCount: 1 59 | scaling0: 1.0 60 | timelineCount: 1 61 | timeline0: 0.0 62 | - Scale - 63 | lowMin: 0.0 64 | lowMax: 0.0 65 | highMin: 32.0 66 | highMax: 32.0 67 | relative: false 68 | scalingCount: 1 69 | scaling0: 1.0 70 | timelineCount: 1 71 | timeline0: 0.0 72 | - Velocity - 73 | active: true 74 | lowMin: 0.0 75 | lowMax: 0.0 76 | highMin: 30.0 77 | highMax: 300.0 78 | relative: false 79 | scalingCount: 1 80 | scaling0: 1.0 81 | timelineCount: 1 82 | timeline0: 0.0 83 | - Angle - 84 | active: true 85 | lowMin: 90.0 86 | lowMax: 90.0 87 | highMin: 45.0 88 | highMax: 135.0 89 | relative: false 90 | scalingCount: 3 91 | scaling0: 1.0 92 | scaling1: 0.0 93 | scaling2: 0.0 94 | timelineCount: 3 95 | timeline0: 0.0 96 | timeline1: 0.5 97 | timeline2: 1.0 98 | - Rotation - 99 | active: false 100 | - Wind - 101 | active: false 102 | - Gravity - 103 | active: false 104 | - Tint - 105 | colorsCount: 3 106 | colors0: 1.0 107 | colors1: 0.12156863 108 | colors2: 0.047058824 109 | timelineCount: 1 110 | timeline0: 0.0 111 | - Transparency - 112 | lowMin: 0.0 113 | lowMax: 0.0 114 | highMin: 1.0 115 | highMax: 1.0 116 | relative: false 117 | scalingCount: 4 118 | scaling0: 0.0 119 | scaling1: 1.0 120 | scaling2: 0.75 121 | scaling3: 0.0 122 | timelineCount: 4 123 | timeline0: 0.0 124 | timeline1: 0.2 125 | timeline2: 0.8 126 | timeline3: 1.0 127 | - Options - 128 | attached: false 129 | continuous: false 130 | aligned: false 131 | additive: true 132 | behind: false 133 | premultipliedAlpha: false 134 | - Image Path - 135 | texture.png 136 | -------------------------------------------------------------------------------- /assets-async/src/test/resources/ktx/assets/async/particle.p3d: -------------------------------------------------------------------------------- 1 | {unique:{billboardBatch:{class:com.badlogic.gdx.graphics.g3d.particles.ResourceData$SaveData,data:{cfg:{class:com.badlogic.gdx.graphics.g3d.particles.batches.BillboardParticleBatch$Config,mode:Screen}},indices:[0]}},data:[],assets:[{filename:texture.png,type:com.badlogic.gdx.graphics.Texture}],resource:{class:com.badlogic.gdx.graphics.g3d.particles.ParticleEffect,controllers:[{name:Billboard Controller,emitter:{class:com.badlogic.gdx.graphics.g3d.particles.emitters.RegularEmitter,minParticleCount:0,maxParticleCount:200,continous:true,emission:{active:true,lowMin:0,lowMax:0,highMin:250,highMax:250,relative:false,scaling:[1],timeline:[0]},delay:{active:false,lowMin:0,lowMax:0},duration:{active:true,lowMin:3000,lowMax:3000},life:{active:true,lowMin:0,lowMax:0,highMin:500,highMax:1000,relative:false,scaling:[1,1,0.3],timeline:[0,0.66,1]},lifeOffset:{active:false,lowMin:0,lowMax:0,highMin:0,highMax:0,relative:false,scaling:[1],timeline:[0]}},influencers:[{class:com.badlogic.gdx.graphics.g3d.particles.influencers.RegionInfluencer$Single,regions:[{u2:1,v2:1,halfInvAspectRatio:0.5}]},{class:com.badlogic.gdx.graphics.g3d.particles.influencers.SpawnInfluencer,spawnShape:{class:com.badlogic.gdx.graphics.g3d.particles.values.PointSpawnShapeValue,active:false,xOffsetValue:{active:false,lowMin:0,lowMax:0},yOffsetValue:{active:false,lowMin:0,lowMax:0},zOffsetValue:{active:false,lowMin:0,lowMax:0},spawnWidthValue:{active:false,lowMin:0,lowMax:0,highMin:0,highMax:0,relative:false,scaling:[1],timeline:[0]},spawnHeightValue:{active:false,lowMin:0,lowMax:0,highMin:0,highMax:0,relative:false,scaling:[1],timeline:[0]},spawnDepthValue:{active:false,lowMin:0,lowMax:0,highMin:0,highMax:0,relative:false,scaling:[1],timeline:[0]},edges:false}},{class:com.badlogic.gdx.graphics.g3d.particles.influencers.ColorInfluencer$Single,alpha:{active:false,lowMin:0,lowMax:0,highMin:1,highMax:1,relative:false,scaling:[0,0.15,0.5,0],timeline:[0,0.5,0.8,1]},color:{active:false,colors:[1,0.12156863,0.047058824,0,0,0],timeline:[0,1]}},{class:com.badlogic.gdx.graphics.g3d.particles.influencers.DynamicsInfluencer,velocities:[{class:com.badlogic.gdx.graphics.g3d.particles.influencers.DynamicsModifier$PolarAcceleration,isGlobal:false,strengthValue:{active:false,lowMin:0,lowMax:0,highMin:5,highMax:10,relative:false,scaling:[1],timeline:[0]},thetaValue:{active:false,lowMin:0,lowMax:0,highMin:0,highMax:360,relative:false,scaling:[1],timeline:[0]},phiValue:{active:true,lowMin:0,lowMax:0,highMin:-35,highMax:35,relative:false,scaling:[1,0,0],timeline:[0,0.5,1]}}]}],renderer:{class:com.badlogic.gdx.graphics.g3d.particles.renderers.BillboardRenderer}}]}} 2 | -------------------------------------------------------------------------------- /assets-async/src/test/resources/ktx/assets/async/polygon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libktx/ktx/0f28adef8191a15a3c4f453a97fe15524fb3c8fd/assets-async/src/test/resources/ktx/assets/async/polygon.png -------------------------------------------------------------------------------- /assets-async/src/test/resources/ktx/assets/async/polygon.psh: -------------------------------------------------------------------------------- 1 | i polygon.png 2 | s 180.0,7.0,273.0,30.000008,277.0,107.00001,299.0,153.00002,192.0,301.0,15.0,266.0,0.0,218.00002,27.999992,157.0,148.0,186.00002,173.00002,140.0,120.0,142.0,59.000008,73.0,119.00001,4.0 3 | v 180.0,7.0,119.00001,4.0,59.000008,73.0,180.0,7.0,59.000008,73.0,120.0,142.0,180.0,7.0,120.0,142.0,173.00002,140.0,148.0,186.00002,27.999992,157.0,0.0,218.00002,148.0,186.00002,0.0,218.00002,15.0,266.0,148.0,186.00002,15.0,266.0,192.0,301.0,173.00002,140.0,148.0,186.00002,192.0,301.0,180.0,7.0,173.00002,140.0,192.0,301.0,180.0,7.0,192.0,301.0,299.0,153.00002,180.0,7.0,299.0,153.00002,277.0,107.00001,277.0,107.00001,273.0,30.000008,180.0,7.0 4 | u 0.6,0.9766667,0.3966667,0.9866667,0.19666669,0.75666666,0.6,0.9766667,0.19666669,0.75666666,0.4,0.52666664,0.6,0.9766667,0.4,0.52666664,0.5766667,0.5333333,0.49333334,0.37999994,0.09333331,0.4766667,0.0,0.2733333,0.49333334,0.37999994,0.0,0.2733333,0.05,0.113333344,0.49333334,0.37999994,0.05,0.113333344,0.64,-0.0033333302,0.5766667,0.5333333,0.49333334,0.37999994,0.64,-0.0033333302,0.6,0.9766667,0.5766667,0.5333333,0.64,-0.0033333302,0.6,0.9766667,0.64,-0.0033333302,0.99666667,0.48999995,0.6,0.9766667,0.99666667,0.48999995,0.92333335,0.6433333,0.92333335,0.6433333,0.91,0.9,0.6,0.9766667 5 | 6 | -------------------------------------------------------------------------------- /assets-async/src/test/resources/ktx/assets/async/shader.frag: -------------------------------------------------------------------------------- 1 | void main() 2 | { 3 | gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); 4 | } 5 | -------------------------------------------------------------------------------- /assets-async/src/test/resources/ktx/assets/async/shader.vert: -------------------------------------------------------------------------------- 1 | main { 2 | } 3 | -------------------------------------------------------------------------------- /assets-async/src/test/resources/ktx/assets/async/skin.atlas: -------------------------------------------------------------------------------- 1 | 2 | texture.png 3 | size: 32,32 4 | format: RGBA8888 5 | filter: Linear,Linear 6 | repeat: none 7 | button 8 | rotate: false 9 | xy: 1, 1 10 | size: 27, 27 11 | split: 8, 8, 8, 8 12 | pad: 10, 10, 10, 10 13 | orig: 27, 27 14 | offset: 0, 0 15 | index: -1 16 | -------------------------------------------------------------------------------- /assets-async/src/test/resources/ktx/assets/async/skin.json: -------------------------------------------------------------------------------- 1 | { 2 | "com.badlogic.gdx.scenes.scene2d.ui.Button$ButtonStyle": { 3 | "default": { 4 | "up": "button" 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /assets-async/src/test/resources/ktx/assets/async/sound.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libktx/ktx/0f28adef8191a15a3c4f453a97fe15524fb3c8fd/assets-async/src/test/resources/ktx/assets/async/sound.ogg -------------------------------------------------------------------------------- /assets-async/src/test/resources/ktx/assets/async/string.txt: -------------------------------------------------------------------------------- 1 | Content. -------------------------------------------------------------------------------- /assets-async/src/test/resources/ktx/assets/async/texture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libktx/ktx/0f28adef8191a15a3c4f453a97fe15524fb3c8fd/assets-async/src/test/resources/ktx/assets/async/texture.png -------------------------------------------------------------------------------- /assets-async/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker: -------------------------------------------------------------------------------- 1 | mock-maker-inline 2 | -------------------------------------------------------------------------------- /assets/gradle.properties: -------------------------------------------------------------------------------- 1 | projectName=ktx-assets 2 | projectDesc=libGDX assets management utilities for Kotlin applications. 3 | -------------------------------------------------------------------------------- /assets/src/main/kotlin/ktx/assets/TextAssetLoader.kt: -------------------------------------------------------------------------------- 1 | package ktx.assets 2 | 3 | import com.badlogic.gdx.assets.AssetDescriptor 4 | import com.badlogic.gdx.assets.AssetLoaderParameters 5 | import com.badlogic.gdx.assets.AssetManager 6 | import com.badlogic.gdx.assets.loaders.AsynchronousAssetLoader 7 | import com.badlogic.gdx.assets.loaders.FileHandleResolver 8 | import com.badlogic.gdx.assets.loaders.resolvers.InternalFileHandleResolver 9 | import com.badlogic.gdx.files.FileHandle 10 | import com.badlogic.gdx.utils.Array 11 | import com.badlogic.gdx.utils.GdxRuntimeException 12 | import ktx.assets.TextAssetLoader.TextAssetLoaderParameters 13 | 14 | /** 15 | * Allows reading text files with an [AssetManager]. Must be registered via [setLoader]. 16 | * 17 | * Note that [loadAsync] _must_ be called before [loadSync], as usual in case of [AsynchronousAssetLoader] 18 | * implementations. Similarly to standard libGDX loaders, this loader is not considered thread-safe and assumes that 19 | * a single file is loaded at a time. 20 | * 21 | * @param fileResolver not used, required by the superclass. 22 | * @param charset name of the charset used to read text. Can be overridden with 23 | * [TextAssetLoader.TextAssetLoaderParameters]. Should match text files encoding. Defaults to UTF-8. 24 | */ 25 | class TextAssetLoader( 26 | fileResolver: FileHandleResolver = InternalFileHandleResolver(), 27 | private val charset: String = "UTF-8", 28 | ) : AsynchronousAssetLoader(fileResolver) { 29 | @Volatile 30 | var fileContent: String? = null 31 | 32 | override fun loadAsync( 33 | assetManager: AssetManager?, 34 | fileName: String?, 35 | file: FileHandle, 36 | parameter: TextAssetLoaderParameters?, 37 | ) { 38 | fileContent = file.readString(parameter?.charset ?: charset) 39 | } 40 | 41 | override fun loadSync( 42 | assetManager: AssetManager?, 43 | fileName: String?, 44 | file: FileHandle, 45 | parameter: TextAssetLoaderParameters?, 46 | ): String = 47 | try { 48 | fileContent ?: throw GdxRuntimeException("File $fileName was not loaded asynchronously. Call #loadAsync first.") 49 | } finally { 50 | fileContent = null 51 | } 52 | 53 | override fun getDependencies( 54 | fileName: String?, 55 | file: FileHandle?, 56 | parameter: TextAssetLoaderParameters?, 57 | ): Array>? = null 58 | 59 | /** 60 | * Optional parameters used to load text files. 61 | * @param charset name of the charset used to read text. Should match text file encoding. Defaults to UTF-8. 62 | */ 63 | class TextAssetLoaderParameters( 64 | var charset: String = "UTF-8", 65 | ) : AssetLoaderParameters() 66 | } 67 | -------------------------------------------------------------------------------- /assets/src/main/kotlin/ktx/assets/files.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("NOTHING_TO_INLINE") 2 | 3 | package ktx.assets 4 | 5 | import com.badlogic.gdx.Files.FileType 6 | import com.badlogic.gdx.Files.FileType.Internal 7 | import com.badlogic.gdx.Gdx 8 | import com.badlogic.gdx.files.FileHandle 9 | 10 | /** 11 | * @return [FileHandle] instance pointing to a classpath file. Its path matches this string. 12 | */ 13 | inline fun String?.toClasspathFile(): FileHandle = Gdx.files.classpath(this) 14 | 15 | /** 16 | * @return [FileHandle] instance pointing to an internal file. Its path matches this string. 17 | */ 18 | inline fun String?.toInternalFile(): FileHandle = Gdx.files.internal(this) 19 | 20 | /** 21 | * @return [FileHandle] instance pointing to a local file. Its path matches this string. 22 | */ 23 | inline fun String?.toLocalFile(): FileHandle = Gdx.files.local(this) 24 | 25 | /** 26 | * @return [FileHandle] instance pointing to an external file. Its path matches this string. 27 | */ 28 | inline fun String?.toExternalFile(): FileHandle = Gdx.files.external(this) 29 | 30 | /** 31 | * @return [FileHandle] instance pointing to an absolute file. Its path matches this string. 32 | */ 33 | inline fun String?.toAbsoluteFile(): FileHandle = Gdx.files.absolute(this) 34 | 35 | /** 36 | * @param path the path of the requested file. 37 | * @param type type of the requested file. Defaults to [FileType.Internal]. 38 | * @return [FileHandle] instance pointing to a file with the selected path and type. 39 | */ 40 | inline fun file( 41 | path: String, 42 | type: FileType = Internal, 43 | ): FileHandle = Gdx.files.getFileHandle(path, type) 44 | -------------------------------------------------------------------------------- /assets/src/main/kotlin/ktx/assets/pools.kt: -------------------------------------------------------------------------------- 1 | package ktx.assets 2 | 3 | import com.badlogic.gdx.utils.Disposable 4 | import com.badlogic.gdx.utils.Pool 5 | import com.badlogic.gdx.utils.Pool.Poolable 6 | 7 | /** 8 | * Allows to use a [Pool] instance as a functional object. When invoked with no parameters, [Pool] will provide an 9 | * instance of the pooled object type. 10 | * @return an instance of class obtained from the pool. 11 | * @see Pool.obtain 12 | */ 13 | operator fun Pool.invoke(): Type = this.obtain() 14 | 15 | /** 16 | * Allows to use a [Pool] instance as a functional object. When invoked with a parameter, [Pool] will treat the passed 17 | * parameter as an object freed to the pool. 18 | * @param free will be returned to the pool. Might be reset if it implements the [Poolable] interface. 19 | * @see Pool.free 20 | */ 21 | operator fun Pool.invoke(free: Type) = this.free(free) 22 | 23 | /** 24 | * @param initialCapacity initial size of the backing collection. 25 | * @param max max amount stored in the pool. When exceeded, freed objects are no longer accepted. 26 | * @param discard invoked each time an object is rejected or removed from the pool. This might happen if an object is 27 | * freed with [Pool.free] or [Pool.freeAll] if the pool is full, or when [Pool.clear] is called. Optional, defaults 28 | * to resetting the objects implementing the [Poolable] interface to replicate the default behavior. If the objects are 29 | * [Disposable], this lambda should be used to dispose of them. 30 | * @param provider creates instances of the requested objects. 31 | * @return a new [Pool] instance, creating the object with the passed provider. 32 | */ 33 | inline fun pool( 34 | initialCapacity: Int = 16, 35 | max: Int = Int.MAX_VALUE, 36 | crossinline discard: (Type) -> Unit = { 37 | if (it is Poolable) { 38 | it.reset() 39 | } 40 | }, 41 | crossinline provider: () -> Type, 42 | ): Pool = 43 | object : Pool(initialCapacity, max) { 44 | override fun newObject(): Type = provider() 45 | 46 | override fun discard(element: Type) { 47 | discard(element) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /assets/src/main/kotlin/ktx/assets/resolvers.kt: -------------------------------------------------------------------------------- 1 | package ktx.assets 2 | 3 | import com.badlogic.gdx.Files.FileType 4 | import com.badlogic.gdx.Files.FileType.Absolute 5 | import com.badlogic.gdx.Files.FileType.Classpath 6 | import com.badlogic.gdx.Files.FileType.External 7 | import com.badlogic.gdx.Files.FileType.Internal 8 | import com.badlogic.gdx.Files.FileType.Local 9 | import com.badlogic.gdx.assets.loaders.FileHandleResolver 10 | import com.badlogic.gdx.assets.loaders.resolvers.AbsoluteFileHandleResolver 11 | import com.badlogic.gdx.assets.loaders.resolvers.ClasspathFileHandleResolver 12 | import com.badlogic.gdx.assets.loaders.resolvers.ExternalFileHandleResolver 13 | import com.badlogic.gdx.assets.loaders.resolvers.InternalFileHandleResolver 14 | import com.badlogic.gdx.assets.loaders.resolvers.LocalFileHandleResolver 15 | import com.badlogic.gdx.assets.loaders.resolvers.PrefixFileHandleResolver 16 | import com.badlogic.gdx.assets.loaders.resolvers.ResolutionFileResolver 17 | import com.badlogic.gdx.assets.loaders.resolvers.ResolutionFileResolver.Resolution 18 | 19 | /** 20 | * Creates a [FileHandleResolver]. 21 | * @return a new [FileHandleResolver] instance providing files matching this type. 22 | */ 23 | fun FileType.getResolver(): FileHandleResolver = 24 | when (this) { 25 | Classpath -> ClasspathFileHandleResolver() 26 | Internal -> InternalFileHandleResolver() 27 | Local -> LocalFileHandleResolver() 28 | External -> ExternalFileHandleResolver() 29 | Absolute -> AbsoluteFileHandleResolver() 30 | } 31 | 32 | /** 33 | * Decorates this [FileHandleResolver] with a [PrefixFileHandleResolver]. 34 | * @param prefix will be added to file paths before passing them to the original resolver. 35 | * @return a new [PrefixFileHandleResolver] decorating this resolver. 36 | * @see PrefixFileHandleResolver 37 | */ 38 | fun FileHandleResolver.withPrefix(prefix: String) = PrefixFileHandleResolver(this, prefix) 39 | 40 | /** 41 | * Decorates this [FileHandleResolver] with a [ResolutionFileResolver]. 42 | * @param resolutions each [Resolution] points to a folder with assets specific to the chosen bounds. During asset 43 | * loading, screen width and height are chosen to select the closest matching [Resolution], which will be used to 44 | * select the assets directory. 45 | * @return a new [ResolutionFileResolver] decorating this resolver. 46 | * @see ResolutionFileResolver 47 | * @see resolution 48 | */ 49 | fun FileHandleResolver.forResolutions(vararg resolutions: Resolution) = ResolutionFileResolver(this, *resolutions) 50 | 51 | /** 52 | * Factory method for [ResolutionFileResolver.Resolution] that allows to used named parameters. 53 | * @param width portrait width of the resolution. 54 | * @param height portrait height of the resolution. 55 | * @param folder name of the folder with assets for the given resolution. Defaults to "[width]x[height]". 56 | * @return a new [Resolution] instance with the given size. 57 | * @see Resolution 58 | * @see ResolutionFileResolver 59 | */ 60 | fun resolution( 61 | width: Int, 62 | height: Int, 63 | folder: String = "${width}x$height", 64 | ) = Resolution(width, height, folder) 65 | -------------------------------------------------------------------------------- /assets/src/test/kotlin/ktx/assets/FilesTest.kt: -------------------------------------------------------------------------------- 1 | package ktx.assets 2 | 3 | import com.badlogic.gdx.Files.FileType.Absolute 4 | import com.badlogic.gdx.Files.FileType.Classpath 5 | import com.badlogic.gdx.Files.FileType.External 6 | import com.badlogic.gdx.Files.FileType.Internal 7 | import com.badlogic.gdx.Files.FileType.Local 8 | import com.badlogic.gdx.Gdx 9 | import org.junit.Assert.assertEquals 10 | import org.junit.Assert.assertNotNull 11 | import org.junit.Before 12 | import org.junit.Test 13 | 14 | /** 15 | * Tests files-related utilities. 16 | */ 17 | class FilesTest { 18 | @Before 19 | fun `mock Files`() { 20 | Gdx.files = MockFiles() 21 | } 22 | 23 | @Test 24 | fun `should convert string to classpath FileHandle`() { 25 | val file = "my/package/classpath.file".toClasspathFile() 26 | 27 | assertNotNull(file) 28 | assertEquals(Classpath, file.type()) 29 | assertEquals("my/package/classpath.file", file.path()) 30 | } 31 | 32 | @Test 33 | fun `should convert string to internal FileHandle`() { 34 | val file = "internal.file".toInternalFile() 35 | 36 | assertNotNull(file) 37 | assertEquals(Internal, file.type()) 38 | assertEquals("internal.file", file.path()) 39 | } 40 | 41 | @Test 42 | fun `should convert string to local FileHandle`() { 43 | val file = "local.file".toLocalFile() 44 | 45 | assertNotNull(file) 46 | assertEquals(Local, file.type()) 47 | assertEquals("local.file", file.path()) 48 | } 49 | 50 | @Test 51 | fun `should convert string to external FileHandle`() { 52 | val file = "some/directory/external.file".toExternalFile() 53 | 54 | assertNotNull(file) 55 | assertEquals(External, file.type()) 56 | assertEquals("some/directory/external.file", file.path()) 57 | } 58 | 59 | @Test 60 | fun `should convert string to absolute FileHandle`() { 61 | val file = "/home/mock/absolute.file".toAbsoluteFile() 62 | 63 | assertNotNull(file) 64 | assertEquals(Absolute, file.type()) 65 | assertEquals("/home/mock/absolute.file", file.path()) 66 | } 67 | 68 | @Test 69 | fun `should create FileHandle with default type`() { 70 | val file = file("mock.file") 71 | 72 | assertNotNull(file) 73 | assertEquals(Internal, file.type()) 74 | assertEquals("mock.file", file.path()) 75 | } 76 | 77 | @Test 78 | fun `should create FileHandle with custom type`() { 79 | val file = file("/home/ktx/mock.file", type = Absolute) 80 | 81 | assertNotNull(file) 82 | assertEquals(Absolute, file.type()) 83 | assertEquals("/home/ktx/mock.file", file.path()) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /assets/src/test/kotlin/ktx/assets/PoolsTest.kt: -------------------------------------------------------------------------------- 1 | package ktx.assets 2 | 3 | import com.badlogic.gdx.utils.Pool 4 | import com.badlogic.gdx.utils.Pool.Poolable 5 | import org.junit.Assert.assertEquals 6 | import org.junit.Assert.assertNotNull 7 | import org.junit.Assert.assertSame 8 | import org.junit.Assert.assertTrue 9 | import org.junit.Test 10 | 11 | /** 12 | * Tests [Pool] extensions. 13 | */ 14 | class PoolsTest { 15 | @Test 16 | fun `should invoke pool as no parameter function to provide instances`() { 17 | // Given: 18 | val pool = MockPool() 19 | 20 | // When: Pool is called as a function. 21 | val instance = pool() 22 | 23 | // Then: Should work as "obtain": 24 | assertNotNull(instance) 25 | pool.free(instance) 26 | assertSame(instance, pool()) // Since the object was freed, pool should return the same instance. 27 | } 28 | 29 | @Test 30 | fun `should invoke pool as one parameter function to return instances`() { 31 | // Given: 32 | val pool = MockPool() 33 | val instance = pool.obtain() 34 | 35 | // When: Pool is called as a function with object parameter. 36 | pool(instance) 37 | 38 | // Then: Should work as "free". 39 | assertEquals(1, pool.free) 40 | assertSame(instance, pool.obtain()) // Since the object was freed, pool should return the same instance. 41 | } 42 | 43 | @Test 44 | fun `should create new pools with custom providers`() { 45 | // Given: A pool that always returns the same instance: 46 | val provided = "10" 47 | val pool = pool { provided } 48 | 49 | // When: 50 | val obtained = pool() 51 | 52 | // Then: 53 | assertSame(provided, obtained) 54 | } 55 | 56 | @Test 57 | fun `should honor max setting`() { 58 | // Given: 59 | val pool = pool(max = 5) { "Mock." } 60 | 61 | // When: 62 | for (index in 1..10) pool.free("Value.") 63 | 64 | // Then: 65 | assertEquals(5, pool.free) 66 | } 67 | 68 | @Test 69 | fun `should create new pools with a custom discard function`() { 70 | // Given: A pool that adds discarded objects to a list: 71 | val discarded = mutableListOf() 72 | val pool = pool(max = 5, discard = { discarded.add(it) }) { "Mock." } 73 | 74 | // When: 75 | for (index in 1..10) pool.free("Value$index") 76 | 77 | // Then: 78 | assertEquals(listOf("Value6", "Value7", "Value8", "Value9", "Value10"), discarded) 79 | } 80 | 81 | @Test 82 | fun `should create new pools that reset discarded objects by default`() { 83 | // Given: 84 | val pool = pool(max = 1) { SamplePoolable() } 85 | val freed = pool() 86 | val discarded = pool() 87 | pool.free(freed) 88 | 89 | // When: 90 | pool.free(discarded) 91 | 92 | // Then: 93 | assertTrue(discarded.isReset) 94 | } 95 | 96 | /** 97 | * A simple data object implementing the [Poolable] interface. 98 | */ 99 | private class SamplePoolable( 100 | var isReset: Boolean = false, 101 | ) : Poolable { 102 | override fun reset() { 103 | this.isReset = true 104 | } 105 | } 106 | 107 | /** 108 | * Provides new [Any] instances. 109 | */ 110 | private class MockPool : Pool() { 111 | override fun newObject(): Any = Any() 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /assets/src/test/kotlin/ktx/assets/TextAssetLoaderTest.kt: -------------------------------------------------------------------------------- 1 | package ktx.assets 2 | 3 | import com.badlogic.gdx.assets.loaders.AssetLoader 4 | import com.badlogic.gdx.files.FileHandle 5 | import com.badlogic.gdx.utils.GdxRuntimeException 6 | import io.kotlintest.matchers.shouldThrow 7 | import ktx.assets.TextAssetLoader.TextAssetLoaderParameters 8 | import org.junit.Assert 9 | import org.junit.Test 10 | import org.mockito.kotlin.doReturn 11 | import org.mockito.kotlin.mock 12 | import org.mockito.kotlin.verify 13 | 14 | /** 15 | * Tests [TextAssetLoader]: [AssetLoader] implementation for asynchronous reading of text files. 16 | */ 17 | class TextAssetLoaderTest { 18 | @Test 19 | fun `should read text data from FileHandle`() { 20 | // Given: 21 | val loader = TextAssetLoader(charset = "UTF-8") 22 | val file = 23 | mock { 24 | on(it.readString("UTF-8")) doReturn "Content." 25 | } 26 | 27 | // When: 28 | loader.loadAsync(mock(), "test.txt", file, null) 29 | val result = loader.loadSync(mock(), "test.txt", file, null) 30 | 31 | // Then: 32 | Assert.assertEquals("Content.", result) 33 | verify(file).readString("UTF-8") 34 | } 35 | 36 | @Test 37 | fun `should read text data from FileHandle with loading parameters`() { 38 | // Given: 39 | val loader = TextAssetLoader(charset = "UTF-8") 40 | val file = 41 | mock { 42 | on(it.readString("UTF-16")) doReturn "Content." 43 | } 44 | 45 | // When: 46 | loader.loadAsync(mock(), "test.txt", file, TextAssetLoaderParameters(charset = "UTF-16")) 47 | val result = loader.loadSync(mock(), "test.txt", file, TextAssetLoaderParameters(charset = "UTF-16")) 48 | 49 | // Then: 50 | Assert.assertEquals("Content.", result) 51 | verify(file).readString("UTF-16") 52 | } 53 | 54 | @Test 55 | fun `should throw exception when trying to read file without loading it asynchronously`() { 56 | // Given: 57 | val loader = TextAssetLoader(charset = "UTF-8") 58 | 59 | // Expect: 60 | shouldThrow { 61 | loader.loadSync(mock(), "test.txt", mock(), null) 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /assets/src/test/kotlin/ktx/assets/utils.kt: -------------------------------------------------------------------------------- 1 | package ktx.assets 2 | 3 | import com.badlogic.gdx.Files 4 | import com.badlogic.gdx.Files.FileType 5 | import com.badlogic.gdx.Files.FileType.Absolute 6 | import com.badlogic.gdx.Files.FileType.Classpath 7 | import com.badlogic.gdx.Files.FileType.External 8 | import com.badlogic.gdx.Files.FileType.Internal 9 | import com.badlogic.gdx.Files.FileType.Local 10 | import com.badlogic.gdx.files.FileHandle 11 | import java.io.File 12 | 13 | /** 14 | * Provides [FileHandle] instances based on [File]. 15 | */ 16 | class MockFiles : Files { // Implementation note: believe it or not, this was easier to set up than Mockito. 17 | override fun classpath(path: String): FileHandle = getFileHandle(path, Classpath) 18 | 19 | override fun internal(path: String): FileHandle = getFileHandle(path, Internal) 20 | 21 | override fun local(path: String): FileHandle = getFileHandle(path, Local) 22 | 23 | override fun external(path: String): FileHandle = getFileHandle(path, External) 24 | 25 | override fun absolute(path: String): FileHandle = getFileHandle(path, Absolute) 26 | 27 | override fun getFileHandle( 28 | path: String, 29 | type: FileType, 30 | ): FileHandle = MockFileHandle(File(path), type) 31 | 32 | override fun isLocalStorageAvailable(): Boolean = false 33 | 34 | override fun getLocalStoragePath(): String = "" 35 | 36 | override fun isExternalStorageAvailable(): Boolean = false 37 | 38 | override fun getExternalStoragePath(): String = "" 39 | } 40 | 41 | /** 42 | * Exposes protected ([File], [FileType]) constructor of [FileHandle]. 43 | */ 44 | class MockFileHandle( 45 | file: File, 46 | type: FileType?, 47 | ) : FileHandle(file, type) 48 | -------------------------------------------------------------------------------- /async/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import ktx.* 2 | 3 | dependencies { 4 | api("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinCoroutinesVersion") 5 | 6 | testImplementation("com.badlogicgames.gdx:gdx-backend-headless:$gdxVersion") 7 | testImplementation("com.badlogicgames.gdx:gdx-backend-lwjgl:$gdxVersion") 8 | testImplementation("com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-desktop") 9 | testImplementation("me.alexpanov:free-port-finder:1.0") // Version unlikely to change, not parametrized. 10 | testImplementation("com.github.tomakehurst:wiremock:$wireMockVersion") 11 | testImplementation("org.slf4j:slf4j-nop:$slf4jVersion") 12 | } 13 | -------------------------------------------------------------------------------- /async/gradle.properties: -------------------------------------------------------------------------------- 1 | projectName=ktx-async 2 | projectDesc=Kotlin coroutines support and asynchronous operations utilities for libGDX applications. 3 | -------------------------------------------------------------------------------- /async/src/main/kotlin/ktx/async/timer.kt: -------------------------------------------------------------------------------- 1 | package ktx.async 2 | 3 | import com.badlogic.gdx.utils.Timer 4 | 5 | /** 6 | * Simplifies [Timer] API. 7 | * @param delaySeconds the execution will begin after this delay. 8 | * @param task will be executed on the rendering thread. 9 | * @return callback to the task. 10 | */ 11 | inline fun schedule( 12 | delaySeconds: Float, 13 | crossinline task: () -> Unit, 14 | ) = Timer.schedule( 15 | object : Timer.Task() { 16 | override fun run() { 17 | task() 18 | } 19 | }, 20 | delaySeconds, 21 | )!! 22 | 23 | /** 24 | * Simplifies [Timer] API. 25 | * @param intervalSeconds time between each execution. 26 | * @param delaySeconds the execution will begin after this delay. Defaults to 0. 27 | * @param repeatCount **additional** task executions amount. For example, repeat count of 2 causes the task to be 28 | * executed 3 times. Optional. If not set, task will be repeated indefinitely. 29 | * @param task will be repeatedly executed on the rendering thread. 30 | * @return callback to the task. 31 | */ 32 | inline fun interval( 33 | intervalSeconds: Float, 34 | delaySeconds: Float = 0f, 35 | repeatCount: Int = -2, // Timer.FOREVER 36 | crossinline task: () -> Unit, 37 | ) = Timer.schedule( 38 | object : Timer.Task() { 39 | override fun run() { 40 | task() 41 | } 42 | }, 43 | delaySeconds, 44 | intervalSeconds, 45 | repeatCount, 46 | )!! 47 | -------------------------------------------------------------------------------- /async/src/test/kotlin/ktx/async/AsyncTest.kt: -------------------------------------------------------------------------------- 1 | package ktx.async 2 | 3 | import com.badlogic.gdx.ApplicationAdapter 4 | import com.badlogic.gdx.Gdx 5 | import com.badlogic.gdx.backends.headless.HeadlessApplication 6 | import com.badlogic.gdx.utils.async.AsyncExecutor 7 | import org.junit.After 8 | import org.junit.Assert.assertSame 9 | import org.junit.Before 10 | import java.util.concurrent.CompletableFuture 11 | import java.util.concurrent.TimeUnit.SECONDS 12 | 13 | /** 14 | * Base class for asynchronous API tests. Initiates a [HeadlessApplication] to handle the rendering loop and execution 15 | * of scheduled tasks. 16 | */ 17 | abstract class AsyncTest { 18 | @Before 19 | open fun `setup libGDX application`() { 20 | Gdx.app = HeadlessApplication(object : ApplicationAdapter() {}) 21 | val initTask = CompletableFuture() 22 | Gdx.app.postRunnable { 23 | // Saving reference to the rendering thread: 24 | KtxAsync.initiate() 25 | initTask.complete(Unit) 26 | } 27 | initTask.join() 28 | assertSame(getExecutionThread(Gdx.app::postRunnable), getMainRenderingThread()) 29 | } 30 | 31 | /** 32 | * Finds the main rendering [Thread] registered in [MainDispatcher]. 33 | */ 34 | protected fun getMainRenderingThread(): Thread = MainDispatcher.mainThread 35 | 36 | /** 37 | * Finds the [Thread] that [AsyncExecutor] executes tasks with. Note that if the executor uses more than a single 38 | * thread, this method will return only the first one that happens to execute the extraction task. When relying on 39 | * this method to find the executor thread, make sure the executor is single-threaded. 40 | */ 41 | protected fun getExecutionThread(executor: AsyncExecutor): Thread = 42 | getExecutionThread { task -> 43 | executor.submit { task.run() } 44 | } 45 | 46 | /** 47 | * Finds the [Thread] that executes the passed [Runnable]. 48 | */ 49 | private fun getExecutionThread(executor: (Runnable) -> Unit): Thread { 50 | val thread = CompletableFuture() 51 | executor( 52 | Runnable { 53 | thread.complete(Thread.currentThread()) 54 | }, 55 | ) 56 | return thread.get(5L, SECONDS) 57 | } 58 | 59 | /** 60 | * Blocks the current thread for the given amount of [seconds]. 61 | */ 62 | protected fun delay(seconds: Float) { 63 | delay((seconds * 1000).toLong()) 64 | } 65 | 66 | /** 67 | * Blocks the current thread for the given amount of [millis]. 68 | */ 69 | protected fun delay(millis: Long) { 70 | Thread.sleep(millis) 71 | } 72 | 73 | @After 74 | open fun `exit libGDX application`() { 75 | Gdx.app.exit() 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /async/src/test/kotlin/ktx/async/TimerTest.kt: -------------------------------------------------------------------------------- 1 | package ktx.async 2 | 3 | import org.junit.Assert.assertEquals 4 | import org.junit.Assert.assertFalse 5 | import org.junit.Assert.assertTrue 6 | import org.junit.Test 7 | import java.util.concurrent.atomic.AtomicBoolean 8 | import java.util.concurrent.atomic.AtomicInteger 9 | 10 | /** 11 | * Tests [com.badlogic.gdx.utils.Timer] extensions. 12 | */ 13 | class TimerTest : AsyncTest() { 14 | // Most Timer.Task properties are private, unfortunately. Only scheduled status can be tested reliably. 15 | @Test 16 | fun `should schedule asynchronous task`() { 17 | // When: 18 | val task = schedule(delaySeconds = 1f) {} 19 | 20 | // Then: 21 | assertTrue(task.isScheduled) 22 | } 23 | 24 | @Test 25 | fun `should execute asynchronous task`() { 26 | // Given: 27 | val result = AtomicInteger() 28 | 29 | // When: 30 | schedule(delaySeconds = 0.05f) { 31 | result.set(5) 32 | } 33 | 34 | // Then: 35 | delay(0.1f) 36 | assertEquals(5, result.get()) 37 | } 38 | 39 | @Test 40 | fun `should cancel asynchronous task`() { 41 | // Given: 42 | val executed = AtomicBoolean() 43 | val task = schedule(delaySeconds = 0.05f) { executed.set(true) } 44 | 45 | // When: 46 | task.cancel() 47 | 48 | // Then: 49 | delay(0.1f) 50 | assertFalse(executed.get()) 51 | } 52 | 53 | @Test 54 | fun `should schedule repeated asynchronous task`() { 55 | // When: 56 | val task = interval(delaySeconds = 1f, intervalSeconds = 1f) {} 57 | 58 | // Then: 59 | assertTrue(task.isScheduled) 60 | } 61 | 62 | @Test 63 | fun `should execute repeated asynchronous task`() { 64 | // Given: 65 | val executions = AtomicInteger() 66 | 67 | // When: 68 | interval(delaySeconds = 0f, intervalSeconds = 0.05f) { 69 | executions.incrementAndGet() 70 | } 71 | 72 | // Then: 73 | delay(0.2f) 74 | assert(executions.get() > 2) 75 | } 76 | 77 | @Test 78 | fun `should execute repeated asynchronous task N times`() { 79 | // Given: 80 | val executions = AtomicInteger() 81 | 82 | // When: 83 | interval(delaySeconds = 0f, intervalSeconds = 0.05f, repeatCount = 1) { 84 | executions.incrementAndGet() 85 | } 86 | 87 | // Then: executions count should be equal to 1+repeatCount (initial execution + repetitions). 88 | delay(0.2f) 89 | assertEquals(executions.get(), 2) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /async/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker: -------------------------------------------------------------------------------- 1 | mock-maker-inline 2 | -------------------------------------------------------------------------------- /box2d/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import ktx.* 2 | 3 | dependencies { 4 | api("com.badlogicgames.gdx:gdx-box2d:$gdxVersion") 5 | testImplementation("com.badlogicgames.gdx:gdx-box2d-platform:$gdxVersion:natives-desktop") 6 | } 7 | -------------------------------------------------------------------------------- /box2d/gradle.properties: -------------------------------------------------------------------------------- 1 | projectName=ktx-box2d 2 | projectDesc=Box2D physics engine utilities for Kotlin libGDX applications. 3 | -------------------------------------------------------------------------------- /box2d/img/box.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libktx/ktx/0f28adef8191a15a3c4f453a97fe15524fb3c8fd/box2d/img/box.png -------------------------------------------------------------------------------- /box2d/img/chain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libktx/ktx/0f28adef8191a15a3c4f453a97fe15524fb3c8fd/box2d/img/chain.png -------------------------------------------------------------------------------- /box2d/img/circle-custom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libktx/ktx/0f28adef8191a15a3c4f453a97fe15524fb3c8fd/box2d/img/circle-custom.png -------------------------------------------------------------------------------- /box2d/img/circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libktx/ktx/0f28adef8191a15a3c4f453a97fe15524fb3c8fd/box2d/img/circle.png -------------------------------------------------------------------------------- /box2d/img/edge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libktx/ktx/0f28adef8191a15a3c4f453a97fe15524fb3c8fd/box2d/img/edge.png -------------------------------------------------------------------------------- /box2d/img/joint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libktx/ktx/0f28adef8191a15a3c4f453a97fe15524fb3c8fd/box2d/img/joint.png -------------------------------------------------------------------------------- /box2d/img/loop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libktx/ktx/0f28adef8191a15a3c4f453a97fe15524fb3c8fd/box2d/img/loop.png -------------------------------------------------------------------------------- /box2d/img/multiple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libktx/ktx/0f28adef8191a15a3c4f453a97fe15524fb3c8fd/box2d/img/multiple.png -------------------------------------------------------------------------------- /box2d/img/polygon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libktx/ktx/0f28adef8191a15a3c4f453a97fe15524fb3c8fd/box2d/img/polygon.png -------------------------------------------------------------------------------- /box2d/src/main/kotlin/ktx/box2d/Box2DDsl.kt: -------------------------------------------------------------------------------- 1 | package ktx.box2d 2 | 3 | import kotlin.annotation.AnnotationTarget.CLASS 4 | import kotlin.annotation.AnnotationTarget.FUNCTION 5 | import kotlin.annotation.AnnotationTarget.TYPE 6 | import kotlin.annotation.AnnotationTarget.TYPEALIAS 7 | import kotlin.annotation.AnnotationTarget.TYPE_PARAMETER 8 | 9 | /** 10 | * Marks KTX Box2D type-safe builders. 11 | * @see BodyDefinition 12 | * @see FixtureDefinition 13 | */ 14 | @DslMarker 15 | @Target(CLASS, TYPE_PARAMETER, FUNCTION, TYPE, TYPEALIAS) 16 | annotation class Box2DDsl 17 | -------------------------------------------------------------------------------- /box2d/src/main/kotlin/ktx/box2d/fixtures.kt: -------------------------------------------------------------------------------- 1 | package ktx.box2d 2 | 3 | import com.badlogic.gdx.physics.box2d.Filter 4 | import com.badlogic.gdx.physics.box2d.Fixture 5 | import com.badlogic.gdx.physics.box2d.FixtureDef 6 | import kotlin.contracts.ExperimentalContracts 7 | import kotlin.contracts.InvocationKind 8 | import kotlin.contracts.contract 9 | 10 | /** 11 | * Box2D building DSL utility class. [FixtureDef] extension exposing new properties. Note that when using fixture 12 | * builders from [BodyDefinition], [FixtureDefinition.shape] field should not be modified - fixture's shape of the 13 | * chosen type will already be set in the [FixtureDefinition] instance. 14 | * @see BodyDefinition 15 | */ 16 | @Box2DDsl 17 | class FixtureDefinition : FixtureDef() { 18 | /** Custom data object assigned to [Fixture.getUserData]. Allows to store additional data about the [Fixture] without 19 | * having to override the class. Defaults to null. */ 20 | var userData: Any? = null 21 | 22 | /** Invoked after the [Fixture] is fully constructed. 23 | * @see onCreate */ 24 | var creationCallback: ((Fixture) -> Unit)? = null 25 | 26 | /** If true, will dispose of [FixtureDef.shape] right after [Fixture] construction. */ 27 | var disposeOfShape: Boolean = true 28 | 29 | /** 30 | * @param callback will be invoked after the [Fixture] defined by this object will be fully constructed. 31 | * @see creationCallback 32 | */ 33 | fun onCreate(callback: (Fixture) -> Unit) { 34 | creationCallback = callback 35 | } 36 | } 37 | 38 | /** 39 | * Utility extension method for setting up of [FixtureDef.filter]. Allows to copy an existing [Filter] instance to avoid 40 | * copying each property manually. 41 | * @param filter its properties will be copied. 42 | * @return [FixtureDef.filter] of this [FixtureDef] instance. 43 | * @see Filter 44 | */ 45 | fun FixtureDef.filter(filter: Filter): Filter { 46 | val fixtureFilter = this.filter 47 | fixtureFilter.categoryBits = filter.categoryBits 48 | fixtureFilter.maskBits = filter.maskBits 49 | fixtureFilter.groupIndex = filter.groupIndex 50 | return fixtureFilter 51 | } 52 | 53 | /** 54 | * Inlined utility extension method for setting up of [FixtureDef.filter]. Exposes [Filter] properties under `this`. 55 | * @param init inlined. Uses [FixtureDef.filter] as `this`. 56 | */ 57 | @OptIn(ExperimentalContracts::class) 58 | inline fun FixtureDef.filter(init: (@Box2DDsl Filter).() -> Unit) { 59 | contract { callsInPlace(init, InvocationKind.EXACTLY_ONCE) } 60 | filter.init() 61 | } 62 | -------------------------------------------------------------------------------- /box2d/src/test/kotlin/ktx/box2d/Box2DTest.kt: -------------------------------------------------------------------------------- 1 | package ktx.box2d 2 | 3 | import com.badlogic.gdx.math.Vector2 4 | import com.badlogic.gdx.physics.box2d.Box2D 5 | import com.badlogic.gdx.physics.box2d.ChainShape 6 | import com.badlogic.gdx.physics.box2d.EdgeShape 7 | import com.badlogic.gdx.physics.box2d.PolygonShape 8 | import org.junit.Assert.assertEquals 9 | import org.junit.BeforeClass 10 | 11 | /** 12 | * Initiates Box2D native library. Provides comparison methods for common Box2D data objects that cannot be easily 13 | * compared with standard `equals`. 14 | */ 15 | abstract class Box2DTest { 16 | protected val floatTolerance = 0.0001f 17 | 18 | protected fun assertChainEquals( 19 | vertices: Array, 20 | shape: ChainShape, 21 | ) { 22 | assertEquals( 23 | "${vertices.size} vertices expected, ${shape.vertexCount} found instead.", 24 | vertices.size, 25 | shape.vertexCount, 26 | ) 27 | val vertex = Vector2() 28 | vertices.forEachIndexed { index, expected -> 29 | shape.getVertex(index, vertex) 30 | val errorMessage = "Vertex at $index should equal $expected, $vertex found instead." 31 | assertEquals(errorMessage, expected.x, vertex.x, floatTolerance) 32 | assertEquals(errorMessage, expected.y, vertex.y, floatTolerance) 33 | } 34 | } 35 | 36 | protected fun assertPolygonEquals( 37 | vertices: Array, 38 | shape: PolygonShape, 39 | ) { 40 | assertEquals( 41 | "${vertices.size} vertices expected, ${shape.vertexCount} found instead.", 42 | vertices.size, 43 | shape.vertexCount, 44 | ) 45 | val vertex = Vector2() 46 | vertices.forEachIndexed { index, expected -> 47 | shape.getVertex(index, vertex) 48 | val errorMessage = "Vertex at $index should equal $expected, $vertex found instead." 49 | assertEquals(errorMessage, expected.x, vertex.x, floatTolerance) 50 | assertEquals(errorMessage, expected.y, vertex.y, floatTolerance) 51 | } 52 | } 53 | 54 | protected fun assertEdgeEquals( 55 | from: Vector2, 56 | to: Vector2, 57 | edgeShape: EdgeShape, 58 | ) { 59 | val vertex = Vector2() 60 | edgeShape.getVertex1(vertex) 61 | assertEquals(from, vertex) 62 | edgeShape.getVertex2(vertex) 63 | assertEquals(to, vertex) 64 | } 65 | 66 | companion object { 67 | @JvmStatic 68 | @BeforeClass 69 | fun `initiate Box2D`() { 70 | Box2D.init() 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /box2d/src/test/kotlin/ktx/box2d/FixturesTest.kt: -------------------------------------------------------------------------------- 1 | package ktx.box2d 2 | 3 | import com.badlogic.gdx.physics.box2d.Filter 4 | import com.badlogic.gdx.physics.box2d.Fixture 5 | import com.badlogic.gdx.physics.box2d.FixtureDef 6 | import org.junit.Assert.assertEquals 7 | import org.junit.Assert.assertSame 8 | import org.junit.Test 9 | 10 | /** 11 | * Tests utility extensions related to body fixtures. 12 | */ 13 | class FixturesTest : Box2DTest() { 14 | @Test 15 | fun `should set filter properties of FixtureDef`() { 16 | val fixtureDefinition = FixtureDef() 17 | 18 | fixtureDefinition.filter { 19 | categoryBits = 1 20 | maskBits = 2 21 | groupIndex = 3 22 | } 23 | 24 | fixtureDefinition.filter.apply { 25 | assertEquals(1.toShort(), categoryBits) 26 | assertEquals(2.toShort(), maskBits) 27 | assertEquals(3.toShort(), groupIndex) 28 | } 29 | } 30 | 31 | @Test 32 | fun `should configure filter exactly once`() { 33 | val fixtureDefinition = FixtureDef() 34 | val variable: Int 35 | 36 | fixtureDefinition.filter { 37 | variable = 42 38 | } 39 | 40 | assertEquals(42, variable) 41 | } 42 | 43 | @Test 44 | fun `should copy filter properties into filter of FixtureDef`() { 45 | val fixtureDefinition = FixtureDef() 46 | val filter = 47 | Filter().apply { 48 | categoryBits = 1 49 | maskBits = 2 50 | groupIndex = 3 51 | } 52 | 53 | fixtureDefinition.filter(filter) 54 | 55 | fixtureDefinition.filter.apply { 56 | assertEquals(1.toShort(), categoryBits) 57 | assertEquals(2.toShort(), maskBits) 58 | assertEquals(3.toShort(), groupIndex) 59 | } 60 | } 61 | 62 | @Test 63 | fun `should replace creation callback`() { 64 | val fixtureDefinition = FixtureDefinition() 65 | val callback: (Fixture) -> Unit = {} 66 | 67 | fixtureDefinition.onCreate(callback) 68 | 69 | assertSame(callback, fixtureDefinition.creationCallback) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /buildSrc/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-dsl` 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/ktx/Versions.kt: -------------------------------------------------------------------------------- 1 | package ktx 2 | 3 | const val gdxVersion = "1.13.1" 4 | const val kotlinCoroutinesVersion = "1.10.1" 5 | 6 | const val artemisOdbVersion = "2.3.0" 7 | const val ashleyVersion = "1.7.4" 8 | const val gdxAiVersion = "1.8.2" 9 | const val visUiVersion = "1.5.5" 10 | 11 | const val spekVersion = "1.1.5" 12 | const val kotlinTestVersion = "2.0.7" 13 | const val kotlinMockitoVersion = "5.4.0" 14 | const val assertjVersion = "3.26.3" 15 | const val junitVersion = "4.13.2" 16 | const val slf4jVersion = "1.7.36" 17 | const val wireMockVersion = "2.27.2" 18 | const val ktlintVersion = "1.5.0" 19 | -------------------------------------------------------------------------------- /collections/gradle.properties: -------------------------------------------------------------------------------- 1 | projectName=ktx-collections 2 | projectDesc=libGDX custom collections utilities for applications developed with Kotlin. 3 | -------------------------------------------------------------------------------- /freetype-async/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import ktx.* 2 | 3 | dependencies { 4 | api(project(":assets-async")) 5 | api(project(":freetype")) 6 | api("com.badlogicgames.gdx:gdx-freetype:$gdxVersion") 7 | api("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinCoroutinesVersion") 8 | 9 | testImplementation(project(":async").dependencyProject.sourceSets.test.get().output) 10 | testImplementation(project(":freetype").dependencyProject.sourceSets.test.get().output) 11 | testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:$kotlinCoroutinesVersion") 12 | testImplementation("com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-desktop") 13 | testImplementation("com.badlogicgames.gdx:gdx-backend-headless:$gdxVersion") 14 | testImplementation("com.badlogicgames.gdx:gdx-backend-lwjgl:$gdxVersion") 15 | testImplementation("com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-desktop") 16 | } 17 | -------------------------------------------------------------------------------- /freetype-async/gradle.properties: -------------------------------------------------------------------------------- 1 | projectName=ktx-freetype-async 2 | projectDesc=Kotlin utilities for asynchronous FreeType fonts loading in libGDX applications. 3 | -------------------------------------------------------------------------------- /freetype-async/src/test/resources/ktx/freetype/async/hack.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libktx/ktx/0f28adef8191a15a3c4f453a97fe15524fb3c8fd/freetype-async/src/test/resources/ktx/freetype/async/hack.otf -------------------------------------------------------------------------------- /freetype-async/src/test/resources/ktx/freetype/async/hack.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libktx/ktx/0f28adef8191a15a3c4f453a97fe15524fb3c8fd/freetype-async/src/test/resources/ktx/freetype/async/hack.ttf -------------------------------------------------------------------------------- /freetype/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import ktx.* 2 | 3 | dependencies { 4 | api(project(":assets")) 5 | api("com.badlogicgames.gdx:gdx-freetype:$gdxVersion") 6 | 7 | testImplementation("com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-desktop") 8 | testImplementation("com.badlogicgames.gdx:gdx-backend-lwjgl:$gdxVersion") 9 | testImplementation("com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-desktop") 10 | } 11 | -------------------------------------------------------------------------------- /freetype/gradle.properties: -------------------------------------------------------------------------------- 1 | projectName=ktx-freetype 2 | projectDesc=Kotlin utilities for handling FreeType fonts in libGDX applications. 3 | -------------------------------------------------------------------------------- /freetype/src/test/resources/ktx/freetype/hack.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libktx/ktx/0f28adef8191a15a3c4f453a97fe15524fb3c8fd/freetype/src/test/resources/ktx/freetype/hack.otf -------------------------------------------------------------------------------- /freetype/src/test/resources/ktx/freetype/hack.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libktx/ktx/0f28adef8191a15a3c4f453a97fe15524fb3c8fd/freetype/src/test/resources/ktx/freetype/hack.ttf -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | libGroup=io.github.libktx 2 | 3 | kotlinVersion=2.1.10 4 | 5 | dokkaVersion=2.0.0 6 | junitPlatformVersion=1.2.0 7 | configurationsPluginVersion=3.0.3 8 | 9 | # The following properties are mock-ups. To deploy the archives, 10 | # you need to fill these with proper data. To avoid accidental 11 | # commit of your credentials, please keep these in your Gradle 12 | # home folder. 13 | 14 | # Jar signing data: 15 | #signing.keyId= 16 | #signing.password= 17 | #signing.secretKeyRingFile= 18 | 19 | # Jira logging data: 20 | ossrhUsername= 21 | ossrhPassword= 22 | 23 | org.gradle.jvmargs=-Xmx1024m 24 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libktx/ktx/0f28adef8191a15a3c4f453a97fe15524fb3c8fd/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.4-all.zip 4 | networkTimeout=10000 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /graphics/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import ktx.* 2 | 3 | dependencies { 4 | testImplementation("com.badlogicgames.gdx:gdx-backend-lwjgl:$gdxVersion") 5 | testImplementation("com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-desktop") 6 | } 7 | -------------------------------------------------------------------------------- /graphics/gradle.properties: -------------------------------------------------------------------------------- 1 | projectName=ktx-graphics 2 | projectDesc=Kotlin utilities for handling graphics in libGDX applications. 3 | -------------------------------------------------------------------------------- /graphics/src/main/kotlin/ktx/graphics/color.kt: -------------------------------------------------------------------------------- 1 | package ktx.graphics 2 | 3 | import com.badlogic.gdx.graphics.Color 4 | 5 | /** 6 | * Factory methods for libGDX [Color] class. Allows using named parameters. 7 | * @param red red color value. 8 | * @param green green color value. 9 | * @param blue blue color value. 10 | * @param alpha color alpha value. Optional, defaults to 1f (non-transparent). 11 | * @return a new [Color] instance. 12 | */ 13 | fun color( 14 | red: Float, 15 | green: Float, 16 | blue: Float, 17 | alpha: Float = 1f, 18 | ) = Color(red, green, blue, alpha) 19 | 20 | /** 21 | * Allows to copy this [Color] instance, optionally changing chosen properties. 22 | * @param red red color value. If null, will be copied from [Color.r]. Defaults to null. 23 | * @param green green color value. If null, will be copied from [Color.g]. Defaults to null. 24 | * @param blue blue color value. If null, will be copied from [Color.b]. Defaults to null. 25 | * @param alpha color alpha value. If null, will be copied from [Color.a]. Defaults to null. 26 | * @return a new [Color] instance with values copied from this color and optionally overridden by the parameters. */ 27 | fun Color.copy( 28 | red: Float? = null, 29 | green: Float? = null, 30 | blue: Float? = null, 31 | alpha: Float? = null, 32 | ) = Color(red ?: r, green ?: g, blue ?: b, alpha ?: a) 33 | 34 | /** 35 | * Returns the red component of the color. 36 | * Allows using destructuring declarations when working with libGDX [Color] class, for example: 37 | * ``` 38 | * val (red, green, blue) = myColor 39 | * ``` 40 | * @return [Color.r] 41 | */ 42 | @Suppress("NOTHING_TO_INLINE") 43 | inline operator fun Color.component1() = r 44 | 45 | /** 46 | * Returns the green component of the color. 47 | * Allows using destructuring declarations when working with libGDX [Color] class, for example: 48 | * ``` 49 | * val (red, green, blue) = myColor 50 | * ``` 51 | * @return [Color.g] 52 | */ 53 | @Suppress("NOTHING_TO_INLINE") 54 | inline operator fun Color.component2() = g 55 | 56 | /** 57 | * Returns the blue component of the color. 58 | * Allows using destructuring declarations when working with libGDX [Color] class, for example: 59 | * ``` 60 | * val (red, green, blue) = myColor 61 | * ``` 62 | * @return [Color.b] 63 | */ 64 | @Suppress("NOTHING_TO_INLINE") 65 | inline operator fun Color.component3() = b 66 | 67 | /** 68 | * Returns the alpha component of the color. 69 | * Allows using destructuring declarations when working with libGDX [Color] class, for example: 70 | * ``` 71 | * val (red, green, blue, alpha) = myColor 72 | * ``` 73 | * @return [Color.a] 74 | */ 75 | @Suppress("NOTHING_TO_INLINE") 76 | inline operator fun Color.component4() = a 77 | -------------------------------------------------------------------------------- /graphics/src/main/kotlin/ktx/graphics/text.kt: -------------------------------------------------------------------------------- 1 | package ktx.graphics 2 | 3 | import com.badlogic.gdx.graphics.g2d.BitmapFont 4 | import com.badlogic.gdx.graphics.g2d.GlyphLayout 5 | import com.badlogic.gdx.math.Vector2 6 | 7 | /** 8 | * Calculates center position of the chosen [text] written with this [BitmapFont] on an object 9 | * with given [width] and [height] drawn at [x] and [y] coordinates. 10 | * 11 | * The passed [text] rendered with the selected [BitmapFont] at the returned coordinates should 12 | * be in the middle of the object. 13 | * 14 | * Note that [x] and [y] are essentially an optional offset added to the calculated position. 15 | */ 16 | fun BitmapFont.center( 17 | text: String, 18 | width: Float, 19 | height: Float, 20 | x: Float = 0f, 21 | y: Float = 0f, 22 | ): Vector2 { 23 | val layout = GlyphLayout(this, text) 24 | return Vector2(x + (width - layout.width) / 2f, y + (height + layout.height) / 2f) 25 | } 26 | -------------------------------------------------------------------------------- /graphics/src/test/kotlin/ktx/graphics/ColorTest.kt: -------------------------------------------------------------------------------- 1 | package ktx.graphics 2 | 3 | import com.badlogic.gdx.graphics.Color 4 | import org.junit.Assert.assertEquals 5 | import org.junit.Assert.assertNotEquals 6 | import org.junit.Assert.assertNotSame 7 | import org.junit.Test 8 | 9 | class ColorTest { 10 | @Test 11 | fun `should construct Color`() { 12 | val color = color(red = 0.25f, green = 0.5f, blue = 0.75f, alpha = 0.6f) 13 | 14 | assertEquals(0.25f, color.r) 15 | assertEquals(0.5f, color.g) 16 | assertEquals(0.75f, color.b) 17 | assertEquals(0.6f, color.a) 18 | } 19 | 20 | @Test 21 | fun `should construct Color with optional alpha`() { 22 | val color = color(red = 0.25f, green = 0.5f, blue = 0.75f) 23 | 24 | assertEquals(0.25f, color.r) 25 | assertEquals(0.5f, color.g) 26 | assertEquals(0.75f, color.b) 27 | assertEquals(1f, color.a) 28 | } 29 | 30 | @Test 31 | fun `should copy Color values`() { 32 | val color = Color(0.4f, 0.5f, 0.6f, 0.7f) 33 | 34 | val copy = color.copy() 35 | 36 | assertNotSame(color, copy) 37 | assertEquals(0.4f, copy.r) 38 | assertEquals(0.5f, copy.g) 39 | assertEquals(0.6f, copy.b) 40 | assertEquals(0.7f, copy.a) 41 | assertEquals(color, copy) 42 | } 43 | 44 | @Test 45 | fun `should override chosen Color values when copying`() { 46 | val color = Color(0.4f, 0.5f, 0.6f, 0.7f) 47 | 48 | val copy = color.copy(red = 0.25f, green = 0.35f, blue = 0.45f, alpha = 0.55f) 49 | 50 | assertNotSame(color, copy) 51 | assertEquals(0.25f, copy.r) 52 | assertEquals(0.35f, copy.g) 53 | assertEquals(0.45f, copy.b) 54 | assertEquals(0.55f, copy.a) 55 | assertNotEquals(color, copy) 56 | } 57 | 58 | @Test 59 | fun `should destruct Color into red, green, blue and alpha components`() { 60 | val color = Color(0.1f, 0.2f, 0.3f, 0.4f) 61 | 62 | val (red, green, blue, alpha) = color 63 | 64 | assertEquals(color.r, red) 65 | assertEquals(color.g, green) 66 | assertEquals(color.b, blue) 67 | assertEquals(color.a, alpha) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /graphics/src/test/kotlin/ktx/graphics/TextUtilitiesTest.kt: -------------------------------------------------------------------------------- 1 | package ktx.graphics 2 | 3 | import com.badlogic.gdx.Gdx 4 | import com.badlogic.gdx.backends.lwjgl.LwjglFiles 5 | import com.badlogic.gdx.graphics.g2d.BitmapFont 6 | import org.junit.After 7 | import org.junit.Assert.assertEquals 8 | import org.junit.Before 9 | import org.junit.Test 10 | import org.mockito.kotlin.mock 11 | import com.badlogic.gdx.utils.Array as GdxArray 12 | 13 | class TextUtilitiesTest { 14 | @Before 15 | fun `setup files`() { 16 | Gdx.files = LwjglFiles() 17 | } 18 | 19 | @Test 20 | fun `should center text on a rectangle`() { 21 | val font = FakeFont() 22 | val width = 100f 23 | val height = 200f 24 | 25 | val position = font.center("text", width, height) 26 | 27 | assertEquals(38.5f, position.x, 0.1f) 28 | assertEquals(105.5f, position.y, 0.1f) 29 | } 30 | 31 | @Test 32 | fun `should center text on a rectangle at given position`() { 33 | val font = FakeFont() 34 | 35 | val position = font.center("text", x = 100f, y = 200f, width = 100f, height = 200f) 36 | 37 | assertEquals(138.5f, position.x, 0.1f) 38 | assertEquals(305.5f, position.y, 0.1f) 39 | } 40 | 41 | @After 42 | fun `dispose of files`() { 43 | Gdx.files = null 44 | } 45 | 46 | /** 47 | * Loads the .fnt settings file of the default libGDX Arial font, but 48 | * omits loading the textures. For testing purposes. 49 | */ 50 | class FakeFont : 51 | BitmapFont( 52 | BitmapFontData(Gdx.files.classpath("com/badlogic/gdx/utils/lsans-15.fnt"), true), 53 | GdxArray.with(mock()), 54 | true, 55 | ) { 56 | override fun load(data: BitmapFontData?) { 57 | // Do nothing. 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /i18n/gradle.properties: -------------------------------------------------------------------------------- 1 | projectName=ktx-i18n 2 | projectDesc=Internationalization utilities for libGDX applications developed with Kotlin. 3 | -------------------------------------------------------------------------------- /i18n/src/main/kotlin/ktx/i18n/i18n.kt: -------------------------------------------------------------------------------- 1 | package ktx.i18n 2 | 3 | import com.badlogic.gdx.utils.I18NBundle 4 | 5 | /** 6 | * @param key property name in the i18n bundle. 7 | * @param args will replace the argument placeholders in the selected bundle line. The order is preserved and honored. 8 | * @return formatted value mapped to the key extracted from the bundle. 9 | */ 10 | operator fun I18NBundle.get( 11 | key: String, 12 | vararg args: Any?, 13 | ): String = this.format(key, *args) 14 | 15 | /** 16 | * @param line value mapped to its ID will be extracted. Its [toString] method should match property name in i18n bundle. 17 | * @return localized text from the selected bundle. 18 | */ 19 | operator fun I18NBundle.get(line: BundleLine): String = this.get(line.toString()) 20 | 21 | /** 22 | * @param line value mapped to its ID will be extracted. Its [toString] method should match property name in i18n bundle. 23 | * @param args will replace the argument placeholders in the selected bundle line. The order is preserved and honored. 24 | * @return formatted value mapped to the key extracted from the bundle. 25 | */ 26 | operator fun I18NBundle.get( 27 | line: BundleLine, 28 | vararg args: Any?, 29 | ): String = this.format(line.toString(), *args) 30 | 31 | /** 32 | * Represents a single [I18NBundle]. Expects that its [toString] method returns a valid bundle line ID. Advised to be 33 | * implemented by an enum to introduce type-safe bundle representation with compile time safety. 34 | */ 35 | interface BundleLine { 36 | /** 37 | * [I18NBundle] instance storing the localized line. Has to be overridden in order to use [nls] and [invoke] methods. 38 | */ 39 | val bundle: I18NBundle 40 | get() = throw NotImplementedError("Override BundleLine#bundle to use formatting methods and properties.") 41 | 42 | /** 43 | * [I18NBundle] line value provided by [bundle]. Use this property if the bundle line has no arguments. 44 | */ 45 | val nls: String 46 | get() = bundle[toString()] 47 | 48 | /** 49 | * Use this method if the bundle line has any arguments. 50 | * @param arguments will be used to replace placeholders in formatted the line. 51 | * @return formatted [I18NBundle] line value provided by [bundle]. 52 | */ 53 | fun nls(vararg arguments: Any?): String = bundle.format(toString(), *arguments) 54 | 55 | /** 56 | * [nls] alias for extra conciseness. Use this property if the bundle line has no arguments. 57 | * @return [I18NBundle] line value provided by [bundle]. 58 | */ 59 | operator fun invoke(): String = bundle[toString()] 60 | 61 | /** 62 | * [nls] alias for extra conciseness. Use this method if the bundle line has any arguments. 63 | * @param arguments will be used to replace placeholders in formatted the line. 64 | * @return formatted [I18NBundle] line value provided by [bundle]. 65 | */ 66 | operator fun invoke(vararg arguments: Any?): String = bundle.format(toString(), *arguments) 67 | } 68 | -------------------------------------------------------------------------------- /i18n/src/test/kotlin/ktx/i18n/I18nTest.kt: -------------------------------------------------------------------------------- 1 | package ktx.i18n 2 | 3 | import com.badlogic.gdx.files.FileHandle 4 | import com.badlogic.gdx.utils.I18NBundle 5 | import io.kotlintest.mock.mock 6 | import ktx.i18n.I18nTest.BundleEnum.key 7 | import ktx.i18n.I18nTest.BundleEnum.keyWithArgument 8 | import org.junit.After 9 | import org.junit.Assert.assertEquals 10 | import org.junit.Before 11 | import org.junit.Test 12 | import java.io.File 13 | 14 | /** 15 | * Tests internationalization-related classes and functions stored in *i18n.kt*. 16 | */ 17 | class I18nTest { 18 | private val bundleContent = """ 19 | key=Value. 20 | keyWithArgument=Value with {0} argument. 21 | """ 22 | private val bundleFile = FileHandle(File.createTempFile("nls", ".properties")) 23 | private var bundle: I18NBundle = mock() 24 | 25 | @Before 26 | fun `create I18NBundle`() { 27 | bundleFile.writeString(bundleContent, false) 28 | bundle = I18NBundle.createBundle(bundleFile.sibling(bundleFile.nameWithoutExtension())) 29 | BundleEnum.i18nBundle = bundle 30 | } 31 | 32 | @Test 33 | fun `should access bundle lines with brace operator`() { 34 | assertEquals("Value.", bundle["key"]) 35 | } 36 | 37 | @Test 38 | fun `should format bundle lines with brace operator and arguments`() { 39 | assertEquals("Value with 1 argument.", bundle["keyWithArgument", 1]) 40 | } 41 | 42 | @Test 43 | fun `should access values of BundleLine instances with brace operator`() { 44 | assertEquals("Value.", bundle[key]) 45 | } 46 | 47 | @Test 48 | fun `should format values of BundleLine instances with brace operator and arguments`() { 49 | assertEquals("Value with 1 argument.", bundle[keyWithArgument, 1]) 50 | } 51 | 52 | @Test 53 | fun `should find BundleLine value with nls property`() { 54 | assertEquals("Value.", key.nls) 55 | } 56 | 57 | @Test 58 | fun `should format BundleLine value using nls method with arguments`() { 59 | assertEquals("Value with 1 argument.", keyWithArgument.nls(1)) 60 | } 61 | 62 | @Test 63 | fun `should find BundleLine value with invocation syntax`() { 64 | assertEquals("Value.", key()) 65 | } 66 | 67 | @Test 68 | fun `should format BundleLine value with invocation syntax and arguments`() { 69 | assertEquals("Value with 1 argument.", keyWithArgument(1)) 70 | } 71 | 72 | @Test(expected = NotImplementedError::class) 73 | fun `should prohibit bundle property access when not overridden`() { 74 | val bundleLine = object : BundleLine {} 75 | 76 | bundleLine.bundle 77 | } 78 | 79 | @After 80 | fun `delete temporary file`() { 81 | bundleFile.delete() 82 | } 83 | 84 | /** For [BundleLine] tests. */ 85 | @Suppress("ktlint:standard:enum-entry-name-case") 86 | internal enum class BundleEnum : BundleLine { 87 | /** "Value." */ 88 | key, 89 | 90 | /** "Value with {0} argument." */ 91 | keyWithArgument, 92 | 93 | ; 94 | 95 | override val bundle: I18NBundle 96 | get() = i18nBundle!! 97 | 98 | companion object { 99 | var i18nBundle: I18NBundle? = null 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /inject/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import ktx.* 2 | 3 | dependencies { 4 | api(project(":reflect")) 5 | } 6 | -------------------------------------------------------------------------------- /inject/gradle.properties: -------------------------------------------------------------------------------- 1 | projectName=ktx-inject 2 | projectDesc=Lightweight dependency injection without reflection usage for libGDX applications written with Kotlin. 3 | -------------------------------------------------------------------------------- /json/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import ktx.* 2 | 3 | dependencies { 4 | testImplementation("com.badlogicgames.gdx:gdx-backend-lwjgl:$gdxVersion") 5 | testImplementation("com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-desktop") 6 | } 7 | -------------------------------------------------------------------------------- /json/gradle.properties: -------------------------------------------------------------------------------- 1 | projectName=ktx-json 2 | projectDesc=libGDX JSON serialization utilities for Kotlin applications. 3 | -------------------------------------------------------------------------------- /json/src/main/kotlin/ktx/json/json.kt: -------------------------------------------------------------------------------- 1 | package ktx.json 2 | 3 | import com.badlogic.gdx.files.FileHandle 4 | import com.badlogic.gdx.utils.Json 5 | import com.badlogic.gdx.utils.JsonValue 6 | 7 | /** 8 | * Parse an object of type [T] from a [file]. 9 | */ 10 | inline fun Json.fromJson(file: FileHandle): T = fromJson(T::class.java, file) 11 | 12 | /** 13 | * Parse an object of type [T] from a [string]. 14 | */ 15 | inline fun Json.fromJson(string: String): T = fromJson(T::class.java, string) 16 | 17 | /** 18 | * Sets a [tag] to use instead of the fully qualifier class name for type [T]. 19 | * This can make the JSON easier to read. 20 | */ 21 | inline fun Json.addClassTag(tag: String) = addClassTag(tag, T::class.java) 22 | 23 | /** 24 | * Returns the tag for type [T], or null if none was defined. 25 | */ 26 | inline fun Json.getTag(): String? = getTag(T::class.java) 27 | 28 | /** 29 | * Sets the elements in a collection of type [T] to type [E]. 30 | * When the element type is known, the class for each element in the collection 31 | * does not need to be written unless different from the element type. 32 | */ 33 | inline fun Json.setElementType(fieldName: String) = setElementType(T::class.java, fieldName, E::class.java) 34 | 35 | /** 36 | * Registers a [serializer] to use for type [T] instead of the default behavior of 37 | * serializing all of an object fields. 38 | */ 39 | inline fun Json.setSerializer(serializer: Json.Serializer) = setSerializer(T::class.java, serializer) 40 | 41 | /** 42 | * Read a value of type [T] from a [jsonData] attribute with a [name]. 43 | * If [name] is `null`, this will directly read [jsonData] as an object of type [T]. 44 | */ 45 | inline fun Json.readValue( 46 | jsonData: JsonValue, 47 | name: String? = null, 48 | ): T = readValue(T::class.java, if (name == null) jsonData else jsonData.get(name)) 49 | 50 | /** 51 | * Read an iterable value of type [T] with elements of type [E] from a [jsonData] attribute with a [name]. 52 | * If [name] is `null`, this will directly read [jsonData] as an iterable of type [T]. 53 | */ 54 | inline fun , reified E> Json.readArrayValue( 55 | jsonData: JsonValue, 56 | name: String? = null, 57 | ): T = readValue(T::class.java, E::class.java, if (name == null) jsonData else jsonData.get(name)) 58 | -------------------------------------------------------------------------------- /json/src/main/kotlin/ktx/json/serializers.kt: -------------------------------------------------------------------------------- 1 | package ktx.json 2 | 3 | import com.badlogic.gdx.utils.Json 4 | import com.badlogic.gdx.utils.JsonValue 5 | 6 | /** 7 | * Wrapping interface around [com.badlogic.gdx.utils.Json.Serializer]. 8 | * Improves typing by adding nullability information and changes default parameter names. 9 | */ 10 | interface JsonSerializer : Json.Serializer { 11 | override fun read( 12 | json: Json, 13 | jsonValue: JsonValue, 14 | type: Class<*>?, 15 | ): T 16 | 17 | override fun write( 18 | json: Json, 19 | value: T, 20 | type: Class<*>?, 21 | ) 22 | } 23 | 24 | /** 25 | * Wrapping interface around [com.badlogic.gdx.utils.Json.Serializer]. Provides null-safety 26 | * and convenient interface for serializer that is only able to [read]. 27 | * Unlike libGDX [com.badlogic.gdx.utils.Json.ReadOnlySerializer], the default implementation of 28 | * the [write] method throws [UnsupportedOperationException]. 29 | */ 30 | interface ReadOnlyJsonSerializer : JsonSerializer { 31 | override fun write( 32 | json: Json, 33 | value: T, 34 | type: Class<*>?, 35 | ) = throw UnsupportedOperationException("Read-only serializers do not support write method.") 36 | } 37 | 38 | /** 39 | * Factory function to create a [ReadOnlyJsonSerializer] from lambda. 40 | */ 41 | inline fun readOnlySerializer(crossinline reader: (Json, JsonValue, Class<*>?) -> T): Json.Serializer = 42 | object : ReadOnlyJsonSerializer { 43 | override fun read( 44 | json: Json, 45 | jsonValue: JsonValue, 46 | type: Class<*>?, 47 | ): T = reader(json, jsonValue, type) 48 | } 49 | 50 | /** 51 | * Factory function to create a simplified [ReadOnlyJsonSerializer], which accepts only [JsonValue]. 52 | */ 53 | inline fun readOnlySerializer(crossinline read: (JsonValue) -> T): Json.Serializer = 54 | object : ReadOnlyJsonSerializer { 55 | override fun read( 56 | json: Json, 57 | jsonValue: JsonValue, 58 | type: Class<*>?, 59 | ): T = read(jsonValue) 60 | } 61 | -------------------------------------------------------------------------------- /json/src/test/kotlin/ktx/json/serializersTest.kt: -------------------------------------------------------------------------------- 1 | package ktx.json 2 | 3 | import com.badlogic.gdx.utils.Json 4 | import com.badlogic.gdx.utils.JsonValue 5 | import io.kotlintest.matchers.shouldEqual 6 | import io.kotlintest.matchers.shouldThrow 7 | import org.junit.Test 8 | import org.mockito.kotlin.mock 9 | import org.mockito.kotlin.verify 10 | 11 | /** 12 | * Guarantees that [write] method is optional to implement. 13 | */ 14 | @Suppress("unused", "ClassName") 15 | class `should implement ReadOnlyJsonSerializer with no 'write' method overridden` : ReadOnlyJsonSerializer { 16 | override fun read( 17 | json: Json, 18 | jsonValue: JsonValue, 19 | type: Class<*>?, 20 | ): T = throw NotImplementedError() 21 | } 22 | 23 | class ReadOnlyJsonSerializerTest { 24 | @Test 25 | fun `default implementation for 'write' method should throw UnsupportedOperationException`() { 26 | val readOnlyJsonSerializer = 27 | object : ReadOnlyJsonSerializer { 28 | override fun read( 29 | json: Json, 30 | jsonValue: JsonValue, 31 | type: Class<*>?, 32 | ): Any = throw NotImplementedError() 33 | } 34 | 35 | shouldThrow { 36 | readOnlyJsonSerializer.write(Json(), JsonValue(JsonValue.ValueType.`object`), Any::class.java) 37 | } 38 | } 39 | } 40 | 41 | class ReadOnlyJsonSerializerFactoryTest { 42 | @Test 43 | fun `should return readonly serializer that calls lambda function with 3 parameters`() { 44 | val json = Json() 45 | val jsonValue = JsonValue(JsonValue.ValueType.`object`) 46 | val readFunction: (Json, JsonValue, Class<*>?) -> Data = mock() 47 | 48 | readOnlySerializer(readFunction).read(json, jsonValue, Data::class.java) 49 | 50 | verify(readFunction).invoke(json, jsonValue, Data::class.java) 51 | } 52 | 53 | @Test 54 | fun `should return readonly serializer that returns result of calling lambda with 3 parameters`() { 55 | val data = Data(intField = 10, stringField = "Test", doubleField = 11.254) 56 | val jsonData = JsonValue(JsonValue.ValueType.`object`) 57 | val readFunction: (Json, JsonValue, Class<*>?) -> Data = { _, _, _ -> data } 58 | 59 | val value = readOnlySerializer(readFunction).read(Json(), jsonData, Data::class.java) 60 | 61 | value shouldEqual data 62 | } 63 | 64 | @Test 65 | fun `should return readonly serializer that calls lambda function with 2 parameters`() { 66 | val jsonData = JsonValue(JsonValue.ValueType.`object`) 67 | val readFunction: (JsonValue) -> Data = mock() 68 | 69 | readOnlySerializer(readFunction).read(Json(), jsonData, Data::class.java) 70 | 71 | verify(readFunction).invoke(jsonData) 72 | } 73 | 74 | @Test 75 | fun `should return readonly serializer that returns result of calling lambda with 2 parameters`() { 76 | val data = Data(intField = 10, stringField = "Test", doubleField = 11.254) 77 | val jsonData = JsonValue(JsonValue.ValueType.`object`) 78 | val readFunction: (JsonValue) -> Data = { data } 79 | 80 | val value = readOnlySerializer(readFunction).read(Json(), jsonData, Data::class.java) 81 | 82 | value shouldEqual data 83 | } 84 | 85 | data class Data( 86 | val intField: Int, 87 | val stringField: String, 88 | val doubleField: Double, 89 | ) 90 | } 91 | -------------------------------------------------------------------------------- /json/src/test/resources/ktx/json/file.json: -------------------------------------------------------------------------------- 1 | { 2 | "int": 10, 3 | "str": "test", 4 | "bool": true 5 | } 6 | -------------------------------------------------------------------------------- /log/gradle.properties: -------------------------------------------------------------------------------- 1 | projectName=ktx-log 2 | projectDesc=Kotlin wrapper over libGDX cross-platform logging utilities. 3 | -------------------------------------------------------------------------------- /math/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import ktx.* 2 | 3 | dependencies { 4 | testImplementation("com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-desktop") 5 | } 6 | -------------------------------------------------------------------------------- /math/gradle.properties: -------------------------------------------------------------------------------- 1 | projectName=ktx-math 2 | projectDesc=Kotlin utilities for libGDX math API. 3 | -------------------------------------------------------------------------------- /math/src/main/kotlin/ktx/math/circle.kt: -------------------------------------------------------------------------------- 1 | package ktx.math 2 | 3 | import com.badlogic.gdx.math.Circle 4 | 5 | /** 6 | * Operator function that allows to deconstruct this circle. 7 | * @return X component. 8 | */ 9 | operator fun Circle.component1() = this.x 10 | 11 | /** 12 | * Operator function that allows to deconstruct this circle. 13 | * @return Y component. 14 | */ 15 | operator fun Circle.component2() = this.y 16 | 17 | /** 18 | * Operator function that allows to deconstruct this circle. 19 | * @return Radius component. 20 | */ 21 | operator fun Circle.component3() = this.radius 22 | -------------------------------------------------------------------------------- /math/src/main/kotlin/ktx/math/ellipse.kt: -------------------------------------------------------------------------------- 1 | package ktx.math 2 | 3 | import com.badlogic.gdx.math.Ellipse 4 | 5 | /** 6 | * Operator function that allows to deconstruct this ellipse. 7 | * @return X component. 8 | */ 9 | operator fun Ellipse.component1() = this.x 10 | 11 | /** 12 | * Operator function that allows to deconstruct this ellipse. 13 | * @return Y component. 14 | */ 15 | operator fun Ellipse.component2() = this.y 16 | 17 | /** 18 | * Operator function that allows to deconstruct this ellipse. 19 | * @return Width component. 20 | */ 21 | operator fun Ellipse.component3() = this.width 22 | 23 | /** 24 | * Operator function that allows to deconstruct this ellipse. 25 | * @return Height component. 26 | */ 27 | operator fun Ellipse.component4() = this.height 28 | -------------------------------------------------------------------------------- /math/src/main/kotlin/ktx/math/polygon.kt: -------------------------------------------------------------------------------- 1 | package ktx.math 2 | 3 | import com.badlogic.gdx.math.Polygon 4 | 5 | /** 6 | * Operator function that allows to deconstruct this polygon. 7 | * @return X component. 8 | */ 9 | operator fun Polygon.component1() = this.x 10 | 11 | /** 12 | * Operator function that allows to deconstruct this polygon. 13 | * @return Y component. 14 | */ 15 | operator fun Polygon.component2() = this.y 16 | -------------------------------------------------------------------------------- /math/src/main/kotlin/ktx/math/polyline.kt: -------------------------------------------------------------------------------- 1 | package ktx.math 2 | 3 | import com.badlogic.gdx.math.Polyline 4 | 5 | /** 6 | * Operator function that allows to deconstruct this polyline. 7 | * @return X component. 8 | */ 9 | operator fun Polyline.component1() = this.x 10 | 11 | /** 12 | * Operator function that allows to deconstruct this polyline. 13 | * @return Y component. 14 | */ 15 | operator fun Polyline.component2() = this.y 16 | -------------------------------------------------------------------------------- /math/src/main/kotlin/ktx/math/rectangle.kt: -------------------------------------------------------------------------------- 1 | package ktx.math 2 | 3 | import com.badlogic.gdx.math.Rectangle 4 | 5 | /** 6 | * Operator function that allows to deconstruct this rectangle. 7 | * @return X component. 8 | */ 9 | operator fun Rectangle.component1() = this.x 10 | 11 | /** 12 | * Operator function that allows to deconstruct this rectangle. 13 | * @return Y component. 14 | */ 15 | operator fun Rectangle.component2() = this.y 16 | 17 | /** 18 | * Operator function that allows to deconstruct this rectangle. 19 | * @return Width component. 20 | */ 21 | operator fun Rectangle.component3() = this.width 22 | 23 | /** 24 | * Operator function that allows to deconstruct this rectangle. 25 | * @return Height component. 26 | */ 27 | operator fun Rectangle.component4() = this.height 28 | -------------------------------------------------------------------------------- /math/src/test/kotlin/ktx/math/CircleTest.kt: -------------------------------------------------------------------------------- 1 | package ktx.math 2 | 3 | import com.badlogic.gdx.math.Circle 4 | import org.junit.Assert.assertEquals 5 | import org.junit.Test 6 | 7 | class CircleTest { 8 | @Test 9 | fun `should destructure Circle`() { 10 | val circle = Circle(1f, 2f, 3f) 11 | 12 | val (x, y, radius) = circle 13 | 14 | assertEquals(1f, x) 15 | assertEquals(2f, y) 16 | assertEquals(3f, radius) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /math/src/test/kotlin/ktx/math/EllipseTest.kt: -------------------------------------------------------------------------------- 1 | package ktx.math 2 | 3 | import com.badlogic.gdx.math.Ellipse 4 | import org.junit.Assert.assertEquals 5 | import org.junit.Test 6 | 7 | class EllipseTest { 8 | @Test 9 | fun `should destructure Ellipse`() { 10 | val ellipse = Ellipse(1f, 2f, 3f, 4f) 11 | 12 | val (x, y, w, h) = ellipse 13 | 14 | assertEquals(1f, x) 15 | assertEquals(2f, y) 16 | assertEquals(3f, w) 17 | assertEquals(4f, h) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /math/src/test/kotlin/ktx/math/PolygonTest.kt: -------------------------------------------------------------------------------- 1 | package ktx.math 2 | 3 | import com.badlogic.gdx.math.Polygon 4 | import org.junit.Assert.assertEquals 5 | import org.junit.Test 6 | 7 | class PolygonTest { 8 | @Test 9 | fun `should destructure Polygon`() { 10 | val polygon = Polygon().apply { setPosition(1f, 2f) } 11 | 12 | val (x, y) = polygon 13 | 14 | assertEquals(1f, x) 15 | assertEquals(2f, y) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /math/src/test/kotlin/ktx/math/PolylineTest.kt: -------------------------------------------------------------------------------- 1 | package ktx.math 2 | 3 | import com.badlogic.gdx.math.Polyline 4 | import org.junit.Assert.assertEquals 5 | import org.junit.Test 6 | 7 | class PolylineTest { 8 | @Test 9 | fun `should destructure Polyline`() { 10 | val polyline = Polyline().apply { setPosition(1f, 2f) } 11 | 12 | val (x, y) = polyline 13 | 14 | assertEquals(1f, x) 15 | assertEquals(2f, y) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /math/src/test/kotlin/ktx/math/RectangleTest.kt: -------------------------------------------------------------------------------- 1 | package ktx.math 2 | 3 | import com.badlogic.gdx.math.Rectangle 4 | import org.junit.Assert.assertEquals 5 | import org.junit.Test 6 | 7 | class RectangleTest { 8 | @Test 9 | fun `should destructure Rectangle`() { 10 | val rect = Rectangle(1f, 2f, 3f, 4f) 11 | 12 | val (x, y, w, h) = rect 13 | 14 | assertEquals(1f, x) 15 | assertEquals(2f, y) 16 | assertEquals(3f, w) 17 | assertEquals(4f, h) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /preferences/gradle.properties: -------------------------------------------------------------------------------- 1 | projectName=ktx-preferences 2 | projectDesc=libGDX preferences utilities for applications developed with Kotlin. 3 | -------------------------------------------------------------------------------- /reflect/gradle.properties: -------------------------------------------------------------------------------- 1 | projectName=ktx-reflect 2 | projectDesc=libGDX reflection utilities for Kotlin applications. 3 | -------------------------------------------------------------------------------- /scene2d/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import ktx.* 2 | 3 | dependencies { 4 | testImplementation("com.badlogicgames.gdx:gdx-backend-lwjgl:$gdxVersion") 5 | testImplementation("com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-desktop") 6 | // Includes Scene2D skin used in tests: 7 | testImplementation("com.kotcrab.vis:vis-ui:$visUiVersion") 8 | } 9 | -------------------------------------------------------------------------------- /scene2d/gradle.properties: -------------------------------------------------------------------------------- 1 | projectName=ktx-scene2d 2 | projectDesc=libGDX Scene2D GUI building utilities for Kotlin applications. 3 | -------------------------------------------------------------------------------- /scene2d/img/00.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libktx/ktx/0f28adef8191a15a3c4f453a97fe15524fb3c8fd/scene2d/img/00.png -------------------------------------------------------------------------------- /scene2d/img/01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libktx/ktx/0f28adef8191a15a3c4f453a97fe15524fb3c8fd/scene2d/img/01.png -------------------------------------------------------------------------------- /scene2d/img/02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libktx/ktx/0f28adef8191a15a3c4f453a97fe15524fb3c8fd/scene2d/img/02.png -------------------------------------------------------------------------------- /scene2d/img/03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libktx/ktx/0f28adef8191a15a3c4f453a97fe15524fb3c8fd/scene2d/img/03.png -------------------------------------------------------------------------------- /scene2d/img/04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libktx/ktx/0f28adef8191a15a3c4f453a97fe15524fb3c8fd/scene2d/img/04.png -------------------------------------------------------------------------------- /scene2d/img/README.md: -------------------------------------------------------------------------------- 1 | Screen captures of usage examples. 2 | -------------------------------------------------------------------------------- /scene2d/src/main/kotlin/ktx/scene2d/Scene2dDsl.kt: -------------------------------------------------------------------------------- 1 | package ktx.scene2d 2 | 3 | import kotlin.annotation.AnnotationTarget.CLASS 4 | import kotlin.annotation.AnnotationTarget.FUNCTION 5 | import kotlin.annotation.AnnotationTarget.TYPE 6 | import kotlin.annotation.AnnotationTarget.TYPEALIAS 7 | import kotlin.annotation.AnnotationTarget.TYPE_PARAMETER 8 | 9 | /** Should annotate builders of Scene2D widgets. */ 10 | @DslMarker 11 | @Target(CLASS, TYPE_PARAMETER, FUNCTION, TYPE, TYPEALIAS) 12 | annotation class Scene2dDsl 13 | -------------------------------------------------------------------------------- /scene2d/src/main/kotlin/ktx/scene2d/skin.kt: -------------------------------------------------------------------------------- 1 | package ktx.scene2d 2 | 3 | import com.badlogic.gdx.scenes.scene2d.ui.Skin 4 | import com.badlogic.gdx.utils.Array as GdxArray 5 | 6 | /** 7 | * Style used by default by most widget constructors. Most factory methods default to this style if a custom one is not 8 | * chosen. 9 | */ 10 | const val defaultStyle = "default" 11 | 12 | /** 13 | * Style used by default by constructors of some aligned widgets like SplitPane. 14 | */ 15 | const val defaultVerticalStyle = "default-vertical" 16 | 17 | /** 18 | * Style used by default by constructors of some aligned widgets like SplitPane. 19 | */ 20 | const val defaultHorizontalStyle = "default-horizontal" 21 | 22 | /** 23 | * Utility storage for global [Skin] object. The skin will be used by the widget factory methods by default if no custom 24 | * alternative skin instance is passed as an alternative. 25 | */ 26 | object Scene2DSkin { 27 | private var skin: Skin? = null 28 | private val listeners = GdxArray<(Skin) -> Unit>(4) 29 | 30 | /** 31 | * Used by the factory methods by default if no custom [Skin] is passed. Changing this value immediately invokes all 32 | * registered listeners. Throws [IllegalStateException] when accessed before overriding. 33 | */ 34 | var defaultSkin: Skin 35 | get() = 36 | skin ?: throw IllegalStateException( 37 | "Default Scene2D Skin was accessed before initiation. In order to avoid this exception, " + 38 | "import ktx.scene2d.Scene2DSkin and override its defaultSkin property. For example: " + 39 | "Scene2DSkin.defaultSkin = Skin(); Scene2DSkin.defaultSkin = VisUI.getSkin()", 40 | ) 41 | set(value) { 42 | skin = value 43 | for (listener in listeners) { 44 | listener(value) 45 | } 46 | } 47 | 48 | /** 49 | * @param listener will be invoked each time the [Skin] is reloaded. 50 | * @see removeListener 51 | */ 52 | fun addListener(listener: (Skin) -> Unit) { 53 | listeners.add(listener) 54 | } 55 | 56 | /** 57 | * @param listener will no longer be invoked each time the default skin is changed. 58 | */ 59 | fun removeListener(listener: (Skin) -> Unit) { 60 | listeners.removeValue(listener, true) 61 | } 62 | 63 | /** 64 | * Removes all skin reload listeners. 65 | */ 66 | fun clearListeners() { 67 | listeners.clear() 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /scene2d/src/main/kotlin/ktx/scene2d/stage.kt: -------------------------------------------------------------------------------- 1 | package ktx.scene2d 2 | 3 | import com.badlogic.gdx.scenes.scene2d.Actor 4 | import com.badlogic.gdx.scenes.scene2d.Stage 5 | import kotlin.contracts.ExperimentalContracts 6 | import kotlin.contracts.InvocationKind 7 | import kotlin.contracts.contract 8 | 9 | /** 10 | * A dummy widget that allows to add actors directly to the [stage] with Scene2D DSL. 11 | */ 12 | @Scene2dDsl 13 | class StageWidget( 14 | val stage: Stage, 15 | ) : RootWidget { 16 | override fun storeActor(actor: T): T { 17 | stage.addActor(actor) 18 | return actor 19 | } 20 | } 21 | 22 | /** 23 | * Allows to create and add [actors][Actor] directly to this [Stage]. 24 | * @param init inlined. All defined top-level widgets will be added to this [Stage]. 25 | */ 26 | @Scene2dDsl 27 | @OptIn(ExperimentalContracts::class) 28 | inline fun Stage.actors(init: (@Scene2dDsl StageWidget).() -> Unit) { 29 | contract { callsInPlace(init, InvocationKind.EXACTLY_ONCE) } 30 | StageWidget(this).init() 31 | } 32 | -------------------------------------------------------------------------------- /scene2d/src/main/kotlin/ktx/scene2d/tooltip.kt: -------------------------------------------------------------------------------- 1 | package ktx.scene2d 2 | 3 | import com.badlogic.gdx.scenes.scene2d.Actor 4 | import com.badlogic.gdx.scenes.scene2d.ui.Label 5 | import com.badlogic.gdx.scenes.scene2d.ui.Skin 6 | import com.badlogic.gdx.scenes.scene2d.ui.Table 7 | import com.badlogic.gdx.scenes.scene2d.ui.TextTooltip 8 | import com.badlogic.gdx.scenes.scene2d.ui.Tooltip 9 | import com.badlogic.gdx.scenes.scene2d.ui.TooltipManager 10 | import kotlin.contracts.ExperimentalContracts 11 | import kotlin.contracts.InvocationKind 12 | import kotlin.contracts.contract 13 | 14 | /** 15 | * Adds a new [TextTooltip] to this actor. 16 | * @param text will be displayed on the tooltip. 17 | * @param style name of the style of the tooltip. Defaults to [defaultStyle]. 18 | * @param skin [Skin] instance which contains the style. Defaults to [Scene2DSkin.defaultSkin]. 19 | * @param tooltipManager manages tooltips and their settings. Defaults to the global manager obtained with 20 | * [TooltipManager.getInstance]. 21 | * @param init inlined building block, which allows to manage [Label] actor of the [TextTooltip]. Takes the 22 | * [TextTooltip] as its parameter, so it can be modified with the *it* reference as well. See usage examples. 23 | * @return a new [TextTooltip] instance added to the actor. 24 | */ 25 | @Scene2dDsl 26 | @OptIn(ExperimentalContracts::class) 27 | inline fun Actor.textTooltip( 28 | text: String, 29 | style: String = defaultStyle, 30 | skin: Skin = Scene2DSkin.defaultSkin, 31 | tooltipManager: TooltipManager = TooltipManager.getInstance(), 32 | init: (@Scene2dDsl Label).(TextTooltip) -> Unit = {}, 33 | ): TextTooltip { 34 | contract { callsInPlace(init, InvocationKind.EXACTLY_ONCE) } 35 | val tooltip = TextTooltip(text, tooltipManager, skin, style) 36 | tooltip.actor.init(tooltip) 37 | this.addListener(tooltip) 38 | return tooltip 39 | } 40 | 41 | /** 42 | * Adds a new [Tooltip] to this actor, storing a flexible [Table] widget. 43 | * @param background optional name of a drawable which will be extracted from the [Skin] and set as the main table's 44 | * background. Defaults to null. 45 | * @param skin [Skin] instance which contains the background. Will be passed to the [Table]. 46 | * @param tooltipManager manages tooltips and their settings. Defaults to the global manager obtained with 47 | * [TooltipManager.getInstance]. 48 | * @param init inlined building block, which allows to manage main [Table] content and fill it with children. Takes the 49 | * [Tooltip] as its parameter, so it can be modified with the *it* reference. See usage examples. 50 | * @return a new [Tooltip] instance added to this actor. 51 | */ 52 | @Scene2dDsl 53 | @OptIn(ExperimentalContracts::class) 54 | inline fun Actor.tooltip( 55 | background: String? = null, 56 | skin: Skin = Scene2DSkin.defaultSkin, 57 | tooltipManager: TooltipManager = TooltipManager.getInstance(), 58 | init: KTableWidget.(Tooltip) -> Unit = {}, 59 | ): Tooltip { 60 | contract { callsInPlace(init, InvocationKind.EXACTLY_ONCE) } 61 | val table = KTableWidget(skin) 62 | if (background != null) { 63 | table.setBackground(background) 64 | } 65 | val tooltip = Tooltip(table, tooltipManager) 66 | table.init(tooltip) 67 | this.addListener(tooltip) 68 | return tooltip 69 | } 70 | -------------------------------------------------------------------------------- /scene2d/src/test/kotlin/ktx/scene2d/Scene2DSkinTest.kt: -------------------------------------------------------------------------------- 1 | package ktx.scene2d 2 | 3 | import com.badlogic.gdx.scenes.scene2d.ui.Skin 4 | import io.kotlintest.matchers.shouldThrow 5 | import org.junit.Assert 6 | import org.junit.Test 7 | import kotlin.reflect.full.declaredMemberProperties 8 | import kotlin.reflect.jvm.javaField 9 | 10 | class Scene2DSkinTest { 11 | @Test 12 | fun `should throw exception if defaultSkin is not initialized`() { 13 | // Resetting Scene2DSkin with reflection to ensure that `skin` is null: 14 | Scene2DSkin::class 15 | .declaredMemberProperties 16 | .filter { it.name == "skin" } 17 | .map { it.javaField!! } 18 | .onEach { it.isAccessible = true } 19 | .forEach { it.set(Scene2DSkin, null) } 20 | 21 | shouldThrow { 22 | Scene2DSkin.defaultSkin 23 | } 24 | } 25 | 26 | @Test 27 | fun `should invoke skin reload listeners`() { 28 | var invoked = false 29 | 30 | Scene2DSkin.addListener { invoked = true } 31 | 32 | Scene2DSkin.defaultSkin = Skin() // Invokes setter, should trigger listeners. 33 | Assert.assertTrue(invoked) 34 | } 35 | 36 | @Test 37 | fun `should remove listeners`() { 38 | var amount = 0 39 | val listener: (Skin) -> Unit = { amount++ } 40 | Scene2DSkin.addListener(listener) 41 | 42 | Scene2DSkin.defaultSkin = Skin() // Invokes setter, should trigger listeners. 43 | Assert.assertEquals(1, amount) 44 | 45 | Scene2DSkin.removeListener(listener) 46 | Scene2DSkin.defaultSkin = Skin() // Invokes setter, should trigger listeners. 47 | Assert.assertEquals(1, amount) 48 | } 49 | 50 | @Test 51 | fun `should clear listeners`() { 52 | var amount = 0 53 | Scene2DSkin.addListener { amount++ } 54 | Scene2DSkin.addListener { amount++ } 55 | 56 | Scene2DSkin.defaultSkin = Skin() // Invokes setter, should trigger listeners. 57 | Assert.assertEquals(2, amount) 58 | 59 | Scene2DSkin.clearListeners() 60 | Scene2DSkin.defaultSkin = Skin() // Invokes setter, should trigger listeners. 61 | Assert.assertEquals(2, amount) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /scene2d/src/test/kotlin/ktx/scene2d/StageWidgetTest.kt: -------------------------------------------------------------------------------- 1 | package ktx.scene2d 2 | 3 | import com.badlogic.gdx.scenes.scene2d.Actor 4 | import com.badlogic.gdx.scenes.scene2d.Stage 5 | import com.badlogic.gdx.scenes.scene2d.ui.Button 6 | import com.badlogic.gdx.scenes.scene2d.ui.Dialog 7 | import com.badlogic.gdx.scenes.scene2d.ui.Label 8 | import com.badlogic.gdx.scenes.scene2d.ui.Table 9 | import com.badlogic.gdx.scenes.scene2d.ui.Window 10 | import org.junit.Assert.assertEquals 11 | import org.junit.Assert.assertFalse 12 | import org.junit.Assert.assertSame 13 | import org.junit.Assert.assertTrue 14 | import org.junit.Test 15 | import org.mockito.kotlin.mock 16 | 17 | class StageWidgetTest : ApplicationTest() { 18 | @Test 19 | fun `should add Actor to the Stage`() { 20 | val stage = Stage(mock(), mock()) 21 | val actor = Actor() 22 | val stageWidget = StageWidget(stage) 23 | 24 | stageWidget.storeActor(actor) 25 | 26 | assertTrue(actor in stage.actors) 27 | assertTrue(actor in stage.root.children) 28 | } 29 | 30 | @Test 31 | fun `should add multiple top-level actors to the Stage within actors block`() { 32 | val stage = Stage(mock(), mock()) 33 | val label: Label 34 | val table: Table 35 | val button: Button 36 | 37 | stage.actors { 38 | label = label("Test") 39 | table = 40 | table { 41 | button = button() 42 | } 43 | assertSame(stage, this.stage) 44 | } 45 | 46 | assertTrue(label in stage.actors) 47 | assertTrue(label in stage.root.children) 48 | assertTrue(table in stage.actors) 49 | assertTrue(table in stage.root.children) 50 | assertFalse(button in stage.actors) 51 | assertFalse(button in stage.root.children) 52 | assertTrue(button in table.children) 53 | } 54 | 55 | @Test 56 | fun `should configure Stage exactly once`() { 57 | val stage = Stage(mock(), mock()) 58 | val variable: Int 59 | 60 | stage.actors { 61 | variable = 42 62 | } 63 | 64 | assertEquals(42, variable) 65 | } 66 | 67 | @Test 68 | fun `should store root actors in Stage when using actors block`() { 69 | val stage = Stage(mock(), mock()) 70 | val window: Window 71 | val dialog: Dialog 72 | val label: Label 73 | 74 | stage.actors { 75 | window = window("Test") 76 | dialog = 77 | dialog("Test") { 78 | label = label("Test") 79 | } 80 | } 81 | 82 | assertTrue(window in stage.actors) 83 | assertTrue(window in stage.root.children) 84 | assertTrue(dialog in stage.actors) 85 | assertTrue(dialog in stage.root.children) 86 | assertFalse(label in stage.actors) 87 | assertFalse(label in stage.root.children) 88 | assertTrue(label in dialog.children) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /scene2d/src/test/kotlin/ktx/scene2d/TooltipTest.kt: -------------------------------------------------------------------------------- 1 | package ktx.scene2d 2 | 3 | import com.badlogic.gdx.graphics.Color 4 | import com.badlogic.gdx.scenes.scene2d.Actor 5 | import com.badlogic.gdx.scenes.scene2d.ui.Label 6 | import com.badlogic.gdx.scenes.scene2d.ui.Tooltip 7 | import org.junit.Assert.assertEquals 8 | import org.junit.Assert.assertNotNull 9 | import org.junit.Assert.assertTrue 10 | import org.junit.Test 11 | 12 | /** 13 | * Tests extension methods that allow to add [Tooltip] instances to all actors. 14 | */ 15 | class TooltipTest : ApplicationTest() { 16 | @Test 17 | fun `should add TextTooltip`() { 18 | val actor = Actor() 19 | 20 | val tooltip = actor.textTooltip("Test.") 21 | 22 | assertNotNull(tooltip) 23 | assertTrue(tooltip in actor.listeners) 24 | assertEquals("Test.", tooltip.actor.text.toString()) 25 | } 26 | 27 | @Test 28 | fun `should add TextTooltip with init block`() { 29 | val actor = Actor() 30 | val variable: Int 31 | 32 | val tooltip = 33 | actor.textTooltip("Test.") { 34 | // Changing Label color: 35 | color = Color.BLUE 36 | variable = 42 37 | } 38 | 39 | assertNotNull(tooltip) 40 | assertTrue(tooltip in actor.listeners) 41 | assertEquals("Test.", tooltip.actor.text.toString()) 42 | assertEquals(Color.BLUE, tooltip.actor.color) 43 | assertEquals(42, variable) 44 | } 45 | 46 | @Test 47 | fun `should add Tooltip with init block`() { 48 | val actor = Actor() 49 | val variable: Int 50 | 51 | val tooltip = 52 | actor.tooltip { 53 | // Changing Table color: 54 | color = Color.BLUE 55 | // Adding child to Table content: 56 | label("Test.") 57 | variable = 42 58 | } 59 | 60 | assertNotNull(tooltip) 61 | assertTrue(tooltip in actor.listeners) 62 | assertEquals("Test.", (tooltip.actor.children.first() as Label).text.toString()) 63 | assertEquals(Color.BLUE, tooltip.actor.color) 64 | assertEquals(42, variable) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /scene2d/src/test/kotlin/ktx/scene2d/testUtilities.kt: -------------------------------------------------------------------------------- 1 | package ktx.scene2d 2 | 3 | import com.badlogic.gdx.Gdx 4 | import com.badlogic.gdx.backends.lwjgl.LwjglFiles 5 | import com.badlogic.gdx.backends.lwjgl.LwjglNativesLoader 6 | import com.kotcrab.vis.ui.VisUI 7 | import org.junit.AfterClass 8 | import org.junit.Before 9 | import org.junit.BeforeClass 10 | import org.mockito.kotlin.mock 11 | 12 | /** 13 | * Utility value for numeric tests. 14 | */ 15 | const val TOLERANCE = 0.0001f 16 | 17 | /** 18 | * Tests that require mocked libGDX environment should inherit from this class. 19 | */ 20 | abstract class ApplicationTest { 21 | companion object { 22 | @JvmStatic 23 | @BeforeClass 24 | fun `initiate libGDX context`() { 25 | LwjglNativesLoader.load() 26 | 27 | Gdx.files = LwjglFiles() 28 | Gdx.graphics = mock() 29 | Gdx.gl20 = mock() 30 | Gdx.app = mock() 31 | Gdx.gl = mock() 32 | 33 | // Includes a default skin filled with styles of all Scene2D widgets: 34 | VisUI.load() 35 | } 36 | 37 | @JvmStatic 38 | @AfterClass 39 | fun `destroy libGDX context`() { 40 | Gdx.graphics = null 41 | Gdx.files = null 42 | Gdx.gl20 = null 43 | Gdx.app = null 44 | Gdx.gl = null 45 | 46 | VisUI.dispose() 47 | } 48 | } 49 | 50 | @Before 51 | fun setDefaultSkin() { 52 | Scene2DSkin.defaultSkin = VisUI.getSkin() 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /script/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import ktx.* 2 | 3 | dependencies { 4 | api(kotlin("scripting-jsr223")) 5 | 6 | testImplementation("com.badlogicgames.gdx:gdx-backend-lwjgl3:$gdxVersion") 7 | testImplementation("com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-desktop") 8 | } 9 | -------------------------------------------------------------------------------- /script/gradle.properties: -------------------------------------------------------------------------------- 1 | projectName=ktx-script 2 | projectDesc=Kotlin scripting engine for libGDX applications. 3 | -------------------------------------------------------------------------------- /script/src/test/resources/ktx/script/broken.script: -------------------------------------------------------------------------------- 1 | import 2 | -------------------------------------------------------------------------------- /script/src/test/resources/ktx/script/receiver: -------------------------------------------------------------------------------- 1 | text = "test" 2 | -------------------------------------------------------------------------------- /script/src/test/resources/ktx/script/script.kts: -------------------------------------------------------------------------------- 1 | "test" 2 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | mavenCentral() 5 | } 6 | } 7 | 8 | include( 9 | "actors", 10 | "ai", 11 | "app", 12 | "artemis", 13 | "ashley", 14 | "assets", 15 | "assets-async", 16 | "async", 17 | "box2d", 18 | "collections", 19 | "json", 20 | "graphics", 21 | "freetype", 22 | "freetype-async", 23 | "i18n", 24 | "inject", 25 | "log", 26 | "math", 27 | "preferences", 28 | "reflect", 29 | "scene2d", 30 | "script", 31 | "style", 32 | "tiled", 33 | "vis", 34 | "vis-style" 35 | ) 36 | -------------------------------------------------------------------------------- /style/gradle.properties: -------------------------------------------------------------------------------- 1 | projectName=ktx-style 2 | projectDesc=libGDX Scene2D GUI style building utilities for Kotlin applications. 3 | -------------------------------------------------------------------------------- /tiled/gradle.properties: -------------------------------------------------------------------------------- 1 | projectName=ktx-tiled 2 | projectDesc=Tiled utilities for libGDX applications written with Kotlin. 3 | -------------------------------------------------------------------------------- /tiled/src/main/kotlin/ktx/tiled/batchTiledMapRenderer.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("PackageDirectoryMismatch") 2 | 3 | package com.badlogic.gdx.maps.tiled.renderers 4 | 5 | /** 6 | * This file is used as a workaround for the tiledMapRenderer extensions. They need 7 | * to call [BatchTiledMapRenderer.beginRender] and [BatchTiledMapRenderer.endRender] methods 8 | * which are protected methods. Since this file matches the package of the [BatchTiledMapRenderer], 9 | * we can call protected methods of it and use our wrapper functions for public API extensions. 10 | */ 11 | 12 | @PublishedApi 13 | internal fun BatchTiledMapRenderer.beginInternal() = this.beginRender() 14 | 15 | @PublishedApi 16 | internal fun BatchTiledMapRenderer.endInternal() = this.endRender() 17 | -------------------------------------------------------------------------------- /tiled/src/main/kotlin/ktx/tiled/exceptions.kt: -------------------------------------------------------------------------------- 1 | package ktx.tiled 2 | 3 | import com.badlogic.gdx.maps.MapLayer 4 | import com.badlogic.gdx.maps.MapObject 5 | import com.badlogic.gdx.maps.MapProperties 6 | import com.badlogic.gdx.maps.objects.TextureMapObject 7 | import com.badlogic.gdx.maps.tiled.TiledMap 8 | import com.badlogic.gdx.utils.GdxRuntimeException 9 | 10 | /** 11 | * Common type of exceptions thrown by the Tiled API extensions. 12 | */ 13 | open class TiledException( 14 | message: String, 15 | cause: Throwable? = null, 16 | ) : GdxRuntimeException(message, cause) 17 | 18 | /** 19 | * [GdxRuntimeException] that is thrown when trying to access a non-existing property 20 | * of a [MapProperties] instance. 21 | */ 22 | class MissingPropertyException( 23 | message: String, 24 | cause: Throwable? = null, 25 | ) : TiledException(message, cause) 26 | 27 | /** 28 | * [GdxRuntimeException] that is thrown when trying to access a non-existing [MapLayer] 29 | * of a [TiledMap] instance. 30 | */ 31 | class MissingLayerException( 32 | message: String, 33 | cause: Throwable? = null, 34 | ) : TiledException(message, cause) 35 | 36 | /** 37 | * [GdxRuntimeException] that is thrown when trying to access a shape of a [MapObject] 38 | * that do not have any shape such as the [TextureMapObject]. 39 | */ 40 | class MissingShapeException( 41 | message: String, 42 | cause: Throwable? = null, 43 | ) : TiledException(message, cause) 44 | -------------------------------------------------------------------------------- /tiled/src/main/kotlin/ktx/tiled/mapLayers.kt: -------------------------------------------------------------------------------- 1 | package ktx.tiled 2 | 3 | import com.badlogic.gdx.maps.MapLayer 4 | import com.badlogic.gdx.maps.MapLayers 5 | import com.badlogic.gdx.maps.MapProperties 6 | 7 | /** 8 | * Extension method to directly access the [MapProperties] of a [MapLayer]. If the property 9 | * is not defined then this method throws a [MissingPropertyException]. 10 | * @param key property name. 11 | * @return value of the property. 12 | * @throws MissingPropertyException If the property is not defined. 13 | */ 14 | inline fun MapLayer.property(key: String): T = 15 | properties[key, T::class.java] 16 | ?: throw MissingPropertyException("Property $key does not exist for layer $name") 17 | 18 | /** 19 | * Extension method to directly access the [MapProperties] of a [MapLayer]. The type is automatically 20 | * derived from the type of the given default value. If the property is not defined the [defaultValue] 21 | * will be returned. 22 | * @param key property name. 23 | * @param defaultValue default value in case the property is missing. 24 | * @return value of the property or defaultValue if property is missing. 25 | */ 26 | inline fun MapLayer.property( 27 | key: String, 28 | defaultValue: T, 29 | ): T = properties[key, defaultValue, T::class.java] 30 | 31 | /** 32 | * Extension method to directly access the [MapProperties] of a [MapLayer]. If the property 33 | * is not defined then this method returns null. 34 | * @param key property name. 35 | * @return value of the property or null if the property is missing. 36 | */ 37 | inline fun MapLayer.propertyOrNull(key: String): T? = properties[key, T::class.java] 38 | 39 | /** 40 | * Extension method to directly access the [MapProperties] of a [MapLayer] and its 41 | * [containsKey][MapProperties.containsKey] method. 42 | * @param key property name. 43 | * @return true if the property exists. Otherwise false. 44 | */ 45 | fun MapLayer.containsProperty(key: String) = properties.containsKey(key) 46 | 47 | /** 48 | * Returns **true** if and only if the [MapLayers] collection is empty. 49 | */ 50 | fun MapLayers.isEmpty() = this.count <= 0 51 | 52 | /** 53 | * Returns **true** if and only if the [MapLayers] collection is not empty. 54 | */ 55 | fun MapLayers.isNotEmpty() = this.count > 0 56 | -------------------------------------------------------------------------------- /tiled/src/main/kotlin/ktx/tiled/mapProperties.kt: -------------------------------------------------------------------------------- 1 | package ktx.tiled 2 | 3 | import com.badlogic.gdx.maps.MapProperties 4 | 5 | /** 6 | * Allows to check if a [MapProperties] instance contains a property mapped to [key] 7 | * with Kotlin `in` operator. 8 | * @param key name of the property. 9 | * @return true if the property with the given [key] can be found in this [MapProperties]. False otherwise. 10 | */ 11 | operator fun MapProperties.contains(key: String): Boolean = containsKey(key) 12 | 13 | /** 14 | * Allows to add a property mapped to [key] with given [value] using Kotlin 15 | * square braces assignment operator. 16 | * @param key name of the property. 17 | * @param value value of the property. 18 | */ 19 | operator fun MapProperties.set( 20 | key: String, 21 | value: Any, 22 | ) = put(key, value) 23 | -------------------------------------------------------------------------------- /tiled/src/main/kotlin/ktx/tiled/tiledMapRenderer.kt: -------------------------------------------------------------------------------- 1 | package ktx.tiled 2 | 3 | import com.badlogic.gdx.graphics.OrthographicCamera 4 | import com.badlogic.gdx.maps.tiled.renderers.BatchTiledMapRenderer 5 | import com.badlogic.gdx.maps.tiled.renderers.beginInternal 6 | import com.badlogic.gdx.maps.tiled.renderers.endInternal 7 | import com.badlogic.gdx.math.Matrix4 8 | import com.badlogic.gdx.math.Rectangle 9 | 10 | /** 11 | * Automatically calls [BatchTiledMapRenderer.beginRender] and [BatchTiledMapRenderer.endRender]. 12 | * @param camera A camera to set on the renderer before [BatchTiledMapRenderer.beginRender]. 13 | * @param block inlined. Executed after [BatchTiledMapRenderer.beginRender] and before [BatchTiledMapRenderer.endRender]. 14 | */ 15 | inline fun T.use( 16 | camera: OrthographicCamera, 17 | block: (T) -> Unit, 18 | ) { 19 | this.setView(camera) 20 | this.use(block) 21 | } 22 | 23 | /** 24 | * Automatically calls [BatchTiledMapRenderer.beginRender] and [BatchTiledMapRenderer.endRender]. 25 | * @param projection A projection matrix to set on the renderer before [BatchTiledMapRenderer.beginRender]. 26 | * @param x map render boundary x value. 27 | * @param y map render boundary y value. 28 | * @param width map render boundary width value. 29 | * @param height map render boundary height value. 30 | * @param block inlined. Executed after [BatchTiledMapRenderer.beginRender] and before [BatchTiledMapRenderer.endRender]. 31 | */ 32 | inline fun T.use( 33 | projection: Matrix4, 34 | x: Float, 35 | y: Float, 36 | width: Float, 37 | height: Float, 38 | block: (T) -> Unit, 39 | ) { 40 | this.setView(projection, x, y, width, height) 41 | this.use(block) 42 | } 43 | 44 | /** 45 | * Automatically calls [BatchTiledMapRenderer.beginRender] and [BatchTiledMapRenderer.endRender]. 46 | * @param projection A projection matrix to set on the renderer before [BatchTiledMapRenderer.beginRender]. 47 | * @param mapBoundary map render boundary. 48 | * @param block inlined. Executed after [BatchTiledMapRenderer.beginRender] and before [BatchTiledMapRenderer.endRender]. 49 | */ 50 | inline fun T.use( 51 | projection: Matrix4, 52 | mapBoundary: Rectangle, 53 | block: (T) -> Unit, 54 | ) = this.use(projection, mapBoundary.x, mapBoundary.y, mapBoundary.width, mapBoundary.height, block) 55 | 56 | /** 57 | * Automatically calls [BatchTiledMapRenderer.beginRender] and [BatchTiledMapRenderer.endRender]. 58 | * @param block inlined. Executed after [BatchTiledMapRenderer.beginRender] and before [BatchTiledMapRenderer.endRender]. 59 | */ 60 | inline fun T.use(block: (T) -> Unit) { 61 | this.beginInternal() 62 | 63 | block(this) 64 | 65 | this.endInternal() 66 | } 67 | -------------------------------------------------------------------------------- /tiled/src/main/kotlin/ktx/tiled/tiledMapTileSets.kt: -------------------------------------------------------------------------------- 1 | package ktx.tiled 2 | 3 | import com.badlogic.gdx.maps.MapProperties 4 | import com.badlogic.gdx.maps.tiled.TiledMapTileSet 5 | 6 | /** 7 | * Extension method to directly access the [MapProperties] of a [TiledMapTileSet]. If the property 8 | * is not defined then this method throws a [MissingPropertyException]. 9 | * @param key property name. 10 | * @return value of the property. 11 | * @throws MissingPropertyException If the property is not defined. 12 | */ 13 | inline fun TiledMapTileSet.property(key: String): T = 14 | properties[key, T::class.java] 15 | ?: throw MissingPropertyException("Property $key does not exist for tile set $name") 16 | 17 | /** 18 | * Extension method to directly access the [MapProperties] of a [TiledMapTileSet]. The type is automatically 19 | * derived from the type of the given default value. If the property is not defined the [defaultValue] 20 | * will be returned. 21 | * @param key property name. 22 | * @param defaultValue default value in case the property is missing. 23 | * @return value of the property or defaultValue if property is missing. 24 | */ 25 | inline fun TiledMapTileSet.property( 26 | key: String, 27 | defaultValue: T, 28 | ): T = properties[key, defaultValue, T::class.java] 29 | 30 | /** 31 | * Extension method to directly access the [MapProperties] of a [TiledMapTileSet]. If the property 32 | * is not defined then this method returns null. 33 | * @param key property name. 34 | * @return value of the property or null if the property is missing. 35 | */ 36 | inline fun TiledMapTileSet.propertyOrNull(key: String): T? = properties[key, T::class.java] 37 | 38 | /** 39 | * Extension method to directly access the [MapProperties] of a [TiledMapTileSet] and its 40 | * [containsKey][MapProperties.containsKey] method. 41 | * @param key property name. 42 | * @return true if the property exists. Otherwise false. 43 | */ 44 | fun TiledMapTileSet.containsProperty(key: String) = properties.containsKey(key) 45 | -------------------------------------------------------------------------------- /tiled/src/main/kotlin/ktx/tiled/tiledMapTiles.kt: -------------------------------------------------------------------------------- 1 | package ktx.tiled 2 | 3 | import com.badlogic.gdx.maps.MapProperties 4 | import com.badlogic.gdx.maps.tiled.TiledMapTile 5 | 6 | /** 7 | * Extension method to directly access the [MapProperties] of a [TiledMapTile]. If the property 8 | * is not defined then this method throws a [MissingPropertyException]. 9 | * @param key property name. 10 | * @return value of the property. 11 | * @throws MissingPropertyException If the property is not defined. 12 | */ 13 | inline fun TiledMapTile.property(key: String): T = 14 | properties[key, T::class.java] 15 | ?: throw MissingPropertyException("Property $key does not exist for tile $id") 16 | 17 | /** 18 | * Extension method to directly access the [MapProperties] of a [TiledMapTile]. The type is automatically 19 | * derived from the type of the given default value. If the property is not defined the [defaultValue] 20 | * will be returned. 21 | * @param key property name. 22 | * @param defaultValue default value in case the property is missing. 23 | * @return value of the property or defaultValue if property is missing. 24 | */ 25 | inline fun TiledMapTile.property( 26 | key: String, 27 | defaultValue: T, 28 | ): T = properties[key, defaultValue, T::class.java] 29 | 30 | /** 31 | * Extension method to directly access the [MapProperties] of a [TiledMapTile]. If the property 32 | * is not defined then this method returns null. 33 | * @param key property name. 34 | * @return value of the property or null if the property is missing. 35 | */ 36 | inline fun TiledMapTile.propertyOrNull(key: String): T? = properties[key, T::class.java] 37 | 38 | /** 39 | * Extension method to directly access the [MapProperties] of a [TiledMapTile] and its 40 | * [containsKey][MapProperties.containsKey] method. 41 | * @param key property name. 42 | * @return true if the property exists. Otherwise false. 43 | */ 44 | fun TiledMapTile.containsProperty(key: String) = properties.containsKey(key) 45 | -------------------------------------------------------------------------------- /tiled/src/test/kotlin/ktx/tiled/MapLayerTest.kt: -------------------------------------------------------------------------------- 1 | package ktx.tiled 2 | 3 | import com.badlogic.gdx.maps.MapLayer 4 | import com.badlogic.gdx.maps.MapLayers 5 | import org.junit.Assert.assertEquals 6 | import org.junit.Assert.assertFalse 7 | import org.junit.Assert.assertNull 8 | import org.junit.Assert.assertTrue 9 | import org.junit.Test 10 | 11 | class MapLayerTest { 12 | private val mapLayer = 13 | MapLayer().apply { 14 | properties.apply { 15 | put("active", true) 16 | put("customProperty", 123) 17 | } 18 | } 19 | 20 | @Test 21 | fun `should retrieve properties from MapLayer`() { 22 | assertEquals(true, mapLayer.property("active")) 23 | assertEquals(123, mapLayer.property("customProperty")) 24 | } 25 | 26 | @Test 27 | fun `should retrieve properties from MapLayer with default value`() { 28 | assertEquals(true, mapLayer.property("active", false)) 29 | assertEquals(123, mapLayer.property("customProperty", 0)) 30 | assertEquals(-1f, mapLayer.property("x", -1f)) 31 | } 32 | 33 | @Test 34 | fun `should retrieve properties from MapLayer without default value`() { 35 | assertNull(mapLayer.propertyOrNull("x")) 36 | assertEquals(123, mapLayer.propertyOrNull("customProperty")) 37 | } 38 | 39 | @Test 40 | fun `should check if property from MapLayer exists`() { 41 | assertTrue(mapLayer.containsProperty("active")) 42 | assertFalse(mapLayer.containsProperty("x")) 43 | } 44 | 45 | @Test(expected = MissingPropertyException::class) 46 | fun `should not retrieve non-existing property from MapLayer`() { 47 | mapLayer.property("non-existing") 48 | } 49 | 50 | @Test 51 | fun `should return true when MapLayers is empty`() { 52 | val actual = MapLayers() 53 | 54 | assertTrue(actual.isEmpty()) 55 | } 56 | 57 | @Test 58 | fun `should return false when MapLayers is not empty`() { 59 | val actual = MapLayers() 60 | actual.add(MapLayer()) 61 | 62 | assertFalse(actual.isEmpty()) 63 | } 64 | 65 | @Test 66 | fun `should return true when MapLayers becomes empty`() { 67 | val actual = MapLayers() 68 | actual.add(MapLayer()) 69 | actual.remove(0) 70 | 71 | assertTrue(actual.isEmpty()) 72 | } 73 | 74 | @Test 75 | fun `should return true when MapLayers is not empty`() { 76 | val actual = MapLayers() 77 | actual.add(MapLayer()) 78 | 79 | assertTrue(actual.isNotEmpty()) 80 | } 81 | 82 | @Test 83 | fun `should return false when MapLayers is empty`() { 84 | val actual = MapLayers() 85 | 86 | assertFalse(actual.isNotEmpty()) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /tiled/src/test/kotlin/ktx/tiled/MapPropertiesTest.kt: -------------------------------------------------------------------------------- 1 | package ktx.tiled 2 | 3 | import com.badlogic.gdx.maps.MapProperties 4 | import org.junit.Assert.assertEquals 5 | import org.junit.Assert.assertFalse 6 | import org.junit.Assert.assertTrue 7 | import org.junit.Test 8 | 9 | class MapPropertiesTest { 10 | @Test 11 | fun `should check if a property exists`() { 12 | val mapProperties = MapProperties() 13 | mapProperties.put("key", "value") 14 | 15 | val result = "key" in mapProperties 16 | 17 | assertTrue(result) 18 | } 19 | 20 | @Test 21 | fun `should check if a property exists given missing key`() { 22 | val mapProperties = MapProperties() 23 | 24 | val result = "key" in mapProperties 25 | 26 | assertFalse(result) 27 | } 28 | 29 | @Test 30 | fun `should add property`() { 31 | val mapProperties = MapProperties() 32 | 33 | mapProperties["key"] = "value" 34 | 35 | assertEquals("value", mapProperties["key"]) 36 | } 37 | 38 | @Test 39 | fun `should override property`() { 40 | val mapProperties = MapProperties() 41 | mapProperties.put("key", "old") 42 | 43 | mapProperties["key"] = "new" 44 | 45 | assertEquals("new", mapProperties["key"]) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /tiled/src/test/kotlin/ktx/tiled/TiledMapRendererTest.kt: -------------------------------------------------------------------------------- 1 | package ktx.tiled 2 | 3 | import com.badlogic.gdx.graphics.OrthographicCamera 4 | import com.badlogic.gdx.maps.tiled.renderers.BatchTiledMapRenderer 5 | import com.badlogic.gdx.maps.tiled.renderers.beginInternal 6 | import com.badlogic.gdx.maps.tiled.renderers.endInternal 7 | import com.badlogic.gdx.math.Matrix4 8 | import com.badlogic.gdx.math.Rectangle 9 | import io.kotlintest.mock.mock 10 | import org.junit.Assert.assertSame 11 | import org.junit.Test 12 | import org.mockito.kotlin.any 13 | import org.mockito.kotlin.never 14 | import org.mockito.kotlin.verify 15 | 16 | class TiledMapRendererTest { 17 | @Test 18 | fun `should call beginRender and endRender without setView`() { 19 | val renderer = mock() 20 | 21 | renderer.use { 22 | verify(renderer).beginInternal() 23 | assertSame(renderer, it) 24 | verify(renderer, never()).endInternal() 25 | } 26 | verify(renderer).endInternal() 27 | verify(renderer, never()).setView(any()) 28 | } 29 | 30 | @Test 31 | fun `should call beginRender and endRender with camera setView`() { 32 | val renderer = mock() 33 | val camera = OrthographicCamera() 34 | 35 | renderer.use(camera) { 36 | verify(renderer).beginInternal() 37 | verify(renderer).setView(camera) 38 | assertSame(renderer, it) 39 | verify(renderer, never()).endInternal() 40 | } 41 | verify(renderer).endInternal() 42 | } 43 | 44 | @Test 45 | fun `should call beginRender and endRender with map boundary setView`() { 46 | val renderer = mock() 47 | val mat4 = Matrix4() 48 | val mapBoundary = Rectangle(1f, 2f, 3f, 4f) 49 | 50 | renderer.use(mat4, mapBoundary) { 51 | verify(renderer).beginInternal() 52 | verify(renderer).setView(mat4, mapBoundary.x, mapBoundary.y, mapBoundary.width, mapBoundary.height) 53 | assertSame(renderer, it) 54 | verify(renderer, never()).endInternal() 55 | } 56 | verify(renderer).endInternal() 57 | } 58 | 59 | @Test 60 | fun `should call beginRender and endRender with map boundary rectangle setView`() { 61 | val renderer = mock() 62 | val mat4 = Matrix4() 63 | val mapBoundary = Rectangle(1f, 2f, 3f, 4f) 64 | 65 | renderer.use(mat4, mapBoundary) { 66 | verify(renderer).beginInternal() 67 | verify(renderer).setView(mat4, mapBoundary.x, mapBoundary.y, mapBoundary.width, mapBoundary.height) 68 | assertSame(renderer, it) 69 | verify(renderer, never()).endInternal() 70 | } 71 | verify(renderer).endInternal() 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /tiled/src/test/kotlin/ktx/tiled/TiledMapTileSetTest.kt: -------------------------------------------------------------------------------- 1 | package ktx.tiled 2 | 3 | import com.badlogic.gdx.maps.tiled.TiledMapTileSet 4 | import org.junit.Assert.assertEquals 5 | import org.junit.Assert.assertFalse 6 | import org.junit.Assert.assertNull 7 | import org.junit.Assert.assertTrue 8 | import org.junit.Test 9 | 10 | class TiledMapTileSetTest { 11 | private val tileset = 12 | TiledMapTileSet().apply { 13 | properties.apply { 14 | put("tilesetProp1", true) 15 | put("tilesetProp2", 123) 16 | } 17 | } 18 | 19 | @Test 20 | fun `should retrieve properties from TileSet`() { 21 | assertEquals(true, tileset.property("tilesetProp1")) 22 | assertEquals(123, tileset.property("tilesetProp2")) 23 | } 24 | 25 | @Test 26 | fun `should retrieve properties from TileSet with default value`() { 27 | assertEquals(true, tileset.property("tilesetProp1", false)) 28 | assertEquals(123, tileset.property("tilesetProp2", 0)) 29 | assertEquals(-1f, tileset.property("non-existing", -1f)) 30 | } 31 | 32 | @Test 33 | fun `should retrieve properties from TileSet without default value`() { 34 | assertNull(tileset.propertyOrNull("non-existing")) 35 | assertEquals(123, tileset.propertyOrNull("tilesetProp2")) 36 | } 37 | 38 | @Test 39 | fun `should check if property from TileSet exists`() { 40 | assertTrue(tileset.containsProperty("tilesetProp1")) 41 | assertFalse(tileset.containsProperty("non-existing")) 42 | } 43 | 44 | @Test(expected = MissingPropertyException::class) 45 | fun `should not retrieve non-existing property from TileSet`() { 46 | tileset.property("non-existing") 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tiled/src/test/kotlin/ktx/tiled/tiledMapTilesTest.kt: -------------------------------------------------------------------------------- 1 | package ktx.tiled 2 | 3 | import com.badlogic.gdx.graphics.g2d.TextureRegion 4 | import com.badlogic.gdx.maps.tiled.TiledMapTile 5 | import com.badlogic.gdx.maps.tiled.tiles.AnimatedTiledMapTile 6 | import com.badlogic.gdx.maps.tiled.tiles.StaticTiledMapTile 7 | import com.badlogic.gdx.utils.Array 8 | import org.junit.Assert.assertEquals 9 | import org.junit.Assert.assertFalse 10 | import org.junit.Assert.assertNull 11 | import org.junit.Assert.assertTrue 12 | import org.junit.Test 13 | 14 | abstract class TiledMapTileTest { 15 | abstract val tile: T 16 | 17 | @Test 18 | fun `should retrieve properties from tile`() { 19 | assertEquals(true, tile.property("prop1")) 20 | assertEquals("text", tile.property("prop2")) 21 | } 22 | 23 | @Test 24 | fun `should retrieve properties from tile with default value`() { 25 | assertEquals(true, tile.property("prop1", false)) 26 | assertEquals("text", tile.property("prop2", "")) 27 | assertEquals(-1f, tile.property("non-existing", -1f)) 28 | } 29 | 30 | @Test 31 | fun `should retrieve properties from tile without default value`() { 32 | assertNull(tile.propertyOrNull("non-existing")) 33 | assertEquals("text", tile.propertyOrNull("prop2")) 34 | } 35 | 36 | @Test 37 | fun `should check if property from tile exists`() { 38 | assertTrue(tile.containsProperty("prop1")) 39 | assertTrue(tile.containsProperty("prop2")) 40 | assertFalse(tile.containsProperty("non-existing")) 41 | } 42 | 43 | @Test(expected = MissingPropertyException::class) 44 | fun `retrieve non-existing property from Tile using exception`() { 45 | tile.property("non-existing") 46 | } 47 | } 48 | 49 | class StaticTiledMapTileTest : TiledMapTileTest() { 50 | override val tile = 51 | StaticTiledMapTile(TextureRegion()).apply { 52 | properties.apply { 53 | put("prop1", true) 54 | put("prop2", "text") 55 | } 56 | } 57 | } 58 | 59 | class AnimatedTiledMapTileTest : TiledMapTileTest() { 60 | override val tile = 61 | AnimatedTiledMapTile(1f, Array()).apply { 62 | properties.apply { 63 | put("prop1", true) 64 | put("prop2", "text") 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /version.txt: -------------------------------------------------------------------------------- 1 | 1.13.1-rc1 2 | -------------------------------------------------------------------------------- /vis-style/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import ktx.* 2 | 3 | dependencies { 4 | api(project(":style")) 5 | api("com.kotcrab.vis:vis-ui:$visUiVersion") 6 | } 7 | -------------------------------------------------------------------------------- /vis-style/gradle.properties: -------------------------------------------------------------------------------- 1 | projectName=ktx-vis-style 2 | projectDesc=libGDX VisUI style building utilities for Kotlin applications. 3 | -------------------------------------------------------------------------------- /vis/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import ktx.* 2 | 3 | dependencies { 4 | api(project(":scene2d")) 5 | api("com.kotcrab.vis:vis-ui:$visUiVersion") 6 | 7 | testImplementation(project(":scene2d").dependencyProject.sourceSets.test.get().output) 8 | testImplementation("com.badlogicgames.gdx:gdx-backend-lwjgl:$gdxVersion") 9 | testImplementation("com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-desktop") 10 | } 11 | -------------------------------------------------------------------------------- /vis/gradle.properties: -------------------------------------------------------------------------------- 1 | projectName=ktx-vis 2 | projectDesc=VisUI GUI building utilities for libGDX applications written in Kotlin. 3 | -------------------------------------------------------------------------------- /vis/img/README.md: -------------------------------------------------------------------------------- 1 | Screen captures of usage examples. 2 | -------------------------------------------------------------------------------- /vis/img/form.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libktx/ktx/0f28adef8191a15a3c4f453a97fe15524fb3c8fd/vis/img/form.png -------------------------------------------------------------------------------- /vis/img/list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libktx/ktx/0f28adef8191a15a3c4f453a97fe15524fb3c8fd/vis/img/list.png -------------------------------------------------------------------------------- /vis/img/menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libktx/ktx/0f28adef8191a15a3c4f453a97fe15524fb3c8fd/vis/img/menu.png -------------------------------------------------------------------------------- /vis/img/tabs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libktx/ktx/0f28adef8191a15a3c4f453a97fe15524fb3c8fd/vis/img/tabs.png -------------------------------------------------------------------------------- /vis/img/tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libktx/ktx/0f28adef8191a15a3c4f453a97fe15524fb3c8fd/vis/img/tree.png -------------------------------------------------------------------------------- /vis/src/main/kotlin/ktx/scene2d/vis/tooltip.kt: -------------------------------------------------------------------------------- 1 | package ktx.scene2d.vis 2 | 3 | import com.badlogic.gdx.scenes.scene2d.Actor 4 | import com.badlogic.gdx.utils.Align 5 | import com.kotcrab.vis.ui.widget.Tooltip 6 | import com.kotcrab.vis.ui.widget.VisLabel 7 | import ktx.scene2d.Scene2dDsl 8 | import ktx.scene2d.defaultStyle 9 | import ktx.scene2d.scene2d 10 | import kotlin.contracts.ExperimentalContracts 11 | import kotlin.contracts.InvocationKind 12 | import kotlin.contracts.contract 13 | 14 | /** 15 | * Creates and adds [Tooltip] to this [Actor]. 16 | * @param content content of the displayed tooltip. Can be defined with [scene2d]. 17 | * @param style name of the tooltip style. Defaults to [defaultStyle]. 18 | * @param init will be invoked on the [Tooltip], allowing to customize it. 19 | * @return a new instance of [Tooltip] added to this [Actor]. 20 | */ 21 | @Scene2dDsl 22 | @OptIn(ExperimentalContracts::class) 23 | fun Actor.visTooltip( 24 | content: Actor, 25 | style: String = defaultStyle, 26 | init: (@Scene2dDsl Tooltip).() -> Unit = {}, 27 | ): Tooltip { 28 | contract { callsInPlace(init, InvocationKind.EXACTLY_ONCE) } 29 | val tooltip = Tooltip(style) 30 | tooltip.content = content 31 | tooltip.target = this 32 | tooltip.init() 33 | return tooltip 34 | } 35 | 36 | /** 37 | * Creates and adds [Tooltip] with a [VisLabel] to this [Actor]. 38 | * @param text content of the [VisLabel]. 39 | * @param textAlign allows to customize text alignment of the [VisLabel]. 40 | * @param style name of the tooltip style. Defaults to [defaultStyle]. 41 | * @param init will be invoked on the [Tooltip], allowing to customize it. 42 | * @return a new instance of [Tooltip] added to this [Actor]. 43 | */ 44 | @Scene2dDsl 45 | @OptIn(ExperimentalContracts::class) 46 | fun Actor.visTextTooltip( 47 | text: CharSequence, 48 | textAlign: Int = Align.center, 49 | style: String = defaultStyle, 50 | init: (@Scene2dDsl Tooltip).() -> Unit = {}, 51 | ): Tooltip { 52 | contract { callsInPlace(init, InvocationKind.EXACTLY_ONCE) } 53 | val label = VisLabel(text) 54 | label.setAlignment(textAlign) 55 | val tooltip = Tooltip(style) 56 | tooltip.content = label 57 | tooltip.target = this 58 | tooltip.init() 59 | return tooltip 60 | } 61 | -------------------------------------------------------------------------------- /vis/src/main/kotlin/ktx/scene2d/vis/validation.kt: -------------------------------------------------------------------------------- 1 | package ktx.scene2d.vis 2 | 3 | import com.badlogic.gdx.scenes.scene2d.ui.Label 4 | import com.badlogic.gdx.scenes.scene2d.utils.Disableable 5 | import com.kotcrab.vis.ui.util.form.FormValidator 6 | import ktx.scene2d.Scene2dDsl 7 | import ktx.scene2d.defaultStyle 8 | import kotlin.contracts.ExperimentalContracts 9 | import kotlin.contracts.InvocationKind 10 | import kotlin.contracts.contract 11 | 12 | /** 13 | * [FormValidator] factory function. 14 | * @param targetToDisable target actor that will be disabled if form is invalid. 15 | * @param messageLabel label will be changed when the form is valid or invalid. May be null. 16 | * @param style name of the [FormValidator] style. 17 | * @return a new instance of a [FormValidator] 18 | */ 19 | @Scene2dDsl 20 | @OptIn(ExperimentalContracts::class) 21 | inline fun validator( 22 | targetToDisable: Disableable? = null, 23 | messageLabel: Label? = null, 24 | style: String = defaultStyle, 25 | init: FormValidator.() -> Unit, 26 | ): FormValidator { 27 | contract { callsInPlace(init, InvocationKind.EXACTLY_ONCE) } 28 | return FormValidator(targetToDisable, messageLabel, style).apply(init) 29 | } 30 | -------------------------------------------------------------------------------- /vis/src/test/kotlin/ktx/scene2d/vis/TooltipsTest.kt: -------------------------------------------------------------------------------- 1 | package ktx.scene2d.vis 2 | 3 | import com.badlogic.gdx.scenes.scene2d.Actor 4 | import com.badlogic.gdx.utils.Align 5 | import com.kotcrab.vis.ui.widget.VisLabel 6 | import com.kotcrab.vis.ui.widget.VisTable 7 | import ktx.scene2d.ApplicationTest 8 | import ktx.scene2d.TOLERANCE 9 | import org.junit.Assert.assertEquals 10 | import org.junit.Assert.assertTrue 11 | import org.junit.Test 12 | 13 | class TooltipsTest : ApplicationTest() { 14 | @Test 15 | fun `should create Tooltip`() { 16 | val actor = Actor() 17 | val content = VisTable() 18 | 19 | val tooltip = actor.visTooltip(content) 20 | 21 | assertEquals(tooltip.content, content) 22 | } 23 | 24 | @Test 25 | fun `should create Tooltip with init block`() { 26 | val actor = Actor() 27 | var initInvoked: Boolean 28 | 29 | val tooltip = 30 | actor.visTooltip(VisTable()) { 31 | initInvoked = true 32 | fadeTime = 0.5f 33 | } 34 | 35 | assertEquals(0.5f, tooltip.fadeTime, TOLERANCE) 36 | assertTrue(initInvoked) 37 | } 38 | 39 | @Test 40 | fun `should create Tooltip with Label`() { 41 | val actor = Actor() 42 | val text = "text tooltip" 43 | 44 | val tooltip = actor.visTextTooltip(text, Align.right) 45 | 46 | assertEquals(text, (tooltip.content as VisLabel).text.toString()) 47 | assertEquals(Align.right, (tooltip.content as VisLabel).labelAlign) 48 | } 49 | 50 | @Test 51 | fun `should create text Tooltip with init block`() { 52 | val actor = Actor() 53 | val text = "text tooltip" 54 | var initInvoked: Boolean 55 | 56 | val tooltip = 57 | actor.visTextTooltip(text) { 58 | initInvoked = true 59 | fadeTime = 0.5f 60 | } 61 | 62 | assertEquals(0.5f, tooltip.fadeTime, TOLERANCE) 63 | assertEquals(text, (tooltip.content as VisLabel).text.toString()) 64 | assertTrue(initInvoked) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /vis/src/test/kotlin/ktx/scene2d/vis/ValidationTest.kt: -------------------------------------------------------------------------------- 1 | package ktx.scene2d.vis 2 | 3 | import ktx.scene2d.ApplicationTest 4 | import org.junit.Assert.assertNotNull 5 | import org.junit.Assert.assertTrue 6 | import org.junit.Test 7 | 8 | class ValidationTest : ApplicationTest() { 9 | @Test 10 | fun `should create FormValidator`() { 11 | var invoked: Boolean 12 | 13 | val formValidator = 14 | validator { 15 | invoked = true 16 | } 17 | 18 | assertNotNull(formValidator) 19 | assertTrue(invoked) 20 | } 21 | } 22 | --------------------------------------------------------------------------------