Merge branch '4.2' into gdextension

# Conflicts:
#	spine-godot/spine_godot/SpineAtlasResource.cpp
This commit is contained in:
Mario Zechner 2024-10-07 08:38:57 +02:00
commit f8a0b5b6f7
282 changed files with 22005 additions and 903 deletions

35
.github/workflows/spine-android.yml vendored Normal file
View File

@ -0,0 +1,35 @@
name: Build spine-android
on:
push:
paths:
- 'spine-android/**'
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
distribution: 'zulu'
java-version: "17"
- name: Setup Android SDK
uses: android-actions/setup-android@v3
with:
api-level: 34
build-tools: 35.0.0
- name: Cache Gradle packages
uses: actions/cache@v3
with:
path: ~/.gradle/caches
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
restore-keys: ${{ runner.os }}-gradle
- name: Build spine-android
working-directory: spine-android
run: ./gradlew publishReleasePublicationToSonaType -PossrhUsername=${{ secrets.SONATYPE_USER }} -PossrhPassword=${{ secrets.SONATYPE_PASSWORD }}

View File

@ -14,8 +14,8 @@ jobs:
matrix:
version:
[
{"tag": "4.1.4-stable", "version": "4.1.4.stable", "mono": false},
{"tag": "4.1.4-stable", "version": "4.1.4.stable", "mono": true},
{"tag": "4.3-stable", "version": "4.3.stable", "mono": false},
{"tag": "4.3-stable", "version": "4.3.stable", "mono": true},
{"tag": "4.2.2-stable", "version": "4.2.2.stable", "mono": false},
{"tag": "4.2.2-stable", "version": "4.2.2.stable", "mono": true},
]

View File

@ -34,7 +34,7 @@ env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_EC2_METADATA_DISABLED: true
EM_VERSION: 3.1.18
EM_VERSION: 3.1.26
GODOT_TAG: ${{ inputs.godot_tag }}
GODOT_VERSION: ${{ inputs.godot_version }}
GODOT_MONO: ${{ inputs.godot_mono }}
@ -302,6 +302,42 @@ jobs:
name: ${{ format('{0}-{1}{2}.zip', 'godot-template-web-release', env.GODOT_TAG, env.GODOT_MONO_UPLOAD_SUFFIX) }}
path: spine-godot/godot/bin/web_release.zip
- name: Upload artifacts no threads debug
uses: actions/upload-artifact@v3
with:
name: ${{ format('{0}-{1}{2}.zip', 'godot-template-web-nothreads-debug', env.GODOT_TAG, env.GODOT_MONO_UPLOAD_SUFFIX) }}
path: spine-godot/godot/bin/web_nothreads_debug.zip
- name: Upload artifacts no threads release
uses: actions/upload-artifact@v3
with:
name: ${{ format('{0}-{1}{2}.zip', 'godot-template-web-nothreads-release', env.GODOT_TAG, env.GODOT_MONO_UPLOAD_SUFFIX) }}
path: spine-godot/godot/bin/web_nothreads_release.zip
- name: Upload artifacts dlink debug
uses: actions/upload-artifact@v3
with:
name: ${{ format('{0}-{1}{2}.zip', 'godot-template-web-dlink-debug', env.GODOT_TAG, env.GODOT_MONO_UPLOAD_SUFFIX) }}
path: spine-godot/godot/bin/web_dlink_debug.zip
- name: Upload artifacts dlink release
uses: actions/upload-artifact@v3
with:
name: ${{ format('{0}-{1}{2}.zip', 'godot-template-web-dlink-release', env.GODOT_TAG, env.GODOT_MONO_UPLOAD_SUFFIX) }}
path: spine-godot/godot/bin/web_dlink_release.zip
- name: Upload artifacts dlink nothreads debug
uses: actions/upload-artifact@v3
with:
name: ${{ format('{0}-{1}{2}.zip', 'godot-template-web-dlink-nothreads-debug', env.GODOT_TAG, env.GODOT_MONO_UPLOAD_SUFFIX) }}
path: spine-godot/godot/bin/web_dlink_nothreads_debug.zip
- name: Upload artifacts dlink nothreads release
uses: actions/upload-artifact@v3
with:
name: ${{ format('{0}-{1}{2}.zip', 'godot-template-web-dlink-nothreads-release', env.GODOT_TAG, env.GODOT_MONO_UPLOAD_SUFFIX) }}
path: spine-godot/godot/bin/web_dlink_nothreads_release.zip
upload-to-s3:
needs: [godot-editor-windows, godot-editor-linux, godot-editor-macos, godot-template-ios, godot-template-macos, godot-template-windows, godot-template-linux, godot-template-android, godot-template-web]
runs-on: ubuntu-latest
@ -309,75 +345,105 @@ jobs:
steps:
- name: Download godot-editor-windows artifact
uses: actions/download-artifact@v2
uses: actions/download-artifact@v4
with:
name: ${{ format('{0}-{1}{2}.zip', 'godot-editor-windows', env.GODOT_TAG, env.GODOT_MONO_UPLOAD_SUFFIX) }}
- name: Download godot-editor-linux artifact
uses: actions/download-artifact@v2
uses: actions/download-artifact@v4
with:
name: ${{ format('{0}-{1}{2}.zip', 'godot-editor-linux', env.GODOT_TAG, env.GODOT_MONO_UPLOAD_SUFFIX) }}
- name: Download godot-editor-macos artifact
uses: actions/download-artifact@v2
uses: actions/download-artifact@v4
with:
name: ${{ format('{0}-{1}{2}.zip', 'godot-editor-macos', env.GODOT_TAG, env.GODOT_MONO_UPLOAD_SUFFIX) }}
- name: Download godot-template-ios artifact
uses: actions/download-artifact@v2
uses: actions/download-artifact@v4
with:
name: ${{ format('{0}-{1}{2}.zip', 'godot-template-ios', env.GODOT_TAG, env.GODOT_MONO_UPLOAD_SUFFIX) }}
- name: Download godot-template-macos artifact
uses: actions/download-artifact@v2
uses: actions/download-artifact@v4
with:
name: ${{ format('{0}-{1}{2}.zip', 'godot-template-macos', env.GODOT_TAG, env.GODOT_MONO_UPLOAD_SUFFIX) }}
- name: Download godot-template-windows-release artifact
uses: actions/download-artifact@v2
uses: actions/download-artifact@v4
with:
name: ${{ format('{0}-{1}{2}.zip', 'godot-template-windows-release', env.GODOT_TAG, env.GODOT_MONO_UPLOAD_SUFFIX) }}
- name: Download godot-template-windows-debug artifact
uses: actions/download-artifact@v2
uses: actions/download-artifact@v4
with:
name: ${{ format('{0}-{1}{2}.zip', 'godot-template-windows-debug', env.GODOT_TAG, env.GODOT_MONO_UPLOAD_SUFFIX) }}
- name: Download godot-template-linux-release artifact
uses: actions/download-artifact@v2
uses: actions/download-artifact@v4
with:
name: ${{ format('{0}-{1}{2}.zip', 'godot-template-linux-release', env.GODOT_TAG, env.GODOT_MONO_UPLOAD_SUFFIX) }}
- name: Download godot-template-linux-debug artifact
uses: actions/download-artifact@v2
uses: actions/download-artifact@v4
with:
name: ${{ format('{0}-{1}{2}.zip', 'godot-template-linux-debug', env.GODOT_TAG, env.GODOT_MONO_UPLOAD_SUFFIX) }}
- name: Download godot-template-android-release artifact
uses: actions/download-artifact@v2
uses: actions/download-artifact@v4
with:
name: ${{ format('{0}-{1}{2}.zip', 'godot-template-android-release', env.GODOT_TAG, env.GODOT_MONO_UPLOAD_SUFFIX) }}
- name: Download godot-template-android-debug artifact
uses: actions/download-artifact@v2
uses: actions/download-artifact@v4
with:
name: ${{ format('{0}-{1}{2}.zip', 'godot-template-android-debug', env.GODOT_TAG, env.GODOT_MONO_UPLOAD_SUFFIX) }}
- name: Download godot-template-android-source artifact
uses: actions/download-artifact@v2
uses: actions/download-artifact@v4
with:
name: ${{ format('{0}-{1}{2}.zip', 'godot-template-android-source', env.GODOT_TAG, env.GODOT_MONO_UPLOAD_SUFFIX) }}
- name: Download godot-template-web-release artifact
uses: actions/download-artifact@v2
uses: actions/download-artifact@v4
with:
name: ${{ format('{0}-{1}{2}.zip', 'godot-template-web-release', env.GODOT_TAG, env.GODOT_MONO_UPLOAD_SUFFIX) }}
- name: Download godot-template-web-debug artifact
uses: actions/download-artifact@v2
uses: actions/download-artifact@v4
with:
name: ${{ format('{0}-{1}{2}.zip', 'godot-template-web-debug', env.GODOT_TAG, env.GODOT_MONO_UPLOAD_SUFFIX) }}
- name: Download godot-template-web-nothreads-release artifact
uses: actions/download-artifact@v4
with:
name: ${{ format('{0}-{1}{2}.zip', 'godot-template-web-nothreads-release', env.GODOT_TAG, env.GODOT_MONO_UPLOAD_SUFFIX) }}
- name: Download godot-template-web-nothreads-debug artifact
uses: actions/download-artifact@v4
with:
name: ${{ format('{0}-{1}{2}.zip', 'godot-template-web-nothreads-debug', env.GODOT_TAG, env.GODOT_MONO_UPLOAD_SUFFIX) }}
- name: Download godot-template-web-dlink-release artifact
uses: actions/download-artifact@v4
with:
name: ${{ format('{0}-{1}{2}.zip', 'godot-template-web-dlink-release', env.GODOT_TAG, env.GODOT_MONO_UPLOAD_SUFFIX) }}
- name: Download godot-template-web-dlink-debug artifact
uses: actions/download-artifact@v4
with:
name: ${{ format('{0}-{1}{2}.zip', 'godot-template-web-dlink-debug', env.GODOT_TAG, env.GODOT_MONO_UPLOAD_SUFFIX) }}
- name: Download godot-template-web-dlink-nothreads-release artifact
uses: actions/download-artifact@v4
with:
name: ${{ format('{0}-{1}{2}.zip', 'godot-template-web-dlink-nothreads-release', env.GODOT_TAG, env.GODOT_MONO_UPLOAD_SUFFIX) }}
- name: Download godot-template-web-dlink-nothreads-debug artifact
uses: actions/download-artifact@v4
with:
name: ${{ format('{0}-{1}{2}.zip', 'godot-template-web-dlink-nothreads-debug', env.GODOT_TAG, env.GODOT_MONO_UPLOAD_SUFFIX) }}
- name: Upload artifacts to S3
shell: bash
if: env.AWS_ACCESS_KEY_ID != null
@ -391,9 +457,24 @@ jobs:
aws s3 cp godot-editor-windows.zip s3://spine-godot/$BRANCH/$GODOT_TAG/
aws s3 cp godot-editor-linux.zip s3://spine-godot/$BRANCH/$GODOT_TAG/
aws s3 cp godot-editor-macos.zip s3://spine-godot/$BRANCH/$GODOT_TAG/
echo "$GODOT_VERSION" > version.txt
# Extract the major and minor version from GODOT_VERSION
GODOT_MAJOR_MINOR=$(echo "$GODOT_VERSION" | sed -E 's/^([0-9]+\.[0-9]+).*/\1/')
# Check if the version is >= 4.3
if [[ $(echo "$GODOT_MAJOR_MINOR >= 4.3" | bc) -eq 1 ]]; then
mv web_release.zip web_nothreads_release.zip
mv web_debug.zip web_nothreads_debug.zip
WEB_RELEASE_FILE="web_nothreads_release.zip"
WEB_DEBUG_FILE="web_nothreads_debug.zip"
else
WEB_RELEASE_FILE="web_release.zip"
WEB_DEBUG_FILE="web_debug.zip"
fi
ls -lah
zip spine-godot-templates-$BRANCH-$GODOT_TAG.zip ios.zip macos.zip windows_debug_x86_64.exe windows_release_x86_64.exe linux_debug.x86_64 linux_release.x86_64 web_debug.zip web_release.zip android_release.apk android_debug.apk android_source.zip version.txt
zip spine-godot-templates-$BRANCH-$GODOT_TAG.zip ios.zip macos.zip windows_debug_x86_64.exe windows_release_x86_64.exe linux_debug.x86_64 linux_release.x86_64 "$WEB_DEBUG_FILE" "$WEB_RELEASE_FILE" android_release.apk android_debug.apk android_source.zip version.txt
aws s3 cp spine-godot-templates-$BRANCH-$GODOT_TAG.zip s3://spine-godot/$BRANCH/$GODOT_TAG/spine-godot-templates-$BRANCH-$GODOT_TAG.tpz
upload-to-s3-mono:
@ -403,42 +484,42 @@ jobs:
steps:
- name: Download godot-editor-windows artifact
uses: actions/download-artifact@v2
uses: actions/download-artifact@v4
with:
name: ${{ format('{0}-{1}{2}.zip', 'godot-editor-windows', env.GODOT_TAG, env.GODOT_MONO_UPLOAD_SUFFIX) }}
- name: Download godot-editor-linux artifact
uses: actions/download-artifact@v2
uses: actions/download-artifact@v4
with:
name: ${{ format('{0}-{1}{2}.zip', 'godot-editor-linux', env.GODOT_TAG, env.GODOT_MONO_UPLOAD_SUFFIX) }}
- name: Download godot-editor-macos artifact
uses: actions/download-artifact@v2
uses: actions/download-artifact@v4
with:
name: ${{ format('{0}-{1}{2}.zip', 'godot-editor-macos', env.GODOT_TAG, env.GODOT_MONO_UPLOAD_SUFFIX) }}
- name: Download godot-template-macos artifact
uses: actions/download-artifact@v2
uses: actions/download-artifact@v4
with:
name: ${{ format('{0}-{1}{2}.zip', 'godot-template-macos', env.GODOT_TAG, env.GODOT_MONO_UPLOAD_SUFFIX) }}
- name: Download godot-template-windows-release artifact
uses: actions/download-artifact@v2
uses: actions/download-artifact@v4
with:
name: ${{ format('{0}-{1}{2}.zip', 'godot-template-windows-release', env.GODOT_TAG, env.GODOT_MONO_UPLOAD_SUFFIX) }}
- name: Download godot-template-windows-debug artifact
uses: actions/download-artifact@v2
uses: actions/download-artifact@v4
with:
name: ${{ format('{0}-{1}{2}.zip', 'godot-template-windows-debug', env.GODOT_TAG, env.GODOT_MONO_UPLOAD_SUFFIX) }}
- name: Download godot-template-linux-release artifact
uses: actions/download-artifact@v2
uses: actions/download-artifact@v4
with:
name: ${{ format('{0}-{1}{2}.zip', 'godot-template-linux-release', env.GODOT_TAG, env.GODOT_MONO_UPLOAD_SUFFIX) }}
- name: Download godot-template-linux-debug artifact
uses: actions/download-artifact@v2
uses: actions/download-artifact@v4
with:
name: ${{ format('{0}-{1}{2}.zip', 'godot-template-linux-debug', env.GODOT_TAG, env.GODOT_MONO_UPLOAD_SUFFIX) }}

View File

@ -12,8 +12,8 @@ env:
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_EC2_METADATA_DISABLED: true
EM_VERSION: 3.1.14
GODOT_TAG: 3.5.3-stable
GODOT_VERSION: 3.5.3.stable
GODOT_TAG: 3.6-stable
GODOT_VERSION: 3.6.stable
jobs:
godot-editor-windows:
@ -33,7 +33,7 @@ jobs:
./spine-godot/build/build.sh release_debug
- name: Upload artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: godot-editor-windows.zip
path: spine-godot/godot/bin/godot.windows.opt.tools.64.exe
@ -57,7 +57,7 @@ jobs:
./spine-godot/build/build.sh release_debug
- name: Upload artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: godot-editor-linux.zip
path: spine-godot/godot/bin/godot.x11.opt.tools.64
@ -82,7 +82,7 @@ jobs:
popd
- name: Upload artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: godot-editor-macos.zip
path: spine-godot/godot/bin/godot-editor-macos.zip
@ -103,7 +103,7 @@ jobs:
./spine-godot/build/build-templates.sh ios
- name: Upload artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: godot-template-ios.zip
path: spine-godot/godot/bin/iphone.zip
@ -124,7 +124,7 @@ jobs:
./spine-godot/build/build-templates.sh macos
- name: Upload artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: godot-template-macos.zip
path: spine-godot/godot/bin/osx.zip
@ -147,13 +147,13 @@ jobs:
./spine-godot/build/build-templates.sh linux
- name: Upload artifacts debug
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: godot-template-linux-debug.zip
path: spine-godot/godot/bin/linux_x11_64_debug
- name: Upload artifacts release
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: godot-template-linux-release.zip
path: spine-godot/godot/bin/linux_x11_64_release
@ -175,13 +175,13 @@ jobs:
./spine-godot/build/build-templates.sh windows
- name: Upload artifacts debug
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: godot-template-windows-debug.zip
path: spine-godot/godot/bin/windows_64_debug.exe
- name: Upload artifacts release
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: godot-template-windows-release.zip
path: spine-godot/godot/bin/windows_64_release.exe
@ -203,7 +203,7 @@ jobs:
- name: Set up Java 11
uses: actions/setup-java@v1
with:
java-version: 11
java-version: 17
- name: Setup python and scons
uses: ./.github/actions/setup-godot-deps-3
@ -215,19 +215,19 @@ jobs:
./spine-godot/build/build-templates.sh android
- name: Upload artifacts debug
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: godot-template-android-debug.zip
path: spine-godot/godot/bin/android_debug.apk
- name: Upload artifacts release
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: godot-template-android-release.zip
path: spine-godot/godot/bin/android_release.apk
- name: Upload artifacts source
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: godot-template-android-source.zip
path: spine-godot/godot/bin/android_source.zip
@ -257,13 +257,13 @@ jobs:
./spine-godot/build/build-templates.sh web
- name: Upload artifacts debug
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: godot-template-web-debug.zip
path: spine-godot/godot/bin/webassembly_debug.zip
- name: Upload artifacts release
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: godot-template-web-release.zip
path: spine-godot/godot/bin/webassembly_release.zip
@ -273,72 +273,72 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Download godot-editor-windows artifact
uses: actions/download-artifact@v2
uses: actions/download-artifact@v4
with:
name: godot-editor-windows.zip
- name: Download godot-editor-linux artifact
uses: actions/download-artifact@v2
uses: actions/download-artifact@v4
with:
name: godot-editor-linux.zip
- name: Download godot-editor-macos artifact
uses: actions/download-artifact@v2
uses: actions/download-artifact@v4
with:
name: godot-editor-macos.zip
- name: Download godot-template-ios artifact
uses: actions/download-artifact@v2
uses: actions/download-artifact@v4
with:
name: godot-template-ios.zip
- name: Download godot-template-macos artifact
uses: actions/download-artifact@v2
uses: actions/download-artifact@v4
with:
name: godot-template-macos.zip
- name: Download godot-template-windows-release artifact
uses: actions/download-artifact@v2
uses: actions/download-artifact@v4
with:
name: godot-template-windows-release.zip
- name: Download godot-template-windows-debug artifact
uses: actions/download-artifact@v2
uses: actions/download-artifact@v4
with:
name: godot-template-windows-debug.zip
- name: Download godot-template-linux-release artifact
uses: actions/download-artifact@v2
uses: actions/download-artifact@v4
with:
name: godot-template-linux-release.zip
- name: Download godot-template-linux-debug artifact
uses: actions/download-artifact@v2
uses: actions/download-artifact@v4
with:
name: godot-template-linux-debug.zip
- name: Download godot-template-android-release artifact
uses: actions/download-artifact@v2
uses: actions/download-artifact@v4
with:
name: godot-template-android-release.zip
- name: Download godot-template-android-debug artifact
uses: actions/download-artifact@v2
uses: actions/download-artifact@v4
with:
name: godot-template-android-debug.zip
- name: Download godot-template-android-source artifact
uses: actions/download-artifact@v2
uses: actions/download-artifact@v4
with:
name: godot-template-android-source.zip
- name: Download godot-template-web-release artifact
uses: actions/download-artifact@v2
uses: actions/download-artifact@v4
with:
name: godot-template-web-release.zip
- name: Download godot-template-web-debug artifact
uses: actions/download-artifact@v2
uses: actions/download-artifact@v4
with:
name: godot-template-web-debug.zip

18
.github/workflows/spine-haxe.yml vendored Normal file
View File

@ -0,0 +1,18 @@
name: Build spine-haxe
on:
push:
paths:
- 'spine-haxe/**'
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build spine-haxe
working-directory: spine-haxe
env:
HAXE_UPDATE_URL: ${{secrets.HAXE_UPDATE_URL}}
run: ./build.sh

View File

@ -10,26 +10,20 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 1.8
uses: actions/setup-java@v3
- uses: actions/checkout@v2
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
distribution: 'zulu'
java-version: "8"
server-id: sonatype-nexus-snapshots
server-username: MAVEN_USERNAME
server-password: MAVEN_PASSWORD
java-version: '17'
distribution: 'temurin'
- name: Cache Maven packages
- name: Cache Gradle packages
uses: actions/cache@v3
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-m2
path: ~/.gradle/caches
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
restore-keys: ${{ runner.os }}-gradle
- name: Build spine-libgdx
working-directory: spine-libgdx/spine-libgdx
run: mvn clean deploy
env:
MAVEN_USERNAME: ${{ secrets.SONATYPE_USER }}
MAVEN_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
working-directory: spine-libgdx
run: ./gradlew publishReleasePublicationToSonaType -PossrhUsername=${{ secrets.SONATYPE_USER }} -PossrhPassword=${{ secrets.SONATYPE_PASSWORD }}

View File

@ -88,6 +88,7 @@
- **Breaking**: Starting with Unreal Engine 5.3 imported `.skel`/`.json` and `.atlas` files in the same folder must NOT have a common prefix. E.g. `skeleton.json` and `skeleton.atlas` will not work. Make sure to rename at least one of the two files so there is no prefix collision, e.g. `skeleton-data.json` and `skeleton.atlas`.
- Added compatibility with UE 5.3
- Added more example maps
- Added blueprint-callable methods `PhysicsTranslate()`, `PhysicsRotate()` and `ResetPhysicsConstraints()` (which will reset all physics constraints in the skeleton) to `SpineSkeletonComponent` and `SpineWidget`.
### Godot
@ -122,7 +123,7 @@
### Unity
- **Officially supported Unity versions are 2017.1-2022.1**.
- **Officially supported Unity versions are 2017.1-2023.1**.
- **Additions**
@ -160,6 +161,9 @@
- All Spine Outline shaders, including the URP outline shader, now provide an additional parameter `Width in Screen Space`. Enable it to keep the outline width constant in screen space instead of texture space. Requires more expensive computations, so enable only where necessary. Defaults to `disabled` to maintain existing behaviour.
- Added support for BlendModeMaterials at runtime instantiation from files via an additional method `SkeletonDataAsset.SetupRuntimeBlendModeMaterials`. See example scene `Spine Examples/Other Examples/Instantiate from Script` for a usage example.
- SkeletonGraphic: You can now offset the skeleton mesh relative to the pivot via a newly added green circle handle. This allows you to e.g. frame only the face of a skeleton inside a masked frame. Previously offsetting the pivot downwards fails when `Layout Scale Mode` scales the mesh smaller and towards the pivot (e.g. the feet) and thus out of the frame. Now you can keep the pivot in the center of the `RectTransform` while offsetting only the mesh downwards, keeping the desired skeleton area (e.g. the face) centered while resizing. Moving the new larger green circle handle moves the mesh offset, while moving the blue pivot circle handle moves the pivot as usual.
- `Universal Render Pipeline/Spine/Skeleton` shader now performs proper alpha-testing when `Depth Write` is enabled, using the existing `Shadow alpha cutoff` parameter.
- `SkeletonRootMotion` components now provide a public `Initialize()` method which is automatically called when calling `skeletonAnimation.Initialize(true)` to update the necessary skeleton references. If a different root bone shall be used, be sure to set `skeletonRootMotion.rootMotionBoneName` before calling `skeletonAnimation.Initialize(true)`.
- Skeleton Mecanim: Added new `Mix Mode` `Match`. When selected, Spine animation weights are calculated to best match the provided Mecanim clip weights. This mix mode is recommended on any layer using blend tree nodes.
- **Breaking changes**
@ -172,6 +176,9 @@
- Inspector: String attribute `SpineSkin()` now allows to include `<None>` in the list of parameters. Previously the `includeNone=true` parameter of the `SpineSkin()` attribute defaulted to `true` but was ignored. Now it defaults to `false` and has an effect on the list. Only the Inspector GUI is affected by this behaviour change.
- `SkeletonGraphicRenderTexture` example component: `protected RawImage quadRawImage` was changed to `protected SkeletonSubmeshGraphic quadMaskableGraphic` for a bugfix. This is only relevant for subclasses of `SkeletonGraphicRenderTexture` or when querying the `RawImage` component via e.g. `skeletonGraphicRenderTexture.quad.GetComponent<RawImage>()`.
- Fixed a bug where when Linear color space is used and `PMA vertex colors` enabled, additive slots add a too dark (too transparent) color value. If you want the old incorrect behaviour (darker additive slots) or are not using Linear but Gamma color space, you can comment-out the define `LINEAR_COLOR_SPACE_FIX_ADDITIVE_ALPHA` in `MeshGenerator.cs` to deactivate the fix or just to skip unnecessary instructions.
- Fixed SkeletonRootMotion components ignoring parent bone scale when set by transform constraints. Using applied scale of parent bone now. If you need the old behaviour, comment out the line `#define USE_APPLIED_PARENT_SCALE` in SkeletonRootMotionBase.cs.
- Fixed SkeletonUtility callback update order when used with SkeletonRootMotion components so that the position when following a bone is updated after SkeletonRootMotion clears root-bone position. The order of SkeletonUtilityBone callbacks is changed to be later to achieve this. This is a breaking change in the unlikely case that you are using SkeletonRootMotion together with SkeletonUtility and subscribed to `UpdateLocal`, `UpdateWorld` or `UpdateComplete` yourself and relied on a certain callback order. One solution is to then resubscribe your own callback events accordingly by calling
`.UpdateLocal -= Callback; .UpdateLocal += Callback;`.
- **Changes of default values**
@ -337,6 +344,7 @@
- `VertexEffect` has been removed.
### Cocos2d-x
- Renamed `SkeletonRenderer` to `SkeletonRendererCocos2dX` to avoid name clash with spine-cpp class.
### SFML
@ -421,6 +429,7 @@
- Made `SkeletonGraphic.unscaledTime` parameter protected, use the new property `UnscaledTime` instead.
- `SkeletonGraphic` `OnRebuild` callback delegate is now issued after the skeleton has been initialized, before the `AnimationState` component is initialized. This makes behaviour consistent with `SkeletonAnimation` and `SkeletonMecanim` component behaviour. Use the new callback `OnAnimationRebuild` if you want to receive a callback after the `SkeletonGraphic` `AnimationState` has been initialized.
- Changed name of prefab skeleton meshes stored at prefabs from `Skeleton Prefab Mesh "name"` to `Skeleton Prefab Mesh [name]` to avoid issues with quotes in mesh asset names (see [this issue](https://github.com/EsotericSoftware/spine-runtimes/issues/2572)). Likely this change poses no problems at all, however if you are parsing the prefab's mesh name for whatever reason, be sure to adjust the pattern accordingly.
- **Changes of default values**

View File

@ -35,6 +35,29 @@ cp -f ../mix-and-match/export/*.json "$ROOT/spine-libgdx/spine-libgdx-tests/asse
cp -f ../mix-and-match/export/*.skel "$ROOT/spine-libgdx/spine-libgdx-tests/assets/mix-and-match/"
cp -f ../mix-and-match/export/*-pma.* "$ROOT/spine-libgdx/spine-libgdx-tests/assets/mix-and-match/"
echo "spine-android"
rm "$ROOT/spine-android/app/src/main/assets/"*
cp -f ../celestial-circus/export/celestial-circus-pro.skel "$ROOT/spine-android/app/src/main/assets/"
cp -f ../celestial-circus/export/celestial-circus.atlas "$ROOT/spine-android/app/src/main/assets"
cp -f ../celestial-circus/export/celestial-circus.png "$ROOT/spine-android/app/src/main/assets"
cp -f ../dragon/export/dragon-ess.skel "$ROOT/spine-android/app/src/main/assets/"
cp -f ../dragon/export/dragon.atlas "$ROOT/spine-android/app/src/main/assets"
cp -f ../dragon/export/dragon.png "$ROOT/spine-android/app/src/main/assets"
cp -f ../dragon/export/dragon_2.png "$ROOT/spine-android/app/src/main/assets"
cp -f ../dragon/export/dragon_3.png "$ROOT/spine-android/app/src/main/assets"
cp -f ../dragon/export/dragon_4.png "$ROOT/spine-android/app/src/main/assets"
cp -f ../dragon/export/dragon_5.png "$ROOT/spine-android/app/src/main/assets"
cp -f ../mix-and-match/export/mix-and-match-pro.skel "$ROOT/spine-android/app/src/main/assets/"
cp -f ../mix-and-match/export/mix-and-match.atlas "$ROOT/spine-android/app/src/main/assets/"
cp -f ../mix-and-match/export/mix-and-match.png "$ROOT/spine-android/app/src/main/assets/"
cp -f ../spineboy/export/spineboy-pro.skel "$ROOT/spine-android/app/src/main/assets/"
cp -f ../spineboy/export/spineboy-pro.json "$ROOT/spine-android/app/src/main/assets/"
cp -f ../spineboy/export/spineboy.atlas "$ROOT/spine-android/app/src/main/assets/"
cp -f ../spineboy/export/spineboy.png "$ROOT/spine-android/app/src/main/assets/"
rm -f "$ROOT/spine-libgdx/spine-libgdx-tests/assets/sack/"*
mkdir -p "$ROOT/spine-libgdx/spine-libgdx-tests/assets/sack/"
cp -f ../sack/export/sack-pro.json "$ROOT/spine-libgdx/spine-libgdx-tests/assets/sack/"
@ -453,6 +476,19 @@ cp -f ../spineboy/export/spineboy-ess.json "$ROOT/spine-ts/spine-canvas/example/
cp -f ../spineboy/export/spineboy.atlas "$ROOT/spine-ts/spine-canvas/example/assets/"
cp -f ../spineboy/export/spineboy.png "$ROOT/spine-ts/spine-canvas/example/assets/"
rm "$ROOT/spine-ts/spine-canvaskit/example/assets/"*
cp -f ../spineboy/export/spineboy-pro.skel "$ROOT/spine-ts/spine-canvaskit/example/assets/"
cp -f ../spineboy/export/spineboy.atlas "$ROOT/spine-ts/spine-canvaskit/example/assets/"
cp -f ../spineboy/export/spineboy.png "$ROOT/spine-ts/spine-canvaskit/example/assets/"
cp -f ../mix-and-match/export/mix-and-match-pro.skel "$ROOT/spine-ts/spine-canvaskit/example/assets/"
cp -f ../mix-and-match/export/mix-and-match.atlas "$ROOT/spine-ts/spine-canvaskit/example/assets/"
cp -f ../mix-and-match/export/mix-and-match.png "$ROOT/spine-ts/spine-canvaskit/example/assets/"
cp -f ../celestial-circus/export/celestial-circus-pro.json "$ROOT/spine-ts/spine-canvaskit/example/assets/"
cp -f ../celestial-circus/export/celestial-circus.atlas "$ROOT/spine-ts/spine-canvaskit/example/assets/"
cp -f ../celestial-circus/export/celestial-circus.png "$ROOT/spine-ts/spine-canvaskit/example/assets/"
rm "$ROOT/spine-ts/spine-threejs/example/assets/"*
cp -f ../raptor/export/raptor-pro.json "$ROOT/spine-ts/spine-threejs/example/assets/"
cp -f ../raptor/export/raptor.atlas "$ROOT/spine-ts/spine-threejs/example/assets/"

View File

@ -8,7 +8,8 @@ spotless {
lineEndings 'UNIX'
java {
target 'spine-libgdx/**/*.java'
target 'spine-libgdx/**/*.java',
'spine-android/**/*.java'
eclipse().configFile('formatters/eclipse-formatter.xml')
}

15
spine-android/.gitignore vendored Normal file
View File

@ -0,0 +1,15 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties

1
spine-android/app/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

View File

@ -0,0 +1,75 @@
plugins {
alias(libs.plugins.androidApplication)
alias(libs.plugins.jetbrainsKotlinAndroid)
}
android {
namespace = "com.esotericsoftware.spine"
compileSdk = 34
defaultConfig {
applicationId = "com.esotericsoftware.spine"
minSdk = 23
targetSdk = 34
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary = true
}
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.5.1"
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
}
dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose)
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.ui)
implementation(libs.androidx.ui.graphics)
implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.material3)
implementation(libs.androidx.navigation.compose)
implementation(libs.appcompat)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.ui.test.junit4)
debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest)
implementation(project(":spine-android"))
// implementation("com.esotericsoftware.spine:spine-android:4.2.2-SNAPSHOT")
}

21
spine-android/app/proguard-rules.pro vendored Normal file
View File

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@ -0,0 +1,24 @@
package com.esotericsoftware.android
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.esotericsoftware.spine", appContext.packageName)
}
}

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.SpineAndroidExamples"
tools:targetApi="34">
<activity
android:name="MainActivity"
android:exported="true"
android:theme="@style/Theme.SpineAndroidExamples">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".SimpleAnimationActivity"
android:theme="@style/Theme.AppCompat.Light.NoActionBar">
</activity>
</application>
</manifest>

View File

@ -0,0 +1,173 @@
celestial-circus.png
size: 1024, 1024
filter: Linear, Linear
scale: 0.4
arm-back-down
bounds: 324, 401, 38, 82
rotate: 90
arm-back-up
bounds: 290, 44, 83, 116
rotate: 90
arm-front-down
bounds: 706, 2, 36, 78
rotate: 90
arm-front-up
bounds: 860, 138, 77, 116
bench
bounds: 725, 256, 189, 48
body-bottom
bounds: 879, 868, 154, 124
rotate: 90
body-top
bounds: 725, 128, 126, 133
rotate: 90
chest
bounds: 408, 26, 104, 93
cloud-back
bounds: 752, 378, 202, 165
cloud-front
bounds: 2, 2, 325, 196
rotate: 90
collar
bounds: 786, 13, 47, 26
ear
bounds: 1002, 643, 20, 28
eye-back-shadow
bounds: 428, 395, 14, 10
eye-front-shadow
bounds: 704, 529, 24, 14
eye-reflex-back
bounds: 860, 128, 8, 7
rotate: 90
eye-reflex-front
bounds: 726, 386, 10, 7
eye-white-back
bounds: 835, 23, 13, 16
eye-white-front
bounds: 1005, 1000, 22, 17
rotate: 90
eyelashes-down-back
bounds: 232, 329, 11, 6
rotate: 90
eyelashes-down-front
bounds: 913, 851, 15, 6
rotate: 90
eyelashes-top-back
bounds: 408, 395, 18, 10
eyelashes-top-front
bounds: 702, 179, 30, 16
rotate: 90
face
bounds: 514, 26, 93, 102
rotate: 90
feathers-back
bounds: 954, 625, 46, 46
feathers-front
bounds: 706, 40, 72, 86
fringe-middle-back
bounds: 200, 6, 33, 52
rotate: 90
fringe-middle-front
bounds: 878, 76, 60, 50
rotate: 90
fringe-side-back
bounds: 780, 41, 27, 94
rotate: 90
fringe-side-front
bounds: 939, 161, 26, 93
glove-bottom-back
bounds: 954, 572, 51, 41
rotate: 90
glove-bottom-front
bounds: 916, 256, 47, 48
hair-back-1
bounds: 444, 395, 132, 306
rotate: 90
hair-back-2
bounds: 438, 211, 80, 285
rotate: 90
hair-back-3
bounds: 719, 306, 70, 268
rotate: 90
hair-back-4
bounds: 438, 121, 88, 262
rotate: 90
hair-back-5
bounds: 438, 293, 88, 279
rotate: 90
hair-back-6
bounds: 200, 41, 88, 286
hair-hat-shadow
bounds: 232, 398, 90, 41
hand-back
bounds: 954, 673, 60, 47
rotate: 90
hand-front
bounds: 967, 172, 53, 60
hat-back
bounds: 954, 802, 64, 45
rotate: 90
hat-front
bounds: 780, 70, 96, 56
head-back
bounds: 618, 17, 102, 86
rotate: 90
jabot
bounds: 967, 234, 70, 55
rotate: 90
leg-back
bounds: 232, 441, 210, 333
leg-front
bounds: 444, 529, 258, 320
logo-brooch
bounds: 954, 545, 16, 25
mouth
bounds: 408, 121, 22, 6
neck
bounds: 232, 342, 39, 56
rotate: 90
nose
bounds: 742, 529, 6, 7
rotate: 90
nose-highlight
bounds: 719, 300, 4, 4
nose-shadow
bounds: 869, 128, 7, 8
pupil-back
bounds: 730, 529, 10, 14
pupil-front
bounds: 254, 21, 12, 18
rope-back
bounds: 232, 383, 10, 492
rotate: 90
rope-front
bounds: 232, 383, 10, 492
rotate: 90
rope-front-bottom
bounds: 954, 735, 42, 65
skirt
bounds: 2, 776, 440, 246
sock-bow
bounds: 408, 407, 33, 32
spine-logo-body
bounds: 879, 853, 13, 32
rotate: 90
star-big
bounds: 939, 141, 18, 24
rotate: 90
star-medium
bounds: 742, 537, 6, 8
rotate: 90
star-small
bounds: 719, 378, 3, 4
rotate: 90
underskirt
bounds: 2, 329, 445, 228
rotate: 90
underskirt-back
bounds: 444, 851, 433, 171
wing-back
bounds: 290, 129, 146, 252
wing-front
bounds: 704, 545, 304, 248
rotate: 90

Binary file not shown.

After

Width:  |  Height:  |  Size: 790 KiB

Binary file not shown.

View File

@ -0,0 +1,112 @@
dragon.png
size: 1024, 1024
filter: Linear, Linear
front-toe-a
bounds: 797, 381, 29, 50
front-toe-b
bounds: 942, 118, 56, 57
head
bounds: 647, 81, 296, 260
rotate: 90
left-front-leg
bounds: 942, 250, 84, 57
rotate: 90
left-front-thigh
bounds: 852, 7, 84, 72
left-wing01
bounds: 736, 433, 264, 589
right-rear-toe
bounds: 647, 2, 109, 77
right-wing01
bounds: 2, 379, 365, 643
right-wing02
bounds: 369, 379, 365, 643
right-wing03
bounds: 2, 12, 365, 643
rotate: 90
tail03
bounds: 758, 6, 73, 92
rotate: 90
tail04
bounds: 942, 177, 56, 71
tail05
bounds: 736, 379, 52, 59
rotate: 90
tail06
bounds: 942, 336, 95, 68
rotate: 90
thiagobrayner
bounds: 909, 81, 350, 31
rotate: 90
dragon_2.png
size: 1024, 1024
filter: Linear, Linear
back
bounds: 795, 32, 190, 185
chin
bounds: 647, 157, 214, 146
rotate: 90
left-rear-leg
bounds: 795, 219, 206, 177
rotate: 90
left-wing02
bounds: 736, 427, 264, 589
right-wing04
bounds: 2, 373, 365, 643
right-wing05
bounds: 369, 373, 365, 643
right-wing06
bounds: 2, 6, 365, 643
rotate: 90
tail01
bounds: 647, 2, 120, 153
dragon_3.png
size: 1024, 1024
filter: Linear, Linear
chest
bounds: 740, 299, 136, 122
left-rear-thigh
bounds: 647, 218, 91, 149
left-wing03
bounds: 736, 423, 264, 589
right-front-leg
bounds: 850, 196, 101, 89
rotate: 90
right-front-thigh
bounds: 740, 189, 108, 108
right-rear-leg
bounds: 878, 321, 116, 100
right-rear-thigh
bounds: 647, 67, 91, 149
right-wing07
bounds: 2, 369, 365, 643
right-wing08
bounds: 369, 369, 365, 643
right-wing09
bounds: 2, 2, 365, 643
rotate: 90
tail02
bounds: 740, 67, 95, 120
dragon_4.png
size: 1024, 1024
filter: Linear, Linear
left-wing04
bounds: 2, 268, 264, 589
left-wing05
bounds: 268, 268, 264, 589
left-wing06
bounds: 534, 268, 264, 589
left-wing07
bounds: 2, 2, 264, 589
rotate: 90
dragon_5.png
size: 1024, 1024
filter: Linear, Linear
left-wing08
bounds: 2, 2, 264, 589
left-wing09
bounds: 268, 2, 264, 589

Binary file not shown.

After

Width:  |  Height:  |  Size: 292 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1,358 @@
mix-and-match.png
size: 1024, 512
filter: Linear, Linear
scale: 0.5
base-head
bounds: 118, 70, 95, 73
boy/arm-front
bounds: 831, 311, 36, 115
rotate: 90
boy/backpack
bounds: 249, 357, 119, 153
boy/backpack-pocket
bounds: 628, 193, 34, 62
rotate: 90
boy/backpack-strap-front
bounds: 330, 263, 38, 88
rotate: 90
boy/backpack-up
bounds: 482, 171, 21, 70
boy/body
bounds: 845, 413, 97, 132
rotate: 90
boy/boot-ribbon-front
bounds: 234, 304, 9, 11
boy/collar
bounds: 471, 243, 73, 29
rotate: 90
boy/ear
bounds: 991, 352, 19, 23
rotate: 90
boy/eye-back-low-eyelid
bounds: 66, 72, 17, 6
boy/eye-back-pupil
bounds: 694, 279, 8, 9
rotate: 90
boy/eye-back-up-eyelid
bounds: 460, 101, 23, 5
rotate: 90
boy/eye-back-up-eyelid-back
bounds: 979, 414, 19, 10
rotate: 90
boy/eye-front-low-eyelid
bounds: 1015, 203, 22, 7
rotate: 90
boy/eye-front-pupil
bounds: 309, 50, 9, 9
boy/eye-front-up-eyelid
bounds: 991, 373, 31, 6
boy/eye-front-up-eyelid-back
bounds: 107, 76, 26, 9
rotate: 90
boy/eye-iris-back
bounds: 810, 260, 17, 17
boy/eye-iris-front
bounds: 902, 230, 18, 18
boy/eye-white-back
bounds: 599, 179, 20, 12
boy/eye-white-front
bounds: 544, 183, 27, 13
boy/eyebrow-back
bounds: 1002, 225, 20, 11
rotate: 90
boy/eyebrow-front
bounds: 975, 234, 25, 11
boy/hair-back
bounds: 629, 289, 122, 81
rotate: 90
boy/hair-bangs
bounds: 505, 180, 70, 37
rotate: 90
boy/hair-side
bounds: 979, 435, 25, 43
rotate: 90
boy/hand-backfingers
bounds: 858, 183, 19, 21
boy/hand-front-fingers
bounds: 879, 183, 19, 21
boy/hat
bounds: 218, 121, 93, 56
boy/leg-front
bounds: 85, 104, 31, 158
boy/mouth-close
bounds: 467, 100, 21, 5
girl-blue-cape/mouth-close
bounds: 467, 100, 21, 5
girl-spring-dress/mouth-close
bounds: 467, 100, 21, 5
girl/mouth-close
bounds: 467, 100, 21, 5
boy/mouth-smile
bounds: 1015, 258, 29, 7
rotate: 90
boy/nose
bounds: 323, 79, 17, 10
boy/pompom
bounds: 979, 462, 48, 43
rotate: 90
boy/zip
bounds: 922, 231, 14, 23
rotate: 90
girl-blue-cape/back-eyebrow
bounds: 527, 106, 18, 12
rotate: 90
girl-blue-cape/body-dress
bounds: 2, 264, 109, 246
girl-blue-cape/body-ribbon
bounds: 576, 193, 50, 38
girl-blue-cape/cape-back
bounds: 113, 317, 134, 193
girl-blue-cape/cape-back-up
bounds: 504, 305, 123, 106
girl-blue-cape/cape-ribbon
bounds: 396, 118, 50, 18
rotate: 90
girl-blue-cape/cape-shoulder-back
bounds: 420, 243, 49, 59
girl-blue-cape/cape-shoulder-front
bounds: 2, 2, 62, 76
girl-blue-cape/cape-up-front
bounds: 118, 145, 98, 117
girl-blue-cape/ear
bounds: 837, 181, 19, 23
girl-spring-dress/ear
bounds: 837, 181, 19, 23
girl/ear
bounds: 837, 181, 19, 23
girl-blue-cape/eye-back-low-eyelid
bounds: 810, 252, 17, 6
girl-spring-dress/eye-back-low-eyelid
bounds: 810, 252, 17, 6
girl/eye-back-low-eyelid
bounds: 810, 252, 17, 6
girl-blue-cape/eye-back-pupil
bounds: 309, 40, 8, 9
rotate: 90
girl-spring-dress/eye-back-pupil
bounds: 309, 40, 8, 9
rotate: 90
girl/eye-back-pupil
bounds: 309, 40, 8, 9
rotate: 90
girl-blue-cape/eye-back-up-eyelid
bounds: 573, 179, 24, 12
girl-spring-dress/eye-back-up-eyelid
bounds: 573, 179, 24, 12
girl/eye-back-up-eyelid
bounds: 573, 179, 24, 12
girl-blue-cape/eye-back-up-eyelid-back
bounds: 380, 105, 17, 11
rotate: 90
girl-spring-dress/eye-back-up-eyelid-back
bounds: 380, 105, 17, 11
rotate: 90
girl/eye-back-up-eyelid-back
bounds: 380, 105, 17, 11
rotate: 90
girl-blue-cape/eye-front-low-eyelid
bounds: 1016, 353, 18, 6
rotate: 90
girl-spring-dress/eye-front-low-eyelid
bounds: 1016, 353, 18, 6
rotate: 90
girl/eye-front-low-eyelid
bounds: 1016, 353, 18, 6
rotate: 90
girl-blue-cape/eye-front-pupil
bounds: 363, 94, 9, 9
girl-spring-dress/eye-front-pupil
bounds: 363, 94, 9, 9
girl/eye-front-pupil
bounds: 363, 94, 9, 9
girl-blue-cape/eye-front-up-eyelid
bounds: 679, 413, 30, 14
rotate: 90
girl-spring-dress/eye-front-up-eyelid
bounds: 679, 413, 30, 14
rotate: 90
girl/eye-front-up-eyelid
bounds: 679, 413, 30, 14
rotate: 90
girl-blue-cape/eye-front-up-eyelid-back
bounds: 947, 234, 26, 11
girl-spring-dress/eye-front-up-eyelid-back
bounds: 947, 234, 26, 11
girl/eye-front-up-eyelid-back
bounds: 947, 234, 26, 11
girl-blue-cape/eye-iris-back
bounds: 323, 105, 17, 17
girl-blue-cape/eye-iris-front
bounds: 467, 107, 18, 18
girl-blue-cape/eye-white-back
bounds: 621, 175, 20, 16
girl-spring-dress/eye-white-back
bounds: 621, 175, 20, 16
girl-blue-cape/eye-white-front
bounds: 643, 175, 20, 16
girl-spring-dress/eye-white-front
bounds: 643, 175, 20, 16
girl/eye-white-front
bounds: 643, 175, 20, 16
girl-blue-cape/front-eyebrow
bounds: 309, 101, 18, 12
rotate: 90
girl-blue-cape/hair-back
bounds: 712, 317, 117, 98
girl-blue-cape/hair-bangs
bounds: 313, 170, 91, 40
rotate: 90
girl-blue-cape/hair-head-side-back
bounds: 544, 198, 30, 52
girl-blue-cape/hair-head-side-front
bounds: 466, 127, 41, 42
girl-blue-cape/hair-side
bounds: 175, 2, 36, 71
rotate: 90
girl-blue-cape/hand-front-fingers
bounds: 902, 207, 19, 21
girl-spring-dress/hand-front-fingers
bounds: 902, 207, 19, 21
girl-blue-cape/leg-front
bounds: 519, 413, 30, 158
rotate: 90
girl-blue-cape/mouth-smile
bounds: 1015, 227, 29, 7
rotate: 90
girl-spring-dress/mouth-smile
bounds: 1015, 227, 29, 7
rotate: 90
girl/mouth-smile
bounds: 1015, 227, 29, 7
rotate: 90
girl-blue-cape/nose
bounds: 342, 82, 11, 7
girl-spring-dress/nose
bounds: 342, 82, 11, 7
girl/nose
bounds: 342, 82, 11, 7
girl-blue-cape/sleeve-back
bounds: 416, 95, 42, 29
girl-blue-cape/sleeve-front
bounds: 249, 303, 52, 119
rotate: 90
girl-spring-dress/arm-front
bounds: 829, 292, 17, 111
rotate: 90
girl-spring-dress/back-eyebrow
bounds: 309, 81, 18, 12
rotate: 90
girl-spring-dress/body-up
bounds: 66, 2, 64, 66
girl-spring-dress/cloak-down
bounds: 758, 227, 50, 50
girl-spring-dress/cloak-up
bounds: 628, 229, 64, 58
girl-spring-dress/eye-iris-back
bounds: 342, 105, 17, 17
girl-spring-dress/eye-iris-front
bounds: 487, 107, 18, 18
girl-spring-dress/front-eyebrow
bounds: 323, 91, 18, 12
girl-spring-dress/hair-back
bounds: 370, 417, 147, 93
girl-spring-dress/hair-bangs
bounds: 829, 250, 91, 40
girl-spring-dress/hair-head-side-back
bounds: 509, 126, 30, 52
girl-spring-dress/hair-head-side-front
bounds: 816, 206, 41, 42
girl-spring-dress/hair-side
bounds: 248, 2, 36, 71
rotate: 90
girl-spring-dress/leg-front
bounds: 831, 381, 30, 158
rotate: 90
girl-spring-dress/neck
bounds: 85, 70, 20, 32
girl-spring-dress/shoulder-ribbon
bounds: 175, 44, 36, 24
girl-spring-dress/skirt
bounds: 2, 80, 182, 81
rotate: 90
girl-spring-dress/underskirt
bounds: 519, 445, 175, 65
girl/arm-front
bounds: 712, 279, 36, 115
rotate: 90
girl/back-eyebrow
bounds: 309, 61, 18, 12
rotate: 90
girl/bag-base
bounds: 694, 219, 62, 58
girl/bag-strap-front
bounds: 370, 304, 12, 96
rotate: 90
girl/bag-top
bounds: 765, 175, 49, 50
girl/body
bounds: 370, 318, 97, 132
rotate: 90
girl/boot-ribbon-front
bounds: 323, 64, 13, 13
girl/eye-iris-back
bounds: 361, 105, 17, 17
girl/eye-iris-front
bounds: 507, 106, 18, 18
girl/eye-white-back
bounds: 665, 175, 20, 16
girl/front-eyebrow
bounds: 343, 91, 18, 12
girl/hair-back
bounds: 696, 417, 147, 93
girl/hair-bangs
bounds: 922, 247, 91, 40
girl/hair-flap-down-front
bounds: 415, 171, 70, 65
rotate: 90
girl/hair-head-side-back
bounds: 991, 381, 30, 52
girl/hair-head-side-front
bounds: 859, 206, 41, 42
girl/hair-patch
bounds: 132, 2, 66, 41
rotate: 90
girl/hair-side
bounds: 692, 181, 36, 71
rotate: 90
girl/hair-strand-back-1
bounds: 948, 289, 58, 74
rotate: 90
girl/hair-strand-back-2
bounds: 355, 170, 91, 58
rotate: 90
girl/hair-strand-back-3
bounds: 215, 40, 92, 79
girl/hair-strand-front-1
bounds: 234, 263, 38, 94
rotate: 90
girl/hair-strand-front-2
bounds: 576, 233, 70, 50
rotate: 90
girl/hair-strand-front-3
bounds: 313, 124, 44, 81
rotate: 90
girl/hand-front-fingers
bounds: 923, 208, 19, 21
girl/hat
bounds: 218, 179, 93, 82
girl/leg-front
bounds: 831, 349, 30, 158
rotate: 90
girl/pompom
bounds: 416, 126, 48, 43
girl/scarf
bounds: 113, 264, 119, 51
girl/scarf-back
bounds: 502, 252, 72, 51
girl/zip
bounds: 816, 179, 19, 25

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@ -0,0 +1,94 @@
spineboy.png
size: 1024, 256
filter: Linear, Linear
scale: 0.5
crosshair
bounds: 352, 7, 45, 45
eye-indifferent
bounds: 862, 105, 47, 45
eye-surprised
bounds: 505, 79, 47, 45
front-bracer
bounds: 826, 66, 29, 40
front-fist-closed
bounds: 786, 65, 38, 41
front-fist-open
bounds: 710, 51, 43, 44
rotate: 90
front-foot
bounds: 210, 6, 63, 35
front-shin
bounds: 665, 128, 41, 92
rotate: 90
front-thigh
bounds: 2, 2, 23, 56
rotate: 90
front-upper-arm
bounds: 250, 205, 23, 49
goggles
bounds: 665, 171, 131, 83
gun
bounds: 798, 152, 105, 102
head
bounds: 2, 27, 136, 149
hoverboard-board
bounds: 2, 178, 246, 76
hoverboard-thruster
bounds: 722, 96, 30, 32
rotate: 90
hoverglow-small
bounds: 275, 81, 137, 38
mouth-grind
bounds: 614, 97, 47, 30
mouth-oooo
bounds: 612, 65, 47, 30
mouth-smile
bounds: 661, 64, 47, 30
muzzle-glow
bounds: 382, 54, 25, 25
muzzle-ring
bounds: 275, 54, 25, 105
rotate: 90
muzzle01
bounds: 911, 95, 67, 40
rotate: 90
muzzle02
bounds: 792, 108, 68, 42
muzzle03
bounds: 956, 171, 83, 53
rotate: 90
muzzle04
bounds: 275, 7, 75, 45
muzzle05
bounds: 140, 3, 68, 38
neck
bounds: 250, 182, 18, 21
portal-bg
bounds: 140, 43, 133, 133
portal-flare1
bounds: 554, 65, 56, 30
portal-flare2
bounds: 759, 112, 57, 31
rotate: 90
portal-flare3
bounds: 554, 97, 58, 30
portal-shade
bounds: 275, 121, 133, 133
portal-streaks1
bounds: 410, 126, 126, 128
portal-streaks2
bounds: 538, 129, 125, 125
rear-bracer
bounds: 857, 67, 28, 36
rear-foot
bounds: 663, 96, 57, 30
rear-shin
bounds: 414, 86, 38, 89
rotate: 90
rear-thigh
bounds: 756, 63, 28, 47
rear-upper-arm
bounds: 60, 5, 20, 44
rotate: 90
torso
bounds: 905, 164, 49, 90

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 KiB

View File

@ -0,0 +1,174 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software or
* otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
package com.esotericsoftware.spine
import android.util.Log
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.ArrowBack
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.viewinterop.AndroidView
import androidx.navigation.NavHostController
import com.badlogic.gdx.graphics.Color
import com.esotericsoftware.spine.android.SpineController
import com.esotericsoftware.spine.android.SpineView
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AnimationState(nav: NavHostController) {
val TAG = "AnimationState"
val controller = remember {
SpineController { controller ->
controller.skeleton.setScaleX(0.5f)
controller.skeleton.setScaleY(0.5f)
controller.skeleton.findSlot("gun")?.color?.set(Color(1f, 0f, 0f, 1f))
controller.animationStateData.setDefaultMix(0.2f)
controller.animationState.setAnimation(0, "walk", true).setListener(object : AnimationState.AnimationStateListener {
override fun start(entry: AnimationState.TrackEntry?) {
Log.d(TAG, "Walk animation event start")
}
override fun interrupt(entry: AnimationState.TrackEntry?) {
Log.d(TAG, "Walk animation event interrupt")
}
override fun end(entry: AnimationState.TrackEntry?) {
Log.d(TAG, "Walk animation event end")
}
override fun dispose(entry: AnimationState.TrackEntry?) {
Log.d(TAG, "Walk animation event dispose")
}
override fun complete(entry: AnimationState.TrackEntry?) {
Log.d(TAG, "Walk animation event complete")
}
override fun event(entry: AnimationState.TrackEntry?, event: Event?) {
Log.d(TAG, "Walk animation event event")
}
})
controller.animationState.addAnimation(0, "jump", false, 2f)
controller.animationState.addAnimation(0, "run", true, 0f).setListener(object : AnimationState.AnimationStateListener {
override fun start(entry: AnimationState.TrackEntry?) {
Log.d(TAG, "Run animation event start")
}
override fun interrupt(entry: AnimationState.TrackEntry?) {
Log.d(TAG, "Run animation event interrupt")
}
override fun end(entry: AnimationState.TrackEntry?) {
Log.d(TAG, "Run animation event end")
}
override fun dispose(entry: AnimationState.TrackEntry?) {
Log.d(TAG, "Run animation event dispose")
}
override fun complete(entry: AnimationState.TrackEntry?) {
Log.d(TAG, "Run animation event complete")
}
override fun event(entry: AnimationState.TrackEntry?, event: Event?) {
Log.d(TAG, "Run animation event event")
}
})
controller.animationState.addListener(object : AnimationState.AnimationStateListener {
override fun start(entry: AnimationState.TrackEntry?) {}
override fun interrupt(entry: AnimationState.TrackEntry?) {}
override fun end(entry: AnimationState.TrackEntry?) {}
override fun dispose(entry: AnimationState.TrackEntry?) {}
override fun complete(entry: AnimationState.TrackEntry?) {}
override fun event(entry: AnimationState.TrackEntry?, event: Event?) {
if (event != null) {
Log.d(TAG, "User event: { name: ${event.data.name}, intValue: ${event.int}, floatValue: ${event.float}, stringValue: ${event.string} }")
}
}
})
Log.d(TAG, "Current: ${controller.animationState.getCurrent(0)?.getAnimation()?.getName()}");
}
}
Scaffold(
topBar = {
TopAppBar(
title = { Text(text = Destination.AnimationStateEvents.title) },
navigationIcon = {
IconButton({ nav.navigateUp() }) {
Icon(
Icons.Rounded.ArrowBack,
null,
)
}
}
)
}
) { paddingValues ->
Column(
modifier = Modifier.padding(paddingValues),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text("See output in console!")
AndroidView(
factory = { context ->
SpineView.loadFromAssets(
"spineboy.atlas",
"spineboy-pro.json",
context,
controller
)
}
)
}
}
}

View File

@ -0,0 +1,91 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software or
* otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
package com.esotericsoftware.spine
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.ArrowBack
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.viewinterop.AndroidView
import androidx.navigation.NavHostController
import com.esotericsoftware.spine.android.DebugRenderer
import com.esotericsoftware.spine.android.SpineController
import com.esotericsoftware.spine.android.SpineView
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DebugRendering(nav: NavHostController) {
val debugRenderer = remember {
DebugRenderer()
}
Scaffold(
topBar = {
TopAppBar(
title = { Text(text = Destination.DebugRendering.title) },
navigationIcon = {
IconButton({ nav.navigateUp() }) {
Icon(
Icons.Rounded.ArrowBack,
null,
)
}
}
)
}
) { paddingValues ->
AndroidView(
factory = { context ->
SpineView.loadFromAssets(
"spineboy.atlas",
"spineboy-pro.json",
context,
SpineController.Builder { controller ->
controller.animationState.setAnimation(0, "walk", true)
}
.setOnAfterPaint { controller, canvas, commands ->
debugRenderer.render(controller.drawable, canvas, commands)
}
.build()
)
},
modifier = Modifier.padding(paddingValues)
)
}
}

View File

@ -0,0 +1,237 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software or
* otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
package com.esotericsoftware.spine
import androidx.compose.foundation.gestures.detectDragGestures
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.ArrowBack
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.positionInParent
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.toSize
import androidx.compose.ui.viewinterop.AndroidView
import androidx.navigation.NavHostController
import com.esotericsoftware.spine.android.AndroidSkeletonDrawable
import com.esotericsoftware.spine.android.AndroidTextureAtlas
import com.esotericsoftware.spine.android.SpineController
import com.esotericsoftware.spine.android.SpineView
import com.esotericsoftware.spine.android.utils.SkeletonDataUtils
import kotlin.random.Random
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DisableRendering(nav: NavHostController) {
Scaffold(
topBar = {
TopAppBar(
title = { Text(text = Destination.DisableRendering.title) },
navigationIcon = {
IconButton({ nav.navigateUp() }) {
Icon(
Icons.Rounded.ArrowBack,
null,
)
}
}
)
}
) { paddingValues ->
val visibleSpineBoys = remember {
mutableStateListOf<Int>()
}
Column(
modifier = Modifier
.padding(paddingValues)
.padding()
.onGloballyPositioned { coordinates ->
print(coordinates.size.toSize())
}
) {
Column(
modifier = Modifier
.padding(8.dp)
) {
Text("There are ${visibleSpineBoys.count()} spine boys visible. Scroll around to find the odd one out...")
Text("Rendering is disabled when the spine view moves out of the viewport, preserving CPU/GPU resources.", color = Color.Gray)
}
SpineBoys(visibleSpineBoys)
}
}
}
@Composable
fun SpineBoys(visibleSpineBoys: MutableList<Int>) {
var boxSize by remember { mutableStateOf(Size.Zero) }
val offsetX = remember { mutableFloatStateOf(0f) }
val offsetY = remember { mutableFloatStateOf(0f) }
Box(
modifier = Modifier
.fillMaxSize()
.clipToBounds()
.onGloballyPositioned { coordinates ->
boxSize = coordinates.size.toSize()
}
.pointerInput(Unit) {
detectDragGestures { change, dragAmount ->
change.consume()
offsetX.floatValue += dragAmount.x
offsetY.floatValue += dragAmount.y
}
}
) {
if (boxSize != Size.Zero) {
val contentSize = boxSize * 4f
val context = LocalContext.current
val cachedAtlas =
remember { AndroidTextureAtlas.fromAsset("spineboy.atlas", context) }
val cachedSkeletonData = remember {
SkeletonDataUtils.fromAsset(
cachedAtlas,
"spineboy-pro.json",
context
)
}
val spineboys = remember {
val rng = Random(System.currentTimeMillis())
List(100) { index ->
val scale = 0.1f + rng.nextFloat() * 0.2f
val position = Offset(
rng.nextFloat() * contentSize.width,
rng.nextFloat() * contentSize.height
)
SpineBoyData(
index,
scale,
position,
if (index == 99) "hoverboard" else "walk"
)
}
}
spineboys.forEach { spineBoyData ->
val isSpineBoyVisible = remember { mutableStateOf(false) }
Box(modifier = Modifier
.offset {
IntOffset(
(-(contentSize.width / 2) + spineBoyData.position.x + offsetX.floatValue.toInt()).toInt(),
(-(contentSize.height / 2) + spineBoyData.position.y + offsetY.floatValue.toInt()).toInt(),
)
}
.size(
(boxSize.width * spineBoyData.scale).dp,
(boxSize.height * spineBoyData.scale).dp
)
.onGloballyPositioned { coordinates ->
val positionInRoot = coordinates.positionInParent()
val size = coordinates.size.toSize()
val isInViewport = positionInRoot.x < boxSize.width &&
positionInRoot.x + size.width > 0 &&
positionInRoot.y < boxSize.height &&
positionInRoot.y + size.height > 0
isSpineBoyVisible.value = isInViewport
val visibleSpineBoysAsSet = visibleSpineBoys.toMutableSet()
if (isInViewport) {
visibleSpineBoysAsSet.add(spineBoyData.id)
} else {
visibleSpineBoysAsSet.remove(spineBoyData.id)
}
visibleSpineBoys.clear()
visibleSpineBoys.addAll(visibleSpineBoysAsSet)
}
) {
AndroidView(
factory = { ctx ->
SpineView.loadFromDrawable(
AndroidSkeletonDrawable(cachedAtlas, cachedSkeletonData),
ctx,
SpineController {
it.animationState.setAnimation(
0,
spineBoyData.animation,
true
)
}
).apply {
isRendering = false
}
},
update = { view ->
view.isRendering = isSpineBoyVisible.value
}
)
}
}
}
}
}
data class SpineBoyData(
val id: Int,
val scale: Float,
val position: Offset,
val animation: String
)

View File

@ -0,0 +1,228 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software or
* otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
package com.esotericsoftware.spine
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.ArrowBack
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.draw.drawWithCache
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.ColorMatrix
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.Paint
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.navigation.NavHostController
import com.esotericsoftware.spine.android.AndroidSkeletonDrawable
import com.esotericsoftware.spine.android.SkeletonRenderer
import com.esotericsoftware.spine.android.SpineController
import com.esotericsoftware.spine.android.SpineView
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DressUp(nav: NavHostController) {
val context = LocalContext.current
val thumbnailSize = 150f
val drawable = remember {
AndroidSkeletonDrawable.fromAsset(
"mix-and-match.atlas",
"mix-and-match-pro.skel",
context
)
}
val renderer = remember {
SkeletonRenderer()
}
val customSkin = remember {
mutableStateOf<Skin?>(null)
}
val skinImages = remember {
mutableStateMapOf<String, ImageBitmap>()
}
val selectedSkins = remember {
mutableStateMapOf<String, Boolean>()
}
val controller = remember {
SpineController { controller ->
controller.animationState.setAnimation(0, "dance", true)
}
}
fun toggleSkin(skinName: String) {
selectedSkins[skinName] = !(selectedSkins[skinName] ?: false)
drawable.skeleton.setSkin("default")
customSkin.value = Skin("custom-skin");
for (selectedSkinKey in selectedSkins.keys) {
if (selectedSkins[selectedSkinKey] == true) {
val selectedSkin = drawable.skeletonData.findSkin(selectedSkinKey)
if (selectedSkin != null) customSkin.value?.addSkin(selectedSkin)
}
}
val customSkinValue = customSkin.value
if (customSkinValue != null) {
drawable.skeleton.setSkin(customSkinValue)
}
drawable.skeleton.setSlotsToSetupPose()
}
val localDensity = LocalDensity.current
LaunchedEffect(Unit) {
for (skin in drawable.skeletonData.getSkins()) {
if (skin.getName() == "default") continue
val skeleton = drawable.skeleton
skeleton.setSkin(skin)
skeleton.setToSetupPose()
skeleton.update(0f)
skeleton.updateWorldTransform(Skeleton.Physics.update)
skinImages[skin.getName()] = renderer.renderToBitmap(
with(localDensity) { thumbnailSize.dp.toPx() },
with(localDensity) { thumbnailSize.dp.toPx() },
0xffffffff.toInt(),
skeleton,
).asImageBitmap()
selectedSkins[skin.getName()] = false
}
toggleSkin("full-skins/girl");
}
Scaffold(
topBar = {
TopAppBar(
title = { Text(text = Destination.DressUp.title) },
navigationIcon = {
IconButton({ nav.navigateUp() }) {
Icon(
Icons.Rounded.ArrowBack,
null,
)
}
}
)
}
) { paddingValues ->
Row(
modifier = Modifier
.padding(paddingValues)
) {
Column(
modifier = Modifier
.width(thumbnailSize.dp)
.verticalScroll(rememberScrollState())
) {
skinImages.keys.forEach { skinName ->
Box(modifier = Modifier
.clickable {
toggleSkin(skinName)
}
.then(
if (selectedSkins[skinName] == true) {
Modifier
} else {
Modifier.grayScale()
}
)
) {
Image(
painter = BitmapPainter(skinImages[skinName]!!),
contentDescription = null
)
}
}
}
Column(
modifier = Modifier
.clipToBounds()
) {
AndroidView(
factory = { context ->
SpineView.loadFromDrawable(drawable, context, controller)
}
)
}
}
}
}
fun Modifier.grayScale(): Modifier {
val saturationMatrix = ColorMatrix().apply { setToSaturation(0f) }
val saturationFilter = ColorFilter.colorMatrix(saturationMatrix)
val paint = Paint().apply { colorFilter = saturationFilter }
return drawWithCache {
val canvasBounds = Rect(Offset.Zero, size)
onDrawWithContent {
drawIntoCanvas {
it.saveLayer(canvasBounds, paint)
drawContent()
it.restore()
}
}
}
}

View File

@ -0,0 +1,140 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software or
* otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
package com.esotericsoftware.spine
import android.graphics.Point
import androidx.compose.foundation.gestures.detectDragGestures
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.ArrowBack
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.viewinterop.AndroidView
import androidx.navigation.NavHostController
import com.badlogic.gdx.math.Vector2
import com.esotericsoftware.spine.android.SpineController
import com.esotericsoftware.spine.android.SpineView
import com.esotericsoftware.spine.android.bounds.Alignment
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun IKFollowing(nav: NavHostController) {
val containerHeight = remember { mutableIntStateOf(0) }
val dragPosition = remember { mutableStateOf(Point(0, 0)) }
val crossHairPosition = remember { mutableStateOf<Point?>(null) }
val controller = remember {
SpineController.Builder { controller ->
controller.animationState.setAnimation(0, "walk", true)
controller.animationState.setAnimation(1, "aim", true)
}
.setOnAfterUpdateWorldTransforms {
val worldPosition = crossHairPosition.value ?: return@setOnAfterUpdateWorldTransforms
val skeleton = it.skeleton
val bone = skeleton.findBone("crosshair") ?: return@setOnAfterUpdateWorldTransforms
val parent = bone.parent ?: return@setOnAfterUpdateWorldTransforms
val position = parent.worldToLocal(Vector2(worldPosition.x.toFloat(), worldPosition.y.toFloat()))
bone.x = position.x
bone.y = position.y
}
.build()
}
Scaffold(
topBar = {
TopAppBar(
title = { Text(text = Destination.IKFollowing.title) },
navigationIcon = {
IconButton({ nav.navigateUp() }) {
Icon(
Icons.Rounded.ArrowBack,
null,
)
}
}
)
}
) { paddingValues ->
Box(modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
.onGloballyPositioned { coordinates ->
containerHeight.intValue = coordinates.size.height
}
.pointerInput(Unit) {
detectDragGestures(
onDragStart = { offset ->
dragPosition.value = Point(offset.x.toInt(), offset.y.toInt())
},
onDrag = { _, dragAmount ->
dragPosition.value = Point(
(dragPosition.value.x + dragAmount.x).toInt(),
(dragPosition.value.y + dragAmount.y).toInt()
)
val invertedYDragPosition = Point(
dragPosition.value.x,
containerHeight.intValue - dragPosition.value.y,
)
crossHairPosition.value = controller.toSkeletonCoordinates(
invertedYDragPosition
)
},
)
}
) {
AndroidView(
factory = { context ->
SpineView.loadFromAssets(
"spineboy.atlas",
"spineboy-pro.json",
context,
controller
).apply {
alignment = Alignment.CENTER_LEFT
}
}
)
}
}
}

View File

@ -0,0 +1,220 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software or
* otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
package com.esotericsoftware.spine
import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.Card
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.esotericsoftware.spine.ui.theme.SpineAndroidExamplesTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
AppContent()
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AppContent() {
val navController = rememberNavController()
SpineAndroidExamplesTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
NavHost(
navController = navController,
startDestination = Destination.Samples.route
) {
composable(
Destination.Samples.route
) {
Scaffold(
topBar = { TopAppBar(title = { Text(text = Destination.Samples.title) }) }
) { paddingValues ->
Samples(
navController,
listOf(
Destination.SimpleAnimation,
Destination.PlayPause,
Destination.AnimationStateEvents,
Destination.DebugRendering,
Destination.DressUp,
Destination.IKFollowing,
Destination.Physics,
Destination.DisableRendering
),
paddingValues
)
}
}
composable(
Destination.SimpleAnimation.route
) {
SimpleAnimation(navController)
}
composable(
Destination.PlayPause.route
) {
PlayPause(navController)
}
composable(
Destination.AnimationStateEvents.route
) {
AnimationState(navController)
}
composable(
Destination.DebugRendering.route
) {
DebugRendering(navController)
}
composable(
Destination.DressUp.route
) {
DressUp(navController)
}
composable(
Destination.IKFollowing.route
) {
IKFollowing(navController)
}
composable(
Destination.Physics.route
) {
Physics(navController)
}
composable(
Destination.DisableRendering.route
) {
DisableRendering(navController)
}
}
}
}
}
@Composable
fun Samples(
nav: NavHostController,
samples: List<Destination>,
paddingValues: PaddingValues
) {
LazyColumn(
verticalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier
.padding(8.dp)
.padding(paddingValues)
) {
item {
Text(text = "Kotlin + Jetpack Compose", Modifier.padding(8.dp))
}
samples.forEach {
item {
Card(
Modifier
.fillMaxWidth()
.clickable(onClick = { nav.navigate(it.route) }),
shape = MaterialTheme.shapes.large
) {
Text(text = it.title, Modifier.padding(24.dp))
}
}
}
item {
Text(text = "Java + XML", Modifier.padding(8.dp))
}
item {
Card(
Modifier
.fillMaxWidth()
.clickable(onClick = {
nav.context.startActivity(
Intent(
nav.context,
SimpleAnimationActivity::class.java
)
)
}),
shape = MaterialTheme.shapes.large
) {
Text(text = "Simple Animation", Modifier.padding(24.dp))
}
}
}
}
sealed class Destination(val route: String, val title: String) {
data object Samples: Destination("samples", "Spine Android Examples")
data object SimpleAnimation : Destination("simpleAnimation", "Simple Animation")
data object PlayPause : Destination("playPause", "Play/Pause")
data object DebugRendering: Destination("debugRendering", "Debug Renderer")
data object AnimationStateEvents : Destination("animationStateEvents", "Animation State Listener")
data object DressUp : Destination("dressUp", "Dress Up")
data object IKFollowing : Destination("ikFollowing", "IK Following")
data object Physics: Destination("physics", "Physics (drag anywhere)")
data object DisableRendering: Destination("disableRendering", "Disable Rendering")
}

View File

@ -0,0 +1,153 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software or
* otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
package com.esotericsoftware.spine
import android.graphics.Point
import androidx.compose.foundation.gestures.detectDragGestures
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.ArrowBack
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.viewinterop.AndroidView
import androidx.navigation.NavHostController
import com.esotericsoftware.spine.android.SpineController
import com.esotericsoftware.spine.android.SpineView
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun Physics(nav: NavHostController) {
val containerHeight = remember { mutableIntStateOf(0) }
val dragPosition = remember { mutableStateOf(Point(0, 0)) }
val mousePosition = remember { mutableStateOf<Point?>(null) }
val lastMousePosition = remember { mutableStateOf<Point?>(null) }
val controller = remember {
SpineController.Builder { controller ->
controller.animationState.setAnimation(0, "eyeblink-long", true)
controller.animationState.setAnimation(1, "wings-and-feet", true)
}
.setOnAfterUpdateWorldTransforms { controller ->
val lastMousePositionValue = lastMousePosition.value
if (lastMousePositionValue == null) {
lastMousePosition.value = mousePosition.value
return@setOnAfterUpdateWorldTransforms
}
val mousePositionValue = mousePosition.value ?: return@setOnAfterUpdateWorldTransforms
val dx = mousePositionValue.x - lastMousePositionValue.x
val dy = mousePositionValue.y - lastMousePositionValue.y
val position = Point(
controller.skeleton.x.toInt(),
controller.skeleton.y.toInt()
)
position.x += dx
position.y += dy
controller.skeleton.setPosition(position.x.toFloat(), position.y.toFloat());
lastMousePosition.value = mousePositionValue
}
.build()
}
Scaffold(
topBar = {
TopAppBar(
title = { Text(text = Destination.Physics.title) },
navigationIcon = {
IconButton({ nav.navigateUp() }) {
Icon(
Icons.Rounded.ArrowBack,
null,
)
}
}
)
}
) { paddingValues ->
Box(modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
.onGloballyPositioned { coordinates ->
containerHeight.intValue = coordinates.size.height
}
.pointerInput(Unit) {
detectDragGestures(
onDragStart = { offset ->
dragPosition.value = Point(offset.x.toInt(), offset.y.toInt())
},
onDrag = { _, dragAmount ->
dragPosition.value = Point(
(dragPosition.value.x + dragAmount.x).toInt(),
(dragPosition.value.y + dragAmount.y).toInt()
)
val invertedYDragPosition = Point(
dragPosition.value.x,
containerHeight.intValue - dragPosition.value.y,
)
mousePosition.value = controller.toSkeletonCoordinates(
invertedYDragPosition
)
},
onDragEnd = { ->
mousePosition.value = null;
lastMousePosition.value = null;
}
)
}
) {
AndroidView(
factory = { context ->
SpineView.loadFromAssets(
"celestial-circus.atlas",
"celestial-circus-pro.skel",
context,
controller
)
},
modifier = Modifier.padding(paddingValues)
)
}
}
}

View File

@ -0,0 +1,99 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software or
* otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
package com.esotericsoftware.spine
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.ArrowBack
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.viewinterop.AndroidView
import androidx.navigation.NavHostController
import com.esotericsoftware.spine.android.SpineController
import com.esotericsoftware.spine.android.SpineView
import com.esotericsoftware.spine.android.bounds.SkinAndAnimationBounds
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun PlayPause(
nav: NavHostController
) {
val controller = remember {
SpineController { controller ->
controller.animationState.setAnimation(0, "flying", true)
}
}
val isPlaying = remember { mutableStateOf(controller.isPlaying) }
Scaffold(
topBar = {
TopAppBar(
title = { Text(text = Destination.PlayPause.title) },
navigationIcon = {
IconButton({ nav.navigateUp() }) {
Icon(
Icons.Rounded.ArrowBack,
null,
)
}
},
actions = {
Button(onClick = {
if (controller.isPlaying) controller.pause() else controller.resume()
isPlaying.value = controller.isPlaying
}) {
Text(text = if (isPlaying.value) "Pause" else "Play")
}
}
)
}
) { paddingValues ->
AndroidView(
factory = { ctx ->
SpineView.Builder(ctx, controller)
.setLoadFromAssets("dragon.atlas", "dragon-ess.skel")
.setBoundsProvider(SkinAndAnimationBounds("flying"))
.build()
},
modifier = Modifier.padding(paddingValues)
)
}
}

View File

@ -0,0 +1,91 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software or
* otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
package com.esotericsoftware.spine
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.ArrowBack
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.viewinterop.AndroidView
import androidx.navigation.NavHostController
import com.esotericsoftware.spine.android.SpineController
import com.esotericsoftware.spine.android.SpineView
import java.io.File
import java.net.URL
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SimpleAnimation(nav: NavHostController) {
Scaffold(
topBar = {
TopAppBar(
title = { Text(text = Destination.SimpleAnimation.title) },
navigationIcon = {
IconButton({ nav.navigateUp() }) {
Icon(
Icons.Rounded.ArrowBack,
null,
)
}
}
)
}
) { paddingValues ->
AndroidView(
factory = { context ->
SpineView.loadFromAssets(
"spineboy.atlas",
"spineboy-pro.json",
context,
SpineController {
it.animationState.setAnimation(0, "walk", true)
}
)
// SpineView.loadFromHttp(
// URL("https://raw.githubusercontent.com/EsotericSoftware/spine-runtimes/4.2/examples/spineboy/export/spineboy.atlas"),
// URL("https://raw.githubusercontent.com/EsotericSoftware/spine-runtimes/4.2/examples/spineboy/export/spineboy-pro.skel"),
// context.filesDir,
// context,
// SpineController {
// it.animationState.setAnimation(0, "walk", true)
// }
// )
},
modifier = Modifier.padding(paddingValues)
)
}
}

View File

@ -0,0 +1,76 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software or
* otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
package com.esotericsoftware.spine;
import android.os.Bundle;
import android.view.MenuItem;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import com.esotericsoftware.spine.android.SpineController;
import com.esotericsoftware.spine.android.SpineView;
public class SimpleAnimationActivity extends AppCompatActivity {
/** @noinspection FieldCanBeLocal */
private SpineView spineView;
/** @noinspection FieldCanBeLocal */
private SpineController spineController;
@Override
protected void onCreate (Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_simple_animation);
// Set up the toolbar
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
if (getSupportActionBar() != null) {
getSupportActionBar().setTitle("Simple Animation");
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
}
spineView = findViewById(R.id.spineView);
spineController = new SpineController(controller -> controller.getAnimationState().setAnimation(0, "walk", true));
spineView.setController(spineController);
spineView.loadFromAsset("spineboy.atlas", "spineboy-pro.json");
}
@Override
public boolean onOptionsItemSelected (MenuItem item) {
if (item.getItemId() == android.R.id.home) {
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
}

View File

@ -0,0 +1,40 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software or
* otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
package com.esotericsoftware.spine.ui.theme
import androidx.compose.ui.graphics.Color
val Purple80 = Color(0xFFD0BCFF)
val PurpleGrey80 = Color(0xFFCCC2DC)
val Pink80 = Color(0xFFEFB8C8)
val Purple40 = Color(0xFF6650a4)
val PurpleGrey40 = Color(0xFF625b71)
val Pink40 = Color(0xFF7D5260)

View File

@ -0,0 +1,99 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software or
* otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
package com.esotericsoftware.spine.ui.theme
import android.app.Activity
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.core.view.WindowCompat
private val DarkColorScheme = darkColorScheme(
primary = Purple80,
secondary = PurpleGrey80,
tertiary = Pink80
)
private val LightColorScheme = lightColorScheme(
primary = Purple40,
secondary = PurpleGrey40,
tertiary = Pink40
/* Other default colors to override
background = Color(0xFFFFFBFE),
surface = Color(0xFFFFFBFE),
onPrimary = Color.White,
onSecondary = Color.White,
onTertiary = Color.White,
onBackground = Color(0xFF1C1B1F),
onSurface = Color(0xFF1C1B1F),
*/
)
@Composable
fun SpineAndroidExamplesTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+
dynamicColor: Boolean = true,
content: @Composable () -> Unit
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
darkTheme -> DarkColorScheme
else -> LightColorScheme
}
val view = LocalView.current
if (!view.isInEditMode) {
SideEffect {
val window = (view.context as Activity).window
window.statusBarColor = colorScheme.primary.toArgb()
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
}
}
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content
)
}

View File

@ -0,0 +1,63 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software or
* otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
package com.esotericsoftware.spine.ui.theme
import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
// Set of Material typography styles to start with
val Typography = Typography(
bodyLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.5.sp
)
/* Other default text styles to override
titleLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 22.sp,
lineHeight = 28.sp,
letterSpacing = 0.sp
),
labelSmall = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Medium,
fontSize = 11.sp,
lineHeight = 16.sp,
letterSpacing = 0.5.sp
)
*/
)

View File

@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>

View File

@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 637 KiB

View File

@ -0,0 +1,21 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".SimpleAnimationActivity">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:theme="@style/ThemeOverlay.AppCompat.ActionBar"
android:elevation="4dp"
android:layout_alignParentTop="true" />
<com.esotericsoftware.spine.android.SpineView
android:id="@+id/spineView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/toolbar" />
</RelativeLayout>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources>

View File

@ -0,0 +1,3 @@
<resources>
<string name="app_name">Spine Android Examples</string>
</resources>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.SpineAndroidExamples" parent="android:Theme.Material.Light.NoActionBar" />
</resources>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?><!--
Sample backup rules file; uncomment and customize as necessary.
See https://developer.android.com/guide/topics/data/autobackup
for details.
Note: This file is ignored for devices older that API 31
See https://developer.android.com/about/versions/12/backup-restore
-->
<full-backup-content>
<!--
<include domain="sharedpref" path="."/>
<exclude domain="sharedpref" path="device.xml"/>
-->
</full-backup-content>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?><!--
Sample data extraction rules file; uncomment and customize as necessary.
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
for details.
-->
<data-extraction-rules>
<cloud-backup>
<!-- TODO: Use <include> and <exclude> to control what is backed up.
<include .../>
<exclude .../>
-->
</cloud-backup>
<!--
<device-transfer>
<include .../>
<exclude .../>
</device-transfer>
-->
</data-extraction-rules>

View File

@ -0,0 +1,17 @@
package com.esotericsoftware.android
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}

View File

@ -0,0 +1,6 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
alias(libs.plugins.androidApplication) apply false
alias(libs.plugins.jetbrainsKotlinAndroid) apply false
alias(libs.plugins.androidLibrary) apply false
}

View File

@ -0,0 +1,23 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. For more details, visit
# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
# Enables namespacing of each library's R class so that its R class includes only the
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true

View File

@ -0,0 +1,38 @@
[versions]
agp = "8.3.1"
kotlin = "1.9.0"
coreKtx = "1.10.1"
junit = "4.13.2"
junitVersion = "1.1.5"
espressoCore = "3.5.1"
lifecycleRuntimeKtx = "2.6.1"
activityCompose = "1.7.0"
composeBom = "2023.08.00"
appcompat = "1.6.1"
navigationCompose = "2.7.7"
appcompatVersion = "1.7.0"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
androidx-ui = { group = "androidx.compose.ui", name = "ui" }
androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigationCompose" }
appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompatVersion" }
[plugins]
androidApplication = { id = "com.android.application", version.ref = "agp" }
jetbrainsKotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
androidLibrary = { id = "com.android.library", version.ref = "agp" }

Binary file not shown.

View File

@ -0,0 +1,6 @@
#Thu Apr 25 11:12:13 CEST 2024
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

185
spine-android/gradlew vendored Executable file
View File

@ -0,0 +1,185 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"

89
spine-android/gradlew.bat vendored Normal file
View File

@ -0,0 +1,89 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

14
spine-android/publish.sh Executable file
View File

@ -0,0 +1,14 @@
#!/bin/sh
#
# 1. Set up PGP key for signing
# 2. Create ~/.gradle/gradle.properties
# 3. Add
# ossrhUsername=<sonatype-token-user-name>
# ossrhPassword=<sonatype-token>
# signing.gnupg.passphrase=<pgp-key-passphrase>
#
# After publishing via this script, log into https://oss.sonatype.org and release it manually after
# checks pass ("Release & Drop").
set -e
./gradlew publishReleasePublicationToSonaTypeRepository --info

View File

@ -0,0 +1,38 @@
pluginManagement {
repositories {
google {
content {
includeGroupByRegex("com\\.android.*")
includeGroupByRegex("com\\.google.*")
includeGroupByRegex("androidx.*")
}
}
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven {
url = uri("https://oss.sonatype.org/content/repositories/snapshots")
}
mavenLocal()
}
}
rootProject.name = "Spine Android Examples"
includeBuild("../spine-libgdx") {
dependencySubstitution {
substitute(module("com.esotericsoftware.spine:spine-libgdx")).using(project(":spine-libgdx"))
}
}
//includeBuild("../../libgdx") {
// dependencySubstitution {
// substitute(module("com.badlogicgames.gdx:gdx")).using(project(":gdx"))
// }
//}
include(":app")
include(":spine-android")

View File

@ -0,0 +1 @@
/build

View File

@ -0,0 +1,134 @@
plugins {
alias(libs.plugins.androidLibrary)
`maven-publish`
signing
}
android {
namespace = "com.esotericsoftware.spine"
compileSdk = 34
defaultConfig {
minSdk = 23
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
}
dependencies {
implementation(libs.androidx.appcompat)
api("com.badlogicgames.gdx:gdx:1.12.2-SNAPSHOT")
api("com.esotericsoftware.spine:spine-libgdx:4.2.7")
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
}
val libraryVersion = "4.2.8-SNAPSHOT";
tasks.register<Jar>("sourceJar") {
archiveClassifier.set("sources")
from(android.sourceSets["main"].java.srcDirs)
}
afterEvaluate {
publishing {
publications {
create<MavenPublication>("release") {
artifact(tasks.getByName("bundleReleaseAar"))
artifact(tasks.getByName("sourceJar"))
groupId = "com.esotericsoftware.spine"
artifactId = "spine-android"
version = libraryVersion
pom {
packaging = "aar"
name.set("spine-android")
description.set("Spine Runtime for Android")
url.set("https://github.com/esotericsoftware/spine-runtimes")
licenses {
license {
name.set("Spine Runtimes License")
url.set("http://esotericsoftware.com/spine-runtimes-license")
}
}
developers {
developer {
name.set("Esoteric Software")
email.set("contact@esotericsoftware.com")
}
}
scm {
url.set(pom.url.get())
connection.set("scm:git:${url.get()}.git")
developerConnection.set("scm:git:${url.get()}.git")
}
withXml {
val dependenciesNode = asNode().appendNode("dependencies")
configurations.api.get().dependencies.forEach { dependency ->
dependenciesNode.appendNode("dependency").apply {
appendNode("groupId", dependency.group)
appendNode("artifactId", dependency.name)
appendNode("version", dependency.version)
appendNode("scope", "compile")
}
}
configurations.implementation.get().dependencies.forEach { dependency ->
dependenciesNode.appendNode("dependency").apply {
appendNode("groupId", dependency.group)
appendNode("artifactId", dependency.name)
appendNode("version", dependency.version)
appendNode("scope", "runtime")
}
}
}
}
}
}
repositories {
maven {
name = "SonaType"
url = uri(if (libraryVersion.endsWith("-SNAPSHOT")) {
"https://oss.sonatype.org/content/repositories/snapshots"
} else {
"https://oss.sonatype.org/service/local/staging/deploy/maven2"
})
credentials {
username = project.findProperty("ossrhUsername") as String?
password = project.findProperty("ossrhPassword") as String?
}
}
}
}
signing {
useGpgCmd()
sign(publishing.publications["release"])
sign(tasks.getByName("sourceJar"))
}
tasks.withType<Sign> {
onlyIf { !libraryVersion.endsWith("-SNAPSHOT") }
}
}

View File

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@ -0,0 +1,25 @@
package com.esotericsoftware.android;
import android.content.Context;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/** Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a> */
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext () {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
assertEquals("com.esotericsoftware.spine.test", appContext.getPackageName());
}
}

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@ -0,0 +1,108 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software or
* otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
package com.esotericsoftware.spine.android;
import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.utils.Null;
import com.esotericsoftware.spine.Skin;
import com.esotericsoftware.spine.attachments.AttachmentLoader;
import com.esotericsoftware.spine.attachments.BoundingBoxAttachment;
import com.esotericsoftware.spine.attachments.ClippingAttachment;
import com.esotericsoftware.spine.attachments.MeshAttachment;
import com.esotericsoftware.spine.attachments.PathAttachment;
import com.esotericsoftware.spine.attachments.PointAttachment;
import com.esotericsoftware.spine.attachments.RegionAttachment;
import com.esotericsoftware.spine.attachments.Sequence;
/** An {@link AttachmentLoader} that configures attachments using texture regions from an {@link AndroidTextureAtlas}.
* <p>
* See <a href='http://esotericsoftware.com/spine-loading-skeleton-data#JSON-and-binary-data'>Loading skeleton data</a> in the
* Spine Runtimes Guide. */
@SuppressWarnings("javadoc")
public class AndroidAtlasAttachmentLoader implements AttachmentLoader {
private AndroidTextureAtlas atlas;
public AndroidAtlasAttachmentLoader (AndroidTextureAtlas atlas) {
if (atlas == null) throw new IllegalArgumentException("atlas cannot be null.");
this.atlas = atlas;
}
private void loadSequence (String name, String basePath, Sequence sequence) {
TextureRegion[] regions = sequence.getRegions();
for (int i = 0, n = regions.length; i < n; i++) {
String path = sequence.getPath(basePath, i);
regions[i] = atlas.findRegion(path);
if (regions[i] == null) throw new RuntimeException("Region not found in atlas: " + path + " (sequence: " + name + ")");
}
}
public RegionAttachment newRegionAttachment (Skin skin, String name, String path, @Null Sequence sequence) {
RegionAttachment attachment = new RegionAttachment(name);
if (sequence != null)
loadSequence(name, path, sequence);
else {
AtlasRegion region = atlas.findRegion(path);
if (region == null)
throw new RuntimeException("Region not found in atlas: " + path + " (region attachment: " + name + ")");
attachment.setRegion(region);
}
return attachment;
}
public MeshAttachment newMeshAttachment (Skin skin, String name, String path, @Null Sequence sequence) {
MeshAttachment attachment = new MeshAttachment(name);
if (sequence != null)
loadSequence(name, path, sequence);
else {
AtlasRegion region = atlas.findRegion(path);
if (region == null)
throw new RuntimeException("Region not found in atlas: " + path + " (mesh attachment: " + name + ")");
attachment.setRegion(region);
}
return attachment;
}
public BoundingBoxAttachment newBoundingBoxAttachment (Skin skin, String name) {
return new BoundingBoxAttachment(name);
}
public ClippingAttachment newClippingAttachment (Skin skin, String name) {
return new ClippingAttachment(name);
}
public PathAttachment newPathAttachment (Skin skin, String name) {
return new PathAttachment(name);
}
public PointAttachment newPointAttachment (Skin skin, String name) {
return new PointAttachment(name);
}
}

View File

@ -0,0 +1,158 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software or
* otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
package com.esotericsoftware.spine.android;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.FloatArray;
import com.esotericsoftware.spine.Animation;
import com.esotericsoftware.spine.AnimationState;
import com.esotericsoftware.spine.AnimationStateData;
import com.esotericsoftware.spine.Skeleton;
import com.esotericsoftware.spine.SkeletonData;
import com.esotericsoftware.spine.android.utils.SkeletonDataUtils;
import java.io.File;
import java.net.URL;
/** A {@link AndroidSkeletonDrawable} bundles loading updating updating an {@link AndroidTextureAtlas}, {@link Skeleton}, and
* {@link AnimationState} into a single easy-to-use class.
*
* Use the {@link AndroidSkeletonDrawable#fromAsset(String, String, Context)},
* {@link AndroidSkeletonDrawable#fromFile(File, File)}, or {@link AndroidSkeletonDrawable#fromHttp(URL, URL, File)} methods to
* construct a {@link AndroidSkeletonDrawable}. To have multiple skeleton drawable instances share the same
* {@link AndroidTextureAtlas} and {@link SkeletonData}, use the constructor.
*
* You can then directly access the {@link AndroidSkeletonDrawable#getAtlas()}, {@link AndroidSkeletonDrawable#getSkeletonData()},
* {@link AndroidSkeletonDrawable#getSkeleton()}, {@link AndroidSkeletonDrawable#getAnimationStateData()}, and
* {@link AndroidSkeletonDrawable#getAnimationState()} to query and animate the skeleton. Use the {@link AnimationState} to queue
* animations on one or more tracks via {@link AnimationState#setAnimation(int, Animation, boolean)} or
* {@link AnimationState#addAnimation(int, Animation, boolean, float)}.
*
* To update the {@link AnimationState} and apply it to the {@link Skeleton}, call the
* {@link AndroidSkeletonDrawable#update(float)} function, providing it a delta time in seconds to advance the animations.
*
* To render the current pose of the {@link Skeleton}, use {@link SkeletonRenderer#render(Skeleton)},
* {@link SkeletonRenderer#renderToCanvas(Canvas, Array)}, {@link SkeletonRenderer#renderToBitmap(float, float, int, Skeleton)},
* depending on your needs. */
public class AndroidSkeletonDrawable {
private final AndroidTextureAtlas atlas;
private final SkeletonData skeletonData;
private final Skeleton skeleton;
private final AnimationStateData animationStateData;
private final AnimationState animationState;
/** Constructs a new skeleton drawable from the given (possibly shared) {@link AndroidTextureAtlas} and
* {@link SkeletonData}. */
public AndroidSkeletonDrawable (AndroidTextureAtlas atlas, SkeletonData skeletonData) {
this.atlas = atlas;
this.skeletonData = skeletonData;
skeleton = new Skeleton(skeletonData);
animationStateData = new AnimationStateData(skeletonData);
animationState = new AnimationState(animationStateData);
skeleton.updateWorldTransform(Skeleton.Physics.none);
}
/** Updates the {@link AnimationState} using the {@code delta} time given in seconds, applies the animation state to the
* {@link Skeleton} and updates the world transforms of the skeleton to calculate its current pose. */
public void update (float delta) {
animationState.update(delta);
animationState.apply(skeleton);
skeleton.update(delta);
skeleton.updateWorldTransform(Skeleton.Physics.update);
}
/** Get the {@link AndroidTextureAtlas} */
public AndroidTextureAtlas getAtlas () {
return atlas;
}
/** Get the {@link Skeleton} */
public Skeleton getSkeleton () {
return skeleton;
}
/** Get the {@link SkeletonData} */
public SkeletonData getSkeletonData () {
return skeletonData;
}
/** Get the {@link AnimationStateData} */
public AnimationStateData getAnimationStateData () {
return animationStateData;
}
/** Get the {@link AnimationState} */
public AnimationState getAnimationState () {
return animationState;
}
/** Constructs a new skeleton drawable from the {@code atlasFileName} and {@code skeletonFileName} from the the apps resources
* using {@link Context}.
*
* Throws an exception in case the data could not be loaded. */
public static AndroidSkeletonDrawable fromAsset (String atlasFileName, String skeletonFileName, Context context) {
AndroidTextureAtlas atlas = AndroidTextureAtlas.fromAsset(atlasFileName, context);
SkeletonData skeletonData = SkeletonDataUtils.fromAsset(atlas, skeletonFileName, context);
return new AndroidSkeletonDrawable(atlas, skeletonData);
}
/** Constructs a new skeleton drawable from the {@code atlasFile} and {@code skeletonFile}.
*
* Throws an exception in case the data could not be loaded. */
public static AndroidSkeletonDrawable fromFile (File atlasFile, File skeletonFile) {
AndroidTextureAtlas atlas = AndroidTextureAtlas.fromFile(atlasFile);
SkeletonData skeletonData = SkeletonDataUtils.fromFile(atlas, skeletonFile);
return new AndroidSkeletonDrawable(atlas, skeletonData);
}
/** Constructs a new skeleton drawable from the {@code atlasUrl} and {@code skeletonUrl}.
*
* Throws an exception in case the data could not be loaded. */
public static AndroidSkeletonDrawable fromHttp (URL atlasUrl, URL skeletonUrl, File targetDirectory) {
AndroidTextureAtlas atlas = AndroidTextureAtlas.fromHttp(atlasUrl, targetDirectory);
SkeletonData skeletonData = SkeletonDataUtils.fromHttp(atlas, skeletonUrl, targetDirectory);
return new AndroidSkeletonDrawable(atlas, skeletonData);
}
}

View File

@ -0,0 +1,99 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software or
* otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
package com.esotericsoftware.spine.android;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.utils.ObjectMap;
import com.esotericsoftware.spine.BlendMode;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Shader;
/** A class holding an {@link Bitmap} of an {@link AndroidTextureAtlas} page image with it's associated blend modes and paints. */
public class AndroidTexture extends Texture {
private Bitmap bitmap;
private ObjectMap<BlendMode, Paint> paints = new ObjectMap<>();
protected AndroidTexture (Bitmap bitmap) {
super();
this.bitmap = bitmap;
for (BlendMode blendMode : BlendMode.values()) {
Paint paint = new Paint();
BitmapShader shader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
paint.setShader(shader);
switch (blendMode) {
case normal:
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER));
break;
case multiply:
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER));
break;
case additive:
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD));
break;
case screen:
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SCREEN));
break;
default:
break;
}
paints.put(blendMode, paint);
}
}
public Bitmap getBitmap () {
return bitmap;
}
public Paint getPaint (BlendMode blendMode) {
return paints.get(blendMode);
}
@Override
public int getWidth () {
return bitmap.getWidth();
}
@Override
public int getHeight () {
return bitmap.getHeight();
}
@Override
public void dispose () {
bitmap.recycle();
}
}

View File

@ -0,0 +1,221 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software or
* otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
package com.esotericsoftware.spine.android;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Files;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion;
import com.badlogic.gdx.graphics.g2d.TextureAtlas.TextureAtlasData;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Null;
import com.esotericsoftware.spine.android.utils.HttpUtils;
import android.content.Context;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.Paint;
import android.graphics.BitmapFactory;
import android.os.Build;
/** Atlas data loaded from a `.atlas` file and its corresponding `.png` files. For each atlas image, a corresponding
* {@link Bitmap} and {@link Paint} is constructed, which are used when rendering a skeleton that uses this atlas.
*
* Use the static methods {@link AndroidTextureAtlas#fromAsset(String, Context)}, {@link AndroidTextureAtlas#fromFile(File)}, and
* {@link AndroidTextureAtlas#fromHttp(URL, File)} to load an atlas. */
public class AndroidTextureAtlas {
private interface BitmapLoader {
Bitmap load (String path);
}
private final Array<AndroidTexture> textures = new Array<>();
private final Array<AtlasRegion> regions = new Array<>();
private AndroidTextureAtlas (TextureAtlasData data, BitmapLoader bitmapLoader) {
for (TextureAtlasData.Page page : data.getPages()) {
page.texture = new AndroidTexture(bitmapLoader.load(page.textureFile.path()));
textures.add((AndroidTexture)page.texture);
}
for (TextureAtlasData.Region region : data.getRegions()) {
AtlasRegion atlasRegion = new AtlasRegion(region.page.texture, region.left, region.top, //
region.rotate ? region.height : region.width, //
region.rotate ? region.width : region.height);
atlasRegion.index = region.index;
atlasRegion.name = region.name;
atlasRegion.offsetX = region.offsetX;
atlasRegion.offsetY = region.offsetY;
atlasRegion.originalHeight = region.originalHeight;
atlasRegion.originalWidth = region.originalWidth;
atlasRegion.rotate = region.rotate;
atlasRegion.degrees = region.degrees;
atlasRegion.names = region.names;
atlasRegion.values = region.values;
if (region.flip) atlasRegion.flip(false, true);
regions.add(atlasRegion);
}
}
/** Returns the first region found with the specified name. This method uses string comparison to find the region, so the
* result should be cached rather than calling this method multiple times. */
public @Null AtlasRegion findRegion (String name) {
for (int i = 0, n = regions.size; i < n; i++)
if (regions.get(i).name.equals(name)) return regions.get(i);
return null;
}
public Array<AndroidTexture> getTextures () {
return textures;
}
public Array<AtlasRegion> getRegions () {
return regions;
}
/** Loads an {@link AndroidTextureAtlas} from the file {@code atlasFileName} from assets using {@link Context}.
*
* Throws a {@link RuntimeException} in case the atlas could not be loaded. */
public static AndroidTextureAtlas fromAsset (String atlasFileName, Context context) {
TextureAtlasData data = new TextureAtlasData();
AssetManager assetManager = context.getAssets();
try {
FileHandle inputFile = new FileHandle() {
@Override
public InputStream read () {
try {
return assetManager.open(atlasFileName);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
};
data.load(inputFile, new FileHandle(atlasFileName).parent(), false);
} catch (Throwable t) {
throw new RuntimeException(t);
}
return new AndroidTextureAtlas(data, path -> {
path = path.startsWith("/") ? path.substring(1) : path;
try (InputStream in = new BufferedInputStream(assetManager.open(path))) {
return BitmapFactory.decodeStream(in);
} catch (Throwable t) {
throw new RuntimeException(t);
}
});
}
/** Loads an {@link AndroidTextureAtlas} from the file {@code atlasFileName}.
*
* Throws a {@link RuntimeException} in case the atlas could not be loaded. */
public static AndroidTextureAtlas fromFile (File atlasFile) {
TextureAtlasData data;
try {
data = loadTextureAtlasData(atlasFile);
} catch (Exception e) {
throw new RuntimeException(e);
}
return new AndroidTextureAtlas(data, path -> {
File imageFile = new File(path);
try (InputStream in = new BufferedInputStream(inputStream(imageFile))) {
return BitmapFactory.decodeStream(in);
} catch (Throwable t) {
throw new RuntimeException(t);
}
});
}
/** Loads an {@link AndroidTextureAtlas} from the URL {@code atlasURL}.
*
* Throws a {@link Exception} in case the atlas could not be loaded. */
public static AndroidTextureAtlas fromHttp (URL atlasUrl, File targetDirectory) {
File atlasFile = HttpUtils.downloadFrom(atlasUrl, targetDirectory);
TextureAtlasData data;
try {
data = loadTextureAtlasData(atlasFile);
} catch (Exception e) {
throw new RuntimeException(e);
}
return new AndroidTextureAtlas(data, path -> {
String fileName = path.substring(path.lastIndexOf('/') + 1);
String atlasUrlPath = atlasUrl.getPath();
int lastSlashIndex = atlasUrlPath.lastIndexOf('/');
String imagePath = atlasUrlPath.substring(0, lastSlashIndex + 1) + fileName;
File imageFile;
try {
URL imageUrl = new URL(atlasUrl.getProtocol(), atlasUrl.getHost(), atlasUrl.getPort(), imagePath);
imageFile = HttpUtils.downloadFrom(imageUrl, targetDirectory);
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
try (InputStream in = new BufferedInputStream(inputStream(imageFile))) {
return BitmapFactory.decodeStream(in);
} catch (Throwable t) {
throw new RuntimeException(t);
}
});
}
private static InputStream inputStream (File file) throws Exception {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
return Files.newInputStream(file.toPath());
} else {
// noinspection IOStreamConstructor
return new FileInputStream(file);
}
}
private static TextureAtlasData loadTextureAtlasData (File atlasFile) {
TextureAtlasData data = new TextureAtlasData();
FileHandle inputFile = new FileHandle() {
@Override
public InputStream read () {
try {
return new FileInputStream(atlasFile);
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
}
};
data.load(inputFile, new FileHandle(atlasFile).parent(), false);
return data;
}
}

View File

@ -0,0 +1,54 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software or
* otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
package com.esotericsoftware.spine.android;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import com.badlogic.gdx.utils.Array;
import com.esotericsoftware.spine.Bone;
/** Renders debug information for a {@link AndroidSkeletonDrawable}, like bone locations, to a {@link Canvas}. See
* {@link DebugRenderer#render}. */
public class DebugRenderer {
public void render (AndroidSkeletonDrawable drawable, Canvas canvas, Array<SkeletonRenderer.RenderCommand> commands) {
Paint bonePaint = new Paint();
bonePaint.setColor(android.graphics.Color.BLUE);
bonePaint.setStyle(Paint.Style.FILL);
for (Bone bone : drawable.getSkeleton().getBones()) {
float x = bone.getWorldX();
float y = bone.getWorldY();
canvas.drawRect(new RectF(x - 2.5f, y - 2.5f, x + 2.5f, y + 2.5f), bonePaint);
}
}
}

View File

@ -0,0 +1,301 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software or
* otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
package com.esotericsoftware.spine.android;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.FloatArray;
import com.badlogic.gdx.utils.IntArray;
import com.badlogic.gdx.utils.Pool;
import com.badlogic.gdx.utils.ShortArray;
import com.esotericsoftware.spine.BlendMode;
import com.esotericsoftware.spine.Skeleton;
import com.esotericsoftware.spine.Slot;
import com.esotericsoftware.spine.attachments.Attachment;
import com.esotericsoftware.spine.attachments.ClippingAttachment;
import com.esotericsoftware.spine.attachments.MeshAttachment;
import com.esotericsoftware.spine.attachments.RegionAttachment;
import com.esotericsoftware.spine.utils.SkeletonClipping;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.os.Build;
/** Is responsible to transform the {@link Skeleton} with its current pose to {@link SkeletonRenderer.RenderCommand} commands and
* render them to a {@link Canvas}. */
public class SkeletonRenderer {
/** Stores the vertices, indices, and atlas page index to be used for rendering one or more attachments of a {@link Skeleton}
* to a {@link Canvas}. See the implementation of {@link SkeletonRenderer#render(Skeleton)} and
* {@link SkeletonRenderer#renderToCanvas(Canvas, Array)} on how to use this data to render it to a {@link Canvas}. */
public static class RenderCommand implements Pool.Poolable {
FloatArray vertices = new FloatArray(32);
FloatArray uvs = new FloatArray(32);
IntArray colors = new IntArray(32);
ShortArray indices = new ShortArray(32);
BlendMode blendMode;
AndroidTexture texture;
@Override
public void reset () {
vertices.setSize(0);
uvs.setSize(0);
colors.setSize(0);
indices.setSize(0);
blendMode = null;
texture = null;
}
}
static private final short[] quadTriangles = {0, 1, 2, 2, 3, 0};
private final SkeletonClipping clipper = new SkeletonClipping();
private final Pool<RenderCommand> commandPool = new Pool<RenderCommand>(10) {
@Override
protected RenderCommand newObject () {
return new RenderCommand();
}
};
private final Array<RenderCommand> commandList = new Array<RenderCommand>();
/** Created the {@link RenderCommand} commands from the skeletons current pose. */
public Array<RenderCommand> render (Skeleton skeleton) {
Color color = null, skeletonColor = skeleton.getColor();
float r = skeletonColor.r, g = skeletonColor.g, b = skeletonColor.b, a = skeletonColor.a;
commandPool.freeAll(commandList);
commandList.clear();
RenderCommand command = commandPool.obtain();
commandList.add(command);
int vertexStart = 0;
Object[] drawOrder = skeleton.getDrawOrder().items;
for (int i = 0, n = skeleton.getDrawOrder().size; i < n; i++) {
Slot slot = (Slot)drawOrder[i];
if (!slot.getBone().isActive()) {
clipper.clipEnd(slot);
continue;
}
int verticesLength = 0;
int vertexSize = 2;
float[] uvs = null;
short[] indices = null;
Attachment attachment = slot.getAttachment();
if (attachment == null) {
continue;
}
if (attachment instanceof RegionAttachment) {
RegionAttachment region = (RegionAttachment)attachment;
verticesLength = vertexSize << 2;
if (region.getSequence() != null) region.getSequence().apply(slot, region);
AndroidTexture texture = (AndroidTexture)region.getRegion().getTexture();
BlendMode blendMode = slot.getData().getBlendMode();
if (command.blendMode == null && command.texture == null) {
command.blendMode = blendMode;
command.texture = texture;
}
if (command.blendMode != blendMode || command.texture != texture || command.vertices.size + verticesLength > 64000) {
command = commandPool.obtain();
commandList.add(command);
vertexStart = 0;
command.blendMode = blendMode;
command.texture = texture;
}
command.vertices.setSize(command.vertices.size + verticesLength);
region.computeWorldVertices(slot, command.vertices.items, vertexStart, vertexSize);
uvs = region.getUVs();
indices = quadTriangles;
color = region.getColor();
} else if (attachment instanceof MeshAttachment) {
MeshAttachment mesh = (MeshAttachment)attachment;
verticesLength = mesh.getWorldVerticesLength();
if (mesh.getSequence() != null) mesh.getSequence().apply(slot, mesh);
AndroidTexture texture = (AndroidTexture)mesh.getRegion().getTexture();
BlendMode blendMode = slot.getData().getBlendMode();
if (command.blendMode == null && command.texture == null) {
command.blendMode = blendMode;
command.texture = texture;
}
if (command.blendMode != blendMode || command.texture != texture || command.vertices.size + verticesLength > 64000) {
command = commandPool.obtain();
commandList.add(command);
vertexStart = 0;
command.blendMode = blendMode;
command.texture = texture;
}
command.vertices.setSize(command.vertices.size + verticesLength);
mesh.computeWorldVertices(slot, 0, verticesLength, command.vertices.items, vertexStart, vertexSize);
uvs = mesh.getUVs();
indices = mesh.getTriangles();
color = mesh.getColor();
} else if (attachment instanceof ClippingAttachment) {
ClippingAttachment clip = (ClippingAttachment)attachment;
clipper.clipStart(slot, clip);
continue;
} else {
continue;
}
Color slotColor = slot.getColor();
int c = (int)(a * slotColor.a * color.a * 255) << 24 //
| (int)(r * slotColor.r * color.r * 255) << 16 //
| (int)(g * slotColor.g * color.g * 255) << 8 //
| (int)(b * slotColor.b * color.b * 255);
int indicesStart = command.indices.size;
int indicesLength = indices.length;
if (clipper.isClipping()) {
clipper.clipTrianglesUnpacked(command.vertices.items, vertexStart, indices, indices.length, uvs);
// Copy clipped vertices over, overwritting the previous vertices of this attachment
FloatArray clippedVertices = clipper.getClippedVertices();
command.vertices.setSize(vertexStart + clippedVertices.size);
System.arraycopy(clippedVertices.items, 0, command.vertices.items, vertexStart, clippedVertices.size);
// Copy UVs over, post-processing below
command.uvs.addAll(clipper.getClippedUvs());
// Copy indices over, post-processing below
command.indices.addAll(clipper.getClippedTriangles());
// Update verticesLength with the clipped number of vertices * 2, and indices length
// with the number of clipped indices.
verticesLength = clippedVertices.size;
indicesLength = clipper.getClippedTriangles().size;
} else {
// Copy UVs over, post-processing below
command.uvs.addAll(uvs);
// Copy indices over, post-processing below
command.indices.addAll(indices);
}
// Post-process UVs, require scaling by bitmap size
float[] uvsArray = command.uvs.items;
for (int ii = vertexStart, w = command.texture.getWidth(), h = command.texture.getHeight(),
nn = vertexStart + verticesLength; ii < nn; ii += 2) {
uvsArray[ii] = uvsArray[ii] * w;
uvsArray[ii + 1] = uvsArray[ii + 1] * h;
}
// Fill colors array
command.colors.setSize(command.colors.size + (verticesLength >> 1));
int[] colorsArray = command.colors.items;
for (int ii = vertexStart >> 1, nn = (vertexStart >> 1) + (verticesLength >> 1); ii < nn; ii++) {
colorsArray[ii] = c;
}
// Post-process indices array, need to be offset by index of the mesh's first vertex.
int firstIndex = vertexStart >> 1;
short[] indicesArray = command.indices.items;
for (int ii = indicesStart, nn = indicesStart + indicesLength; ii < nn; ii++) {
indicesArray[ii] += firstIndex;
}
vertexStart += verticesLength;
clipper.clipEnd(slot);
}
clipper.clipEnd();
if (commandList.size == 1 && commandList.get(0).vertices.size == 0) {
commandPool.freeAll(commandList);
commandList.clear();
}
return commandList;
}
/** Renders the {@link RenderCommand} commands created from the skeleton current pose to the given {@link Canvas}. Does not
* perform any scaling or fitting. */
public void renderToCanvas (Canvas canvas, Array<RenderCommand> commands) {
for (int i = 0; i < commands.size; i++) {
RenderCommand command = commands.get(i);
if (Build.VERSION.SDK_INT >= 29) {
canvas.drawVertices(Canvas.VertexMode.TRIANGLES, command.vertices.size, command.vertices.items, 0, command.uvs.items,
0, command.colors.items, 0, command.indices.items, 0, command.indices.size,
command.texture.getPaint(command.blendMode));
} else {
// See https://github.com/EsotericSoftware/spine-runtimes/issues/2638
int[] colors = command.colors.items;
int[] colorsCopy = new int[command.vertices.size];
System.arraycopy(colors, 0, colorsCopy, 0, command.colors.size);
canvas.drawVertices(Canvas.VertexMode.TRIANGLES, command.vertices.size, command.vertices.items, 0, command.uvs.items,
0, colorsCopy, 0, command.indices.items, 0, command.indices.size, command.texture.getPaint(command.blendMode));
}
}
}
/** Renders the {@link Skeleton} with its current pose to a {@link Bitmap}.
*
* @param width The width of the bitmap in pixels.
* @param height The height of the bitmap in pixels.
* @param bgColor The background color.
* @param skeleton The skeleton to render. */
public Bitmap renderToBitmap (float width, float height, int bgColor, Skeleton skeleton) {
Vector2 offset = new Vector2(0, 0);
Vector2 size = new Vector2(0, 0);
FloatArray floatArray = new FloatArray();
skeleton.getBounds(offset, size, floatArray);
RectF bounds = new RectF(offset.x, offset.y, offset.x + size.x, offset.y + size.y);
float scale = (1 / (bounds.width() > bounds.height() ? bounds.width() / width : bounds.height() / height));
Bitmap bitmap = Bitmap.createBitmap((int)width, (int)height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
Paint paint = new Paint();
paint.setColor(bgColor);
paint.setStyle(Paint.Style.FILL);
// Draw background
canvas.drawRect(0, 0, width, height, paint);
// Transform canvas
canvas.translate(width / 2, height / 2);
canvas.scale(scale, -scale);
canvas.translate(-(bounds.left + bounds.width() / 2), -(bounds.top + bounds.height() / 2));
renderToCanvas(canvas, render(skeleton));
return bitmap;
}
}

View File

@ -0,0 +1,287 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software or
* otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
package com.esotericsoftware.spine.android;
import android.graphics.Canvas;
import android.graphics.Point;
import androidx.annotation.Nullable;
import com.badlogic.gdx.utils.Array;
import com.esotericsoftware.spine.AnimationState;
import com.esotericsoftware.spine.AnimationStateData;
import com.esotericsoftware.spine.Skeleton;
import com.esotericsoftware.spine.SkeletonData;
import com.esotericsoftware.spine.android.callbacks.SpineControllerAfterPaintCallback;
import com.esotericsoftware.spine.android.callbacks.SpineControllerBeforePaintCallback;
import com.esotericsoftware.spine.android.callbacks.SpineControllerCallback;
/** Controls how the skeleton of a {@link SpineView} is animated and rendered.
*
* Upon initialization of a {@link SpineView}, the provided {@code onInitialized} callback method is called once. This method can
* be used to set up the initial animation(s) of the skeleton, among other things.
*
* After initialization is complete, the {@link SpineView} is rendered at the screen refresh rate. In each frame, the
* {@link AnimationState} is updated and applied to the {@link Skeleton}.
*
* Next, the optionally provided method {@code onBeforeUpdateWorldTransforms} is called, which can modify the skeleton before its
* current pose is calculated using {@link Skeleton#updateWorldTransform(Skeleton.Physics)}. After
* {@link Skeleton#updateWorldTransform(Skeleton.Physics)} has completed, the optional {@code onAfterUpdateWorldTransforms} method
* is called, which can modify the current pose before rendering the skeleton.
*
* Before the skeleton's current pose is rendered by the {@link SpineView}, the optional {@code onBeforePaint} is called, which
* allows rendering backgrounds or other objects that should go behind the skeleton on the {@link Canvas}. The {@link SpineView}
* then renders the skeleton's current pose and finally calls the optional {@code onAfterPaint}, which can render additional
* objects on top of the skeleton.
*
* The underlying {@link AndroidTextureAtlas}, {@link SkeletonData}, {@link Skeleton}, {@link AnimationStateData},
* {@link AnimationState}, and {@link AndroidSkeletonDrawable} can be accessed through their respective getters to inspect and/or
* modify the skeleton and its associated data. Accessing this data is only allowed if the {@link SpineView} and its data have
* been initialized and have not been disposed of yet.
*
* By default, the widget updates and renders the skeleton every frame. The {@code pause} method can be used to pause updating and
* rendering the skeleton. The {@link SpineController#resume()} method resumes updating and rendering the skeleton. The
* {@link SpineController#isPlaying()} getter reports the current state. */
public class SpineController {
/** Used to build {@link SpineController} instances. */
public static class Builder {
private final SpineControllerCallback onInitialized;
private SpineControllerCallback onBeforeUpdateWorldTransforms;
private SpineControllerCallback onAfterUpdateWorldTransforms;
private SpineControllerBeforePaintCallback onBeforePaint;
private SpineControllerAfterPaintCallback onAfterPaint;
/** Instantiate a {@link Builder} used to build a {@link SpineController}, which controls how the skeleton of a
* {@link SpineView} is animated and rendered. Upon initialization of a {@link SpineView}, the provided
* {@code onInitialized} callback method is called once. This method can be used to set up the initial animation(s) of the
* skeleton, among other things.
*
* @param onInitialized Upon initialization of a {@link SpineView}, the provided {@code onInitialized} callback method is
* called once. This method can be used to set up the initial animation(s) of the skeleton, among other things. */
public Builder (SpineControllerCallback onInitialized) {
this.onInitialized = onInitialized;
}
/** Sets the {@code onBeforeUpdateWorldTransforms} callback. It is called before the skeleton's current pose is calculated
* using {@link Skeleton#updateWorldTransform(Skeleton.Physics)}. It can be used to modify the skeleton before the pose
* calculation. */
public Builder setOnBeforeUpdateWorldTransforms (SpineControllerCallback onBeforeUpdateWorldTransforms) {
this.onBeforeUpdateWorldTransforms = onBeforeUpdateWorldTransforms;
return this;
}
/** Sets the {@code onAfterUpdateWorldTransforms} callback. This method is called after the skeleton's current pose is
* calculated using {@link Skeleton#updateWorldTransform(Skeleton.Physics)}. It can be used to modify the current pose
* before rendering the skeleton. */
public Builder setOnAfterUpdateWorldTransforms (SpineControllerCallback onAfterUpdateWorldTransforms) {
this.onAfterUpdateWorldTransforms = onAfterUpdateWorldTransforms;
return this;
}
/** Sets the {@code onBeforePaint} callback. It is called before the skeleton's current pose is rendered by the
* {@link SpineView}. It allows rendering backgrounds or other objects that should go behind the skeleton on the
* {@link Canvas}. */
public Builder setOnBeforePaint (SpineControllerBeforePaintCallback onBeforePaint) {
this.onBeforePaint = onBeforePaint;
return this;
}
/** Sets the {@code onAfterPaint} callback. It is called after the skeleton's current pose is rendered by the
* {@link SpineView}. It allows rendering additional objects on top of the skeleton. */
public Builder setOnAfterPaint (SpineControllerAfterPaintCallback onAfterPaint) {
this.onAfterPaint = onAfterPaint;
return this;
}
public SpineController build () {
SpineController spineController = new SpineController(onInitialized);
spineController.onBeforeUpdateWorldTransforms = onBeforeUpdateWorldTransforms;
spineController.onAfterUpdateWorldTransforms = onAfterUpdateWorldTransforms;
spineController.onBeforePaint = onBeforePaint;
spineController.onAfterPaint = onAfterPaint;
return spineController;
}
}
private final SpineControllerCallback onInitialized;
private @Nullable SpineControllerCallback onBeforeUpdateWorldTransforms;
private @Nullable SpineControllerCallback onAfterUpdateWorldTransforms;
private @Nullable SpineControllerBeforePaintCallback onBeforePaint;
private @Nullable SpineControllerAfterPaintCallback onAfterPaint;
private AndroidSkeletonDrawable drawable;
private boolean playing = true;
private double offsetX = 0;
private double offsetY = 0;
private double scaleX = 1;
private double scaleY = 1;
/** Instantiate a {@link SpineController}, which controls how the skeleton of a {@link SpineView} is animated and rendered.
* Upon initialization of a {@link SpineView}, the provided {@code onInitialized} callback method is called once. This method
* can be used to set up the initial animation(s) of the skeleton, among other things.
*
* @param onInitialized Upon initialization of a {@link SpineView}, the provided {@code onInitialized} callback method is
* called once. This method can be used to set up the initial animation(s) of the skeleton, among other things. */
public SpineController (SpineControllerCallback onInitialized) {
this.onInitialized = onInitialized;
}
protected void init (AndroidSkeletonDrawable drawable) {
this.drawable = drawable;
if (onInitialized != null) {
onInitialized.execute(this);
}
}
/** The {@link AndroidTextureAtlas} from which images to render the skeleton are sourced. */
public AndroidTextureAtlas getAtlas () {
if (drawable == null) throw new RuntimeException("Controller is not initialized yet.");
return drawable.getAtlas();
}
/** The setup-pose data used by the skeleton. */
public SkeletonData getSkeletonDate () {
if (drawable == null) throw new RuntimeException("Controller is not initialized yet.");
return drawable.getSkeletonData();
}
/** The {@link Skeleton}. */
public Skeleton getSkeleton () {
if (drawable == null) throw new RuntimeException("Controller is not initialized yet.");
return drawable.getSkeleton();
}
/** The mixing information used by the {@link AnimationState}. */
public AnimationStateData getAnimationStateData () {
if (drawable == null) throw new RuntimeException("Controller is not initialized yet.");
return drawable.getAnimationStateData();
}
/** The {@link AnimationState} used to manage animations that are being applied to the skeleton. */
public AnimationState getAnimationState () {
if (drawable == null) throw new RuntimeException("Controller is not initialized yet.");
return drawable.getAnimationState();
}
/** The {@link AndroidSkeletonDrawable}. */
public AndroidSkeletonDrawable getDrawable () {
if (drawable == null) throw new RuntimeException("Controller is not initialized yet.");
return drawable;
}
/** Checks if the {@link SpineView} is initialized. */
public boolean isInitialized () {
return drawable != null;
}
/** Checks if the animation is currently playing. */
public boolean isPlaying () {
return playing;
}
/** Pauses updating and rendering the skeleton. */
public void pause () {
if (playing) {
playing = false;
}
}
/** Resumes updating and rendering the skeleton. */
public void resume () {
if (!playing) {
playing = true;
}
}
/** Transforms the coordinates given in the {@link SpineView} coordinate system in {@code position} to the skeleton coordinate
* system. See the {@code IKFollowing.kt} example for how to use this to move a bone based on user touch input. */
public Point toSkeletonCoordinates (Point position) {
int x = position.x;
int y = position.y;
return new Point((int)(x / scaleX - offsetX), (int)(y / scaleY - offsetY));
}
/** Sets the {@code onBeforeUpdateWorldTransforms} callback. It is called before the skeleton's current pose is calculated
* using {@link Skeleton#updateWorldTransform(Skeleton.Physics)}. It can be used to modify the skeleton before the pose
* calculation. */
public void setOnBeforeUpdateWorldTransforms (@Nullable SpineControllerCallback onBeforeUpdateWorldTransforms) {
this.onBeforeUpdateWorldTransforms = onBeforeUpdateWorldTransforms;
}
/** Sets the {@code onAfterUpdateWorldTransforms} callback. This method is called after the skeleton's current pose is
* calculated using {@link Skeleton#updateWorldTransform(Skeleton.Physics)}. It can be used to modify the current pose before
* rendering the skeleton. */
public void setOnAfterUpdateWorldTransforms (@Nullable SpineControllerCallback onAfterUpdateWorldTransforms) {
this.onAfterUpdateWorldTransforms = onAfterUpdateWorldTransforms;
}
/** Sets the {@code onBeforePaint} callback. It is called before the skeleton's current pose is rendered by the
* {@link SpineView}. It allows rendering backgrounds or other objects that should go behind the skeleton on the
* {@link Canvas}. */
public void setOnBeforePaint (@Nullable SpineControllerBeforePaintCallback onBeforePaint) {
this.onBeforePaint = onBeforePaint;
}
/** Sets the {@code onAfterPaint} callback. It is called after the skeleton's current pose is rendered by the
* {@link SpineView}. It allows rendering additional objects on top of the skeleton. */
public void setOnAfterPaint (@Nullable SpineControllerAfterPaintCallback onAfterPaint) {
this.onAfterPaint = onAfterPaint;
}
protected void setCoordinateTransform (double offsetX, double offsetY, double scaleX, double scaleY) {
this.offsetX = offsetX;
this.offsetY = offsetY;
this.scaleX = scaleX;
this.scaleY = scaleY;
}
protected void callOnBeforeUpdateWorldTransforms () {
if (onBeforeUpdateWorldTransforms != null) {
onBeforeUpdateWorldTransforms.execute(this);
}
}
protected void callOnAfterUpdateWorldTransforms () {
if (onAfterUpdateWorldTransforms != null) {
onAfterUpdateWorldTransforms.execute(this);
}
}
protected void callOnBeforePaint (Canvas canvas) {
if (onBeforePaint != null) {
onBeforePaint.execute(this, canvas);
}
}
protected void callOnAfterPaint (Canvas canvas, Array<SkeletonRenderer.RenderCommand> renderCommands) {
if (onAfterPaint != null) {
onAfterPaint.execute(this, canvas, renderCommands);
}
}
}

View File

@ -0,0 +1,419 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software or
* otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
package com.esotericsoftware.spine.android;
import com.badlogic.gdx.utils.Array;
import com.esotericsoftware.spine.android.bounds.Alignment;
import com.esotericsoftware.spine.android.bounds.Bounds;
import com.esotericsoftware.spine.android.bounds.BoundsProvider;
import com.esotericsoftware.spine.android.bounds.ContentMode;
import com.esotericsoftware.spine.android.bounds.SetupPoseBounds;
import com.esotericsoftware.spine.android.callbacks.AndroidSkeletonDrawableLoader;
import com.esotericsoftware.spine.Skeleton;
import android.content.Context;
import android.graphics.Canvas;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.util.AttributeSet;
import android.view.Choreographer;
import android.view.View;
import androidx.annotation.NonNull;
import java.io.File;
import java.net.URL;
/** A {@link View} to display a Spine skeleton. The skeleton can be loaded from an asset bundle
* ({@link SpineView#loadFromAssets(String, String, Context, SpineController)}), local files
* ({@link SpineView#loadFromFile(File, File, Context, SpineController)}), URLs
* ({@link SpineView#loadFromHttp(URL, URL, File, Context, SpineController)}), or a pre-loaded {@link AndroidSkeletonDrawable}
* using ({@link SpineView#loadFromDrawable(AndroidSkeletonDrawable, Context, SpineController)}).
*
* The skeleton displayed by a {@link SpineView} can be controlled via a {@link SpineController}.
*
* The size of the widget can be derived from the bounds provided by a {@link BoundsProvider}. If the widget is not sized by the
* bounds computed by the {@link BoundsProvider}, the widget will use the computed bounds to fit the skeleton inside the widget's
* dimensions. */
public class SpineView extends View implements Choreographer.FrameCallback {
/** Used to build {@link SpineView} instances. */
public static class Builder {
private final Context context;
private final SpineController controller;
private String atlasFileName;
private String skeletonFileName;
private File atlasFile;
private File skeletonFile;
private URL atlasUrl;
private URL skeletonUrl;
private File targetDirectory;
private AndroidSkeletonDrawable drawable;
private BoundsProvider boundsProvider = new SetupPoseBounds();
private Alignment alignment = Alignment.CENTER;
private ContentMode contentMode = ContentMode.FIT;
/** Instantiate a {@link Builder} used to build a {@link SpineView}, which is a {@link View} to display a Spine skeleton.
*
* @param controller The skeleton displayed by a {@link SpineView} can be controlled via a {@link SpineController}. */
public Builder (Context context, SpineController controller) {
this.context = context;
this.controller = controller;
}
/** Loads assets from your app assets for the {@link SpineView} if set. The {@code atlasFileName} specifies the `.atlas`
* file to be loaded for the images used to render the skeleton. The {@code skeletonFileName} specifies either a Skeleton
* `.json` or `.skel` file containing the skeleton data. */
public Builder setLoadFromAssets (String atlasFileName, String skeletonFileName) {
this.atlasFileName = atlasFileName;
this.skeletonFileName = skeletonFileName;
return this;
}
/** Loads assets from files for the {@link SpineView} if set. The {@code atlasFile} specifies the `.atlas` file to be loaded
* for the images used to render the skeleton. The {@code skeletonFile} specifies either a Skeleton `.json` or `.skel` file
* containing the skeleton data. */
public Builder setLoadFromFile (File atlasFile, File skeletonFile) {
this.atlasFile = atlasFile;
this.skeletonFile = skeletonFile;
return this;
}
/** Loads assets from http for the {@link SpineView} if set. The {@code atlasUrl} specifies the `.atlas` url to be loaded
* for the images used to render the skeleton. The {@code skeletonUrl} specifies either a Skeleton `.json` or `.skel` url
* containing the skeleton data. */
public Builder setLoadFromHttp (URL atlasUrl, URL skeletonUrl, File targetDirectory) {
this.atlasUrl = atlasUrl;
this.skeletonUrl = skeletonUrl;
this.targetDirectory = targetDirectory;
return this;
}
/** Uses the {@link AndroidSkeletonDrawable} for the {@link SpineView} if set. */
public Builder setLoadFromDrawable (AndroidSkeletonDrawable drawable) {
this.drawable = drawable;
return this;
}
/** Get the {@link BoundsProvider} used to compute the bounds of the {@link Skeleton} inside the view. The default is
* {@link SetupPoseBounds}. */
public Builder setBoundsProvider (BoundsProvider boundsProvider) {
this.boundsProvider = boundsProvider;
return this;
}
/** Get the {@link ContentMode} used to fit the {@link Skeleton} inside the view. The default is {@link ContentMode#FIT}. */
public Builder setContentMode (ContentMode contentMode) {
this.contentMode = contentMode;
return this;
}
/** Set the {@link Alignment} used to align the {@link Skeleton} inside the view. The default is {@link Alignment#CENTER} */
public Builder setAlignment (Alignment alignment) {
this.alignment = alignment;
return this;
}
/** Builds a new {@link SpineView}.
*
* After initialization is complete, the provided {@code SpineController} is invoked as per the {@link SpineController}
* semantics, to allow modifying how the skeleton inside the widget is animated and rendered. */
public SpineView build () {
SpineView spineView = new SpineView(context, controller);
spineView.boundsProvider = boundsProvider;
spineView.alignment = alignment;
spineView.contentMode = contentMode;
if (atlasFileName != null && skeletonFileName != null) {
spineView.loadFromAsset(atlasFileName, skeletonFileName);
} else if (atlasFile != null && skeletonFile != null) {
spineView.loadFromFile(atlasFile, skeletonFile);
} else if (atlasUrl != null && skeletonUrl != null && targetDirectory != null) {
spineView.loadFromHttp(atlasUrl, skeletonUrl, targetDirectory);
} else if (drawable != null) {
spineView.loadFromDrawable(drawable);
}
return spineView;
}
}
private long lastTime = 0;
private float delta = 0;
private float offsetX = 0;
private float offsetY = 0;
private float scaleX = 1;
private float scaleY = 1;
private float x = 0;
private float y = 0;
private final SkeletonRenderer renderer = new SkeletonRenderer();
private Boolean rendering = true;
private Bounds computedBounds = new Bounds();
private SpineController controller;
private BoundsProvider boundsProvider = new SetupPoseBounds();
private Alignment alignment = Alignment.CENTER;
private ContentMode contentMode = ContentMode.FIT;
/** Constructs a new {@link SpineView}.
*
* After initialization is complete, the provided {@code SpineController} is invoked as per the {@link SpineController}
* semantics, to allow modifying how the skeleton inside the widget is animated and rendered. */
public SpineView (Context context, SpineController controller) {
super(context);
this.controller = controller;
// See https://github.com/EsotericSoftware/spine-runtimes/issues/2638
if (Build.VERSION.SDK_INT < 29) {
this.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
}
/** Constructs a new {@link SpineView} without providing a {@link SpineController}, which you need to provide using
* {@link SpineView#setController(SpineController)}. */
public SpineView (Context context, AttributeSet attrs) {
super(context, attrs);
// Set properties by view id
}
/** Constructs a new {@link SpineView} without providing a {@link SpineController}, which you need to provide using
* {@link SpineView#setController(SpineController)}. */
public SpineView (Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// Set properties by view id
}
/** Constructs a new {@link SpineView} from files in your app assets. The {@code atlasFileName} specifies the `.atlas` file to
* be loaded for the images used to render the skeleton. The {@code skeletonFileName} specifies either a Skeleton `.json` or
* `.skel` file containing the skeleton data.
*
* After initialization is complete, the provided {@code controller} is invoked as per the {@link SpineController} semantics,
* to allow modifying how the skeleton inside the widget is animated and rendered. */
public static SpineView loadFromAssets (String atlasFileName, String skeletonFileName, Context context,
SpineController controller) {
SpineView spineView = new SpineView(context, controller);
spineView.loadFromAsset(atlasFileName, skeletonFileName);
return spineView;
}
/** Constructs a new {@link SpineView} from files. The {@code atlasFile} specifies the `.atlas` file to be loaded for the
* images used to render the skeleton. The {@code skeletonFile} specifies either a Skeleton `.json` or `.skel` file containing
* the skeleton data.
*
* After initialization is complete, the provided {@code SpineController} is invoked as per the {@link SpineController}
* semantics, to allow modifying how the skeleton inside the widget is animated and rendered. */
public static SpineView loadFromFile (File atlasFile, File skeletonFile, Context context, SpineController controller) {
SpineView spineView = new SpineView(context, controller);
spineView.loadFromFile(atlasFile, skeletonFile);
return spineView;
}
/** Constructs a new {@link SpineView} from HTTP URLs. The {@code atlasUrl} specifies the `.atlas` url to be loaded for the
* images used to render the skeleton. The {@code skeletonUrl} specifies either a Skeleton `.json` or `.skel` url containing
* the skeleton data.
*
* After initialization is complete, the provided {@code SpineController} is invoked as per the {@link SpineController}
* semantics, to allow modifying how the skeleton inside the widget is animated and rendered. */
public static SpineView loadFromHttp (URL atlasUrl, URL skeletonUrl, File targetDirectory, Context context,
SpineController controller) {
SpineView spineView = new SpineView(context, controller);
spineView.loadFromHttp(atlasUrl, skeletonUrl, targetDirectory);
return spineView;
}
/** Constructs a new {@link SpineView} from a {@link AndroidSkeletonDrawable}.
*
* After initialization is complete, the provided {@code SpineController} is invoked as per the {@link SpineController}
* semantics, to allow modifying how the skeleton inside the widget is animated and rendered. */
public static SpineView loadFromDrawable (AndroidSkeletonDrawable drawable, Context context, SpineController controller) {
SpineView spineView = new SpineView(context, controller);
spineView.loadFromDrawable(drawable);
return spineView;
}
/** The same as {@link SpineView#loadFromAssets(String, String, Context, SpineController)}, but can be used after instantiating
* the view via {@link SpineView#SpineView(Context, SpineController)}. */
public void loadFromAsset (String atlasFileName, String skeletonFileName) {
loadFrom( () -> AndroidSkeletonDrawable.fromAsset(atlasFileName, skeletonFileName, getContext()));
}
/** The same as {@link SpineView#loadFromFile(File, File, Context, SpineController)}, but can be used after instantiating the
* view via {@link SpineView#SpineView(Context, SpineController)}. */
public void loadFromFile (File atlasFile, File skeletonFile) {
loadFrom( () -> AndroidSkeletonDrawable.fromFile(atlasFile, skeletonFile));
}
/** The same as {@link SpineView#loadFromHttp(URL, URL, File, Context, SpineController)}, but can be used after instantiating
* the view via {@link SpineView#SpineView(Context, SpineController)}. */
public void loadFromHttp (URL atlasUrl, URL skeletonUrl, File targetDirectory) {
loadFrom( () -> AndroidSkeletonDrawable.fromHttp(atlasUrl, skeletonUrl, targetDirectory));
}
/** The same as {@link SpineView#loadFromDrawable(AndroidSkeletonDrawable, Context, SpineController)}, but can be used after
* instantiating the view via {@link SpineView#SpineView(Context, SpineController)}. */
public void loadFromDrawable (AndroidSkeletonDrawable drawable) {
loadFrom( () -> drawable);
}
/** Get the {@link SpineController} */
public SpineController getController () {
return controller;
}
/** Set the {@link SpineController}. Only do this if you use {@link SpineView#SpineView(Context, AttributeSet)},
* {@link SpineView#SpineView(Context, AttributeSet, int)}, or create the {@link SpineView} in an XML layout. */
public void setController (SpineController controller) {
this.controller = controller;
}
/** Get the {@link Alignment} used to align the {@link Skeleton} inside the view. The default is {@link Alignment#CENTER} */
public Alignment getAlignment () {
return alignment;
}
/** Set the {@link Alignment}. */
public void setAlignment (Alignment alignment) {
this.alignment = alignment;
updateCanvasTransform();
}
/** Get the {@link ContentMode} used to fit the {@link Skeleton} inside the view. The default is {@link ContentMode#FIT}. */
public ContentMode getContentMode () {
return contentMode;
}
/** Set the {@link ContentMode}. */
public void setContentMode (ContentMode contentMode) {
this.contentMode = contentMode;
updateCanvasTransform();
}
/** Get the {@link BoundsProvider} used to compute the bounds of the {@link Skeleton} inside the view. The default is
* {@link SetupPoseBounds}. */
public BoundsProvider getBoundsProvider () {
return boundsProvider;
}
/** Set the {@link BoundsProvider}. */
public void setBoundsProvider (BoundsProvider boundsProvider) {
this.boundsProvider = boundsProvider;
updateCanvasTransform();
}
/** Check if rendering is enabled. */
public Boolean isRendering () {
return rendering;
}
/** Set to disable or enable rendering. Disable it when the spine view is out of bounds and you want to preserve CPU/GPU
* resources. */
public void setRendering (Boolean rendering) {
this.rendering = rendering;
}
private void loadFrom (AndroidSkeletonDrawableLoader loader) {
Handler mainHandler = new Handler(Looper.getMainLooper());
Thread backgroundThread = new Thread( () -> {
final AndroidSkeletonDrawable skeletonDrawable = loader.load();
mainHandler.post( () -> {
computedBounds = boundsProvider.computeBounds(skeletonDrawable);
updateCanvasTransform();
controller.init(skeletonDrawable);
Choreographer.getInstance().postFrameCallback(SpineView.this);
});
});
backgroundThread.start();
}
@Override
public void onDraw (@NonNull Canvas canvas) {
super.onDraw(canvas);
if (controller == null || !controller.isInitialized() || !rendering) {
return;
}
if (controller.isPlaying()) {
controller.callOnBeforeUpdateWorldTransforms();
controller.getDrawable().update(delta);
controller.callOnAfterUpdateWorldTransforms();
}
canvas.save();
canvas.translate(offsetX, offsetY);
canvas.scale(scaleX, scaleY * -1);
canvas.translate(x, y);
controller.callOnBeforePaint(canvas);
Array<SkeletonRenderer.RenderCommand> commands = renderer.render(controller.getSkeleton());
renderer.renderToCanvas(canvas, commands);
controller.callOnAfterPaint(canvas, commands);
canvas.restore();
}
@Override
protected void onSizeChanged (int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
updateCanvasTransform();
}
private void updateCanvasTransform () {
if (controller == null) {
return;
}
x = (float)(-computedBounds.getX() - computedBounds.getWidth() / 2.0
- (alignment.getX() * computedBounds.getWidth() / 2.0));
y = (float)(-computedBounds.getY() - computedBounds.getHeight() / 2.0
- (alignment.getY() * computedBounds.getHeight() / 2.0));
switch (contentMode) {
case FIT:
scaleX = scaleY = (float)Math.min(getWidth() / computedBounds.getWidth(), getHeight() / computedBounds.getHeight());
break;
case FILL:
scaleX = scaleY = (float)Math.max(getWidth() / computedBounds.getWidth(), getHeight() / computedBounds.getHeight());
break;
}
offsetX = (float)(getWidth() / 2.0 + (alignment.getX() * getWidth() / 2.0));
offsetY = (float)(getHeight() / 2.0 + (alignment.getY() * getHeight() / 2.0));
controller.setCoordinateTransform(x + offsetX / scaleX, y + offsetY / scaleY, scaleX, scaleY);
}
// Choreographer.FrameCallback
@Override
public void doFrame (long frameTimeNanos) {
if (lastTime != 0) delta = (frameTimeNanos - lastTime) / 1e9f;
lastTime = frameTimeNanos;
invalidate();
Choreographer.getInstance().postFrameCallback(this);
}
}

View File

@ -0,0 +1,52 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software or
* otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
package com.esotericsoftware.spine.android.bounds;
/** How a view should be aligned within another view. */
public enum Alignment {
TOP_LEFT(-1.0f, -1.0f), TOP_CENTER(0.0f, -1.0f), TOP_RIGHT(1.0f, -1.0f), CENTER_LEFT(-1.0f, 0.0f), CENTER(0.0f,
0.0f), CENTER_RIGHT(1.0f, 0.0f), BOTTOM_LEFT(-1.0f, 1.0f), BOTTOM_CENTER(0.0f, 1.0f), BOTTOM_RIGHT(1.0f, 1.0f);
private final float x;
private final float y;
Alignment (float x, float y) {
this.x = x;
this.y = y;
}
public float getX () {
return x;
}
public float getY () {
return y;
}
}

View File

@ -0,0 +1,101 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software or
* otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
package com.esotericsoftware.spine.android.bounds;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.FloatArray;
import com.esotericsoftware.spine.Skeleton;
/** Bounds denoted by the top left corner coordinates {@code x} and {@code y} and the {@code width} and {@code height}. */
public class Bounds {
private double x;
private double y;
private double width;
private double height;
public Bounds () {
this.x = 0;
this.y = 0;
this.width = 0;
this.height = 0;
}
public Bounds (double x, double y, double width, double height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
public Bounds (Skeleton skeleton) {
Vector2 offset = new Vector2(0, 0);
Vector2 size = new Vector2(0, 0);
FloatArray floatArray = new FloatArray();
skeleton.getBounds(offset, size, floatArray);
x = offset.x;
y = offset.y;
width = size.x;
height = size.y;
}
public double getX () {
return x;
}
public void setX (double x) {
this.x = x;
}
public double getY () {
return y;
}
public void setY (double y) {
this.y = y;
}
public double getWidth () {
return width;
}
public void setWidth (double width) {
this.width = width;
}
public double getHeight () {
return height;
}
public void setHeight (double height) {
this.height = height;
}
}

View File

@ -0,0 +1,38 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software or
* otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
package com.esotericsoftware.spine.android.bounds;
import com.esotericsoftware.spine.android.AndroidSkeletonDrawable;
/** A {@link BoundsProvider} that calculates the bounding box of the skeleton based on the visible attachments in the setup
* pose. */
public interface BoundsProvider {
Bounds computeBounds (AndroidSkeletonDrawable drawable);
}

View File

@ -0,0 +1,38 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software or
* otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
package com.esotericsoftware.spine.android.bounds;
/** How a view should be inscribed into another view. */
public enum ContentMode {
/** As large as possible while still containing the source view entirely within the target view. */
FIT,
/** Fill the target view by distorting the source's aspect ratio. */
FILL
}

View File

@ -0,0 +1,52 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software or
* otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
package com.esotericsoftware.spine.android.bounds;
import com.esotericsoftware.spine.android.AndroidSkeletonDrawable;
/** A {@link BoundsProvider} that returns fixed bounds. */
public class RawBounds implements BoundsProvider {
final Double x;
final Double y;
final Double width;
final Double height;
public RawBounds (Double x, Double y, Double width, Double height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
@Override
public Bounds computeBounds (AndroidSkeletonDrawable drawable) {
return new Bounds(x, y, width, height);
}
}

View File

@ -0,0 +1,42 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software or
* otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
package com.esotericsoftware.spine.android.bounds;
import com.esotericsoftware.spine.android.AndroidSkeletonDrawable;
/** A {@link BoundsProvider} that calculates the bounding box of the skeleton based on the visible attachments in the setup
* pose. */
public class SetupPoseBounds implements BoundsProvider {
@Override
public Bounds computeBounds (AndroidSkeletonDrawable drawable) {
return new Bounds(drawable.getSkeleton());
}
}

View File

@ -0,0 +1,111 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software or
* otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
package com.esotericsoftware.spine.android.bounds;
import com.esotericsoftware.spine.Animation;
import com.esotericsoftware.spine.SkeletonData;
import com.esotericsoftware.spine.Skin;
import com.esotericsoftware.spine.android.AndroidSkeletonDrawable;
import java.util.Collections;
import java.util.List;
/** A {@link BoundsProvider} that calculates the bounding box needed for a combination of skins and an animation. */
public class SkinAndAnimationBounds implements BoundsProvider {
private final List<String> skins;
private final String animation;
private final double stepTime;
/** Constructs a new provider that will use the given {@code skins} and {@code animation} to calculate the bounding box of the
* skeleton. If no skins are given, the default skin is used. The {@code stepTime}, given in seconds, defines at what interval
* the bounds should be sampled across the entire animation. */
public SkinAndAnimationBounds (List<String> skins, String animation, double stepTime) {
this.skins = (skins == null || skins.isEmpty()) ? Collections.singletonList("default") : skins;
this.animation = animation;
this.stepTime = stepTime;
}
/** Constructs a new provider that will use the given {@code skins} and {@code animation} to calculate the bounding box of the
* skeleton. If no skins are given, the default skin is used. The {@code stepTime} has default value 0.1. */
public SkinAndAnimationBounds (List<String> skins, String animation) {
this(skins, animation, 0.1);
}
/** Constructs a new provider that will use the given {@code skins} and {@code animation} to calculate the bounding box of the
* skeleton. The default skin is used. The {@code stepTime} has default value 0.1. */
public SkinAndAnimationBounds (String animation) {
this(Collections.emptyList(), animation, 0.1);
}
@Override
public Bounds computeBounds (AndroidSkeletonDrawable drawable) {
SkeletonData data = drawable.getSkeletonData();
Skin oldSkin = drawable.getSkeleton().getSkin();
Skin customSkin = new Skin("custom-skin");
for (String skinName : skins) {
Skin skin = data.findSkin(skinName);
if (skin == null) continue;
customSkin.addSkin(skin);
}
drawable.getSkeleton().setSkin(customSkin);
drawable.getSkeleton().setToSetupPose();
Animation animation = (this.animation != null) ? data.findAnimation(this.animation) : null;
double minX = Double.POSITIVE_INFINITY;
double minY = Double.POSITIVE_INFINITY;
double maxX = Double.NEGATIVE_INFINITY;
double maxY = Double.NEGATIVE_INFINITY;
if (animation == null) {
Bounds bounds = new Bounds(drawable.getSkeleton());
minX = bounds.getX();
minY = bounds.getY();
maxX = minX + bounds.getWidth();
maxY = minY + bounds.getHeight();
} else {
drawable.getAnimationState().setAnimation(0, animation, false);
int steps = (int)Math.max((animation.getDuration() / stepTime), 1.0);
for (int i = 0; i < steps; i++) {
drawable.update(i > 0 ? (float)stepTime : 0);
Bounds bounds = new Bounds(drawable.getSkeleton());
minX = Math.min(minX, bounds.getX());
minY = Math.min(minY, bounds.getY());
maxX = Math.max(maxX, minX + bounds.getWidth());
maxY = Math.max(maxY, minY + bounds.getHeight());
}
}
drawable.getSkeleton().setSkin("default");
drawable.getAnimationState().clearTracks();
if (oldSkin != null) drawable.getSkeleton().setSkin(oldSkin);
drawable.getSkeleton().setToSetupPose();
drawable.update(0);
return new Bounds(minX, minY, maxX - minX, maxY - minY);
}
}

View File

@ -0,0 +1,37 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software or
* otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
package com.esotericsoftware.spine.android.callbacks;
import com.esotericsoftware.spine.android.AndroidSkeletonDrawable;
@FunctionalInterface
public interface AndroidSkeletonDrawableLoader {
AndroidSkeletonDrawable load ();
}

View File

@ -0,0 +1,43 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software or
* otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
package com.esotericsoftware.spine.android.callbacks;
import android.graphics.Canvas;
import com.badlogic.gdx.utils.Array;
import com.esotericsoftware.spine.android.SkeletonRenderer;
import com.esotericsoftware.spine.android.SpineController;
import java.util.List;
@FunctionalInterface
public interface SpineControllerAfterPaintCallback {
void execute (SpineController controller, Canvas canvas, Array<SkeletonRenderer.RenderCommand> commands);
}

View File

@ -0,0 +1,42 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software or
* otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
package com.esotericsoftware.spine.android.callbacks;
import android.graphics.Canvas;
import com.esotericsoftware.spine.android.SkeletonRenderer;
import com.esotericsoftware.spine.android.SpineController;
import java.util.List;
@FunctionalInterface
public interface SpineControllerBeforePaintCallback {
void execute (SpineController controller, Canvas canvas);
}

Some files were not shown because too many files have changed in this diff Show More