mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-03-26 22:49:01 +08:00
Fix format-xx.sh files wrt logging, add proper Swift formatting configuration.
This commit is contained in:
parent
0dd86dfdc1
commit
d409ff23ff
@ -27,7 +27,7 @@ let package = Package(
|
|||||||
.byName(
|
.byName(
|
||||||
name: "Spine",
|
name: "Spine",
|
||||||
condition: .when(platforms: [
|
condition: .when(platforms: [
|
||||||
.iOS,
|
.iOS
|
||||||
])
|
])
|
||||||
),
|
),
|
||||||
"SpineCppLite",
|
"SpineCppLite",
|
||||||
@ -38,7 +38,7 @@ let package = Package(
|
|||||||
.target(
|
.target(
|
||||||
name: "Spine",
|
name: "Spine",
|
||||||
dependencies: [
|
dependencies: [
|
||||||
"SpineCppLite", "SpineShadersStructs"
|
"SpineCppLite", "SpineShadersStructs",
|
||||||
],
|
],
|
||||||
path: "spine-ios/Sources/Spine"
|
path: "spine-ios/Sources/Spine"
|
||||||
),
|
),
|
||||||
@ -46,13 +46,13 @@ let package = Package(
|
|||||||
name: "SpineCppLite",
|
name: "SpineCppLite",
|
||||||
path: "spine-ios/Sources/SpineCppLite",
|
path: "spine-ios/Sources/SpineCppLite",
|
||||||
linkerSettings: [
|
linkerSettings: [
|
||||||
.linkedLibrary("c++"),
|
.linkedLibrary("c++")
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
.systemLibrary(
|
.systemLibrary(
|
||||||
name: "SpineShadersStructs",
|
name: "SpineShadersStructs",
|
||||||
path: "spine-ios/Sources/SpineShadersStructs"
|
path: "spine-ios/Sources/SpineShadersStructs"
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
cxxLanguageStandard: .cxx11
|
cxxLanguageStandard: .cxx11
|
||||||
)
|
)
|
||||||
|
|||||||
52
formatters/.swift-format
Normal file
52
formatters/.swift-format
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"lineLength": 150,
|
||||||
|
"indentation": {
|
||||||
|
"spaces": 4
|
||||||
|
},
|
||||||
|
"tabWidth": 4,
|
||||||
|
"maximumBlankLines": 2,
|
||||||
|
"respectsExistingLineBreaks": true,
|
||||||
|
"lineBreakBeforeControlFlowKeywords": false,
|
||||||
|
"lineBreakBeforeEachArgument": false,
|
||||||
|
"lineBreakBeforeEachGenericRequirement": false,
|
||||||
|
"prioritizeKeepingFunctionOutputTogether": true,
|
||||||
|
"indentConditionalCompilationBlocks": true,
|
||||||
|
"lineBreakAroundMultilineExpressionChainComponents": false,
|
||||||
|
"rules": {
|
||||||
|
"AllPublicDeclarationsHaveDocumentation": false,
|
||||||
|
"AlwaysUseLowerCamelCase": true,
|
||||||
|
"AmbiguousTrailingClosureOverload": true,
|
||||||
|
"BeginDocumentationCommentWithOneLineSummary": false,
|
||||||
|
"DoNotUseSemicolons": true,
|
||||||
|
"DontRepeatTypeInStaticProperties": true,
|
||||||
|
"FileScopedDeclarationPrivacy": true,
|
||||||
|
"FullyIndirectEnum": true,
|
||||||
|
"GroupNumericLiterals": true,
|
||||||
|
"IdentifiersMustBeASCII": true,
|
||||||
|
"NeverForceUnwrap": false,
|
||||||
|
"NeverUseForceTry": false,
|
||||||
|
"NeverUseImplicitlyUnwrappedOptionals": false,
|
||||||
|
"NoAccessLevelOnExtensionDeclaration": true,
|
||||||
|
"NoBlockComments": true,
|
||||||
|
"NoCasesWithOnlyFallthrough": true,
|
||||||
|
"NoEmptyTrailingClosureParentheses": true,
|
||||||
|
"NoLabelsInCasePatterns": true,
|
||||||
|
"NoLeadingUnderscores": false,
|
||||||
|
"NoParensAroundConditions": true,
|
||||||
|
"NoVoidReturnOnFunctionSignature": true,
|
||||||
|
"OneCasePerLine": true,
|
||||||
|
"OneVariableDeclarationPerLine": true,
|
||||||
|
"OnlyOneTrailingClosureArgument": false,
|
||||||
|
"OrderedImports": true,
|
||||||
|
"ReturnVoidInsteadOfEmptyTuple": true,
|
||||||
|
"UseEarlyExits": false,
|
||||||
|
"UseLetInEveryBoundCaseVariable": true,
|
||||||
|
"UseShorthandTypeNames": true,
|
||||||
|
"UseSingleLinePropertyGetter": true,
|
||||||
|
"UseSynthesizedInitializer": true,
|
||||||
|
"UseTripleSlashForDocumentationComments": true,
|
||||||
|
"UseWhereClausesInForLoops": false,
|
||||||
|
"ValidateDocumentationComments": false
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,16 +1,21 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
# Format C/C++ files with clang-format
|
|
||||||
echo "Formatting C/C++ files..."
|
|
||||||
|
|
||||||
dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
|
dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
|
||||||
|
|
||||||
|
# Source logging utilities
|
||||||
|
source "$dir/logging/logging.sh"
|
||||||
|
|
||||||
|
log_title "C/C++ Formatting"
|
||||||
|
|
||||||
# Store original directory
|
# Store original directory
|
||||||
pushd "$dir" > /dev/null
|
pushd "$dir" > /dev/null
|
||||||
|
|
||||||
if [ ! -f ".clang-format" ]; then
|
log_action "Checking for formatters/.clang-format"
|
||||||
echo "Error: .clang-format not found in formatters directory"
|
if [ -f ".clang-format" ]; then
|
||||||
|
log_ok
|
||||||
|
else
|
||||||
|
log_fail
|
||||||
popd > /dev/null
|
popd > /dev/null
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
@ -22,47 +27,47 @@ cpp_dirs=(
|
|||||||
"../spine-cpp/src/spine"
|
"../spine-cpp/src/spine"
|
||||||
"../spine-cpp/spine-cpp-lite"
|
"../spine-cpp/spine-cpp-lite"
|
||||||
"../spine-cpp/tests"
|
"../spine-cpp/tests"
|
||||||
|
|
||||||
# spine-c
|
# spine-c
|
||||||
"../spine-c/include"
|
"../spine-c/include"
|
||||||
"../spine-c/src"
|
"../spine-c/src"
|
||||||
"../spine-c/src/generated"
|
"../spine-c/src/generated"
|
||||||
"../spine-c/tests"
|
"../spine-c/tests"
|
||||||
|
|
||||||
# spine-godot
|
# spine-godot
|
||||||
"../spine-godot/spine_godot"
|
"../spine-godot/spine_godot"
|
||||||
|
|
||||||
# spine-ue
|
# spine-ue
|
||||||
"../spine-ue/Source/SpineUE"
|
"../spine-ue/Source/SpineUE"
|
||||||
"../spine-ue/Plugins/SpinePlugin/Source/SpinePlugin/Public"
|
"../spine-ue/Plugins/SpinePlugin/Source/SpinePlugin/Public"
|
||||||
"../spine-ue/Plugins/SpinePlugin/Source/SpinePlugin/Private"
|
"../spine-ue/Plugins/SpinePlugin/Source/SpinePlugin/Private"
|
||||||
"../spine-ue/Plugins/SpinePlugin/Source/SpineEditorPlugin/Public"
|
"../spine-ue/Plugins/SpinePlugin/Source/SpineEditorPlugin/Public"
|
||||||
"../spine-ue/Plugins/SpinePlugin/Source/SpineEditorPlugin/Private"
|
"../spine-ue/Plugins/SpinePlugin/Source/SpineEditorPlugin/Private"
|
||||||
|
|
||||||
# spine-glfw
|
# spine-glfw
|
||||||
"../spine-glfw/src"
|
"../spine-glfw/src"
|
||||||
"../spine-glfw/example"
|
"../spine-glfw/example"
|
||||||
|
|
||||||
# spine-sdl
|
# spine-sdl
|
||||||
"../spine-sdl/src"
|
"../spine-sdl/src"
|
||||||
"../spine-sdl/example"
|
"../spine-sdl/example"
|
||||||
|
|
||||||
# spine-sfml
|
# spine-sfml
|
||||||
"../spine-sfml/c/src/spine"
|
"../spine-sfml/c/src/spine"
|
||||||
"../spine-sfml/c/example"
|
"../spine-sfml/c/example"
|
||||||
"../spine-sfml/cpp/src/spine"
|
"../spine-sfml/cpp/src/spine"
|
||||||
"../spine-sfml/cpp/example"
|
"../spine-sfml/cpp/example"
|
||||||
|
|
||||||
# spine-cocos2dx
|
# spine-cocos2dx
|
||||||
"../spine-cocos2dx/spine-cocos2dx/src/spine"
|
"../spine-cocos2dx/spine-cocos2dx/src/spine"
|
||||||
"../spine-cocos2dx/example/Classes"
|
"../spine-cocos2dx/example/Classes"
|
||||||
|
|
||||||
# spine-ios
|
# spine-ios
|
||||||
"../spine-ios/Sources/SpineCppLite"
|
"../spine-ios/Sources/SpineCppLite"
|
||||||
"../spine-ios/Sources/SpineCppLite/include"
|
"../spine-ios/Sources/SpineCppLite/include"
|
||||||
"../spine-ios/Sources/SpineShadersStructs"
|
"../spine-ios/Sources/SpineShadersStructs"
|
||||||
"../spine-ios/Example/Spine iOS Example"
|
"../spine-ios/Example/Spine iOS Example"
|
||||||
|
|
||||||
# spine-flutter
|
# spine-flutter
|
||||||
"../spine-flutter/ios/Classes"
|
"../spine-flutter/ios/Classes"
|
||||||
"../spine-flutter/macos/Classes"
|
"../spine-flutter/macos/Classes"
|
||||||
@ -86,22 +91,16 @@ for cpp_dir in "${cpp_dirs[@]}"; do
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
echo "Found ${#files[@]} C/C++ files to format"
|
|
||||||
|
|
||||||
# Format all files in one call - works for both Docker and native
|
log_action "Formatting ${#files[@]} C/C++ files"
|
||||||
echo "Formatting ${#files[@]} files..."
|
if FORMAT_OUTPUT=$(clang-format -i -style=file:".clang-format" "${files[@]}" 2>&1); then
|
||||||
if ! clang-format -i -style=file:".clang-format" "${files[@]}" 2>&1; then
|
log_ok
|
||||||
echo "Error: clang-format failed"
|
|
||||||
errors=1
|
|
||||||
else
|
else
|
||||||
errors=0
|
log_fail
|
||||||
|
log_error_output "$FORMAT_OUTPUT"
|
||||||
|
popd > /dev/null
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ $errors -gt 0 ]; then
|
|
||||||
echo "Completed with $errors errors"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "C/C++ formatting complete"
|
|
||||||
|
|
||||||
# Return to original directory
|
# Return to original directory
|
||||||
popd > /dev/null
|
popd > /dev/null
|
||||||
@ -1,48 +1,66 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
# Format C# files with dotnet-format
|
|
||||||
echo "Formatting C# files..."
|
|
||||||
|
|
||||||
dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
|
dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
|
||||||
|
|
||||||
|
# Source logging utilities
|
||||||
|
source "$dir/logging/logging.sh"
|
||||||
|
|
||||||
|
log_title "C# Formatting"
|
||||||
|
|
||||||
if command -v dotnet &> /dev/null; then
|
if command -v dotnet &> /dev/null; then
|
||||||
# Store original directory
|
# Store original directory
|
||||||
pushd "$dir" > /dev/null
|
pushd "$dir" > /dev/null
|
||||||
|
|
||||||
# Copy .editorconfig to C# directories
|
|
||||||
cp .editorconfig ../spine-csharp/ 2>/dev/null || true
|
cp .editorconfig ../spine-csharp/ 2>/dev/null || true
|
||||||
cp .editorconfig ../spine-monogame/ 2>/dev/null || true
|
cp .editorconfig ../spine-monogame/ 2>/dev/null || true
|
||||||
cp .editorconfig ../spine-unity/ 2>/dev/null || true
|
cp .editorconfig ../spine-unity/ 2>/dev/null || true
|
||||||
|
|
||||||
# Format spine-csharp
|
# Format spine-csharp
|
||||||
|
log_action "Formatting spine-csharp"
|
||||||
pushd ../spine-csharp > /dev/null
|
pushd ../spine-csharp > /dev/null
|
||||||
dotnet format spine-csharp.csproj --no-restore --verbosity quiet 2>/dev/null || echo "Warning: Some issues with spine-csharp formatting"
|
if DOTNET_OUTPUT=$(dotnet format spine-csharp.csproj --no-restore --verbosity quiet 2>&1); then
|
||||||
|
log_ok
|
||||||
|
else
|
||||||
|
log_warn
|
||||||
|
log_detail "$DOTNET_OUTPUT"
|
||||||
|
fi
|
||||||
popd > /dev/null
|
popd > /dev/null
|
||||||
|
|
||||||
# Format spine-monogame
|
# Format spine-monogame
|
||||||
|
log_action "Formatting spine-monogame"
|
||||||
pushd ../spine-monogame > /dev/null
|
pushd ../spine-monogame > /dev/null
|
||||||
dotnet format --no-restore --verbosity quiet 2>/dev/null || echo "Warning: Some issues with spine-monogame formatting"
|
if DOTNET_OUTPUT=$(dotnet format --no-restore --verbosity quiet 2>&1); then
|
||||||
|
log_ok
|
||||||
|
else
|
||||||
|
log_warn
|
||||||
|
log_detail "$DOTNET_OUTPUT"
|
||||||
|
fi
|
||||||
popd > /dev/null
|
popd > /dev/null
|
||||||
|
|
||||||
# Format spine-unity - look for .cs files directly
|
# Format spine-unity - look for .cs files directly
|
||||||
if [ -d ../spine-unity ]; then
|
log_action "Formatting spine-unity C# files"
|
||||||
echo "Formatting spine-unity C# files directly..."
|
pushd ../spine-unity > /dev/null
|
||||||
pushd ../spine-unity > /dev/null
|
# Find all .cs files and format them using dotnet format whitespace
|
||||||
# Find all .cs files and format them using dotnet format whitespace
|
cs_files=$(find . -name "*.cs" -type f -not -path "./Library/*" -not -path "./Temp/*" -not -path "./obj/*" -not -path "./bin/*" | wc -l | tr -d ' ')
|
||||||
|
if [ "$cs_files" -gt 0 ]; then
|
||||||
find . -name "*.cs" -type f -not -path "./Library/*" -not -path "./Temp/*" -not -path "./obj/*" -not -path "./bin/*" | while read -r file; do
|
find . -name "*.cs" -type f -not -path "./Library/*" -not -path "./Temp/*" -not -path "./obj/*" -not -path "./bin/*" | while read -r file; do
|
||||||
dotnet format whitespace --include "$file" --no-restore 2>/dev/null || true
|
dotnet format whitespace --include "$file" --no-restore 2>/dev/null || true
|
||||||
done
|
done
|
||||||
popd > /dev/null
|
log_ok
|
||||||
|
else
|
||||||
|
log_skip
|
||||||
fi
|
fi
|
||||||
|
popd > /dev/null
|
||||||
|
|
||||||
# Clean up .editorconfig files
|
|
||||||
rm -f ../spine-csharp/.editorconfig
|
rm -f ../spine-csharp/.editorconfig
|
||||||
rm -f ../spine-monogame/.editorconfig
|
rm -f ../spine-monogame/.editorconfig
|
||||||
rm -f ../spine-unity/.editorconfig
|
rm -f ../spine-unity/.editorconfig
|
||||||
|
|
||||||
# Return to original directory
|
# Return to original directory
|
||||||
popd > /dev/null
|
popd > /dev/null
|
||||||
else
|
else
|
||||||
echo "Warning: dotnet not found. Skipping C# formatting."
|
log_fail
|
||||||
|
log_error_output "dotnet not found. Please install .NET SDK to format C# files."
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
@ -1,22 +1,44 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
# Format Dart files
|
|
||||||
echo "Formatting Dart files..."
|
|
||||||
|
|
||||||
dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
|
dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
|
||||||
|
|
||||||
|
# Source logging utilities
|
||||||
|
source "$dir/logging/logging.sh"
|
||||||
|
|
||||||
|
log_title "Dart Formatting"
|
||||||
|
|
||||||
# Store original directory
|
# Store original directory
|
||||||
pushd "$dir" > /dev/null
|
pushd "$dir" > /dev/null
|
||||||
|
|
||||||
if command -v dart &> /dev/null; then
|
if command -v dart &> /dev/null; then
|
||||||
find .. -name "*.dart" \
|
dart_files=$(find .. -name "*.dart" \
|
||||||
-not -path "*/.*" \
|
-not -path "*/.*" \
|
||||||
-not -path "*/node_modules/*" \
|
-not -path "*/node_modules/*" \
|
||||||
-not -path "*/build/*" \
|
-not -path "*/build/*" | wc -l | tr -d ' ')
|
||||||
-exec dart format --page-width 120 {} +
|
|
||||||
|
if [ "$dart_files" -gt 0 ]; then
|
||||||
|
log_action "Formatting $dart_files Dart files"
|
||||||
|
if DART_OUTPUT=$(find .. -name "*.dart" \
|
||||||
|
-not -path "*/.*" \
|
||||||
|
-not -path "*/node_modules/*" \
|
||||||
|
-not -path "*/build/*" \
|
||||||
|
-exec dart format --page-width 120 {} + 2>&1); then
|
||||||
|
log_ok
|
||||||
|
else
|
||||||
|
log_fail
|
||||||
|
log_error_output "$DART_OUTPUT"
|
||||||
|
popd > /dev/null
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log_action "Formatting Dart files"
|
||||||
|
log_skip
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
echo "Warning: dart not found. Skipping Dart formatting."
|
log_fail
|
||||||
|
log_error_output "dart not found. Please install Dart SDK to format Dart files."
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Return to original directory
|
# Return to original directory
|
||||||
|
|||||||
@ -1,21 +1,45 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
# Format Haxe files
|
|
||||||
echo "Formatting Haxe files..."
|
|
||||||
|
|
||||||
dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
|
dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
|
||||||
|
|
||||||
|
# Source logging utilities
|
||||||
|
source "$dir/logging/logging.sh"
|
||||||
|
|
||||||
|
log_title "Haxe Formatting"
|
||||||
|
|
||||||
# Store original directory
|
# Store original directory
|
||||||
pushd "$dir" > /dev/null
|
pushd "$dir" > /dev/null
|
||||||
|
|
||||||
if command -v haxelib &> /dev/null && haxelib list formatter &> /dev/null; then
|
if command -v haxelib &> /dev/null; then
|
||||||
# Format spine-haxe directory
|
log_action "Checking Haxe formatter availability"
|
||||||
if [ -d ../spine-haxe ]; then
|
if HAXELIB_OUTPUT=$(haxelib list formatter 2>&1); then
|
||||||
haxelib run formatter -s ../spine-haxe
|
log_ok
|
||||||
|
|
||||||
|
# Format spine-haxe directory
|
||||||
|
if [ -d ../spine-haxe ]; then
|
||||||
|
log_action "Formatting spine-haxe directory"
|
||||||
|
if FORMATTER_OUTPUT=$(haxelib run formatter -s ../spine-haxe 2>&1); then
|
||||||
|
log_ok
|
||||||
|
else
|
||||||
|
log_fail
|
||||||
|
log_error_output "$FORMATTER_OUTPUT"
|
||||||
|
popd > /dev/null
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log_action "Formatting spine-haxe directory"
|
||||||
|
log_skip
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log_fail
|
||||||
|
log_error_output "Haxe formatter not found. Install with: haxelib install formatter"
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
echo "Warning: haxe formatter not found. Install with: haxelib install formatter"
|
log_fail
|
||||||
|
log_error_output "haxelib not found. Please install Haxe to format Haxe files."
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Return to original directory
|
# Return to original directory
|
||||||
|
|||||||
@ -1,11 +1,13 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
# Format Java files with Eclipse formatter
|
|
||||||
echo "Formatting Java files..."
|
|
||||||
|
|
||||||
dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
|
dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
|
||||||
|
|
||||||
|
# Source logging utilities
|
||||||
|
source "$dir/logging/logging.sh"
|
||||||
|
|
||||||
|
log_title "Java Formatting"
|
||||||
|
|
||||||
# Store original directory
|
# Store original directory
|
||||||
pushd "$dir" > /dev/null
|
pushd "$dir" > /dev/null
|
||||||
|
|
||||||
@ -14,9 +16,17 @@ jar_file="eclipse-formatter/target/eclipse-formatter-1.0.0-jar-with-dependencies
|
|||||||
src_file="eclipse-formatter/src/main/java/com/esotericsoftware/spine/formatter/EclipseFormatter.java"
|
src_file="eclipse-formatter/src/main/java/com/esotericsoftware/spine/formatter/EclipseFormatter.java"
|
||||||
|
|
||||||
if [ ! -f "$jar_file" ] || [ "$src_file" -nt "$jar_file" ]; then
|
if [ ! -f "$jar_file" ] || [ "$src_file" -nt "$jar_file" ]; then
|
||||||
echo "Building Eclipse formatter..."
|
log_action "Building Eclipse formatter"
|
||||||
pushd eclipse-formatter > /dev/null
|
pushd eclipse-formatter > /dev/null
|
||||||
mvn -q clean package
|
if MVN_OUTPUT=$(mvn -q clean package 2>&1); then
|
||||||
|
log_ok
|
||||||
|
else
|
||||||
|
log_fail
|
||||||
|
log_error_output "$MVN_OUTPUT"
|
||||||
|
popd > /dev/null
|
||||||
|
popd > /dev/null
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
popd > /dev/null
|
popd > /dev/null
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -30,13 +40,22 @@ java_files=$(find ../spine-libgdx ../spine-android -name "*.java" -type f \
|
|||||||
|
|
||||||
# Run the formatter
|
# Run the formatter
|
||||||
if [ -n "$java_files" ]; then
|
if [ -n "$java_files" ]; then
|
||||||
echo "Running Eclipse formatter on Java files..."
|
java_count=$(echo "$java_files" | wc -l | tr -d ' ')
|
||||||
java -jar eclipse-formatter/target/eclipse-formatter-1.0.0-jar-with-dependencies.jar \
|
log_action "Formatting $java_count Java files"
|
||||||
|
if FORMATTER_OUTPUT=$(java -jar eclipse-formatter/target/eclipse-formatter-1.0.0-jar-with-dependencies.jar \
|
||||||
eclipse-formatter.xml \
|
eclipse-formatter.xml \
|
||||||
$java_files
|
$java_files 2>&1); then
|
||||||
|
log_ok
|
||||||
|
else
|
||||||
|
log_fail
|
||||||
|
log_error_output "$FORMATTER_OUTPUT"
|
||||||
|
popd > /dev/null
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log_action "Formatting Java files"
|
||||||
|
log_skip
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Java formatting complete"
|
|
||||||
|
|
||||||
# Return to original directory
|
# Return to original directory
|
||||||
popd > /dev/null
|
popd > /dev/null
|
||||||
@ -1,22 +1,45 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
# Format Swift files
|
|
||||||
echo "Formatting Swift files..."
|
|
||||||
|
|
||||||
dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
|
dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
|
||||||
|
|
||||||
|
# Source logging utilities
|
||||||
|
source "$dir/logging/logging.sh"
|
||||||
|
|
||||||
|
log_title "Swift Formatting"
|
||||||
|
|
||||||
# Store original directory
|
# Store original directory
|
||||||
pushd "$dir" > /dev/null
|
pushd "$dir" > /dev/null
|
||||||
|
|
||||||
if command -v swift-format &> /dev/null; then
|
if command -v swift-format &> /dev/null; then
|
||||||
find .. -name "*.swift" \
|
|
||||||
|
swift_files=$(find .. -name "*.swift" -type f \
|
||||||
-not -path "*/.*" \
|
-not -path "*/.*" \
|
||||||
-not -path "*/build/*" \
|
-not -path "*/build/*" \
|
||||||
-not -path "*/DerivedData/*" \
|
-not -path "*/DerivedData/*" | wc -l | tr -d ' ')
|
||||||
| xargs swift-format -i
|
|
||||||
|
if [ "$swift_files" -gt 0 ]; then
|
||||||
|
log_action "Formatting $swift_files Swift files"
|
||||||
|
if SWIFT_OUTPUT=$(find .. -name "*.swift" -type f \
|
||||||
|
-not -path "*/.*" \
|
||||||
|
-not -path "*/build/*" \
|
||||||
|
-not -path "*/DerivedData/*" \
|
||||||
|
-print0 | xargs -0 swift-format --in-place --configuration .swift-format 2>&1); then
|
||||||
|
log_ok
|
||||||
|
else
|
||||||
|
log_fail
|
||||||
|
log_error_output "$SWIFT_OUTPUT"
|
||||||
|
popd > /dev/null
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log_action "Formatting Swift files"
|
||||||
|
log_skip
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
echo "Warning: swift-format not found. Install from https://github.com/apple/swift-format"
|
log_fail
|
||||||
|
log_error_output "swift-format not found. Install via brew install swift-format"
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Return to original directory
|
# Return to original directory
|
||||||
|
|||||||
@ -1,29 +1,53 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
# Format TypeScript files with tsfmt
|
|
||||||
echo "Formatting TypeScript files..."
|
|
||||||
|
|
||||||
dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
|
dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
|
||||||
|
|
||||||
|
# Source logging utilities
|
||||||
|
source "$dir/logging/logging.sh"
|
||||||
|
|
||||||
|
log_title "TypeScript Formatting"
|
||||||
|
|
||||||
# Store original directory
|
# Store original directory
|
||||||
pushd "$dir" > /dev/null
|
pushd "$dir" > /dev/null
|
||||||
|
|
||||||
# Check if tsfmt.json files match
|
# Check if tsfmt.json files match
|
||||||
if ! cmp -s ../spine-ts/tsfmt.json ../tests/tsfmt.json; then
|
log_action "Checking tsfmt.json consistency"
|
||||||
echo -e "\033[1;31mERROR: spine-ts/tsfmt.json and tests/tsfmt.json differ!\033[0m"
|
if CMP_OUTPUT=$(cmp -s ../spine-ts/tsfmt.json ../tests/tsfmt.json 2>&1); then
|
||||||
echo -e "\033[1;31mPlease sync them to ensure consistent formatting.\033[0m"
|
log_ok
|
||||||
|
else
|
||||||
|
log_fail
|
||||||
|
log_error_output "spine-ts/tsfmt.json and tests/tsfmt.json differ!"
|
||||||
|
log_detail "Please sync them to ensure consistent formatting."
|
||||||
popd > /dev/null
|
popd > /dev/null
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Format TypeScript files
|
# Format TypeScript files
|
||||||
|
log_action "Formatting spine-ts TypeScript files"
|
||||||
pushd ../spine-ts > /dev/null
|
pushd ../spine-ts > /dev/null
|
||||||
npm run format
|
if NPM_OUTPUT=$(npm run format 2>&1); then
|
||||||
|
log_ok
|
||||||
|
else
|
||||||
|
log_fail
|
||||||
|
log_error_output "$NPM_OUTPUT"
|
||||||
|
popd > /dev/null
|
||||||
|
popd > /dev/null
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
popd > /dev/null
|
popd > /dev/null
|
||||||
|
|
||||||
|
log_action "Formatting tests TypeScript files"
|
||||||
pushd ../tests > /dev/null
|
pushd ../tests > /dev/null
|
||||||
npm run format
|
if NPM_OUTPUT=$(npm run format 2>&1); then
|
||||||
|
log_ok
|
||||||
|
else
|
||||||
|
log_fail
|
||||||
|
log_error_output "$NPM_OUTPUT"
|
||||||
|
popd > /dev/null
|
||||||
|
popd > /dev/null
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
popd > /dev/null
|
popd > /dev/null
|
||||||
|
|
||||||
# Return to original directory
|
# Return to original directory
|
||||||
|
|||||||
@ -89,7 +89,8 @@ while [[ $# -gt 0 ]]; do
|
|||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
log_fail "Unknown option: $1"
|
log_fail
|
||||||
|
log_error_output "Unknown option: $1"
|
||||||
log_detail "Use --help for usage information"
|
log_detail "Use --help for usage information"
|
||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
@ -98,54 +99,33 @@ done
|
|||||||
|
|
||||||
log_title "Code Formatting"
|
log_title "Code Formatting"
|
||||||
|
|
||||||
# Call individual formatter scripts
|
# Call individual formatter scripts (they handle their own logging)
|
||||||
if [ "$FORMAT_CPP" = true ]; then
|
if [ "$FORMAT_CPP" = true ]; then
|
||||||
log_section "C/C++"
|
|
||||||
log_action "Formatting C/C++ files"
|
|
||||||
"$dir/format-cpp.sh"
|
"$dir/format-cpp.sh"
|
||||||
log_ok "C/C++ formatting completed"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$FORMAT_JAVA" = true ]; then
|
if [ "$FORMAT_JAVA" = true ]; then
|
||||||
log_section "Java"
|
|
||||||
log_action "Formatting Java files"
|
|
||||||
"$dir/format-java.sh"
|
"$dir/format-java.sh"
|
||||||
log_ok "Java formatting completed"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$FORMAT_CSHARP" = true ]; then
|
if [ "$FORMAT_CSHARP" = true ]; then
|
||||||
log_section "C#"
|
|
||||||
log_action "Formatting C# files"
|
|
||||||
"$dir/format-csharp.sh"
|
"$dir/format-csharp.sh"
|
||||||
log_ok "C# formatting completed"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$FORMAT_TS" = true ]; then
|
if [ "$FORMAT_TS" = true ]; then
|
||||||
log_section "TypeScript"
|
|
||||||
log_action "Formatting TypeScript files"
|
|
||||||
"$dir/format-ts.sh"
|
"$dir/format-ts.sh"
|
||||||
log_ok "TypeScript formatting completed"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$FORMAT_DART" = true ]; then
|
if [ "$FORMAT_DART" = true ]; then
|
||||||
log_section "Dart"
|
|
||||||
log_action "Formatting Dart files"
|
|
||||||
"$dir/format-dart.sh"
|
"$dir/format-dart.sh"
|
||||||
log_ok "Dart formatting completed"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$FORMAT_HAXE" = true ]; then
|
if [ "$FORMAT_HAXE" = true ]; then
|
||||||
log_section "Haxe"
|
|
||||||
log_action "Formatting Haxe files"
|
|
||||||
"$dir/format-haxe.sh"
|
"$dir/format-haxe.sh"
|
||||||
log_ok "Haxe formatting completed"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$FORMAT_SWIFT" = true ]; then
|
if [ "$FORMAT_SWIFT" = true ]; then
|
||||||
log_section "Swift"
|
|
||||||
log_action "Formatting Swift files"
|
|
||||||
"$dir/format-swift.sh"
|
"$dir/format-swift.sh"
|
||||||
log_ok "Swift formatting completed"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log_summary "✓ All formatting completed"
|
log_summary "✓ All formatting completed"
|
||||||
@ -43,42 +43,41 @@ NC='\033[0m' # No Color
|
|||||||
|
|
||||||
# Design principles:
|
# Design principles:
|
||||||
# 1. Minimal visual noise - use color sparingly for emphasis
|
# 1. Minimal visual noise - use color sparingly for emphasis
|
||||||
# 2. Clear hierarchy - different levels of information have different treatments
|
# 2. Clear hierarchy - different levels of information have different treatments
|
||||||
# 3. Consistent spacing - clean vertical rhythm
|
# 3. Consistent spacing - clean vertical rhythm
|
||||||
# 4. Accessible - readable without colors
|
# 4. Accessible - readable without colors
|
||||||
|
|
||||||
# Main header for script/tool name
|
# Main header for script/tool name
|
||||||
log_title() {
|
log_title() {
|
||||||
echo ""
|
echo -e "${GREEN}${BOLD}$1${NC}"
|
||||||
echo -e "${BOLD}$1${NC}"
|
|
||||||
echo ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Section headers for major phases
|
|
||||||
log_section() {
|
|
||||||
echo -e "${BOLD}${BLUE}$1${NC}"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Individual actions/steps
|
# Individual actions/steps - inline result format
|
||||||
log_action() {
|
log_action() {
|
||||||
echo -e " $1..."
|
echo -n " $1... "
|
||||||
}
|
}
|
||||||
|
|
||||||
# Results - success/failure/info
|
# Results - success/failure/info (on same line)
|
||||||
log_ok() {
|
log_ok() {
|
||||||
echo -e " ${GREEN}✓${NC} $1"
|
echo -e "${GREEN}✓${NC}"
|
||||||
}
|
}
|
||||||
|
|
||||||
log_fail() {
|
log_fail() {
|
||||||
echo -e " ${RED}✗${NC} $1"
|
echo -e "${RED}✗${NC}"
|
||||||
}
|
}
|
||||||
|
|
||||||
log_warn() {
|
log_warn() {
|
||||||
echo -e " ${YELLOW}!${NC} $1"
|
echo -e "${YELLOW}!${NC}"
|
||||||
}
|
}
|
||||||
|
|
||||||
log_skip() {
|
log_skip() {
|
||||||
echo -e " ${GRAY}-${NC} $1"
|
echo -e "${GRAY}-${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# For errors, show full output prominently (not grayed)
|
||||||
|
log_error_output() {
|
||||||
|
echo "$1"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Final summary
|
# Final summary
|
||||||
|
|||||||
@ -18,48 +18,51 @@ This guide defines the terminal output style for all bash scripts in the Spine R
|
|||||||
- **Usage**: Once at the beginning of script execution
|
- **Usage**: Once at the beginning of script execution
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
log_title "Spine-C++ Test"
|
log_title "Spine-C++ Build"
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Section (`log_section`)
|
### 2. Action + Result (inline format)
|
||||||
- **Purpose**: Major phases or groups of operations
|
- **Purpose**: Individual operations with immediate result
|
||||||
- **Style**: Bold blue text, no extra spacing
|
- **Style**: Action on same line as result for density
|
||||||
- **Usage**: Build, Test, Deploy, etc.
|
- **Usage**: `log_action` followed immediately by `log_ok/fail/warn/skip`
|
||||||
|
|
||||||
```bash
|
|
||||||
log_section "Build"
|
|
||||||
log_section "Test"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Action (`log_action`)
|
|
||||||
- **Purpose**: Individual operations in progress
|
|
||||||
- **Style**: Indented, followed by "..."
|
|
||||||
- **Usage**: Before starting an operation
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
log_action "Building all variants"
|
log_action "Building all variants"
|
||||||
log_action "Testing headless-test"
|
log_ok # Green ✓ on same line
|
||||||
|
|
||||||
|
log_action "Testing headless-test"
|
||||||
|
log_fail # Red ✗ on same line
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4. Results
|
**Results**:
|
||||||
- **Purpose**: Outcome of operations
|
- `log_ok` - Green ✓ (success)
|
||||||
- **Style**: Indented with colored symbols
|
- `log_fail` - Red ✗ (failure)
|
||||||
|
- `log_warn` - Yellow ! (warning)
|
||||||
|
- `log_skip` - Gray - (skipped)
|
||||||
|
|
||||||
|
### 4. Error Output (`log_error_output`)
|
||||||
|
- **Purpose**: Full error output when operations fail
|
||||||
|
- **Style**: Normal text (not grayed), prominent
|
||||||
|
- **Usage**: Show command output after failures
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
log_ok "Build completed" # Green ✓
|
log_action "Building"
|
||||||
log_fail "Build failed" # Red ✗
|
if BUILD_OUTPUT=$(command 2>&1); then
|
||||||
log_warn "Deprecated feature" # Yellow !
|
log_ok
|
||||||
log_skip "Not supported on macOS" # Gray -
|
else
|
||||||
|
log_fail
|
||||||
|
log_error_output "$BUILD_OUTPUT"
|
||||||
|
fi
|
||||||
```
|
```
|
||||||
|
|
||||||
### 5. Detail (`log_detail`)
|
### 5. Detail (`log_detail`)
|
||||||
- **Purpose**: Secondary information, error output, debug info
|
- **Purpose**: Secondary information, debug info (not errors)
|
||||||
- **Style**: Gray text, indented
|
- **Style**: Gray text, indented
|
||||||
- **Usage**: Additional context, error messages
|
- **Usage**: Additional context, platform info
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
log_detail "Platform: Darwin"
|
log_detail "Platform: Darwin"
|
||||||
log_detail "$ERROR_OUTPUT"
|
log_detail "Branch: main"
|
||||||
```
|
```
|
||||||
|
|
||||||
### 6. Summary (`log_summary`)
|
### 6. Summary (`log_summary`)
|
||||||
@ -78,58 +81,90 @@ log_summary "✗ Tests failed (3/5)"
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
source ../formatters/logging/logging.sh
|
source ../formatters/logging/logging.sh
|
||||||
|
|
||||||
log_title "Spine-C++ Test"
|
log_title "Spine-C++ Build"
|
||||||
log_detail "Platform: $(uname)"
|
log_detail "Platform: $(uname)"
|
||||||
|
|
||||||
log_section "Build"
|
log_action "Configuring debug build"
|
||||||
log_action "Building all variants"
|
if CMAKE_OUTPUT=$(cmake --preset=debug . 2>&1); then
|
||||||
if BUILD_OUTPUT=$(./build.sh clean release 2>&1); then
|
log_ok
|
||||||
log_ok "Build completed"
|
|
||||||
else
|
else
|
||||||
log_fail "Build failed"
|
log_fail
|
||||||
log_detail "$BUILD_OUTPUT"
|
log_error_output "$CMAKE_OUTPUT"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log_section "Test"
|
log_action "Building"
|
||||||
log_action "Testing headless-test"
|
if BUILD_OUTPUT=$(cmake --build --preset=debug 2>&1); then
|
||||||
if test_result; then
|
log_ok
|
||||||
log_ok "headless-test"
|
|
||||||
else
|
else
|
||||||
log_fail "headless-test - execution failed"
|
log_fail
|
||||||
log_detail "$error_output"
|
log_error_output "$BUILD_OUTPUT"
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log_summary "✓ All tests passed (2/2)"
|
log_summary "✓ Build successful"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Output Preview
|
## Output Preview
|
||||||
|
|
||||||
|
**Success case:**
|
||||||
```
|
```
|
||||||
Spine-C++ Test
|
Spine-C++ Build
|
||||||
|
|
||||||
Platform: Darwin
|
Platform: Darwin
|
||||||
|
|
||||||
Build
|
Configuring debug build... ✓
|
||||||
Building all variants...
|
Building... ✓
|
||||||
✓ Build completed
|
|
||||||
|
|
||||||
Test
|
✓ Build successful
|
||||||
Testing headless-test...
|
```
|
||||||
✓ headless-test
|
|
||||||
Testing headless-test-nostdcpp...
|
|
||||||
✓ headless-test-nostdcpp
|
|
||||||
|
|
||||||
✓ All tests passed (2/2)
|
**Failure case:**
|
||||||
|
```
|
||||||
|
Spine-C++ Build
|
||||||
|
Platform: Darwin
|
||||||
|
|
||||||
|
Configuring debug build... ✗
|
||||||
|
CMake Error: Could not find cmake file...
|
||||||
|
(full error output shown prominently)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Error Handling Best Practices
|
## Error Handling Best Practices
|
||||||
|
|
||||||
1. **Capture output**: Use `OUTPUT=$(command 2>&1)` to capture both stdout and stderr
|
1. **Capture output**: Use `OUTPUT=$(command 2>&1)` to capture both stdout and stderr
|
||||||
2. **Check exit codes**: Always check if critical operations succeeded
|
2. **Check exit codes**: Always check if critical operations succeeded
|
||||||
3. **Show details on failure**: Use `log_detail` to show error output
|
3. **Show errors prominently**: Use `log_error_output` for command failures (not grayed)
|
||||||
4. **Fail fast**: Exit immediately on critical failures
|
4. **Fail fast**: Exit immediately on critical failures
|
||||||
5. **Clear error messages**: Make failure reasons obvious
|
5. **Use inline results**: `log_action` + `log_ok/fail` for dense, scannable output
|
||||||
|
|
||||||
|
## Calling Other Scripts
|
||||||
|
|
||||||
|
When calling other bash scripts that have their own logging:
|
||||||
|
|
||||||
|
1. **Trust their logging**: Don't wrap calls in redundant log actions
|
||||||
|
2. **Capture output for errors**: Use `OUTPUT=$(script.sh 2>&1)` to capture output and only show on failure
|
||||||
|
3. **Let them handle success**: Avoid "script completed" messages when the script shows its own status
|
||||||
|
|
||||||
|
**Good**:
|
||||||
|
```bash
|
||||||
|
# Let the script handle its own logging
|
||||||
|
../formatters/format.sh cpp
|
||||||
|
|
||||||
|
# Or capture output and only show on error
|
||||||
|
if output=$(./setup.sh 2>&1); then
|
||||||
|
log_ok "Setup completed"
|
||||||
|
else
|
||||||
|
log_fail "Setup failed"
|
||||||
|
log_detail "$output"
|
||||||
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
|
**Avoid**:
|
||||||
|
```bash
|
||||||
|
# This creates duplicate logging
|
||||||
|
log_action "Formatting C++ files"
|
||||||
|
../formatters/format.sh cpp
|
||||||
|
log_ok "C++ formatting completed"
|
||||||
|
```
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
if BUILD_OUTPUT=$(./build.sh clean release 2>&1); then
|
if BUILD_OUTPUT=$(./build.sh clean release 2>&1); then
|
||||||
|
|||||||
@ -34,34 +34,30 @@ fi
|
|||||||
|
|
||||||
# Run codegen if requested
|
# Run codegen if requested
|
||||||
if [ "$1" = "codegen" ]; then
|
if [ "$1" = "codegen" ]; then
|
||||||
log_title "Spine-C Code Generation"
|
log_title "spine-c code generation"
|
||||||
|
|
||||||
log_section "Generate"
|
|
||||||
log_action "Generating C bindings"
|
log_action "Generating C bindings"
|
||||||
if CODEGEN_OUTPUT=$(npx -y tsx codegen/src/index.ts 2>&1); then
|
if CODEGEN_OUTPUT=$(npx -y tsx codegen/src/index.ts 2>&1); then
|
||||||
log_ok "Code generation completed"
|
log_ok
|
||||||
else
|
else
|
||||||
log_fail "Code generation failed"
|
log_fail
|
||||||
log_detail "$CODEGEN_OUTPUT"
|
log_error_output "$CODEGEN_OUTPUT"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log_section "Format"
|
|
||||||
log_action "Formatting generated C++ files"
|
|
||||||
../formatters/format.sh cpp
|
../formatters/format.sh cpp
|
||||||
|
|
||||||
log_summary "✓ Code generation successful"
|
log_summary "✓ Code generation successful"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log_title "Spine-C Build"
|
log_title "spine-c build"
|
||||||
|
|
||||||
# Clean only if explicitly requested
|
# Clean only if explicitly requested
|
||||||
if [ "$1" = "clean" ]; then
|
if [ "$1" = "clean" ]; then
|
||||||
log_section "Clean"
|
log_action "Cleaning build directory"
|
||||||
log_action "Removing build directory"
|
|
||||||
rm -rf build
|
rm -rf build
|
||||||
log_ok "Cleaned"
|
log_ok
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Determine build type
|
# Determine build type
|
||||||
@ -71,23 +67,21 @@ if [ "$1" = "release" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Configure and build
|
# Configure and build
|
||||||
log_section "Configure"
|
|
||||||
log_action "Configuring $BUILD_TYPE build"
|
log_action "Configuring $BUILD_TYPE build"
|
||||||
if CMAKE_OUTPUT=$(cmake --preset=$BUILD_TYPE . 2>&1); then
|
if CMAKE_OUTPUT=$(cmake --preset=$BUILD_TYPE . 2>&1); then
|
||||||
log_ok "Configured"
|
log_ok
|
||||||
else
|
else
|
||||||
log_fail "Configuration failed"
|
log_fail
|
||||||
log_detail "$CMAKE_OUTPUT"
|
log_error_output "$CMAKE_OUTPUT"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log_section "Build"
|
|
||||||
log_action "Building"
|
log_action "Building"
|
||||||
if BUILD_OUTPUT=$(cmake --build --preset=$BUILD_TYPE 2>&1); then
|
if BUILD_OUTPUT=$(cmake --build --preset=$BUILD_TYPE 2>&1); then
|
||||||
log_ok "Build completed"
|
log_ok
|
||||||
else
|
else
|
||||||
log_fail "Build failed"
|
log_fail
|
||||||
log_detail "$BUILD_OUTPUT"
|
log_error_output "$BUILD_OUTPUT"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@ -33,34 +33,31 @@ for arg in "$@"; do
|
|||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
log_title "Spine-C++ Build"
|
log_title "spine-cpp build"
|
||||||
|
|
||||||
# Clean if requested
|
# Clean if requested
|
||||||
if [ "$CLEAN" = "true" ]; then
|
if [ "$CLEAN" = "true" ]; then
|
||||||
log_section "Clean"
|
log_action "Cleaning build directory"
|
||||||
log_action "Removing build directory"
|
|
||||||
rm -rf build
|
rm -rf build
|
||||||
log_ok "Cleaned"
|
log_ok
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Configure and build
|
# Configure and build
|
||||||
log_section "Configure"
|
|
||||||
log_action "Configuring $BUILD_TYPE build"
|
log_action "Configuring $BUILD_TYPE build"
|
||||||
if CMAKE_OUTPUT=$(cmake --preset=$BUILD_TYPE $NOFILEIO . 2>&1); then
|
if CMAKE_OUTPUT=$(cmake --preset=$BUILD_TYPE $NOFILEIO . 2>&1); then
|
||||||
log_ok "Configured"
|
log_ok
|
||||||
else
|
else
|
||||||
log_fail "Configuration failed"
|
log_fail
|
||||||
log_detail "$CMAKE_OUTPUT"
|
log_error_output "$CMAKE_OUTPUT"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log_section "Build"
|
|
||||||
log_action "Building"
|
log_action "Building"
|
||||||
if BUILD_OUTPUT=$(cmake --build --preset=$BUILD_TYPE 2>&1); then
|
if BUILD_OUTPUT=$(cmake --build --preset=$BUILD_TYPE 2>&1); then
|
||||||
log_ok "Build completed"
|
log_ok
|
||||||
else
|
else
|
||||||
log_fail "Build failed"
|
log_fail
|
||||||
log_detail "$BUILD_OUTPUT"
|
log_error_output "$BUILD_OUTPUT"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
#
|
#
|
||||||
# Tests all spine-cpp build variants with spineboy example data:
|
# Tests all spine-cpp build variants with spineboy example data:
|
||||||
# - headless-test (regular dynamic)
|
# - headless-test (regular dynamic)
|
||||||
# - headless-test-nostdcpp (nostdcpp dynamic)
|
# - headless-test-nostdcpp (nostdcpp dynamic)
|
||||||
# - headless-test-static (regular static, Linux only)
|
# - headless-test-static (regular static, Linux only)
|
||||||
# - headless-test-nostdcpp-static (nostdcpp static, Linux only)
|
# - headless-test-nostdcpp-static (nostdcpp static, Linux only)
|
||||||
|
|
||||||
@ -35,7 +35,6 @@ EXPECTED_OUTPUT="=== SKELETON DATA ===
|
|||||||
log_title "Spine-C++ Test"
|
log_title "Spine-C++ Test"
|
||||||
log_detail "Platform: $(uname)"
|
log_detail "Platform: $(uname)"
|
||||||
|
|
||||||
log_section "Build"
|
|
||||||
log_action "Building all variants"
|
log_action "Building all variants"
|
||||||
if BUILD_OUTPUT=$(./build.sh clean release 2>&1); then
|
if BUILD_OUTPUT=$(./build.sh clean release 2>&1); then
|
||||||
log_ok "Build completed"
|
log_ok "Build completed"
|
||||||
@ -45,8 +44,6 @@ else
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log_section "Test"
|
|
||||||
|
|
||||||
test_count=0
|
test_count=0
|
||||||
pass_count=0
|
pass_count=0
|
||||||
|
|
||||||
@ -54,12 +51,12 @@ for exe in build/headless-test*; do
|
|||||||
if [ -f "$exe" ] && [ -x "$exe" ]; then
|
if [ -f "$exe" ] && [ -x "$exe" ]; then
|
||||||
exe_name=$(basename "$exe")
|
exe_name=$(basename "$exe")
|
||||||
log_action "Testing $exe_name"
|
log_action "Testing $exe_name"
|
||||||
|
|
||||||
test_count=$((test_count + 1))
|
test_count=$((test_count + 1))
|
||||||
|
|
||||||
if OUTPUT=$("$exe" $SPINEBOY_SKEL $SPINEBOY_ATLAS $SPINEBOY_ANIM 2>&1); then
|
if OUTPUT=$("$exe" $SPINEBOY_SKEL $SPINEBOY_ATLAS $SPINEBOY_ANIM 2>&1); then
|
||||||
actual_output=$(echo "$OUTPUT" | head -10)
|
actual_output=$(echo "$OUTPUT" | head -10)
|
||||||
|
|
||||||
if [ "$actual_output" = "$EXPECTED_OUTPUT" ]; then
|
if [ "$actual_output" = "$EXPECTED_OUTPUT" ]; then
|
||||||
log_ok "$exe_name"
|
log_ok "$exe_name"
|
||||||
pass_count=$((pass_count + 1))
|
pass_count=$((pass_count + 1))
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
import UIKit
|
|
||||||
import Flutter
|
import Flutter
|
||||||
|
import UIKit
|
||||||
|
|
||||||
@UIApplicationMain
|
@UIApplicationMain
|
||||||
@objc class AppDelegate: FlutterAppDelegate {
|
@objc class AppDelegate: FlutterAppDelegate {
|
||||||
override func application(
|
override func application(
|
||||||
_ application: UIApplication,
|
_ application: UIApplication,
|
||||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||||
) -> Bool {
|
) -> Bool {
|
||||||
GeneratedPluginRegistrant.register(with: self)
|
GeneratedPluginRegistrant.register(with: self)
|
||||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,5 @@
|
|||||||
import FlutterMacOS
|
import FlutterMacOS
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
|
||||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import FlutterMacOS
|
|||||||
|
|
||||||
@NSApplicationMain
|
@NSApplicationMain
|
||||||
class AppDelegate: FlutterAppDelegate {
|
class AppDelegate: FlutterAppDelegate {
|
||||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,14 +2,14 @@ import Cocoa
|
|||||||
import FlutterMacOS
|
import FlutterMacOS
|
||||||
|
|
||||||
class MainFlutterWindow: NSWindow {
|
class MainFlutterWindow: NSWindow {
|
||||||
override func awakeFromNib() {
|
override func awakeFromNib() {
|
||||||
let flutterViewController = FlutterViewController.init()
|
let flutterViewController = FlutterViewController.init()
|
||||||
let windowFrame = self.frame
|
let windowFrame = self.frame
|
||||||
self.contentViewController = flutterViewController
|
self.contentViewController = flutterViewController
|
||||||
self.setFrame(windowFrame, display: true)
|
self.setFrame(windowFrame, display: true)
|
||||||
|
|
||||||
RegisterGeneratedPlugins(registry: flutterViewController)
|
RegisterGeneratedPlugins(registry: flutterViewController)
|
||||||
|
|
||||||
super.awakeFromNib()
|
super.awakeFromNib()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,18 +26,24 @@ if echo "$COMMIT_MSG" | grep -qE '^\[haxe\] Release [0-9]+\.[0-9]+\.[0-9]+$'; th
|
|||||||
if [ ! -z "$HAXE_UPDATE_URL" ] && [ ! -z "$BRANCH" ]; then
|
if [ ! -z "$HAXE_UPDATE_URL" ] && [ ! -z "$BRANCH" ]; then
|
||||||
log_section "Deploy"
|
log_section "Deploy"
|
||||||
log_action "Creating release package"
|
log_action "Creating release package"
|
||||||
zip -r "spine-haxe-$VERSION.zip" \
|
if ZIP_OUTPUT=$(zip -r "spine-haxe-$VERSION.zip" \
|
||||||
haxelib.json \
|
haxelib.json \
|
||||||
LICENSE \
|
LICENSE \
|
||||||
README.md \
|
README.md \
|
||||||
spine-haxe > /dev/null 2>&1
|
spine-haxe 2>&1); then
|
||||||
log_ok "Package created"
|
log_ok
|
||||||
|
else
|
||||||
|
log_fail
|
||||||
|
log_error_output "$ZIP_OUTPUT"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
log_action "Uploading to $HAXE_UPDATE_URL$BRANCH"
|
log_action "Uploading to $HAXE_UPDATE_URL$BRANCH"
|
||||||
if curl -f -F "file=@spine-haxe-$VERSION.zip" "$HAXE_UPDATE_URL$BRANCH" > /dev/null 2>&1; then
|
if CURL_OUTPUT=$(curl -f -F "file=@spine-haxe-$VERSION.zip" "$HAXE_UPDATE_URL$BRANCH" 2>&1); then
|
||||||
log_ok "Package deployed"
|
log_ok
|
||||||
else
|
else
|
||||||
log_fail "Upload failed"
|
log_fail
|
||||||
|
log_error_output "$CURL_OUTPUT"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@ -27,8 +27,8 @@
|
|||||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
import Spine
|
import Spine
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
struct SimpleAnimation: View {
|
struct SimpleAnimation: View {
|
||||||
|
|
||||||
@ -46,10 +46,10 @@ struct SimpleAnimation: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
SpineView(
|
SpineView(
|
||||||
from: .bundle(atlasFileName: "spineboy-pma.atlas", skeletonFileName: "spineboy-pro.skel"),
|
from: .bundle(atlasFileName: "spineboy-pma.atlas", skeletonFileName: "spineboy-pro.skel"),
|
||||||
// from: .http(
|
// from: .http(
|
||||||
// atlasURL: URL(string: "https://github.com/esotericsoftware/spine-runtimes/raw/spine-ios/spine-ios/Example/Spine%20iOS%20Example/Assets/spineboy/spineboy.atlas")!,
|
// atlasURL: URL(string: "https://github.com/esotericsoftware/spine-runtimes/raw/spine-ios/spine-ios/Example/Spine%20iOS%20Example/Assets/spineboy/spineboy.atlas")!,
|
||||||
// skeletonURL: URL(string: "https://github.com/esotericsoftware/spine-runtimes/raw/spine-ios/spine-ios/Example/Spine%20iOS%20Example/Assets/spineboy/spineboy-pro.skel")!
|
// skeletonURL: URL(string: "https://github.com/esotericsoftware/spine-runtimes/raw/spine-ios/spine-ios/Example/Spine%20iOS%20Example/Assets/spineboy/spineboy-pro.skel")!
|
||||||
// ),
|
// ),
|
||||||
controller: controller,
|
controller: controller,
|
||||||
mode: .fit,
|
mode: .fit,
|
||||||
alignment: .center
|
alignment: .center
|
||||||
|
|||||||
@ -5,8 +5,8 @@
|
|||||||
// Created by Denis Andrašec on 29.05.24.
|
// Created by Denis Andrašec on 29.05.24.
|
||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
import SpineCppLite
|
import SpineCppLite
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
@main
|
@main
|
||||||
struct Spine_iOS_ExampleApp: App {
|
struct Spine_iOS_ExampleApp: App {
|
||||||
|
|||||||
@ -27,12 +27,12 @@
|
|||||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
import Spine
|
import Spine
|
||||||
import SpineCppLite
|
import SpineCppLite
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
struct AnimationStateEvents: View {
|
struct AnimationStateEvents: View {
|
||||||
|
|
||||||
@StateObject
|
@StateObject
|
||||||
var controller = SpineController(
|
var controller = SpineController(
|
||||||
onInitialized: { controller in
|
onInitialized: { controller in
|
||||||
@ -42,23 +42,25 @@ struct AnimationStateEvents: View {
|
|||||||
controller.animationStateData.defaultMix = 0.2
|
controller.animationStateData.defaultMix = 0.2
|
||||||
let walk = controller.animationState.setAnimationByName(trackIndex: 0, animationName: "walk", loop: true)
|
let walk = controller.animationState.setAnimationByName(trackIndex: 0, animationName: "walk", loop: true)
|
||||||
controller.animationStateWrapper.setTrackEntryListener(entry: walk) { type, entry, event in
|
controller.animationStateWrapper.setTrackEntryListener(entry: walk) { type, entry, event in
|
||||||
print("Walk animation event \(type)");
|
print("Walk animation event \(type)")
|
||||||
}
|
}
|
||||||
controller.animationState.addAnimationByName(trackIndex: 0, animationName: "jump", loop: false, delay: 2)
|
controller.animationState.addAnimationByName(trackIndex: 0, animationName: "jump", loop: false, delay: 2)
|
||||||
let run = controller.animationState.addAnimationByName(trackIndex: 0, animationName: "run", loop: true, delay: 0)
|
let run = controller.animationState.addAnimationByName(trackIndex: 0, animationName: "run", loop: true, delay: 0)
|
||||||
controller.animationStateWrapper.setTrackEntryListener(entry: run) { type, entry, event in
|
controller.animationStateWrapper.setTrackEntryListener(entry: run) { type, entry, event in
|
||||||
print("Run animation event \(type)");
|
print("Run animation event \(type)")
|
||||||
}
|
}
|
||||||
controller.animationStateWrapper.setStateListener { type, entry, event in
|
controller.animationStateWrapper.setStateListener { type, entry, event in
|
||||||
if type == SPINE_EVENT_TYPE_EVENT, let event {
|
if type == SPINE_EVENT_TYPE_EVENT, let event {
|
||||||
print("User event: { name: \(event.data.name ?? "--"), intValue: \(event.intValue), floatValue: \(event.floatValue), stringValue: \(event.stringValue ?? "--") }")
|
print(
|
||||||
|
"User event: { name: \(event.data.name ?? "--"), intValue: \(event.intValue), floatValue: \(event.floatValue), stringValue: \(event.stringValue ?? "--") }"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let current = controller.animationState.getCurrent(trackIndex: 0)?.animation.name ?? "--"
|
let current = controller.animationState.getCurrent(trackIndex: 0)?.animation.name ?? "--"
|
||||||
print("Current: \(current)")
|
print("Current: \(current)")
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack {
|
VStack {
|
||||||
Text("See output in console!")
|
Text("See output in console!")
|
||||||
|
|||||||
@ -27,14 +27,14 @@
|
|||||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
import Spine
|
import Spine
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
struct DebugRendering: View {
|
struct DebugRendering: View {
|
||||||
|
|
||||||
@StateObject
|
@StateObject
|
||||||
var model = DebugRenderingModel()
|
var model = DebugRenderingModel()
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
Color.red.ignoresSafeArea()
|
Color.red.ignoresSafeArea()
|
||||||
@ -61,13 +61,13 @@ struct DebugRendering: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final class DebugRenderingModel: ObservableObject {
|
final class DebugRenderingModel: ObservableObject {
|
||||||
|
|
||||||
@Published
|
@Published
|
||||||
var controller: SpineController!
|
var controller: SpineController!
|
||||||
|
|
||||||
@Published
|
@Published
|
||||||
var boneRects = [BoneRect]()
|
var boneRects = [BoneRect]()
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
controller = SpineController(
|
controller = SpineController(
|
||||||
onInitialized: { controller in
|
onInitialized: { controller in
|
||||||
@ -77,8 +77,9 @@ final class DebugRenderingModel: ObservableObject {
|
|||||||
loop: true
|
loop: true
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onAfterPaint: {
|
onAfterPaint: {
|
||||||
[weak self] controller in guard let self else { return }
|
[weak self] controller in
|
||||||
|
guard let self else { return }
|
||||||
boneRects = controller.drawable.skeleton.bones.map { bone in
|
boneRects = controller.drawable.skeleton.bones.map { bone in
|
||||||
let position = controller.fromSkeletonCoordinates(
|
let position = controller.fromSkeletonCoordinates(
|
||||||
position: CGPointMake(CGFloat(bone.worldX), CGFloat(bone.worldY))
|
position: CGPointMake(CGFloat(bone.worldX), CGFloat(bone.worldY))
|
||||||
|
|||||||
@ -27,11 +27,11 @@
|
|||||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
import Spine
|
import Spine
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
struct DisableRendering: View {
|
struct DisableRendering: View {
|
||||||
|
|
||||||
@StateObject
|
@StateObject
|
||||||
var controller = SpineController(
|
var controller = SpineController(
|
||||||
onInitialized: { controller in
|
onInitialized: { controller in
|
||||||
@ -42,10 +42,10 @@ struct DisableRendering: View {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@State
|
@State
|
||||||
var isRendering: Bool?
|
var isRendering: Bool?
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack {
|
VStack {
|
||||||
List {
|
List {
|
||||||
@ -54,7 +54,7 @@ struct DisableRendering: View {
|
|||||||
Text("Rendering is disabled when the spine view moves out of the viewport, preserving CPU/GPU resources.")
|
Text("Rendering is disabled when the spine view moves out of the viewport, preserving CPU/GPU resources.")
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
}
|
}
|
||||||
|
|
||||||
SpineView(
|
SpineView(
|
||||||
from: .bundle(atlasFileName: "spineboy-pma.atlas", skeletonFileName: "spineboy-pro.skel"),
|
from: .bundle(atlasFileName: "spineboy-pma.atlas", skeletonFileName: "spineboy-pro.skel"),
|
||||||
controller: controller,
|
controller: controller,
|
||||||
@ -69,13 +69,13 @@ struct DisableRendering: View {
|
|||||||
isRendering = false
|
isRendering = false
|
||||||
print("rendering disabled")
|
print("rendering disabled")
|
||||||
}
|
}
|
||||||
|
|
||||||
Text("Foo")
|
Text("Foo")
|
||||||
.frame(minHeight: 400)
|
.frame(minHeight: 400)
|
||||||
|
|
||||||
Text("Bar")
|
Text("Bar")
|
||||||
.frame(minHeight: 400)
|
.frame(minHeight: 400)
|
||||||
|
|
||||||
Text("Baz")
|
Text("Baz")
|
||||||
.frame(minHeight: 400)
|
.frame(minHeight: 400)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,15 +27,15 @@
|
|||||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
import Spine
|
import Spine
|
||||||
import SpineCppLite
|
import SpineCppLite
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
struct DressUp: View {
|
struct DressUp: View {
|
||||||
|
|
||||||
@StateObject
|
@StateObject
|
||||||
var model = DressUpModel()
|
var model = DressUpModel()
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack(spacing: 0) {
|
HStack(spacing: 0) {
|
||||||
List {
|
List {
|
||||||
@ -51,9 +51,9 @@ struct DressUp: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.listStyle(.plain)
|
.listStyle(.plain)
|
||||||
|
|
||||||
Divider()
|
Divider()
|
||||||
|
|
||||||
if let drawable = model.drawable {
|
if let drawable = model.drawable {
|
||||||
SpineView(
|
SpineView(
|
||||||
from: .drawable(drawable),
|
from: .drawable(drawable),
|
||||||
@ -74,23 +74,23 @@ struct DressUp: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final class DressUpModel: ObservableObject {
|
final class DressUpModel: ObservableObject {
|
||||||
|
|
||||||
let thumbnailSize = CGSize(width: 200, height: 200)
|
let thumbnailSize = CGSize(width: 200, height: 200)
|
||||||
|
|
||||||
@Published
|
@Published
|
||||||
var controller: SpineController
|
var controller: SpineController
|
||||||
|
|
||||||
@Published
|
@Published
|
||||||
var drawable: SkeletonDrawableWrapper?
|
var drawable: SkeletonDrawableWrapper?
|
||||||
|
|
||||||
@Published
|
@Published
|
||||||
var skinImages = [String: CGImage]()
|
var skinImages = [String: CGImage]()
|
||||||
|
|
||||||
@Published
|
@Published
|
||||||
var selectedSkins = [String: Bool]()
|
var selectedSkins = [String: Bool]()
|
||||||
|
|
||||||
private var customSkin: Skin?
|
private var customSkin: Skin?
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
controller = SpineController(
|
controller = SpineController(
|
||||||
onInitialized: { controller in
|
onInitialized: { controller in
|
||||||
@ -129,28 +129,28 @@ final class DressUpModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
drawable?.dispose()
|
drawable?.dispose()
|
||||||
customSkin?.dispose()
|
customSkin?.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
func toggleSkin(skinName: String) {
|
func toggleSkin(skinName: String) {
|
||||||
if let drawable {
|
if let drawable {
|
||||||
toggleSkin(skinName: skinName, drawable: drawable)
|
toggleSkin(skinName: skinName, drawable: drawable)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func toggleSkin(skinName: String, drawable: SkeletonDrawableWrapper) {
|
func toggleSkin(skinName: String, drawable: SkeletonDrawableWrapper) {
|
||||||
selectedSkins[skinName] = !(selectedSkins[skinName] ?? false)
|
selectedSkins[skinName] = !(selectedSkins[skinName] ?? false)
|
||||||
customSkin?.dispose()
|
customSkin?.dispose()
|
||||||
customSkin = Skin.create(name: "custom-skin")
|
customSkin = Skin.create(name: "custom-skin")
|
||||||
for skinName in selectedSkins.keys {
|
for skinName in selectedSkins.keys {
|
||||||
if selectedSkins[skinName] == true {
|
if selectedSkins[skinName] == true {
|
||||||
if let skin = drawable.skeletonData.findSkin(name: skinName) {
|
if let skin = drawable.skeletonData.findSkin(name: skinName) {
|
||||||
customSkin?.addSkin(other: skin)
|
customSkin?.addSkin(other: skin)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
drawable.skeleton.skin = customSkin
|
drawable.skeleton.skin = customSkin
|
||||||
drawable.skeleton.setToSetupPose()
|
drawable.skeleton.setToSetupPose()
|
||||||
|
|||||||
@ -27,14 +27,14 @@
|
|||||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
import Spine
|
import Spine
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
struct IKFollowing: View {
|
struct IKFollowing: View {
|
||||||
|
|
||||||
@StateObject
|
@StateObject
|
||||||
var model = IKFollowingModel()
|
var model = IKFollowingModel()
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
SpineView(
|
SpineView(
|
||||||
from: .bundle(atlasFileName: "spineboy-pma.atlas", skeletonFileName: "spineboy-pro.skel"),
|
from: .bundle(atlasFileName: "spineboy-pma.atlas", skeletonFileName: "spineboy-pro.skel"),
|
||||||
@ -64,13 +64,13 @@ struct IKFollowing: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final class IKFollowingModel: ObservableObject {
|
final class IKFollowingModel: ObservableObject {
|
||||||
|
|
||||||
@Published
|
@Published
|
||||||
var controller: SpineController!
|
var controller: SpineController!
|
||||||
|
|
||||||
@Published
|
@Published
|
||||||
var crossHairPosition: CGPoint?
|
var crossHairPosition: CGPoint?
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
controller = SpineController(
|
controller = SpineController(
|
||||||
onInitialized: { controller in
|
onInitialized: { controller in
|
||||||
@ -85,14 +85,15 @@ final class IKFollowingModel: ObservableObject {
|
|||||||
loop: true
|
loop: true
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onAfterUpdateWorldTransforms: {
|
onAfterUpdateWorldTransforms: {
|
||||||
[weak self] controller in guard let self else { return }
|
[weak self] controller in
|
||||||
|
guard let self else { return }
|
||||||
guard let worldPosition = self.crossHairPosition else {
|
guard let worldPosition = self.crossHairPosition else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let bone = controller.skeleton.findBone(boneName: "crosshair")!
|
let bone = controller.skeleton.findBone(boneName: "crosshair")!
|
||||||
if let parent = bone.parent {
|
if let parent = bone.parent {
|
||||||
let position = parent.worldToLocal(worldX: Float(worldPosition.x), worldY: Float(worldPosition.y))
|
let position = parent.worldToLocal(worldX: Float(worldPosition.x), worldY: Float(worldPosition.y))
|
||||||
bone.x = position.x
|
bone.x = position.x
|
||||||
bone.y = position.y
|
bone.y = position.y
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,8 +27,8 @@
|
|||||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
import Spine
|
import Spine
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
struct MainView: View {
|
struct MainView: View {
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
|||||||
@ -27,28 +27,28 @@
|
|||||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
import Spine
|
import Spine
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
struct Physics: View {
|
struct Physics: View {
|
||||||
|
|
||||||
@StateObject
|
@StateObject
|
||||||
var model = PhysicsModel()
|
var model = PhysicsModel()
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
Color(red: 51 / 255, green: 51 / 255, blue: 51 / 255).ignoresSafeArea()
|
Color(red: 51 / 255, green: 51 / 255, blue: 51 / 255).ignoresSafeArea()
|
||||||
SpineView(
|
SpineView(
|
||||||
from: .bundle(atlasFileName: "celestial-circus-pma.atlas", skeletonFileName: "celestial-circus-pro.skel"),
|
from: .bundle(atlasFileName: "celestial-circus-pma.atlas", skeletonFileName: "celestial-circus-pro.skel"),
|
||||||
controller: model.controller
|
controller: model.controller
|
||||||
)
|
)
|
||||||
.gesture(
|
.gesture(
|
||||||
DragGesture(minimumDistance: 0)
|
DragGesture(minimumDistance: 0)
|
||||||
.onChanged { gesture in
|
.onChanged { gesture in
|
||||||
model.updateBonePosition(position: gesture.location)
|
model.updateBonePosition(position: gesture.location)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.navigationTitle("Physics (drag anywhere)")
|
.navigationTitle("Physics (drag anywhere)")
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
}
|
}
|
||||||
@ -59,16 +59,16 @@ struct Physics: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final class PhysicsModel: ObservableObject {
|
final class PhysicsModel: ObservableObject {
|
||||||
|
|
||||||
@Published
|
@Published
|
||||||
var controller: SpineController!
|
var controller: SpineController!
|
||||||
|
|
||||||
@Published
|
@Published
|
||||||
var mousePosition: CGPoint?
|
var mousePosition: CGPoint?
|
||||||
|
|
||||||
@Published
|
@Published
|
||||||
var lastMousePosition: CGPoint?
|
var lastMousePosition: CGPoint?
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
controller = SpineController(
|
controller = SpineController(
|
||||||
onInitialized: { controller in
|
onInitialized: { controller in
|
||||||
@ -84,8 +84,9 @@ final class PhysicsModel: ObservableObject {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
onAfterUpdateWorldTransforms: {
|
onAfterUpdateWorldTransforms: {
|
||||||
[weak self] controller in guard let self else { return }
|
[weak self] controller in
|
||||||
|
guard let self else { return }
|
||||||
|
|
||||||
guard let lastMousePosition else {
|
guard let lastMousePosition else {
|
||||||
self.lastMousePosition = mousePosition
|
self.lastMousePosition = mousePosition
|
||||||
return
|
return
|
||||||
@ -102,7 +103,7 @@ final class PhysicsModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateBonePosition(position: CGPoint) {
|
func updateBonePosition(position: CGPoint) {
|
||||||
mousePosition = controller.toSkeletonCoordinates(position: position)
|
mousePosition = controller.toSkeletonCoordinates(position: position)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,8 +27,8 @@
|
|||||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
import Spine
|
import Spine
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
struct PlayPauseAnimation: View {
|
struct PlayPauseAnimation: View {
|
||||||
|
|
||||||
@ -46,10 +46,10 @@ struct PlayPauseAnimation: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
SpineView(
|
SpineView(
|
||||||
from: .bundle(atlasFileName: "dragon.atlas", skeletonFileName: "dragon-ess.skel"),
|
from: .bundle(atlasFileName: "dragon.atlas", skeletonFileName: "dragon-ess.skel"),
|
||||||
// from: .http(
|
// from: .http(
|
||||||
// atlasURL: URL(string: "https://github.com/esotericsoftware/spine-runtimes/raw/spine-ios/spine-ios/Example/Spine%20iOS%20Example/Assets/dragon/dragon.atlas")!,
|
// atlasURL: URL(string: "https://github.com/esotericsoftware/spine-runtimes/raw/spine-ios/spine-ios/Example/Spine%20iOS%20Example/Assets/dragon/dragon.atlas")!,
|
||||||
// skeletonURL: URL(string: "https://github.com/esotericsoftware/spine-runtimes/raw/spine-ios/spine-ios/Example/Spine%20iOS%20Example/Assets/dragon/dragon-ess.skel")!
|
// skeletonURL: URL(string: "https://github.com/esotericsoftware/spine-runtimes/raw/spine-ios/spine-ios/Example/Spine%20iOS%20Example/Assets/dragon/dragon-ess.skel")!
|
||||||
// ),
|
// ),
|
||||||
controller: controller,
|
controller: controller,
|
||||||
boundsProvider: SkinAndAnimationBounds(animation: "flying")
|
boundsProvider: SkinAndAnimationBounds(animation: "flying")
|
||||||
)
|
)
|
||||||
|
|||||||
@ -27,8 +27,8 @@
|
|||||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
import Spine
|
import Spine
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
struct SimpleAnimation: View {
|
struct SimpleAnimation: View {
|
||||||
|
|
||||||
@ -46,10 +46,10 @@ struct SimpleAnimation: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
SpineView(
|
SpineView(
|
||||||
from: .bundle(atlasFileName: "spineboy-pma.atlas", skeletonFileName: "spineboy-pro.skel"),
|
from: .bundle(atlasFileName: "spineboy-pma.atlas", skeletonFileName: "spineboy-pro.skel"),
|
||||||
// from: .http(
|
// from: .http(
|
||||||
// atlasURL: URL(string: "https://github.com/esotericsoftware/spine-runtimes/raw/spine-ios/spine-ios/Example/Spine%20iOS%20Example/Assets/spineboy/spineboy.atlas")!,
|
// atlasURL: URL(string: "https://github.com/esotericsoftware/spine-runtimes/raw/spine-ios/spine-ios/Example/Spine%20iOS%20Example/Assets/spineboy/spineboy.atlas")!,
|
||||||
// skeletonURL: URL(string: "https://github.com/esotericsoftware/spine-runtimes/raw/spine-ios/spine-ios/Example/Spine%20iOS%20Example/Assets/spineboy/spineboy-pro.skel")!
|
// skeletonURL: URL(string: "https://github.com/esotericsoftware/spine-runtimes/raw/spine-ios/spine-ios/Example/Spine%20iOS%20Example/Assets/spineboy/spineboy-pro.skel")!
|
||||||
// ),
|
// ),
|
||||||
controller: controller,
|
controller: controller,
|
||||||
mode: .fit,
|
mode: .fit,
|
||||||
alignment: .center
|
alignment: .center
|
||||||
|
|||||||
@ -31,11 +31,11 @@ import SwiftUI
|
|||||||
|
|
||||||
struct SimpleAnimationViewControllerRepresentable: UIViewControllerRepresentable {
|
struct SimpleAnimationViewControllerRepresentable: UIViewControllerRepresentable {
|
||||||
typealias UIViewControllerType = SimpleAnimationViewController
|
typealias UIViewControllerType = SimpleAnimationViewController
|
||||||
|
|
||||||
func makeUIViewController(context: Context) -> SimpleAnimationViewController {
|
func makeUIViewController(context: Context) -> SimpleAnimationViewController {
|
||||||
return SimpleAnimationViewController()
|
return SimpleAnimationViewController()
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateUIViewController(_ uiViewController: SimpleAnimationViewController, context: Context) {
|
func updateUIViewController(_ uiViewController: SimpleAnimationViewController, context: Context) {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,12 +27,12 @@
|
|||||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
import Spine
|
import Spine
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
@main
|
@main
|
||||||
struct SpineExampleApp: App {
|
struct SpineExampleApp: App {
|
||||||
|
|
||||||
var body: some Scene {
|
var body: some Scene {
|
||||||
WindowGroup {
|
WindowGroup {
|
||||||
NavigationView {
|
NavigationView {
|
||||||
|
|||||||
@ -39,20 +39,20 @@ public typealias AnimationStateListener = (_ type: EventType, _ entry: TrackEntr
|
|||||||
@objc(SpineAnimationStateWrapper)
|
@objc(SpineAnimationStateWrapper)
|
||||||
@objcMembers
|
@objcMembers
|
||||||
public final class AnimationStateWrapper: NSObject {
|
public final class AnimationStateWrapper: NSObject {
|
||||||
|
|
||||||
public let animationState: AnimationState
|
public let animationState: AnimationState
|
||||||
public let aninationStateEvents: AnimationStateEvents
|
public let aninationStateEvents: AnimationStateEvents
|
||||||
|
|
||||||
private var trackEntryListeners = [spine_track_entry: AnimationStateListener]()
|
private var trackEntryListeners = [spine_track_entry: AnimationStateListener]()
|
||||||
|
|
||||||
private var stateListener: AnimationStateListener?
|
private var stateListener: AnimationStateListener?
|
||||||
|
|
||||||
public init(animationState: AnimationState, aninationStateEvents: AnimationStateEvents) {
|
public init(animationState: AnimationState, aninationStateEvents: AnimationStateEvents) {
|
||||||
self.animationState = animationState
|
self.animationState = animationState
|
||||||
self.aninationStateEvents = aninationStateEvents
|
self.aninationStateEvents = aninationStateEvents
|
||||||
super.init()
|
super.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The listener for events generated by the provided ``TrackEntry``, or nil.
|
/// The listener for events generated by the provided ``TrackEntry``, or nil.
|
||||||
///
|
///
|
||||||
/// A track entry returned from ``AnimationState/setAnimation(trackIndex:animation:loop:)`` is already the current animation
|
/// A track entry returned from ``AnimationState/setAnimation(trackIndex:animation:loop:)`` is already the current animation
|
||||||
@ -64,18 +64,18 @@ public final class AnimationStateWrapper: NSObject {
|
|||||||
trackEntryListeners.removeValue(forKey: entry.wrappee)
|
trackEntryListeners.removeValue(forKey: entry.wrappee)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Increments each track entry ``TrackEntry/trackTime``, setting queued animations as current if needed.
|
/// Increments each track entry ``TrackEntry/trackTime``, setting queued animations as current if needed.
|
||||||
public func update(delta: Float) {
|
public func update(delta: Float) {
|
||||||
animationState.update(delta: delta)
|
animationState.update(delta: delta)
|
||||||
|
|
||||||
let numEvents = spine_animation_state_events_get_num_events(aninationStateEvents.wrappee)
|
let numEvents = spine_animation_state_events_get_num_events(aninationStateEvents.wrappee)
|
||||||
for i in 0..<numEvents {
|
for i in 0..<numEvents {
|
||||||
let type = aninationStateEvents.getEventType(index: i)
|
let type = aninationStateEvents.getEventType(index: i)
|
||||||
|
|
||||||
let entry = aninationStateEvents.getTrackEntry(index: i)
|
let entry = aninationStateEvents.getTrackEntry(index: i)
|
||||||
let event = aninationStateEvents.getEvent(index: i)
|
let event = aninationStateEvents.getEvent(index: i)
|
||||||
|
|
||||||
if let trackEntryListener = trackEntryListeners[entry.wrappee] {
|
if let trackEntryListener = trackEntryListeners[entry.wrappee] {
|
||||||
trackEntryListener(type, entry, event)
|
trackEntryListener(type, entry, event)
|
||||||
}
|
}
|
||||||
@ -88,7 +88,7 @@ public final class AnimationStateWrapper: NSObject {
|
|||||||
}
|
}
|
||||||
aninationStateEvents.reset()
|
aninationStateEvents.reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The listener for events generated for all tracks managed by the ``AnimationState``, or nil.
|
/// The listener for events generated for all tracks managed by the ``AnimationState``, or nil.
|
||||||
///
|
///
|
||||||
/// A track entry returned from ``AnimationState/setAnimation(trackIndex:animation:loop:)`` is already the current animation
|
/// A track entry returned from ``AnimationState/setAnimation(trackIndex:animation:loop:)`` is already the current animation
|
||||||
|
|||||||
@ -27,8 +27,8 @@
|
|||||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import CoreGraphics
|
import CoreGraphics
|
||||||
|
import Foundation
|
||||||
|
|
||||||
/// Base class for bounds providers. A bounds provider calculates the axis aligned bounding box
|
/// Base class for bounds providers. A bounds provider calculates the axis aligned bounding box
|
||||||
/// used to scale and fit a skeleton inside the bounds of a ``SpineUIView``.
|
/// used to scale and fit a skeleton inside the bounds of a ``SpineUIView``.
|
||||||
@ -42,7 +42,7 @@ public protocol BoundsProvider {
|
|||||||
@objc(SpineSetupPoseBounds)
|
@objc(SpineSetupPoseBounds)
|
||||||
@objcMembers
|
@objcMembers
|
||||||
public final class SetupPoseBounds: NSObject, BoundsProvider {
|
public final class SetupPoseBounds: NSObject, BoundsProvider {
|
||||||
|
|
||||||
public override init() {
|
public override init() {
|
||||||
super.init()
|
super.init()
|
||||||
}
|
}
|
||||||
@ -79,10 +79,10 @@ public final class RawBounds: NSObject, BoundsProvider {
|
|||||||
@objc(SpineSkinAndAnimationBounds)
|
@objc(SpineSkinAndAnimationBounds)
|
||||||
@objcMembers
|
@objcMembers
|
||||||
public final class SkinAndAnimationBounds: NSObject, BoundsProvider {
|
public final class SkinAndAnimationBounds: NSObject, BoundsProvider {
|
||||||
|
|
||||||
private let animation: String?
|
private let animation: String?
|
||||||
private let skins: [String]
|
private let skins: [String]
|
||||||
private let stepTime: TimeInterval;
|
private let stepTime: TimeInterval
|
||||||
|
|
||||||
/// Constructs a new provider that will use the given `skins` and `animation` to calculate
|
/// Constructs a new provider that will use the given `skins` and `animation` to calculate
|
||||||
/// the bounding box of the skeleton. If no skins are given, the default skin is used.
|
/// the bounding box of the skeleton. If no skins are given, the default skin is used.
|
||||||
@ -98,7 +98,7 @@ public final class SkinAndAnimationBounds: NSObject, BoundsProvider {
|
|||||||
self.stepTime = stepTime
|
self.stepTime = stepTime
|
||||||
super.init()
|
super.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
public func computeBounds(for drawable: SkeletonDrawableWrapper) -> CGRect {
|
public func computeBounds(for drawable: SkeletonDrawableWrapper) -> CGRect {
|
||||||
let data = drawable.skeletonData
|
let data = drawable.skeletonData
|
||||||
let oldSkin: Skin? = drawable.skeleton.skin
|
let oldSkin: Skin? = drawable.skeleton.skin
|
||||||
@ -110,7 +110,7 @@ public final class SkinAndAnimationBounds: NSObject, BoundsProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
drawable.skeleton.skin = customSkin
|
drawable.skeleton.skin = customSkin
|
||||||
drawable.skeleton.setToSetupPose();
|
drawable.skeleton.setToSetupPose()
|
||||||
|
|
||||||
let animation = animation.flatMap { data.findAnimation(name: $0) }
|
let animation = animation.flatMap { data.findAnimation(name: $0) }
|
||||||
var minX = Float.Magnitude.greatestFiniteMagnitude
|
var minX = Float.Magnitude.greatestFiniteMagnitude
|
||||||
@ -122,14 +122,14 @@ public final class SkinAndAnimationBounds: NSObject, BoundsProvider {
|
|||||||
let steps = Int(max(Double(animation.duration) / stepTime, 1.0))
|
let steps = Int(max(Double(animation.duration) / stepTime, 1.0))
|
||||||
for i in 0..<steps {
|
for i in 0..<steps {
|
||||||
drawable.update(delta: i > 0 ? Float(stepTime) : 0.0)
|
drawable.update(delta: i > 0 ? Float(stepTime) : 0.0)
|
||||||
let bounds = drawable.skeleton.bounds;
|
let bounds = drawable.skeleton.bounds
|
||||||
minX = min(minX, bounds.x)
|
minX = min(minX, bounds.x)
|
||||||
minY = min(minY, bounds.y)
|
minY = min(minY, bounds.y)
|
||||||
maxX = max(maxX, minX + bounds.width)
|
maxX = max(maxX, minX + bounds.width)
|
||||||
maxY = max(maxY, minY + bounds.height)
|
maxY = max(maxY, minY + bounds.height)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let bounds = drawable.skeleton.bounds;
|
let bounds = drawable.skeleton.bounds
|
||||||
minX = bounds.x
|
minX = bounds.x
|
||||||
minY = bounds.y
|
minY = bounds.y
|
||||||
maxX = minX + bounds.width
|
maxX = minX + bounds.width
|
||||||
@ -137,7 +137,7 @@ public final class SkinAndAnimationBounds: NSObject, BoundsProvider {
|
|||||||
}
|
}
|
||||||
drawable.skeleton.setSkinByName(skinName: "default")
|
drawable.skeleton.setSkinByName(skinName: "default")
|
||||||
drawable.animationState.clearTracks()
|
drawable.animationState.clearTracks()
|
||||||
|
|
||||||
if let oldSkin {
|
if let oldSkin {
|
||||||
drawable.skeleton.skin = oldSkin
|
drawable.skeleton.skin = oldSkin
|
||||||
}
|
}
|
||||||
@ -145,14 +145,15 @@ public final class SkinAndAnimationBounds: NSObject, BoundsProvider {
|
|||||||
drawable.update(delta: 0)
|
drawable.update(delta: 0)
|
||||||
customSkin.dispose()
|
customSkin.dispose()
|
||||||
return CGRectMake(CGFloat(minX), CGFloat(minY), CGFloat(maxX - minX), CGFloat(maxY - minY))
|
return CGRectMake(CGFloat(minX), CGFloat(minY), CGFloat(maxX - minX), CGFloat(maxY - minY))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// How a view should be inscribed into another view.
|
/// How a view should be inscribed into another view.
|
||||||
@objc
|
@objc
|
||||||
public enum ContentMode: Int {
|
public enum ContentMode: Int {
|
||||||
case fit /// As large as possible while still containing the source view entirely within the target view.
|
case fit
|
||||||
case fill /// Fill the target view by distorting the source's aspect ratio.
|
/// As large as possible while still containing the source view entirely within the target view.
|
||||||
|
case fill/// Fill the target view by distorting the source's aspect ratio.
|
||||||
}
|
}
|
||||||
|
|
||||||
/// How a view should aligned withing another view.
|
/// How a view should aligned withing another view.
|
||||||
@ -167,7 +168,7 @@ public enum Alignment: Int {
|
|||||||
case bottomLeft
|
case bottomLeft
|
||||||
case bottomCenter
|
case bottomCenter
|
||||||
case bottomRight
|
case bottomRight
|
||||||
|
|
||||||
internal var x: CGFloat {
|
internal var x: CGFloat {
|
||||||
switch self {
|
switch self {
|
||||||
case .topLeft, .centerLeft, .bottomLeft: return -1.0
|
case .topLeft, .centerLeft, .bottomLeft: return -1.0
|
||||||
@ -175,7 +176,7 @@ public enum Alignment: Int {
|
|||||||
case .topRight, .centerRight, .bottomRight: return 1.0
|
case .topRight, .centerRight, .bottomRight: return 1.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal var y: CGFloat {
|
internal var y: CGFloat {
|
||||||
switch self {
|
switch self {
|
||||||
case .topLeft, .topCenter, .topRight: return -1.0
|
case .topLeft, .topCenter, .topRight: return -1.0
|
||||||
|
|||||||
@ -27,8 +27,8 @@
|
|||||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import MetalKit
|
import MetalKit
|
||||||
|
import UIKit
|
||||||
|
|
||||||
extension MTLClearColor {
|
extension MTLClearColor {
|
||||||
init(_ color: UIColor) {
|
init(_ color: UIColor) {
|
||||||
@ -36,9 +36,9 @@ extension MTLClearColor {
|
|||||||
var green: CGFloat = 0
|
var green: CGFloat = 0
|
||||||
var blue: CGFloat = 0
|
var blue: CGFloat = 0
|
||||||
var alpha: CGFloat = 0
|
var alpha: CGFloat = 0
|
||||||
|
|
||||||
color.getRed(&red, green: &green, blue: &blue, alpha: &alpha)
|
color.getRed(&red, green: &green, blue: &blue, alpha: &alpha)
|
||||||
|
|
||||||
self.init(red: Double(red), green: Double(green), blue: Double(blue), alpha: Double(alpha))
|
self.init(red: Double(red), green: Double(green), blue: Double(blue), alpha: Double(alpha))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,8 +27,8 @@
|
|||||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import SpineShadersStructs
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import SpineShadersStructs
|
||||||
import simd
|
import simd
|
||||||
|
|
||||||
extension RenderCommand {
|
extension RenderCommand {
|
||||||
@ -55,7 +55,7 @@ extension RenderCommand {
|
|||||||
)
|
)
|
||||||
vertices.append(vertex)
|
vertices.append(vertex)
|
||||||
}
|
}
|
||||||
|
|
||||||
return vertices
|
return vertices
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -27,21 +27,21 @@
|
|||||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
|
import CoreGraphics
|
||||||
import Foundation
|
import Foundation
|
||||||
import UIKit
|
import UIKit
|
||||||
import CoreGraphics
|
|
||||||
|
|
||||||
public extension SkeletonDrawableWrapper {
|
extension SkeletonDrawableWrapper {
|
||||||
|
|
||||||
/// Render the ``Skeleton`` to a `CGImage`
|
/// Render the ``Skeleton`` to a `CGImage`
|
||||||
///
|
///
|
||||||
/// Parameters:
|
/// Parameters:
|
||||||
/// - size: The size of the `CGImage` that should be rendered.
|
/// - size: The size of the `CGImage` that should be rendered.
|
||||||
/// - backgroundColor: the background color of the image
|
/// - backgroundColor: the background color of the image
|
||||||
/// - scaleFactor: The scale factor. Set this to `UIScreen.main.scale` if you want to show the image in a view
|
/// - scaleFactor: The scale factor. Set this to `UIScreen.main.scale` if you want to show the image in a view
|
||||||
func renderToImage(size: CGSize, backgroundColor: UIColor, scaleFactor: CGFloat = 1) throws -> CGImage? {
|
public func renderToImage(size: CGSize, backgroundColor: UIColor, scaleFactor: CGFloat = 1) throws -> CGImage? {
|
||||||
let spineView = SpineUIView(
|
let spineView = SpineUIView(
|
||||||
controller: SpineController(disposeDrawableOnDeInit: false), // Doesn't own the drawable
|
controller: SpineController(disposeDrawableOnDeInit: false), // Doesn't own the drawable
|
||||||
backgroundColor: backgroundColor
|
backgroundColor: backgroundColor
|
||||||
)
|
)
|
||||||
spineView.frame = CGRect(origin: .zero, size: size)
|
spineView.frame = CGRect(origin: .zero, size: size)
|
||||||
@ -49,12 +49,12 @@ public extension SkeletonDrawableWrapper {
|
|||||||
spineView.enableSetNeedsDisplay = false
|
spineView.enableSetNeedsDisplay = false
|
||||||
spineView.framebufferOnly = false
|
spineView.framebufferOnly = false
|
||||||
spineView.contentScaleFactor = scaleFactor
|
spineView.contentScaleFactor = scaleFactor
|
||||||
|
|
||||||
try spineView.load(drawable: self)
|
try spineView.load(drawable: self)
|
||||||
spineView.renderer?.waitUntilCompleted = true
|
spineView.renderer?.waitUntilCompleted = true
|
||||||
|
|
||||||
spineView.delegate?.draw(in: spineView)
|
spineView.delegate?.draw(in: spineView)
|
||||||
|
|
||||||
guard let texture = spineView.currentDrawable?.texture else {
|
guard let texture = spineView.currentDrawable?.texture else {
|
||||||
throw SpineError("Could not read texture.")
|
throw SpineError("Could not read texture.")
|
||||||
}
|
}
|
||||||
@ -65,18 +65,22 @@ public extension SkeletonDrawableWrapper {
|
|||||||
defer {
|
defer {
|
||||||
data.deallocate()
|
data.deallocate()
|
||||||
}
|
}
|
||||||
|
|
||||||
let region = MTLRegionMake2D(0, 0, width, height)
|
let region = MTLRegionMake2D(0, 0, width, height)
|
||||||
texture.getBytes(data, bytesPerRow: rowBytes, from: region, mipmapLevel: 0)
|
texture.getBytes(data, bytesPerRow: rowBytes, from: region, mipmapLevel: 0)
|
||||||
|
|
||||||
let bitmapInfo = CGBitmapInfo(
|
let bitmapInfo = CGBitmapInfo(
|
||||||
rawValue: CGImageAlphaInfo.premultipliedFirst.rawValue
|
rawValue: CGImageAlphaInfo.premultipliedFirst.rawValue
|
||||||
).union(.byteOrder32Little)
|
).union(.byteOrder32Little)
|
||||||
|
|
||||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||||
guard let context = CGContext(data: data, width: width, height: height, bitsPerComponent: 8, bytesPerRow: rowBytes, space: colorSpace, bitmapInfo: bitmapInfo.rawValue),
|
guard
|
||||||
let cgImage = context.makeImage() else {
|
let context = CGContext(
|
||||||
throw SpineError("Could not create image.")
|
data: data, width: width, height: height, bitsPerComponent: 8, bytesPerRow: rowBytes, space: colorSpace,
|
||||||
|
bitmapInfo: bitmapInfo.rawValue),
|
||||||
|
let cgImage = context.makeImage()
|
||||||
|
else {
|
||||||
|
throw SpineError("Could not create image.")
|
||||||
}
|
}
|
||||||
return cgImage
|
return cgImage
|
||||||
}
|
}
|
||||||
|
|||||||
@ -35,13 +35,13 @@ import MetalKit
|
|||||||
/// Persistent Objects
|
/// Persistent Objects
|
||||||
/// https://developer.apple.com/library/archive/documentation/3DDrawing/Conceptual/MTLBestPracticesGuide/PersistentObjects.html#//apple_ref/doc/uid/TP40016642-CH3-SW1
|
/// https://developer.apple.com/library/archive/documentation/3DDrawing/Conceptual/MTLBestPracticesGuide/PersistentObjects.html#//apple_ref/doc/uid/TP40016642-CH3-SW1
|
||||||
internal final class SpineObjects {
|
internal final class SpineObjects {
|
||||||
|
|
||||||
static let shared = SpineObjects()
|
static let shared = SpineObjects()
|
||||||
|
|
||||||
internal lazy var device: MTLDevice = {
|
internal lazy var device: MTLDevice = {
|
||||||
MTLCreateSystemDefaultDevice()!
|
MTLCreateSystemDefaultDevice()!
|
||||||
}()
|
}()
|
||||||
|
|
||||||
internal lazy var commandQueue: MTLCommandQueue = {
|
internal lazy var commandQueue: MTLCommandQueue = {
|
||||||
device.makeCommandQueue()!
|
device.makeCommandQueue()!
|
||||||
}()
|
}()
|
||||||
|
|||||||
@ -29,18 +29,18 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import MetalKit
|
import MetalKit
|
||||||
import SpineShadersStructs
|
|
||||||
import Spine
|
import Spine
|
||||||
import SpineCppLite
|
import SpineCppLite
|
||||||
|
import SpineShadersStructs
|
||||||
|
|
||||||
protocol SpineRendererDelegate: AnyObject {
|
protocol SpineRendererDelegate: AnyObject {
|
||||||
func spineRendererWillUpdate(_ spineRenderer: SpineRenderer)
|
func spineRendererWillUpdate(_ spineRenderer: SpineRenderer)
|
||||||
func spineRenderer(_ spineRenderer: SpineRenderer, needsUpdate delta: TimeInterval)
|
func spineRenderer(_ spineRenderer: SpineRenderer, needsUpdate delta: TimeInterval)
|
||||||
func spineRendererDidUpdate(_ spineRenderer: SpineRenderer)
|
func spineRendererDidUpdate(_ spineRenderer: SpineRenderer)
|
||||||
|
|
||||||
func spineRendererWillDraw(_ spineRenderer: SpineRenderer)
|
func spineRendererWillDraw(_ spineRenderer: SpineRenderer)
|
||||||
func spineRendererDidDraw(_ spineRenderer: SpineRenderer)
|
func spineRendererDidDraw(_ spineRenderer: SpineRenderer)
|
||||||
|
|
||||||
func spineRendererDidUpdate(_ spineRenderer: SpineRenderer, scaleX: CGFloat, scaleY: CGFloat, offsetX: CGFloat, offsetY: CGFloat, size: CGSize)
|
func spineRendererDidUpdate(_ spineRenderer: SpineRenderer, scaleX: CGFloat, scaleY: CGFloat, offsetX: CGFloat, offsetY: CGFloat, size: CGSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,11 +50,11 @@ protocol SpineRendererDataSource: AnyObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal final class SpineRenderer: NSObject, MTKViewDelegate {
|
internal final class SpineRenderer: NSObject, MTKViewDelegate {
|
||||||
|
|
||||||
private let device: MTLDevice
|
private let device: MTLDevice
|
||||||
private let textures: [MTLTexture]
|
private let textures: [MTLTexture]
|
||||||
private let commandQueue: MTLCommandQueue
|
private let commandQueue: MTLCommandQueue
|
||||||
|
|
||||||
private var sizeInPoints: CGSize = .zero
|
private var sizeInPoints: CGSize = .zero
|
||||||
private var viewPortSize = vector_uint2(0, 0)
|
private var viewPortSize = vector_uint2(0, 0)
|
||||||
private var transform = SpineTransform(
|
private var transform = SpineTransform(
|
||||||
@ -65,17 +65,17 @@ internal final class SpineRenderer: NSObject, MTKViewDelegate {
|
|||||||
internal var lastDraw: CFTimeInterval = 0
|
internal var lastDraw: CFTimeInterval = 0
|
||||||
internal var waitUntilCompleted = false
|
internal var waitUntilCompleted = false
|
||||||
private var pipelineStatesByBlendMode = [Int: MTLRenderPipelineState]()
|
private var pipelineStatesByBlendMode = [Int: MTLRenderPipelineState]()
|
||||||
|
|
||||||
private static let numberOfBuffers = 3
|
private static let numberOfBuffers = 3
|
||||||
private static let defaultBufferSize = 32 * 1024 // 32KB
|
private static let defaultBufferSize = 32 * 1024 // 32KB
|
||||||
|
|
||||||
private var buffers = [MTLBuffer]()
|
private var buffers = [MTLBuffer]()
|
||||||
private let bufferingSemaphore = DispatchSemaphore(value: SpineRenderer.numberOfBuffers)
|
private let bufferingSemaphore = DispatchSemaphore(value: SpineRenderer.numberOfBuffers)
|
||||||
private var currentBufferIndex: Int = 0
|
private var currentBufferIndex: Int = 0
|
||||||
|
|
||||||
weak var dataSource: SpineRendererDataSource?
|
weak var dataSource: SpineRendererDataSource?
|
||||||
weak var delegate: SpineRendererDelegate?
|
weak var delegate: SpineRendererDelegate?
|
||||||
|
|
||||||
internal init(
|
internal init(
|
||||||
device: MTLDevice,
|
device: MTLDevice,
|
||||||
commandQueue: MTLCommandQueue,
|
commandQueue: MTLCommandQueue,
|
||||||
@ -85,18 +85,19 @@ internal final class SpineRenderer: NSObject, MTKViewDelegate {
|
|||||||
) throws {
|
) throws {
|
||||||
self.device = device
|
self.device = device
|
||||||
self.commandQueue = commandQueue
|
self.commandQueue = commandQueue
|
||||||
|
|
||||||
let bundle: Bundle
|
let bundle: Bundle
|
||||||
#if SWIFT_PACKAGE // SPM
|
#if SWIFT_PACKAGE // SPM
|
||||||
bundle = .module
|
bundle = .module
|
||||||
#else // CocoaPods
|
#else // CocoaPods
|
||||||
let bundleURL = Bundle(for: SpineRenderer.self).url(forResource: "SpineBundle", withExtension: "bundle")
|
let bundleURL = Bundle(for: SpineRenderer.self).url(forResource: "SpineBundle", withExtension: "bundle")
|
||||||
bundle = Bundle(url: bundleURL!)!
|
bundle = Bundle(url: bundleURL!)!
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
let defaultLibrary = try device.makeDefaultLibrary(bundle: bundle)
|
let defaultLibrary = try device.makeDefaultLibrary(bundle: bundle)
|
||||||
let textureLoader = MTKTextureLoader(device: device)
|
let textureLoader = MTKTextureLoader(device: device)
|
||||||
textures = try atlasPages
|
textures =
|
||||||
|
try atlasPages
|
||||||
.compactMap { $0.cgImage }
|
.compactMap { $0.cgImage }
|
||||||
.map {
|
.map {
|
||||||
try textureLoader.newTexture(
|
try textureLoader.newTexture(
|
||||||
@ -107,12 +108,12 @@ internal final class SpineRenderer: NSObject, MTKViewDelegate {
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
let blendModes = [
|
let blendModes = [
|
||||||
SPINE_BLEND_MODE_NORMAL,
|
SPINE_BLEND_MODE_NORMAL,
|
||||||
SPINE_BLEND_MODE_ADDITIVE,
|
SPINE_BLEND_MODE_ADDITIVE,
|
||||||
SPINE_BLEND_MODE_MULTIPLY,
|
SPINE_BLEND_MODE_MULTIPLY,
|
||||||
SPINE_BLEND_MODE_SCREEN
|
SPINE_BLEND_MODE_SCREEN,
|
||||||
]
|
]
|
||||||
for blendMode in blendModes {
|
for blendMode in blendModes {
|
||||||
let descriptor = MTLRenderPipelineDescriptor()
|
let descriptor = MTLRenderPipelineDescriptor()
|
||||||
@ -125,15 +126,15 @@ internal final class SpineRenderer: NSObject, MTKViewDelegate {
|
|||||||
)
|
)
|
||||||
pipelineStatesByBlendMode[Int(blendMode.rawValue)] = try device.makeRenderPipelineState(descriptor: descriptor)
|
pipelineStatesByBlendMode[Int(blendMode.rawValue)] = try device.makeRenderPipelineState(descriptor: descriptor)
|
||||||
}
|
}
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
increaseBuffersSize(to: SpineRenderer.defaultBufferSize)
|
increaseBuffersSize(to: SpineRenderer.defaultBufferSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
|
func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
|
||||||
guard let spineView = view as? SpineUIView else { return }
|
guard let spineView = view as? SpineUIView else { return }
|
||||||
|
|
||||||
sizeInPoints = CGSize(width: size.width / UIScreen.main.scale, height: size.height / UIScreen.main.scale)
|
sizeInPoints = CGSize(width: size.width / UIScreen.main.scale, height: size.height / UIScreen.main.scale)
|
||||||
viewPortSize = vector_uint2(UInt32(size.width), UInt32(size.height))
|
viewPortSize = vector_uint2(UInt32(size.width), UInt32(size.height))
|
||||||
setTransform(
|
setTransform(
|
||||||
@ -142,35 +143,36 @@ internal final class SpineRenderer: NSObject, MTKViewDelegate {
|
|||||||
alignment: spineView.alignment
|
alignment: spineView.alignment
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func draw(in view: MTKView) {
|
func draw(in view: MTKView) {
|
||||||
guard dataSource?.isPlaying(self) ?? false else {
|
guard dataSource?.isPlaying(self) ?? false else {
|
||||||
lastDraw = CACurrentMediaTime()
|
lastDraw = CACurrentMediaTime()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
callNeedsUpdate()
|
callNeedsUpdate()
|
||||||
|
|
||||||
// Tripple Buffering
|
// Tripple Buffering
|
||||||
// Source: https://developer.apple.com/library/archive/documentation/3DDrawing/Conceptual/MTLBestPracticesGuide/TripleBuffering.html#//apple_ref/doc/uid/TP40016642-CH5-SW1
|
// Source: https://developer.apple.com/library/archive/documentation/3DDrawing/Conceptual/MTLBestPracticesGuide/TripleBuffering.html#//apple_ref/doc/uid/TP40016642-CH5-SW1
|
||||||
bufferingSemaphore.wait()
|
bufferingSemaphore.wait()
|
||||||
currentBufferIndex = (currentBufferIndex + 1) % SpineRenderer.numberOfBuffers
|
currentBufferIndex = (currentBufferIndex + 1) % SpineRenderer.numberOfBuffers
|
||||||
|
|
||||||
guard let renderCommands = dataSource?.renderCommands(self),
|
guard let renderCommands = dataSource?.renderCommands(self),
|
||||||
let commandBuffer = commandQueue.makeCommandBuffer(),
|
let commandBuffer = commandQueue.makeCommandBuffer(),
|
||||||
let renderPassDescriptor = view.currentRenderPassDescriptor,
|
let renderPassDescriptor = view.currentRenderPassDescriptor,
|
||||||
let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor) else {
|
let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor)
|
||||||
// this can happen if,
|
else {
|
||||||
// - CAMetalLayer is configured with drawable timeout, and CAMetalLayer is run out of Drawable
|
// this can happen if,
|
||||||
// - CAMetalLayer is added to the window with frame size of zero or incorrect layout constraint -> currentRenderPassDescriptor is null
|
// - CAMetalLayer is configured with drawable timeout, and CAMetalLayer is run out of Drawable
|
||||||
|
// - CAMetalLayer is added to the window with frame size of zero or incorrect layout constraint -> currentRenderPassDescriptor is null
|
||||||
bufferingSemaphore.signal()
|
bufferingSemaphore.signal()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
delegate?.spineRendererWillDraw(self)
|
delegate?.spineRendererWillDraw(self)
|
||||||
draw(renderCommands: renderCommands, renderEncoder: renderEncoder, in: view)
|
draw(renderCommands: renderCommands, renderEncoder: renderEncoder, in: view)
|
||||||
delegate?.spineRendererDidDraw(self)
|
delegate?.spineRendererDidDraw(self)
|
||||||
|
|
||||||
renderEncoder.endEncoding()
|
renderEncoder.endEncoding()
|
||||||
view.currentDrawable.flatMap {
|
view.currentDrawable.flatMap {
|
||||||
commandBuffer.present($0)
|
commandBuffer.present($0)
|
||||||
@ -183,14 +185,14 @@ internal final class SpineRenderer: NSObject, MTKViewDelegate {
|
|||||||
commandBuffer.waitUntilCompleted()
|
commandBuffer.waitUntilCompleted()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setTransform(bounds: CGRect, mode: Spine.ContentMode, alignment: Spine.Alignment) {
|
private func setTransform(bounds: CGRect, mode: Spine.ContentMode, alignment: Spine.Alignment) {
|
||||||
let x = -bounds.minX - bounds.width / 2.0
|
let x = -bounds.minX - bounds.width / 2.0
|
||||||
let y = -bounds.minY - bounds.height / 2.0
|
let y = -bounds.minY - bounds.height / 2.0
|
||||||
|
|
||||||
var scaleX: CGFloat = 1.0
|
var scaleX: CGFloat = 1.0
|
||||||
var scaleY: CGFloat = 1.0
|
var scaleY: CGFloat = 1.0
|
||||||
|
|
||||||
switch mode {
|
switch mode {
|
||||||
case .fit:
|
case .fit:
|
||||||
scaleX = min(sizeInPoints.width / bounds.width, sizeInPoints.height / bounds.height)
|
scaleX = min(sizeInPoints.width / bounds.width, sizeInPoints.height / bounds.height)
|
||||||
@ -199,16 +201,16 @@ internal final class SpineRenderer: NSObject, MTKViewDelegate {
|
|||||||
scaleX = max(sizeInPoints.width / bounds.width, sizeInPoints.height / bounds.height)
|
scaleX = max(sizeInPoints.width / bounds.width, sizeInPoints.height / bounds.height)
|
||||||
scaleY = scaleX
|
scaleY = scaleX
|
||||||
}
|
}
|
||||||
|
|
||||||
let offsetX = abs(sizeInPoints.width - bounds.width * scaleX) / 2 * alignment.x
|
let offsetX = abs(sizeInPoints.width - bounds.width * scaleX) / 2 * alignment.x
|
||||||
let offsetY = abs(sizeInPoints.height - bounds.height * scaleY) / 2 * alignment.y
|
let offsetY = abs(sizeInPoints.height - bounds.height * scaleY) / 2 * alignment.y
|
||||||
|
|
||||||
transform = SpineTransform(
|
transform = SpineTransform(
|
||||||
translation: vector_float2(Float(x), Float(y)),
|
translation: vector_float2(Float(x), Float(y)),
|
||||||
scale: vector_float2(Float(scaleX * UIScreen.main.scale), Float(scaleY * UIScreen.main.scale)),
|
scale: vector_float2(Float(scaleX * UIScreen.main.scale), Float(scaleY * UIScreen.main.scale)),
|
||||||
offset: vector_float2(Float(offsetX * UIScreen.main.scale), Float(offsetY * UIScreen.main.scale))
|
offset: vector_float2(Float(offsetX * UIScreen.main.scale), Float(offsetY * UIScreen.main.scale))
|
||||||
)
|
)
|
||||||
|
|
||||||
delegate?.spineRendererDidUpdate(
|
delegate?.spineRendererDidUpdate(
|
||||||
self,
|
self,
|
||||||
scaleX: scaleX,
|
scaleX: scaleX,
|
||||||
@ -218,37 +220,37 @@ internal final class SpineRenderer: NSObject, MTKViewDelegate {
|
|||||||
size: sizeInPoints
|
size: sizeInPoints
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func callNeedsUpdate() {
|
private func callNeedsUpdate() {
|
||||||
if lastDraw == 0 {
|
if lastDraw == 0 {
|
||||||
lastDraw = CACurrentMediaTime()
|
lastDraw = CACurrentMediaTime()
|
||||||
}
|
}
|
||||||
let delta = CACurrentMediaTime() - lastDraw
|
let delta = CACurrentMediaTime() - lastDraw
|
||||||
delegate?.spineRendererWillUpdate(self)
|
delegate?.spineRendererWillUpdate(self)
|
||||||
delegate?.spineRenderer(self, needsUpdate: delta)
|
delegate?.spineRenderer(self, needsUpdate: delta)
|
||||||
lastDraw = CACurrentMediaTime()
|
lastDraw = CACurrentMediaTime()
|
||||||
delegate?.spineRendererDidUpdate(self)
|
delegate?.spineRendererDidUpdate(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func draw(renderCommands: [RenderCommand], renderEncoder: MTLRenderCommandEncoder, in view: MTKView) {
|
private func draw(renderCommands: [RenderCommand], renderEncoder: MTLRenderCommandEncoder, in view: MTKView) {
|
||||||
let allVertices = renderCommands.map { renderCommand in
|
let allVertices = renderCommands.map { renderCommand in
|
||||||
Array(renderCommand.getVertices())
|
Array(renderCommand.getVertices())
|
||||||
}
|
}
|
||||||
let vertices = allVertices.flatMap { $0 }
|
let vertices = allVertices.flatMap { $0 }
|
||||||
let verticesSize = MemoryLayout<SpineVertex>.stride * vertices.count
|
let verticesSize = MemoryLayout<SpineVertex>.stride * vertices.count
|
||||||
|
|
||||||
guard verticesSize > 0 else {
|
guard verticesSize > 0 else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var vertexBuffer = buffers[currentBufferIndex]
|
var vertexBuffer = buffers[currentBufferIndex]
|
||||||
var vertexBufferSize = vertexBuffer.length
|
var vertexBufferSize = vertexBuffer.length
|
||||||
|
|
||||||
if vertexBufferSize < verticesSize {
|
if vertexBufferSize < verticesSize {
|
||||||
increaseBuffersSize(to: verticesSize)
|
increaseBuffersSize(to: verticesSize)
|
||||||
vertexBuffer = buffers[currentBufferIndex]
|
vertexBuffer = buffers[currentBufferIndex]
|
||||||
}
|
}
|
||||||
|
|
||||||
renderEncoder.setViewport(
|
renderEncoder.setViewport(
|
||||||
MTLViewport(
|
MTLViewport(
|
||||||
originX: 0.0,
|
originX: 0.0,
|
||||||
@ -259,9 +261,9 @@ internal final class SpineRenderer: NSObject, MTKViewDelegate {
|
|||||||
zfar: 1.0
|
zfar: 1.0
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
memcpy(vertexBuffer.contents(), vertices, verticesSize)
|
memcpy(vertexBuffer.contents(), vertices, verticesSize)
|
||||||
|
|
||||||
renderEncoder.setVertexBuffer(
|
renderEncoder.setVertexBuffer(
|
||||||
vertexBuffer,
|
vertexBuffer,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
@ -277,7 +279,7 @@ internal final class SpineRenderer: NSObject, MTKViewDelegate {
|
|||||||
length: MemoryLayout.size(ofValue: viewPortSize),
|
length: MemoryLayout.size(ofValue: viewPortSize),
|
||||||
index: Int(SpineVertexInputIndexViewportSize.rawValue)
|
index: Int(SpineVertexInputIndexViewportSize.rawValue)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Buffer Bindings
|
// Buffer Bindings
|
||||||
// https://developer.apple.com/library/archive/documentation/3DDrawing/Conceptual/MTLBestPracticesGuide/BufferBindings.html#//apple_ref/doc/uid/TP40016642-CH28-SW3
|
// https://developer.apple.com/library/archive/documentation/3DDrawing/Conceptual/MTLBestPracticesGuide/BufferBindings.html#//apple_ref/doc/uid/TP40016642-CH28-SW3
|
||||||
var vertexStart = 0
|
var vertexStart = 0
|
||||||
@ -286,9 +288,9 @@ internal final class SpineRenderer: NSObject, MTKViewDelegate {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
renderEncoder.setRenderPipelineState(pipelineState)
|
renderEncoder.setRenderPipelineState(pipelineState)
|
||||||
|
|
||||||
let vertices = allVertices[index]
|
let vertices = allVertices[index]
|
||||||
|
|
||||||
let textureIndex = Int(renderCommand.atlasPage)
|
let textureIndex = Int(renderCommand.atlasPage)
|
||||||
if textures.indices.contains(textureIndex) {
|
if textures.indices.contains(textureIndex) {
|
||||||
renderEncoder.setFragmentTexture(
|
renderEncoder.setFragmentTexture(
|
||||||
@ -296,7 +298,7 @@ internal final class SpineRenderer: NSObject, MTKViewDelegate {
|
|||||||
index: Int(SpineTextureIndexBaseColor.rawValue)
|
index: Int(SpineTextureIndexBaseColor.rawValue)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
renderEncoder.drawPrimitives(
|
renderEncoder.drawPrimitives(
|
||||||
type: .triangle,
|
type: .triangle,
|
||||||
vertexStart: vertexStart,
|
vertexStart: vertexStart,
|
||||||
@ -305,89 +307,89 @@ internal final class SpineRenderer: NSObject, MTKViewDelegate {
|
|||||||
vertexStart += vertices.count
|
vertexStart += vertices.count
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func getPipelineState(blendMode: BlendMode) -> MTLRenderPipelineState? {
|
private func getPipelineState(blendMode: BlendMode) -> MTLRenderPipelineState? {
|
||||||
pipelineStatesByBlendMode[Int(blendMode.rawValue)]
|
pipelineStatesByBlendMode[Int(blendMode.rawValue)]
|
||||||
}
|
}
|
||||||
|
|
||||||
private func increaseBuffersSize(to size: Int) {
|
private func increaseBuffersSize(to size: Int) {
|
||||||
buffers = (0 ..< SpineRenderer.numberOfBuffers).map { _ in
|
buffers = (0..<SpineRenderer.numberOfBuffers).map { _ in
|
||||||
device.makeBuffer(length: size, options: .storageModeShared)!
|
device.makeBuffer(length: size, options: .storageModeShared)!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate extension BlendMode {
|
extension BlendMode {
|
||||||
func sourceRGBBlendFactor(premultipliedAlpha: Bool) -> MTLBlendFactor {
|
fileprivate func sourceRGBBlendFactor(premultipliedAlpha: Bool) -> MTLBlendFactor {
|
||||||
switch self {
|
switch self {
|
||||||
case SPINE_BLEND_MODE_NORMAL:
|
case SPINE_BLEND_MODE_NORMAL:
|
||||||
return premultipliedAlpha ? .one : .sourceAlpha
|
return premultipliedAlpha ? .one : .sourceAlpha
|
||||||
case SPINE_BLEND_MODE_ADDITIVE:
|
case SPINE_BLEND_MODE_ADDITIVE:
|
||||||
// additvie only needs sourceAlpha multiply if it is not pma
|
// additvie only needs sourceAlpha multiply if it is not pma
|
||||||
return premultipliedAlpha ? .one : .sourceAlpha
|
return premultipliedAlpha ? .one : .sourceAlpha
|
||||||
case SPINE_BLEND_MODE_MULTIPLY:
|
case SPINE_BLEND_MODE_MULTIPLY:
|
||||||
return .destinationColor
|
return .destinationColor
|
||||||
case SPINE_BLEND_MODE_SCREEN:
|
case SPINE_BLEND_MODE_SCREEN:
|
||||||
return .one
|
return .one
|
||||||
default:
|
default:
|
||||||
return .one // Should never be called
|
return .one // Should never be called
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var sourceAlphaBlendFactor: MTLBlendFactor {
|
|
||||||
// pma and non-pma has no-relation ship with alpha blending
|
|
||||||
switch self {
|
|
||||||
case SPINE_BLEND_MODE_NORMAL:
|
|
||||||
return .one
|
|
||||||
case SPINE_BLEND_MODE_ADDITIVE:
|
|
||||||
return .one
|
|
||||||
case SPINE_BLEND_MODE_MULTIPLY:
|
|
||||||
return .oneMinusSourceAlpha
|
|
||||||
case SPINE_BLEND_MODE_SCREEN:
|
|
||||||
return .oneMinusSourceColor
|
|
||||||
default:
|
|
||||||
return .one // Should never be called
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var destinationRGBBlendFactor: MTLBlendFactor {
|
fileprivate var sourceAlphaBlendFactor: MTLBlendFactor {
|
||||||
switch self {
|
// pma and non-pma has no-relation ship with alpha blending
|
||||||
case SPINE_BLEND_MODE_NORMAL:
|
switch self {
|
||||||
return .oneMinusSourceAlpha
|
case SPINE_BLEND_MODE_NORMAL:
|
||||||
case SPINE_BLEND_MODE_ADDITIVE:
|
return .one
|
||||||
return .one
|
case SPINE_BLEND_MODE_ADDITIVE:
|
||||||
case SPINE_BLEND_MODE_MULTIPLY:
|
return .one
|
||||||
return .oneMinusSourceAlpha
|
case SPINE_BLEND_MODE_MULTIPLY:
|
||||||
case SPINE_BLEND_MODE_SCREEN:
|
return .oneMinusSourceAlpha
|
||||||
return .oneMinusSourceColor
|
case SPINE_BLEND_MODE_SCREEN:
|
||||||
default:
|
return .oneMinusSourceColor
|
||||||
return .one // Should never be called
|
default:
|
||||||
}
|
return .one // Should never be called
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var destinationAlphaBlendFactor: MTLBlendFactor {
|
fileprivate var destinationRGBBlendFactor: MTLBlendFactor {
|
||||||
switch self {
|
switch self {
|
||||||
case SPINE_BLEND_MODE_NORMAL:
|
case SPINE_BLEND_MODE_NORMAL:
|
||||||
return .oneMinusSourceAlpha
|
return .oneMinusSourceAlpha
|
||||||
case SPINE_BLEND_MODE_ADDITIVE:
|
case SPINE_BLEND_MODE_ADDITIVE:
|
||||||
return .one
|
return .one
|
||||||
case SPINE_BLEND_MODE_MULTIPLY:
|
case SPINE_BLEND_MODE_MULTIPLY:
|
||||||
return .oneMinusSourceAlpha
|
return .oneMinusSourceAlpha
|
||||||
case SPINE_BLEND_MODE_SCREEN:
|
case SPINE_BLEND_MODE_SCREEN:
|
||||||
return .oneMinusSourceColor
|
return .oneMinusSourceColor
|
||||||
default:
|
default:
|
||||||
return .one // Should never be called
|
return .one // Should never be called
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fileprivate var destinationAlphaBlendFactor: MTLBlendFactor {
|
||||||
|
switch self {
|
||||||
|
case SPINE_BLEND_MODE_NORMAL:
|
||||||
|
return .oneMinusSourceAlpha
|
||||||
|
case SPINE_BLEND_MODE_ADDITIVE:
|
||||||
|
return .one
|
||||||
|
case SPINE_BLEND_MODE_MULTIPLY:
|
||||||
|
return .oneMinusSourceAlpha
|
||||||
|
case SPINE_BLEND_MODE_SCREEN:
|
||||||
|
return .oneMinusSourceColor
|
||||||
|
default:
|
||||||
|
return .one // Should never be called
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate extension MTLRenderPipelineColorAttachmentDescriptor {
|
extension MTLRenderPipelineColorAttachmentDescriptor {
|
||||||
|
|
||||||
func apply(blendMode: BlendMode, with premultipliedAlpha: Bool) {
|
fileprivate func apply(blendMode: BlendMode, with premultipliedAlpha: Bool) {
|
||||||
isBlendingEnabled = true
|
isBlendingEnabled = true
|
||||||
sourceRGBBlendFactor = blendMode.sourceRGBBlendFactor(premultipliedAlpha: premultipliedAlpha)
|
sourceRGBBlendFactor = blendMode.sourceRGBBlendFactor(premultipliedAlpha: premultipliedAlpha)
|
||||||
sourceAlphaBlendFactor = blendMode.sourceAlphaBlendFactor
|
sourceAlphaBlendFactor = blendMode.sourceAlphaBlendFactor
|
||||||
destinationRGBBlendFactor = blendMode.destinationRGBBlendFactor
|
destinationRGBBlendFactor = blendMode.destinationRGBBlendFactor
|
||||||
destinationAlphaBlendFactor = blendMode.destinationAlphaBlendFactor
|
destinationAlphaBlendFactor = blendMode.destinationAlphaBlendFactor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,10 +27,10 @@
|
|||||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
|
import CoreGraphics
|
||||||
import Foundation
|
import Foundation
|
||||||
import Spine
|
import Spine
|
||||||
import SpineCppLite
|
import SpineCppLite
|
||||||
import CoreGraphics
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
/// A ``SkeletonDrawableWrapper`` with ``SkeletonDrawable`` bundle loading, updating, and rendering an ``Atlas``, ``Skeleton``, and ``AnimationState``
|
/// A ``SkeletonDrawableWrapper`` with ``SkeletonDrawable`` bundle loading, updating, and rendering an ``Atlas``, ``Skeleton``, and ``AnimationState``
|
||||||
@ -54,19 +54,19 @@ import UIKit
|
|||||||
@objc(SpineSkeletonDrawableWrapper)
|
@objc(SpineSkeletonDrawableWrapper)
|
||||||
@objcMembers
|
@objcMembers
|
||||||
public final class SkeletonDrawableWrapper: NSObject {
|
public final class SkeletonDrawableWrapper: NSObject {
|
||||||
|
|
||||||
public let atlas: Atlas
|
public let atlas: Atlas
|
||||||
public let atlasPages: [UIImage]
|
public let atlasPages: [UIImage]
|
||||||
public let skeletonData: SkeletonData
|
public let skeletonData: SkeletonData
|
||||||
|
|
||||||
public let skeletonDrawable: SkeletonDrawable
|
public let skeletonDrawable: SkeletonDrawable
|
||||||
public let skeleton: Skeleton
|
public let skeleton: Skeleton
|
||||||
public let animationStateData: AnimationStateData
|
public let animationStateData: AnimationStateData
|
||||||
public let animationState: AnimationState
|
public let animationState: AnimationState
|
||||||
public let animationStateWrapper: AnimationStateWrapper
|
public let animationStateWrapper: AnimationStateWrapper
|
||||||
|
|
||||||
internal var disposed = false
|
internal var disposed = false
|
||||||
|
|
||||||
/// Constructs a new skeleton drawable from the `atlasFileName` and `skeletonFileName` from the `main` bundle
|
/// Constructs a new skeleton drawable from the `atlasFileName` and `skeletonFileName` from the `main` bundle
|
||||||
/// or the optionally provided `bundle`.
|
/// or the optionally provided `bundle`.
|
||||||
///
|
///
|
||||||
@ -84,7 +84,7 @@ public final class SkeletonDrawableWrapper: NSObject {
|
|||||||
skeletonData: skeletonData
|
skeletonData: skeletonData
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Constructs a new skeleton drawable from the `atlasFile` and `skeletonFile`.
|
/// Constructs a new skeleton drawable from the `atlasFile` and `skeletonFile`.
|
||||||
///
|
///
|
||||||
/// Throws an `Error` in case the data could not be loaded.
|
/// Throws an `Error` in case the data could not be loaded.
|
||||||
@ -100,7 +100,7 @@ public final class SkeletonDrawableWrapper: NSObject {
|
|||||||
skeletonData: skeletonData
|
skeletonData: skeletonData
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Constructs a new skeleton drawable wrapper from the http `atlasUrl` and `skeletonUrl`.
|
/// Constructs a new skeleton drawable wrapper from the http `atlasUrl` and `skeletonUrl`.
|
||||||
///
|
///
|
||||||
/// Throws an `Error` in case the data could not be loaded.
|
/// Throws an `Error` in case the data could not be loaded.
|
||||||
@ -116,27 +116,27 @@ public final class SkeletonDrawableWrapper: NSObject {
|
|||||||
skeletonData: skeletonData
|
skeletonData: skeletonData
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(atlas: Atlas, atlasPages: [UIImage], skeletonData: SkeletonData) throws {
|
public init(atlas: Atlas, atlasPages: [UIImage], skeletonData: SkeletonData) throws {
|
||||||
self.atlas = atlas
|
self.atlas = atlas
|
||||||
self.atlasPages = atlasPages
|
self.atlasPages = atlasPages
|
||||||
self.skeletonData = skeletonData
|
self.skeletonData = skeletonData
|
||||||
|
|
||||||
guard let nativeSkeletonDrawable = spine_skeleton_drawable_create(skeletonData.wrappee) else {
|
guard let nativeSkeletonDrawable = spine_skeleton_drawable_create(skeletonData.wrappee) else {
|
||||||
throw SpineError("Could not load native skeleton drawable")
|
throw SpineError("Could not load native skeleton drawable")
|
||||||
}
|
}
|
||||||
skeletonDrawable = SkeletonDrawable(nativeSkeletonDrawable)
|
skeletonDrawable = SkeletonDrawable(nativeSkeletonDrawable)
|
||||||
|
|
||||||
guard let nativeSkeleton = spine_skeleton_drawable_get_skeleton(skeletonDrawable.wrappee) else {
|
guard let nativeSkeleton = spine_skeleton_drawable_get_skeleton(skeletonDrawable.wrappee) else {
|
||||||
throw SpineError("Could not load native skeleton")
|
throw SpineError("Could not load native skeleton")
|
||||||
}
|
}
|
||||||
skeleton = Skeleton(nativeSkeleton)
|
skeleton = Skeleton(nativeSkeleton)
|
||||||
|
|
||||||
guard let nativeAnimationStateData = spine_skeleton_drawable_get_animation_state_data(skeletonDrawable.wrappee) else {
|
guard let nativeAnimationStateData = spine_skeleton_drawable_get_animation_state_data(skeletonDrawable.wrappee) else {
|
||||||
throw SpineError("Could not load native animation state data")
|
throw SpineError("Could not load native animation state data")
|
||||||
}
|
}
|
||||||
animationStateData = AnimationStateData(nativeAnimationStateData)
|
animationStateData = AnimationStateData(nativeAnimationStateData)
|
||||||
|
|
||||||
guard let nativeAnimationState = spine_skeleton_drawable_get_animation_state(skeletonDrawable.wrappee) else {
|
guard let nativeAnimationState = spine_skeleton_drawable_get_animation_state(skeletonDrawable.wrappee) else {
|
||||||
throw SpineError("Could not load native animation state")
|
throw SpineError("Could not load native animation state")
|
||||||
}
|
}
|
||||||
@ -148,27 +148,27 @@ public final class SkeletonDrawableWrapper: NSObject {
|
|||||||
skeleton.updateWorldTransform(physics: SPINE_PHYSICS_NONE)
|
skeleton.updateWorldTransform(physics: SPINE_PHYSICS_NONE)
|
||||||
super.init()
|
super.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Updates the ``AnimationState`` using the `delta` time given in seconds, applies the
|
/// Updates the ``AnimationState`` using the `delta` time given in seconds, applies the
|
||||||
/// animation state to the ``Skeleton`` and updates the world transforms of the skeleton
|
/// animation state to the ``Skeleton`` and updates the world transforms of the skeleton
|
||||||
/// to calculate its current pose.
|
/// to calculate its current pose.
|
||||||
public func update(delta: Float) {
|
public func update(delta: Float) {
|
||||||
if disposed { return }
|
if disposed { return }
|
||||||
|
|
||||||
animationStateWrapper.update(delta: delta)
|
animationStateWrapper.update(delta: delta)
|
||||||
animationState.apply(skeleton: skeleton)
|
animationState.apply(skeleton: skeleton)
|
||||||
|
|
||||||
skeleton.update(delta: delta)
|
skeleton.update(delta: delta)
|
||||||
skeleton.updateWorldTransform(physics: SPINE_PHYSICS_UPDATE)
|
skeleton.updateWorldTransform(physics: SPINE_PHYSICS_UPDATE)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func dispose() {
|
public func dispose() {
|
||||||
if disposed { return }
|
if disposed { return }
|
||||||
disposed = true
|
disposed = true
|
||||||
|
|
||||||
atlas.dispose()
|
atlas.dispose()
|
||||||
skeletonData.dispose()
|
skeletonData.dispose()
|
||||||
|
|
||||||
skeletonDrawable.dispose()
|
skeletonDrawable.dispose()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,8 +28,8 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
|
||||||
import SpineCppLite
|
import SpineCppLite
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
public var version: String {
|
public var version: String {
|
||||||
return "\(majorVersion).\(minorVersion)"
|
return "\(majorVersion).\(minorVersion)"
|
||||||
@ -49,22 +49,22 @@ public var minorVersion: Int {
|
|||||||
///
|
///
|
||||||
/// Use the static methods ``Atlas/fromBundle(_:bundle:)``, ``Atlas/fromFile(_:)``, and ``Atlas/fromHttp(_:)`` to load an atlas. Call ``Atlas/dispose()`
|
/// Use the static methods ``Atlas/fromBundle(_:bundle:)``, ``Atlas/fromFile(_:)``, and ``Atlas/fromHttp(_:)`` to load an atlas. Call ``Atlas/dispose()`
|
||||||
/// when the atlas is no longer in use to release its resources.
|
/// when the atlas is no longer in use to release its resources.
|
||||||
public extension Atlas {
|
extension Atlas {
|
||||||
|
|
||||||
/// Loads an ``Atlas`` from the file with name `atlasFileName` in the `main` bundle or the optionally provided [bundle].
|
/// Loads an ``Atlas`` from the file with name `atlasFileName` in the `main` bundle or the optionally provided [bundle].
|
||||||
///
|
///
|
||||||
/// Throws an `Error` in case the atlas could not be loaded.
|
/// Throws an `Error` in case the atlas could not be loaded.
|
||||||
static func fromBundle(_ atlasFileName: String, bundle: Bundle = .main) async throws -> (Atlas, [UIImage]) {
|
public static func fromBundle(_ atlasFileName: String, bundle: Bundle = .main) async throws -> (Atlas, [UIImage]) {
|
||||||
let data = try await FileSource.bundle(fileName: atlasFileName, bundle: bundle).load()
|
let data = try await FileSource.bundle(fileName: atlasFileName, bundle: bundle).load()
|
||||||
return try await Self.fromData(data: data) { name in
|
return try await Self.fromData(data: data) { name in
|
||||||
return try await FileSource.bundle(fileName: name, bundle: bundle).load()
|
return try await FileSource.bundle(fileName: name, bundle: bundle).load()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Loads an ``Atlas`` from the file URL `atlasFile`.
|
/// Loads an ``Atlas`` from the file URL `atlasFile`.
|
||||||
///
|
///
|
||||||
/// Throws an `Error` in case the atlas could not be loaded.
|
/// Throws an `Error` in case the atlas could not be loaded.
|
||||||
static func fromFile(_ atlasFile: URL) async throws -> (Atlas, [UIImage]) {
|
public static func fromFile(_ atlasFile: URL) async throws -> (Atlas, [UIImage]) {
|
||||||
let data = try await FileSource.file(atlasFile).load()
|
let data = try await FileSource.file(atlasFile).load()
|
||||||
return try await Self.fromData(data: data) { name in
|
return try await Self.fromData(data: data) { name in
|
||||||
let dir = atlasFile.deletingLastPathComponent()
|
let dir = atlasFile.deletingLastPathComponent()
|
||||||
@ -72,11 +72,11 @@ public extension Atlas {
|
|||||||
return try await FileSource.file(file).load()
|
return try await FileSource.file(file).load()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Loads an ``Atlas`` from the http URL `atlasURL`.
|
/// Loads an ``Atlas`` from the http URL `atlasURL`.
|
||||||
///
|
///
|
||||||
/// Throws an `Error` in case the atlas could not be loaded.
|
/// Throws an `Error` in case the atlas could not be loaded.
|
||||||
static func fromHttp(_ atlasURL: URL) async throws -> (Atlas, [UIImage]) {
|
public static func fromHttp(_ atlasURL: URL) async throws -> (Atlas, [UIImage]) {
|
||||||
let data = try await FileSource.http(atlasURL).load()
|
let data = try await FileSource.http(atlasURL).load()
|
||||||
return try await Self.fromData(data: data) { name in
|
return try await Self.fromData(data: data) { name in
|
||||||
let dir = atlasURL.deletingLastPathComponent()
|
let dir = atlasURL.deletingLastPathComponent()
|
||||||
@ -84,7 +84,7 @@ public extension Atlas {
|
|||||||
return try await FileSource.http(file).load()
|
return try await FileSource.http(file).load()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func fromData(data: Data, loadFile: (_ name: String) async throws -> Data) async throws -> (Atlas, [UIImage]) {
|
private static func fromData(data: Data, loadFile: (_ name: String) async throws -> Data) async throws -> (Atlas, [UIImage]) {
|
||||||
guard let atlasData = String(data: data, encoding: .utf8) else {
|
guard let atlasData = String(data: data, encoding: .utf8) else {
|
||||||
throw SpineError("Couldn't read atlas bytes as utf8 string")
|
throw SpineError("Couldn't read atlas bytes as utf8 string")
|
||||||
@ -100,10 +100,10 @@ public extension Atlas {
|
|||||||
spine_atlas_dispose(atlas)
|
spine_atlas_dispose(atlas)
|
||||||
throw SpineError("Couldn't load atlas: \(message)")
|
throw SpineError("Couldn't load atlas: \(message)")
|
||||||
}
|
}
|
||||||
|
|
||||||
var atlasPages = [UIImage]()
|
var atlasPages = [UIImage]()
|
||||||
let numImagePaths = spine_atlas_get_num_image_paths(atlas);
|
let numImagePaths = spine_atlas_get_num_image_paths(atlas)
|
||||||
|
|
||||||
for i in 0..<numImagePaths {
|
for i in 0..<numImagePaths {
|
||||||
guard let atlasPageFilePointer = spine_atlas_get_image_path(atlas, i) else {
|
guard let atlasPageFilePointer = spine_atlas_get_image_path(atlas, i) else {
|
||||||
continue
|
continue
|
||||||
@ -115,52 +115,52 @@ public extension Atlas {
|
|||||||
}
|
}
|
||||||
atlasPages.append(image)
|
atlasPages.append(image)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (Atlas(atlas), atlasPages)
|
return (Atlas(atlas), atlasPages)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension SkeletonData {
|
extension SkeletonData {
|
||||||
|
|
||||||
/// Loads a ``SkeletonData`` from the file with name `skeletonFileName` in the main bundle or the optionally provided `bundle`.
|
/// Loads a ``SkeletonData`` from the file with name `skeletonFileName` in the main bundle or the optionally provided `bundle`.
|
||||||
/// Uses the provided ``Atlas`` to resolve attachment images.
|
/// Uses the provided ``Atlas`` to resolve attachment images.
|
||||||
///
|
///
|
||||||
/// Throws an `Error` in case the skeleton data could not be loaded.
|
/// Throws an `Error` in case the skeleton data could not be loaded.
|
||||||
static func fromBundle(atlas: Atlas, skeletonFileName: String, bundle: Bundle = .main) async throws -> SkeletonData {
|
public static func fromBundle(atlas: Atlas, skeletonFileName: String, bundle: Bundle = .main) async throws -> SkeletonData {
|
||||||
return try fromData(
|
return try fromData(
|
||||||
atlas: atlas,
|
atlas: atlas,
|
||||||
data: try await FileSource.bundle(fileName: skeletonFileName, bundle: bundle).load(),
|
data: try await FileSource.bundle(fileName: skeletonFileName, bundle: bundle).load(),
|
||||||
isJson: skeletonFileName.hasSuffix(".json")
|
isJson: skeletonFileName.hasSuffix(".json")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Loads a ``SkeletonData`` from the file URL `skeletonFile`. Uses the provided ``Atlas`` to resolve attachment images.
|
/// Loads a ``SkeletonData`` from the file URL `skeletonFile`. Uses the provided ``Atlas`` to resolve attachment images.
|
||||||
///
|
///
|
||||||
/// Throws an `Error` in case the skeleton data could not be loaded.
|
/// Throws an `Error` in case the skeleton data could not be loaded.
|
||||||
static func fromFile(atlas: Atlas, skeletonFile: URL) async throws -> SkeletonData {
|
public static func fromFile(atlas: Atlas, skeletonFile: URL) async throws -> SkeletonData {
|
||||||
return try fromData(
|
return try fromData(
|
||||||
atlas: atlas,
|
atlas: atlas,
|
||||||
data: try await FileSource.file(skeletonFile).load(),
|
data: try await FileSource.file(skeletonFile).load(),
|
||||||
isJson: skeletonFile.absoluteString.hasSuffix(".json")
|
isJson: skeletonFile.absoluteString.hasSuffix(".json")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Loads a ``SkeletonData`` from the http URL `skeletonFile`. Uses the provided ``Atlas`` to resolve attachment images.
|
/// Loads a ``SkeletonData`` from the http URL `skeletonFile`. Uses the provided ``Atlas`` to resolve attachment images.
|
||||||
///
|
///
|
||||||
/// Throws an `Error` in case the skeleton data could not be loaded.
|
/// Throws an `Error` in case the skeleton data could not be loaded.
|
||||||
static func fromHttp(atlas: Atlas, skeletonURL: URL) async throws -> SkeletonData {
|
public static func fromHttp(atlas: Atlas, skeletonURL: URL) async throws -> SkeletonData {
|
||||||
return try fromData(
|
return try fromData(
|
||||||
atlas: atlas,
|
atlas: atlas,
|
||||||
data: try await FileSource.http(skeletonURL).load(),
|
data: try await FileSource.http(skeletonURL).load(),
|
||||||
isJson: skeletonURL.absoluteString.hasSuffix(".json")
|
isJson: skeletonURL.absoluteString.hasSuffix(".json")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Loads a ``SkeletonData`` from the ``binary`` skeleton `Data`, using the provided ``Atlas`` to resolve attachment images.
|
/// Loads a ``SkeletonData`` from the ``binary`` skeleton `Data`, using the provided ``Atlas`` to resolve attachment images.
|
||||||
///
|
///
|
||||||
/// Throws an `Error` in case the skeleton data could not be loaded.
|
/// Throws an `Error` in case the skeleton data could not be loaded.
|
||||||
static func fromData(atlas: Atlas, data: Data) throws -> SkeletonData {
|
public static func fromData(atlas: Atlas, data: Data) throws -> SkeletonData {
|
||||||
let result = try data.withUnsafeBytes{
|
let result = try data.withUnsafeBytes {
|
||||||
try $0.withMemoryRebound(to: UInt8.self) { buffer in
|
try $0.withMemoryRebound(to: UInt8.self) { buffer in
|
||||||
guard let ptr = buffer.baseAddress else {
|
guard let ptr = buffer.baseAddress else {
|
||||||
throw SpineError("Couldn't read atlas binary")
|
throw SpineError("Couldn't read atlas binary")
|
||||||
@ -187,16 +187,17 @@ public extension SkeletonData {
|
|||||||
}
|
}
|
||||||
return SkeletonData(data)
|
return SkeletonData(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Loads a ``SkeletonData`` from the `json` string, using the provided ``Atlas`` to resolve attachment
|
/// Loads a ``SkeletonData`` from the `json` string, using the provided ``Atlas`` to resolve attachment
|
||||||
/// images.
|
/// images.
|
||||||
///
|
///
|
||||||
/// Throws an `Error` in case the atlas could not be loaded.
|
/// Throws an `Error` in case the atlas could not be loaded.
|
||||||
static func fromJson(atlas: Atlas, json: String) throws -> SkeletonData {
|
public static func fromJson(atlas: Atlas, json: String) throws -> SkeletonData {
|
||||||
let result = try json.utf8CString.withUnsafeBufferPointer { buffer in
|
let result = try json.utf8CString.withUnsafeBufferPointer { buffer in
|
||||||
guard
|
guard
|
||||||
let basePtr = buffer.baseAddress,
|
let basePtr = buffer.baseAddress,
|
||||||
let result = spine_skeleton_data_load_json(atlas.wrappee, basePtr) else {
|
let result = spine_skeleton_data_load_json(atlas.wrappee, basePtr)
|
||||||
|
else {
|
||||||
throw SpineError("Couldn't load skeleton data json")
|
throw SpineError("Couldn't load skeleton data json")
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
@ -213,7 +214,7 @@ public extension SkeletonData {
|
|||||||
}
|
}
|
||||||
return SkeletonData(data)
|
return SkeletonData(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func fromData(atlas: Atlas, data: Data, isJson: Bool) throws -> SkeletonData {
|
private static func fromData(atlas: Atlas, data: Data, isJson: Bool) throws -> SkeletonData {
|
||||||
if isJson {
|
if isJson {
|
||||||
guard let json = String(data: data, encoding: .utf8) else {
|
guard let json = String(data: data, encoding: .utf8) else {
|
||||||
@ -226,12 +227,12 @@ public extension SkeletonData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal extension SkeletonDrawable {
|
extension SkeletonDrawable {
|
||||||
|
|
||||||
func render() -> [RenderCommand] {
|
func render() -> [RenderCommand] {
|
||||||
var commands = [RenderCommand]()
|
var commands = [RenderCommand]()
|
||||||
if disposed { return commands }
|
if disposed { return commands }
|
||||||
|
|
||||||
var nativeCmd = spine_skeleton_drawable_render(wrappee)
|
var nativeCmd = spine_skeleton_drawable_render(wrappee)
|
||||||
repeat {
|
repeat {
|
||||||
if let ncmd = nativeCmd {
|
if let ncmd = nativeCmd {
|
||||||
@ -240,18 +241,18 @@ internal extension SkeletonDrawable {
|
|||||||
} else {
|
} else {
|
||||||
nativeCmd = nil
|
nativeCmd = nil
|
||||||
}
|
}
|
||||||
} while (nativeCmd != nil)
|
} while nativeCmd != nil
|
||||||
|
|
||||||
return commands
|
return commands
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal extension RenderCommand {
|
extension RenderCommand {
|
||||||
|
|
||||||
var numVertices: Int {
|
var numVertices: Int {
|
||||||
Int(spine_render_command_get_num_vertices(wrappee))
|
Int(spine_render_command_get_num_vertices(wrappee))
|
||||||
}
|
}
|
||||||
|
|
||||||
func positions(numVertices: Int) -> [Float] {
|
func positions(numVertices: Int) -> [Float] {
|
||||||
let num = numVertices * 2
|
let num = numVertices * 2
|
||||||
let ptr = spine_render_command_get_positions(wrappee)
|
let ptr = spine_render_command_get_positions(wrappee)
|
||||||
@ -259,7 +260,7 @@ internal extension RenderCommand {
|
|||||||
let buffer = UnsafeBufferPointer(start: validPtr, count: num)
|
let buffer = UnsafeBufferPointer(start: validPtr, count: num)
|
||||||
return Array(buffer)
|
return Array(buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
func uvs(numVertices: Int) -> [Float] {
|
func uvs(numVertices: Int) -> [Float] {
|
||||||
let num = numVertices * 2
|
let num = numVertices * 2
|
||||||
let ptr = spine_render_command_get_uvs(wrappee)
|
let ptr = spine_render_command_get_uvs(wrappee)
|
||||||
@ -267,8 +268,8 @@ internal extension RenderCommand {
|
|||||||
let buffer = UnsafeBufferPointer(start: validPtr, count: num)
|
let buffer = UnsafeBufferPointer(start: validPtr, count: num)
|
||||||
return Array(buffer)
|
return Array(buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
func colors(numVertices: Int) ->[Int32] {
|
func colors(numVertices: Int) -> [Int32] {
|
||||||
let num = numVertices
|
let num = numVertices
|
||||||
let ptr = spine_render_command_get_colors(wrappee)
|
let ptr = spine_render_command_get_colors(wrappee)
|
||||||
guard let validPtr = ptr else { return [] }
|
guard let validPtr = ptr else { return [] }
|
||||||
@ -277,21 +278,21 @@ internal extension RenderCommand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension Skin {
|
extension Skin {
|
||||||
|
|
||||||
/// Constructs a new empty ``Skin`` using the given `name`. Skins constructed this way must be manually disposed via the `dispose` method
|
/// Constructs a new empty ``Skin`` using the given `name`. Skins constructed this way must be manually disposed via the `dispose` method
|
||||||
/// if they are no longer used.
|
/// if they are no longer used.
|
||||||
static func create(name: String) -> Skin {
|
public static func create(name: String) -> Skin {
|
||||||
return Skin(spine_skin_create(name))
|
return Skin(spine_skin_create(name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper
|
// Helper
|
||||||
|
|
||||||
public extension CGRect {
|
extension CGRect {
|
||||||
|
|
||||||
/// Construct a `CGRect` from ``Bounds``
|
/// Construct a `CGRect` from ``Bounds``
|
||||||
init(bounds: Bounds) {
|
public init(bounds: Bounds) {
|
||||||
self = CGRect(
|
self = CGRect(
|
||||||
x: CGFloat(bounds.x),
|
x: CGFloat(bounds.x),
|
||||||
y: CGFloat(bounds.y),
|
y: CGFloat(bounds.y),
|
||||||
@ -305,7 +306,7 @@ internal enum FileSource {
|
|||||||
case bundle(fileName: String, bundle: Bundle = .main)
|
case bundle(fileName: String, bundle: Bundle = .main)
|
||||||
case file(URL)
|
case file(URL)
|
||||||
case http(URL)
|
case http(URL)
|
||||||
|
|
||||||
internal func load() async throws -> Data {
|
internal func load() async throws -> Data {
|
||||||
switch self {
|
switch self {
|
||||||
case .bundle(let fileName, let bundle):
|
case .bundle(let fileName, let bundle):
|
||||||
@ -314,7 +315,7 @@ internal enum FileSource {
|
|||||||
throw SpineError("Provide both file name and file extension")
|
throw SpineError("Provide both file name and file extension")
|
||||||
}
|
}
|
||||||
let name = components.dropLast(1).joined(separator: ".")
|
let name = components.dropLast(1).joined(separator: ".")
|
||||||
|
|
||||||
guard let fileUrl = bundle.url(forResource: name, withExtension: String(ext)) else {
|
guard let fileUrl = bundle.url(forResource: name, withExtension: String(ext)) else {
|
||||||
throw SpineError("Could not load file with name \(name) from bundle")
|
throw SpineError("Could not load file with name \(name) from bundle")
|
||||||
}
|
}
|
||||||
@ -331,9 +332,9 @@ internal enum FileSource {
|
|||||||
} else {
|
} else {
|
||||||
let lock = NSRecursiveLock()
|
let lock = NSRecursiveLock()
|
||||||
nonisolated(unsafe)
|
nonisolated(unsafe)
|
||||||
var isCancelled = false
|
var isCancelled = false
|
||||||
nonisolated(unsafe)
|
nonisolated(unsafe)
|
||||||
var taskHolder:URLSessionDownloadTask? = nil
|
var taskHolder: URLSessionDownloadTask? = nil
|
||||||
return try await withTaskCancellationHandler {
|
return try await withTaskCancellationHandler {
|
||||||
try await withCheckedThrowingContinuation { continuation in
|
try await withCheckedThrowingContinuation { continuation in
|
||||||
let task = URLSession.shared.downloadTask(with: url) { temp, response, error in
|
let task = URLSession.shared.downloadTask(with: url) { temp, response, error in
|
||||||
@ -380,25 +381,25 @@ internal enum FileSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public struct SpineError: Error, CustomStringConvertible {
|
public struct SpineError: Error, CustomStringConvertible {
|
||||||
|
|
||||||
public let description: String
|
public let description: String
|
||||||
|
|
||||||
internal init(_ description: String) {
|
internal init(_ description: String) {
|
||||||
self.description = description
|
self.description = description
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension SkeletonBounds {
|
extension SkeletonBounds {
|
||||||
static func create() -> SkeletonBounds {
|
public static func create() -> SkeletonBounds {
|
||||||
return SkeletonBounds(spine_skeleton_bounds_create())
|
return SkeletonBounds(spine_skeleton_bounds_create())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc public extension Atlas {
|
@objc extension Atlas {
|
||||||
|
|
||||||
var imagePathCount:Int32 {
|
public var imagePathCount: Int32 {
|
||||||
spine_atlas_get_num_image_paths(wrappee)
|
spine_atlas_get_num_image_paths(wrappee)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,8 +27,8 @@
|
|||||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import CoreGraphics
|
import CoreGraphics
|
||||||
|
import Foundation
|
||||||
import QuartzCore
|
import QuartzCore
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
@ -63,27 +63,27 @@ public typealias SpineControllerCallback = (_ controller: SpineController) -> Vo
|
|||||||
/// Per default, ``SkeletonDrawableWrapper`` is disposed when ``SpineController`` is deinitialized. You can disable this behaviour with the ``disposeDrawableOnDeInit`` contructor parameter.
|
/// Per default, ``SkeletonDrawableWrapper`` is disposed when ``SpineController`` is deinitialized. You can disable this behaviour with the ``disposeDrawableOnDeInit`` contructor parameter.
|
||||||
@objcMembers
|
@objcMembers
|
||||||
public final class SpineController: NSObject, ObservableObject {
|
public final class SpineController: NSObject, ObservableObject {
|
||||||
|
|
||||||
public internal(set) var drawable: SkeletonDrawableWrapper!
|
public internal(set) var drawable: SkeletonDrawableWrapper!
|
||||||
|
|
||||||
private let onInitialized: SpineControllerCallback?
|
private let onInitialized: SpineControllerCallback?
|
||||||
private let onBeforeUpdateWorldTransforms: SpineControllerCallback?
|
private let onBeforeUpdateWorldTransforms: SpineControllerCallback?
|
||||||
private let onAfterUpdateWorldTransforms: SpineControllerCallback?
|
private let onAfterUpdateWorldTransforms: SpineControllerCallback?
|
||||||
private let onBeforePaint: SpineControllerCallback?
|
private let onBeforePaint: SpineControllerCallback?
|
||||||
private let onAfterPaint: SpineControllerCallback?
|
private let onAfterPaint: SpineControllerCallback?
|
||||||
private let disposeDrawableOnDeInit: Bool
|
private let disposeDrawableOnDeInit: Bool
|
||||||
|
|
||||||
private var scaleX: CGFloat = 1
|
private var scaleX: CGFloat = 1
|
||||||
private var scaleY: CGFloat = 1
|
private var scaleY: CGFloat = 1
|
||||||
private var offsetX: CGFloat = 0
|
private var offsetX: CGFloat = 0
|
||||||
private var offsetY: CGFloat = 0
|
private var offsetY: CGFloat = 0
|
||||||
|
|
||||||
@Published
|
@Published
|
||||||
public private(set) var isPlaying: Bool = true
|
public private(set) var isPlaying: Bool = true
|
||||||
|
|
||||||
@Published
|
@Published
|
||||||
public private(set) var viewSize: CGSize = .zero
|
public private(set) var viewSize: CGSize = .zero
|
||||||
|
|
||||||
/// Constructs a new ``SpineUIview`` controller. See the class documentation of ``SpineWidgetController`` for information on
|
/// Constructs a new ``SpineUIview`` controller. See the class documentation of ``SpineWidgetController`` for information on
|
||||||
/// the optional arguments.
|
/// the optional arguments.
|
||||||
public init(
|
public init(
|
||||||
@ -100,80 +100,80 @@ public final class SpineController: NSObject, ObservableObject {
|
|||||||
self.onBeforePaint = onBeforePaint
|
self.onBeforePaint = onBeforePaint
|
||||||
self.onAfterPaint = onAfterPaint
|
self.onAfterPaint = onAfterPaint
|
||||||
self.disposeDrawableOnDeInit = disposeDrawableOnDeInit
|
self.disposeDrawableOnDeInit = disposeDrawableOnDeInit
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
if disposeDrawableOnDeInit {
|
if disposeDrawableOnDeInit {
|
||||||
drawable?.dispose() // TODO move drawable out of view?
|
drawable?.dispose() // TODO move drawable out of view?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The ``Atlas`` from which images to render the skeleton are sourced.
|
/// The ``Atlas`` from which images to render the skeleton are sourced.
|
||||||
public var atlas: Atlas {
|
public var atlas: Atlas {
|
||||||
drawable.atlas
|
drawable.atlas
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The setup-pose data used by the skeleton.
|
/// The setup-pose data used by the skeleton.
|
||||||
public var skeletonData: SkeletonData {
|
public var skeletonData: SkeletonData {
|
||||||
drawable.skeletonData
|
drawable.skeletonData
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The ``Skeleton``
|
/// The ``Skeleton``
|
||||||
public var skeleton: Skeleton {
|
public var skeleton: Skeleton {
|
||||||
drawable.skeleton
|
drawable.skeleton
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The mixing information used by the ``AnimationState``
|
/// The mixing information used by the ``AnimationState``
|
||||||
public var animationStateData: AnimationStateData {
|
public var animationStateData: AnimationStateData {
|
||||||
drawable.animationStateData
|
drawable.animationStateData
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The ``AnimationState`` used to manage animations that are being applied to the
|
/// The ``AnimationState`` used to manage animations that are being applied to the
|
||||||
/// skeleton.
|
/// skeleton.
|
||||||
public var animationState: AnimationState {
|
public var animationState: AnimationState {
|
||||||
drawable.animationState
|
drawable.animationState
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The ``AnimationStateWrapper`` used to hold ``AnimationState``, register ``AnimationStateListener`` and call ``AnimationStateWrapper/update(delta:)``
|
/// The ``AnimationStateWrapper`` used to hold ``AnimationState``, register ``AnimationStateListener`` and call ``AnimationStateWrapper/update(delta:)``
|
||||||
public var animationStateWrapper: AnimationStateWrapper {
|
public var animationStateWrapper: AnimationStateWrapper {
|
||||||
drawable.animationStateWrapper
|
drawable.animationStateWrapper
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Transforms the coordinates given in the ``SpineUIView`` coordinate system in `position` to
|
/// Transforms the coordinates given in the ``SpineUIView`` coordinate system in `position` to
|
||||||
/// the skeleton coordinate system. See the `IKFollowing.swift` example how to use this
|
/// the skeleton coordinate system. See the `IKFollowing.swift` example how to use this
|
||||||
/// to move a bone based on user touch input.
|
/// to move a bone based on user touch input.
|
||||||
public func toSkeletonCoordinates(position: CGPoint) -> CGPoint {
|
public func toSkeletonCoordinates(position: CGPoint) -> CGPoint {
|
||||||
let x = position.x;
|
let x = position.x
|
||||||
let y = position.y;
|
let y = position.y
|
||||||
return CGPoint(
|
return CGPoint(
|
||||||
x: (x - viewSize.width / 2) / scaleX - offsetX,
|
x: (x - viewSize.width / 2) / scaleX - offsetX,
|
||||||
y: (y - viewSize.height / 2) / scaleY - offsetY
|
y: (y - viewSize.height / 2) / scaleY - offsetY
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Transforms the coordinates given in skeleton coordinate system to
|
/// Transforms the coordinates given in skeleton coordinate system to
|
||||||
/// the the ``SpineUIView`` coordinates. See the `DebugRendering.swift` example hot to use this to draw rectangles over skeleton bones for debugging purposes.
|
/// the the ``SpineUIView`` coordinates. See the `DebugRendering.swift` example hot to use this to draw rectangles over skeleton bones for debugging purposes.
|
||||||
public func fromSkeletonCoordinates(position: CGPoint) -> CGPoint {
|
public func fromSkeletonCoordinates(position: CGPoint) -> CGPoint {
|
||||||
let x = position.x;
|
let x = position.x
|
||||||
let y = position.y;
|
let y = position.y
|
||||||
return CGPoint(
|
return CGPoint(
|
||||||
x: (x + offsetX) * scaleX,
|
x: (x + offsetX) * scaleX,
|
||||||
y: (y + offsetY) * scaleY
|
y: (y + offsetY) * scaleY
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pauses updating and rendering the skeleton.
|
/// Pauses updating and rendering the skeleton.
|
||||||
public func pause() {
|
public func pause() {
|
||||||
isPlaying = false
|
isPlaying = false
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resumes updating and rendering the skeleton.
|
/// Resumes updating and rendering the skeleton.
|
||||||
public func resume() {
|
public func resume() {
|
||||||
isPlaying = true
|
isPlaying = true
|
||||||
}
|
}
|
||||||
|
|
||||||
internal func load(atlasFile: String, skeletonFile: String, bundle: Bundle = .main) async throws {
|
internal func load(atlasFile: String, skeletonFile: String, bundle: Bundle = .main) async throws {
|
||||||
let atlasAndPages = try await Atlas.fromBundle(atlasFile, bundle: bundle)
|
let atlasAndPages = try await Atlas.fromBundle(atlasFile, bundle: bundle)
|
||||||
let skeletonData = try await SkeletonData.fromBundle(
|
let skeletonData = try await SkeletonData.fromBundle(
|
||||||
@ -190,23 +190,23 @@ public final class SpineController: NSObject, ObservableObject {
|
|||||||
self.drawable = skeletonDrawableWrapper
|
self.drawable = skeletonDrawableWrapper
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal func initialize() {
|
internal func initialize() {
|
||||||
onInitialized?(self)
|
onInitialized?(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension SpineController: SpineRendererDelegate {
|
extension SpineController: SpineRendererDelegate {
|
||||||
|
|
||||||
func spineRendererWillDraw(_ spineRenderer: SpineRenderer) {
|
func spineRendererWillDraw(_ spineRenderer: SpineRenderer) {
|
||||||
onBeforePaint?(self)
|
onBeforePaint?(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
func spineRendererDidDraw(_ spineRenderer: SpineRenderer) {
|
func spineRendererDidDraw(_ spineRenderer: SpineRenderer) {
|
||||||
onAfterPaint?(self)
|
onAfterPaint?(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
func spineRendererDidUpdate(_ spineRenderer: SpineRenderer, scaleX: CGFloat, scaleY: CGFloat, offsetX: CGFloat, offsetY: CGFloat, size: CGSize) {
|
func spineRendererDidUpdate(_ spineRenderer: SpineRenderer, scaleX: CGFloat, scaleY: CGFloat, offsetX: CGFloat, offsetY: CGFloat, size: CGSize) {
|
||||||
self.scaleX = scaleX
|
self.scaleX = scaleX
|
||||||
self.scaleY = scaleY
|
self.scaleY = scaleY
|
||||||
@ -217,27 +217,27 @@ extension SpineController: SpineRendererDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension SpineController: SpineRendererDataSource {
|
extension SpineController: SpineRendererDataSource {
|
||||||
|
|
||||||
func spineRendererWillUpdate(_ spineRenderer: SpineRenderer) {
|
func spineRendererWillUpdate(_ spineRenderer: SpineRenderer) {
|
||||||
onBeforeUpdateWorldTransforms?(self)
|
onBeforeUpdateWorldTransforms?(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
func spineRendererDidUpdate(_ spineRenderer: SpineRenderer) {
|
func spineRendererDidUpdate(_ spineRenderer: SpineRenderer) {
|
||||||
onAfterUpdateWorldTransforms?(self)
|
onAfterUpdateWorldTransforms?(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
func spineRenderer(_ spineRenderer: SpineRenderer, needsUpdate delta: TimeInterval) {
|
func spineRenderer(_ spineRenderer: SpineRenderer, needsUpdate delta: TimeInterval) {
|
||||||
drawable?.update(delta: Float(delta))
|
drawable?.update(delta: Float(delta))
|
||||||
}
|
}
|
||||||
|
|
||||||
func isPlaying(_ spineRenderer: SpineRenderer) -> Bool {
|
func isPlaying(_ spineRenderer: SpineRenderer) -> Bool {
|
||||||
return isPlaying
|
return isPlaying
|
||||||
}
|
}
|
||||||
|
|
||||||
func skeletonDrawable(_ spineRenderer: SpineRenderer) -> SkeletonDrawableWrapper {
|
func skeletonDrawable(_ spineRenderer: SpineRenderer) -> SkeletonDrawableWrapper {
|
||||||
return drawable
|
return drawable
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderCommands(_ spineRenderer: SpineRenderer) -> [RenderCommand] {
|
func renderCommands(_ spineRenderer: SpineRenderer) -> [RenderCommand] {
|
||||||
return drawable?.skeletonDrawable.render() ?? []
|
return drawable?.skeletonDrawable.render() ?? []
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,8 +27,8 @@
|
|||||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import MetalKit
|
import MetalKit
|
||||||
|
import UIKit
|
||||||
|
|
||||||
/// A ``UIView`` to display a Spine skeleton. The skeleton can be loaded from a bundle, local files, http, or a pre-loaded ``SkeletonDrawableWrapper``.
|
/// A ``UIView`` to display a Spine skeleton. The skeleton can be loaded from a bundle, local files, http, or a pre-loaded ``SkeletonDrawableWrapper``.
|
||||||
///
|
///
|
||||||
@ -40,15 +40,15 @@ import MetalKit
|
|||||||
/// This is a direct subclass of ``MTKView`` and is using `Metal` to render the skeleton.
|
/// This is a direct subclass of ``MTKView`` and is using `Metal` to render the skeleton.
|
||||||
@objc
|
@objc
|
||||||
public final class SpineUIView: MTKView {
|
public final class SpineUIView: MTKView {
|
||||||
|
|
||||||
let controller: SpineController
|
let controller: SpineController
|
||||||
let mode: Spine.ContentMode
|
let mode: Spine.ContentMode
|
||||||
let alignment: Spine.Alignment
|
let alignment: Spine.Alignment
|
||||||
let boundsProvider: BoundsProvider
|
let boundsProvider: BoundsProvider
|
||||||
|
|
||||||
internal var computedBounds: CGRect = .zero
|
internal var computedBounds: CGRect = .zero
|
||||||
internal var renderer: SpineRenderer?
|
internal var renderer: SpineRenderer?
|
||||||
|
|
||||||
@objc internal init(
|
@objc internal init(
|
||||||
controller: SpineController = SpineController(),
|
controller: SpineController = SpineController(),
|
||||||
mode: Spine.ContentMode = .fit,
|
mode: Spine.ContentMode = .fit,
|
||||||
@ -60,12 +60,12 @@ public final class SpineUIView: MTKView {
|
|||||||
self.mode = mode
|
self.mode = mode
|
||||||
self.alignment = alignment
|
self.alignment = alignment
|
||||||
self.boundsProvider = boundsProvider
|
self.boundsProvider = boundsProvider
|
||||||
|
|
||||||
super.init(frame: .zero, device: SpineObjects.shared.device)
|
super.init(frame: .zero, device: SpineObjects.shared.device)
|
||||||
clearColor = MTLClearColor(backgroundColor)
|
clearColor = MTLClearColor(backgroundColor)
|
||||||
isOpaque = backgroundColor != .clear
|
isOpaque = backgroundColor != .clear
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An initializer that constructs a new ``SpineUIView`` from a ``SpineViewSource``.
|
/// An initializer that constructs a new ``SpineUIView`` from a ``SpineViewSource``.
|
||||||
///
|
///
|
||||||
/// After initialization is complete, the provided `controller` is invoked as per the ``SpineController`` semantics, to allow
|
/// After initialization is complete, the provided `controller` is invoked as per the ``SpineController`` semantics, to allow
|
||||||
@ -98,7 +98,7 @@ public final class SpineUIView: MTKView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A convenience initializer that constructs a new ``SpineUIView`` from bundled files.
|
/// A convenience initializer that constructs a new ``SpineUIView`` from bundled files.
|
||||||
///
|
///
|
||||||
/// After initialization is complete, the provided `controller` is invoked as per the ``SpineController`` semantics, to allow
|
/// After initialization is complete, the provided `controller` is invoked as per the ``SpineController`` semantics, to allow
|
||||||
@ -125,9 +125,11 @@ public final class SpineUIView: MTKView {
|
|||||||
boundsProvider: BoundsProvider = SetupPoseBounds(),
|
boundsProvider: BoundsProvider = SetupPoseBounds(),
|
||||||
backgroundColor: UIColor = .clear
|
backgroundColor: UIColor = .clear
|
||||||
) {
|
) {
|
||||||
self.init(from: .bundle(atlasFileName: atlasFileName, skeletonFileName: skeletonFileName, bundle: bundle), controller: controller, mode: mode, alignment: alignment, boundsProvider: boundsProvider, backgroundColor: backgroundColor)
|
self.init(
|
||||||
|
from: .bundle(atlasFileName: atlasFileName, skeletonFileName: skeletonFileName, bundle: bundle), controller: controller, mode: mode,
|
||||||
|
alignment: alignment, boundsProvider: boundsProvider, backgroundColor: backgroundColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A convenience initializer that constructs a new ``SpineUIView`` from file URLs.
|
/// A convenience initializer that constructs a new ``SpineUIView`` from file URLs.
|
||||||
///
|
///
|
||||||
/// After initialization is complete, the provided `controller` is invoked as per the ``SpineController`` semantics, to allow
|
/// After initialization is complete, the provided `controller` is invoked as per the ``SpineController`` semantics, to allow
|
||||||
@ -152,9 +154,11 @@ public final class SpineUIView: MTKView {
|
|||||||
boundsProvider: BoundsProvider = SetupPoseBounds(),
|
boundsProvider: BoundsProvider = SetupPoseBounds(),
|
||||||
backgroundColor: UIColor = .clear
|
backgroundColor: UIColor = .clear
|
||||||
) {
|
) {
|
||||||
self.init(from: .file(atlasFile: atlasFile, skeletonFile: skeletonFile), controller: controller, mode: mode, alignment: alignment, boundsProvider: boundsProvider, backgroundColor: backgroundColor)
|
self.init(
|
||||||
|
from: .file(atlasFile: atlasFile, skeletonFile: skeletonFile), controller: controller, mode: mode, alignment: alignment,
|
||||||
|
boundsProvider: boundsProvider, backgroundColor: backgroundColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A convenience initializer that constructs a new ``SpineUIView`` from HTTP.
|
/// A convenience initializer that constructs a new ``SpineUIView`` from HTTP.
|
||||||
///
|
///
|
||||||
/// After initialization is complete, the provided `controller` is invoked as per the ``SpineController`` semantics, to allow
|
/// After initialization is complete, the provided `controller` is invoked as per the ``SpineController`` semantics, to allow
|
||||||
@ -179,9 +183,11 @@ public final class SpineUIView: MTKView {
|
|||||||
boundsProvider: BoundsProvider = SetupPoseBounds(),
|
boundsProvider: BoundsProvider = SetupPoseBounds(),
|
||||||
backgroundColor: UIColor = .clear
|
backgroundColor: UIColor = .clear
|
||||||
) {
|
) {
|
||||||
self.init(from: .http(atlasURL: atlasURL, skeletonURL: skeletonURL), controller: controller, mode: mode, alignment: alignment, boundsProvider: boundsProvider, backgroundColor: backgroundColor)
|
self.init(
|
||||||
|
from: .http(atlasURL: atlasURL, skeletonURL: skeletonURL), controller: controller, mode: mode, alignment: alignment,
|
||||||
|
boundsProvider: boundsProvider, backgroundColor: backgroundColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A convenience initializer that constructs a new ``SpineUIView`` with a ``SkeletonDrawableWrapper``.
|
/// A convenience initializer that constructs a new ``SpineUIView`` with a ``SkeletonDrawableWrapper``.
|
||||||
///
|
///
|
||||||
/// After initialization is complete, the provided `controller` is invoked as per the ``SpineController`` semantics, to allow
|
/// After initialization is complete, the provided `controller` is invoked as per the ``SpineController`` semantics, to allow
|
||||||
@ -203,17 +209,19 @@ public final class SpineUIView: MTKView {
|
|||||||
boundsProvider: BoundsProvider = SetupPoseBounds(),
|
boundsProvider: BoundsProvider = SetupPoseBounds(),
|
||||||
backgroundColor: UIColor = .clear
|
backgroundColor: UIColor = .clear
|
||||||
) {
|
) {
|
||||||
self.init(from: .drawable(drawable), controller: controller, mode: mode, alignment: alignment, boundsProvider: boundsProvider, backgroundColor: backgroundColor)
|
self.init(
|
||||||
|
from: .drawable(drawable), controller: controller, mode: mode, alignment: alignment, boundsProvider: boundsProvider,
|
||||||
|
backgroundColor: backgroundColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal override init(frame frameRect: CGRect, device: MTLDevice?) {
|
internal override init(frame frameRect: CGRect, device: MTLDevice?) {
|
||||||
fatalError("init(frame: device:) has not been implemented. Use init() instead.")
|
fatalError("init(frame: device:) has not been implemented. Use init() instead.")
|
||||||
}
|
}
|
||||||
|
|
||||||
internal required init(coder: NSCoder) {
|
internal required init(coder: NSCoder) {
|
||||||
fatalError("init(coder:) has not been implemented. Use init() instead.")
|
fatalError("init(coder:) has not been implemented. Use init() instead.")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Disable or enable rendering. Disable it when the spine view is out of bounds and you want to preserve CPU/GPU resources.
|
/// Disable or enable rendering. Disable it when the spine view is out of bounds and you want to preserve CPU/GPU resources.
|
||||||
public var isRendering: Bool {
|
public var isRendering: Bool {
|
||||||
get { !super.isPaused }
|
get { !super.isPaused }
|
||||||
@ -227,7 +235,7 @@ public final class SpineUIView: MTKView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension SpineUIView {
|
extension SpineUIView {
|
||||||
|
|
||||||
internal func load(drawable: SkeletonDrawableWrapper) throws {
|
internal func load(drawable: SkeletonDrawableWrapper) throws {
|
||||||
controller.drawable = drawable
|
controller.drawable = drawable
|
||||||
computedBounds = boundsProvider.computeBounds(for: drawable)
|
computedBounds = boundsProvider.computeBounds(for: drawable)
|
||||||
@ -236,7 +244,7 @@ extension SpineUIView {
|
|||||||
)
|
)
|
||||||
controller.initialize()
|
controller.initialize()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func initRenderer(atlasPages: [UIImage]) throws {
|
private func initRenderer(atlasPages: [UIImage]) throws {
|
||||||
renderer = try SpineRenderer(
|
renderer = try SpineRenderer(
|
||||||
device: SpineObjects.shared.device,
|
device: SpineObjects.shared.device,
|
||||||
@ -265,7 +273,7 @@ public enum SpineViewSource {
|
|||||||
case file(atlasFile: URL, skeletonFile: URL)
|
case file(atlasFile: URL, skeletonFile: URL)
|
||||||
case http(atlasURL: URL, skeletonURL: URL)
|
case http(atlasURL: URL, skeletonURL: URL)
|
||||||
case drawable(SkeletonDrawableWrapper)
|
case drawable(SkeletonDrawableWrapper)
|
||||||
|
|
||||||
internal func loadDrawable() async throws -> SkeletonDrawableWrapper {
|
internal func loadDrawable() async throws -> SkeletonDrawableWrapper {
|
||||||
switch self {
|
switch self {
|
||||||
case .bundle(let atlasFileName, let skeletonFileName, let bundle):
|
case .bundle(let atlasFileName, let skeletonFileName, let bundle):
|
||||||
|
|||||||
@ -38,7 +38,7 @@ import SwiftUI
|
|||||||
///
|
///
|
||||||
/// This is a ``UIViewRepresentable`` of `SpineUIView`.
|
/// This is a ``UIViewRepresentable`` of `SpineUIView`.
|
||||||
public struct SpineView: UIViewRepresentable {
|
public struct SpineView: UIViewRepresentable {
|
||||||
|
|
||||||
public typealias UIViewType = SpineUIView
|
public typealias UIViewType = SpineUIView
|
||||||
|
|
||||||
private let source: SpineViewSource
|
private let source: SpineViewSource
|
||||||
@ -46,11 +46,11 @@ public struct SpineView: UIViewRepresentable {
|
|||||||
private let mode: Spine.ContentMode
|
private let mode: Spine.ContentMode
|
||||||
private let alignment: Spine.Alignment
|
private let alignment: Spine.Alignment
|
||||||
private let boundsProvider: BoundsProvider
|
private let boundsProvider: BoundsProvider
|
||||||
private let backgroundColor: UIColor // Not using `SwiftUI.Color`, as briging to `UIColor` prior iOS 14 might not always work.
|
private let backgroundColor: UIColor // Not using `SwiftUI.Color`, as briging to `UIColor` prior iOS 14 might not always work.
|
||||||
|
|
||||||
@Binding
|
@Binding
|
||||||
private var isRendering: Bool?
|
private var isRendering: Bool?
|
||||||
|
|
||||||
/// An initializer that constructs a new ``SpineView`` from a ``SpineViewSource``.
|
/// An initializer that constructs a new ``SpineView`` from a ``SpineViewSource``.
|
||||||
///
|
///
|
||||||
/// After initialization is complete, the provided `controller` is invoked as per the ``SpineController`` semantics, to allow
|
/// After initialization is complete, the provided `controller` is invoked as per the ``SpineController`` semantics, to allow
|
||||||
@ -85,7 +85,7 @@ public struct SpineView: UIViewRepresentable {
|
|||||||
self.backgroundColor = backgroundColor
|
self.backgroundColor = backgroundColor
|
||||||
_isRendering = isRendering
|
_isRendering = isRendering
|
||||||
}
|
}
|
||||||
|
|
||||||
public func makeUIView(context: Context) -> SpineUIView {
|
public func makeUIView(context: Context) -> SpineUIView {
|
||||||
return SpineUIView(
|
return SpineUIView(
|
||||||
from: source,
|
from: source,
|
||||||
@ -96,7 +96,7 @@ public struct SpineView: UIViewRepresentable {
|
|||||||
backgroundColor: backgroundColor
|
backgroundColor: backgroundColor
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func updateUIView(_ uiView: SpineUIView, context: Context) {
|
public func updateUIView(_ uiView: SpineUIView, context: Context) {
|
||||||
if let isRendering {
|
if let isRendering {
|
||||||
uiView.isRendering = isRendering
|
uiView.isRendering = isRendering
|
||||||
|
|||||||
@ -6,9 +6,9 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
|
|
||||||
#if canImport(Spine)
|
|
||||||
@_exported import Spine
|
|
||||||
#endif
|
|
||||||
|
|
||||||
@_exported import SpineCppLite
|
@_exported import SpineCppLite
|
||||||
@_exported import SpineShadersStructs
|
@_exported import SpineShadersStructs
|
||||||
|
|
||||||
|
#if canImport(Spine)
|
||||||
|
@_exported import Spine
|
||||||
|
#endif
|
||||||
|
|||||||
@ -16,21 +16,19 @@ fi
|
|||||||
log_title "Spine-TS Build"
|
log_title "Spine-TS Build"
|
||||||
log_detail "Branch: $BRANCH"
|
log_detail "Branch: $BRANCH"
|
||||||
|
|
||||||
log_section "Setup"
|
|
||||||
log_action "Installing dependencies"
|
log_action "Installing dependencies"
|
||||||
if npm install > /tmp/npm-install.log 2>&1; then
|
if npm install > /tmp/npm-install.log 2>&1; then
|
||||||
log_ok "Dependencies installed"
|
log_ok
|
||||||
else
|
else
|
||||||
log_fail "npm install failed"
|
log_fail
|
||||||
log_detail "$(cat /tmp/npm-install.log)"
|
log_error_output "$(cat /tmp/npm-install.log)"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! [ -z "$TS_UPDATE_URL" ] && ! [ -z "$BRANCH" ];
|
if ! [ -z "$TS_UPDATE_URL" ] && ! [ -z "$BRANCH" ];
|
||||||
then
|
then
|
||||||
log_section "Deploy"
|
|
||||||
log_action "Creating artifacts zip"
|
log_action "Creating artifacts zip"
|
||||||
zip -j spine-ts.zip \
|
if ZIP_OUTPUT=$(zip -j spine-ts.zip \
|
||||||
spine-core/dist/iife/* \
|
spine-core/dist/iife/* \
|
||||||
spine-canvas/dist/iife/* \
|
spine-canvas/dist/iife/* \
|
||||||
spine-webgl/dist/iife/* \
|
spine-webgl/dist/iife/* \
|
||||||
@ -51,13 +49,20 @@ then
|
|||||||
spine-phaser-v3/dist/esm/* \
|
spine-phaser-v3/dist/esm/* \
|
||||||
spine-phaser-v4/dist/esm/* \
|
spine-phaser-v4/dist/esm/* \
|
||||||
spine-webcomponents/dist/esm/* \
|
spine-webcomponents/dist/esm/* \
|
||||||
spine-player/css/spine-player.css > /dev/null 2>&1
|
spine-player/css/spine-player.css 2>&1); then
|
||||||
|
log_ok
|
||||||
|
else
|
||||||
|
log_fail
|
||||||
|
log_error_output "$ZIP_OUTPUT"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
log_action "Uploading to $TS_UPDATE_URL$BRANCH"
|
log_action "Uploading to $TS_UPDATE_URL$BRANCH"
|
||||||
if curl -f -F "file=@spine-ts.zip" "$TS_UPDATE_URL$BRANCH" > /dev/null 2>&1; then
|
if CURL_OUTPUT=$(curl -f -F "file=@spine-ts.zip" "$TS_UPDATE_URL$BRANCH" 2>&1); then
|
||||||
log_ok "Artifacts deployed"
|
log_ok
|
||||||
else
|
else
|
||||||
log_fail "Upload failed"
|
log_fail
|
||||||
|
log_error_output "$CURL_OUTPUT"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@ -17,7 +17,6 @@ fi
|
|||||||
|
|
||||||
# Install dependencies if node_modules doesn't exist
|
# Install dependencies if node_modules doesn't exist
|
||||||
if [ ! -d "node_modules" ]; then
|
if [ ! -d "node_modules" ]; then
|
||||||
log_section "Setup"
|
|
||||||
log_action "Installing dependencies"
|
log_action "Installing dependencies"
|
||||||
if npm install > /tmp/npm-install.log 2>&1; then
|
if npm install > /tmp/npm-install.log 2>&1; then
|
||||||
log_ok "Dependencies installed"
|
log_ok "Dependencies installed"
|
||||||
@ -28,51 +27,42 @@ if [ ! -d "node_modules" ]; then
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log_section "Analyzing API"
|
|
||||||
log_action "Analyzing Java API"
|
log_action "Analyzing Java API"
|
||||||
if output=$(npx -y tsx src/analyze-java-api.ts 2>&1); then
|
if output=$(npx -y tsx src/analyze-java-api.ts 2>&1); then
|
||||||
log_ok "Java API analysis completed"
|
log_ok
|
||||||
else
|
else
|
||||||
log_fail "Failed to analyze Java API"
|
log_fail
|
||||||
log_detail "$output"
|
log_detail "$output"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log_section "Generating Serializer IR"
|
|
||||||
log_action "Generating intermediate representation"
|
log_action "Generating intermediate representation"
|
||||||
if output=$(npx -y tsx src/generate-serializer-ir.ts 2>&1); then
|
if output=$(npx -y tsx src/generate-serializer-ir.ts 2>&1); then
|
||||||
log_ok "Serializer IR generated successfully"
|
log_ok
|
||||||
else
|
else
|
||||||
log_fail "Failed to generate serializer IR"
|
log_fail
|
||||||
log_detail "$output"
|
log_detail "$output"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log_section "Generating Language-Specific Serializers"
|
|
||||||
log_action "Generating Java SkeletonSerializer"
|
log_action "Generating Java SkeletonSerializer"
|
||||||
if output=$(npx -y tsx src/generate-java-serializer.ts 2>&1); then
|
if output=$(npx -y tsx src/generate-java-serializer.ts 2>&1); then
|
||||||
log_ok "Java serializer generated successfully"
|
log_ok
|
||||||
|
|
||||||
log_action "Formatting Java code"
|
|
||||||
../formatters/format.sh java
|
|
||||||
log_ok "Java code formatted"
|
|
||||||
else
|
else
|
||||||
log_fail "Failed to generate Java serializer"
|
log_fail "Failed to generate Java serializer"
|
||||||
log_detail "$output"
|
log_detail "$output"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
../formatters/format-java.sh
|
||||||
|
|
||||||
log_action "Generating C++ SkeletonSerializer"
|
log_action "Generating C++ SkeletonSerializer"
|
||||||
if output=$(npx -y tsx src/generate-cpp-serializer.ts 2>&1); then
|
if output=$(npx -y tsx src/generate-cpp-serializer.ts 2>&1); then
|
||||||
log_ok "C++ serializer generated successfully"
|
log_ok "C++ serializer generated successfully"
|
||||||
|
|
||||||
log_action "Formatting C++ code"
|
|
||||||
../formatters/format.sh cpp
|
|
||||||
log_ok "C++ code formatted"
|
|
||||||
else
|
else
|
||||||
log_fail "Failed to generate C++ serializer"
|
log_fail "Failed to generate C++ serializer"
|
||||||
log_detail "$output"
|
log_detail "$output"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
../formatters/format-cpp.sh
|
||||||
|
|
||||||
log_summary "✓ Serializer generation and formatting completed successfully"
|
log_summary "✓ Serializer generation completed successfully"
|
||||||
@ -16,18 +16,16 @@ fi
|
|||||||
|
|
||||||
# Install dependencies if node_modules doesn't exist
|
# Install dependencies if node_modules doesn't exist
|
||||||
if [ ! -d "node_modules" ]; then
|
if [ ! -d "node_modules" ]; then
|
||||||
log_section "Setup"
|
|
||||||
log_action "Installing dependencies"
|
log_action "Installing dependencies"
|
||||||
if npm install > /tmp/npm-install.log 2>&1; then
|
if npm install > /tmp/npm-install.log 2>&1; then
|
||||||
log_ok "Dependencies installed"
|
log_ok
|
||||||
else
|
else
|
||||||
log_fail "npm install failed"
|
log_fail
|
||||||
log_detail "$(cat /tmp/npm-install.log)"
|
log_detail "$(cat /tmp/npm-install.log)"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log_section "Test"
|
|
||||||
log_action "Running TypeScript test runner"
|
log_action "Running TypeScript test runner"
|
||||||
|
|
||||||
# Run the TypeScript headless test runner with all arguments
|
# Run the TypeScript headless test runner with all arguments
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user