Unverified Commit 242bf304 authored by Nikolay Igotti's avatar Nikolay Igotti Committed by GitHub

Functional Android support (#459)

Co-authored-by: 's avatarIgor Demin <igordmn@gmail.com>
parent 505fc5ce
...@@ -70,8 +70,14 @@ jobs: ...@@ -70,8 +70,14 @@ jobs:
with: with:
distribution: 'adopt' distribution: 'adopt'
java-version: '11' java-version: '11'
- uses: nttld/setup-ndk@v1
with:
ndk-version: r21e
- uses: android-actions/setup-android@v2
# Runs a set of commands using the runners shell # Runs a set of commands using the runners shell
- shell: bash - shell: bash
env:
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
run: | run: |
# TODO: simplify installation of compilers. # TODO: simplify installation of compilers.
sudo apt-get update -y sudo apt-get update -y
...@@ -89,6 +95,7 @@ jobs: ...@@ -89,6 +95,7 @@ jobs:
./gradlew --stacktrace --info -Pskiko.native.enabled=true :skiko:linuxX64Test :skiko:awtTest ./gradlew --stacktrace --info -Pskiko.native.enabled=true :skiko:linuxX64Test :skiko:awtTest
./gradlew --stacktrace --info :skiko:publishToMavenLocal ./gradlew --stacktrace --info :skiko:publishToMavenLocal
./gradlew --stacktrace --info :SkiaAwtSample:installDist # check jvm sample works ./gradlew --stacktrace --info :SkiaAwtSample:installDist # check jvm sample works
./gradlew -Pskiko.android.enabled=true :skiko:publishSkikoJvmRuntimeAndroidX64PublicationToMavenLocal :skiko:publishSkikoJvmRuntimeAndroidArm64PublicationToMavenLocal :skiko:publishAndroidPublicationToMavenLocal
- uses: actions/upload-artifact@v2 - uses: actions/upload-artifact@v2
with: with:
name: test-reports-linux name: test-reports-linux
......
buildscript {
repositories {
google()
mavenCentral()
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
}
dependencies {
classpath("com.android.tools.build:gradle:7.0.2")
}
}
repositories {
mavenLocal()
google()
mavenCentral()
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
}
plugins {
id("com.android.application") version "7.0.2"
kotlin("android") version "1.6.10"
}
val skikoNativeX64 by configurations.creating
val skikoNativeArm64 by configurations.creating
val jniDir = "${projectDir.absolutePath}/src/main/jniLibs"
// TODO: filter .so files only.
val unzipTaskX64 = tasks.register("unzipNativeX64", Copy::class) {
destinationDir = file("$jniDir/x86_64")
from(skikoNativeX64.map { zipTree(it) })
}
val unzipTaskArm64 = tasks.register("unzipNativeArm64", Copy::class) {
destinationDir = file("$jniDir/arm64-v8a")
from(skikoNativeArm64.map { zipTree(it) })
}
android {
compileSdk = 31
defaultConfig {
minSdk = 27
targetSdk = 31
versionCode = 1
versionName = "1.0"
applicationId = "org.jetbrains.skiko.sample"
ndk {
abiFilters += listOf("x86_64", "arm64-v8a")
}
}
buildTypes {
debug {
isDebuggable = true
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
}
var version = if (project.hasProperty("skiko.version")) {
project.properties["skiko.version"] as String
} else {
"0.0.0-SNAPSHOT"
}
// ./gradlew -Pskiko.android.enabled=true \
// publishSkikoJvmRuntimeAndroidX64PublicationToMavenLocal \
// publishSkikoJvmRuntimeAndroidArm64PublicationToMavenLocal \
// publishAndroidPublicationToMavenLocal
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0")
implementation("org.jetbrains.skiko:skiko-android:$version")
skikoNativeX64("org.jetbrains.skiko:skiko-android-runtime-x64:$version")
skikoNativeArm64("org.jetbrains.skiko:skiko-android-runtime-arm64:$version")
}
tasks.withType<org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompile>().configureEach {
dependsOn(unzipTaskX64)
dependsOn(unzipTaskArm64)
}
\ No newline at end of file
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MSYS* | MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
pluginManagement {
repositories {
gradlePluginPortal()
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
google()
}
}
rootProject.name = "SkiaAndroidSample"
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.jetbrains.skiko.sample">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:debuggable="true"
tools:ignore="HardcodedDebugMode">
<activity
android:exported="true"
android:name="MainActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
package org.jetbrains.skiko.sample
import org.jetbrains.skia.*
import org.jetbrains.skia.paragraph.FontCollection
import org.jetbrains.skia.paragraph.ParagraphBuilder
import org.jetbrains.skia.paragraph.ParagraphStyle
import org.jetbrains.skia.paragraph.TextStyle
import org.jetbrains.skiko.*
import kotlin.math.cos
import kotlin.math.sin
import kotlin.math.PI
class Clocks(private val layer: SkiaLayer): SkikoView {
private val platformYOffset = if (hostOs == OS.Ios) 50f else 5f
private var frame = 0
private var xpos = 0.0
private var ypos = 0.0
private var xOffset = 0.0
private var yOffset = 0.0
private var scale = 1.0
private var k = scale
private var rotate = 0.0
private val fontCollection = FontCollection()
.setDefaultFontManager(FontMgr.default)
private val style = ParagraphStyle()
private var inputText = ""
override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) {
canvas.translate(xOffset.toFloat(), yOffset.toFloat())
canvas.scale(scale.toFloat(), scale.toFloat())
canvas.rotate(rotate.toFloat(), (width / 2).toFloat(), (height / 2).toFloat())
val watchFill = Paint().apply { color = 0xFFFFFFFF.toInt() }
val watchStroke = Paint().apply {
color = 0xFF000000.toInt()
mode = PaintMode.STROKE
strokeWidth = 1f
}
val watchStrokeAA = Paint().apply {
color = 0xFF000000.toInt()
mode = PaintMode.STROKE
strokeWidth = 1f
}
val watchFillHover = Paint().apply { color = 0xFFE4FF01.toInt() }
for (x in 0 .. width - 50 step 50) {
for (y in 30 + platformYOffset.toInt() .. height - 50 step 50) {
val hover =
(xpos - xOffset) / scale > x &&
(xpos - xOffset) / scale < x + 50 &&
(ypos - yOffset) / scale > y &&
(ypos - yOffset) / scale < y + 50
val fill = if (hover) watchFillHover else watchFill
val stroke = if (x > width / 2) watchStrokeAA else watchStroke
canvas.drawOval(Rect.makeXYWH(x + 5f, y + 5f, 40f, 40f), fill)
canvas.drawOval(Rect.makeXYWH(x + 5f, y + 5f, 40f, 40f), stroke)
var angle = 0f
while (angle < 2f * PI) {
canvas.drawLine(
(x + 25 - 17 * sin(angle)),
(y + 25 + 17 * cos(angle)),
(x + 25 - 20 * sin(angle)),
(y + 25 + 20 * cos(angle)),
stroke
)
angle += (2.0 * PI / 12.0).toFloat()
}
val time = (nanoTime / 1E6) % 60000 +
(x.toFloat() / width * 5000).toLong() +
(y.toFloat() / width * 5000).toLong()
val angle1 = (time.toFloat() / 5000 * 2f * PI).toFloat()
canvas.drawLine(
x + 25f,
y + 25f,
x + 25f - 15f * sin(angle1),
y + 25f + 15 * cos(angle1),
stroke)
val angle2 = (time / 60000 * 2f * PI).toFloat()
canvas.drawLine(
x + 25f,
y + 25f,
x + 25f - 10f * sin(angle2),
y + 25f + 10f * cos(angle2),
stroke)
}
}
val renderInfo = ParagraphBuilder(style, fontCollection)
.pushStyle(TextStyle().setColor(0xFF000000.toInt()))
.addText("Graphics API: ${layer.renderApi} ✿゚ ${currentSystemTheme}")
.popStyle()
.build()
renderInfo.layout(Float.POSITIVE_INFINITY)
renderInfo.paint(canvas, 5f, platformYOffset)
val input = ParagraphBuilder(style, fontCollection)
.pushStyle(TextStyle().setColor(0xFF000000.toInt()))
.addText("TextInput: $inputText")
.popStyle()
.build()
input.layout(Float.POSITIVE_INFINITY)
input.paint(canvas, 5f, platformYOffset + 20f)
val frames = ParagraphBuilder(style, fontCollection)
.pushStyle(TextStyle().setColor(0xff9BC730L.toInt()).setFontSize(20f))
.addText("Frames: ${frame++}\nAngle: $rotate")
.popStyle()
.build()
frames.layout(Float.POSITIVE_INFINITY)
frames.paint(canvas, ((xpos - xOffset) / scale).toFloat(), ((ypos - yOffset) / scale).toFloat())
canvas.resetMatrix()
}
override fun onPointerEvent(event: SkikoPointerEvent) {
when (event.kind) {
SkikoPointerEventKind.DOWN,
SkikoPointerEventKind.MOVE -> {
xpos = event.x
ypos = event.y
}
SkikoPointerEventKind.DRAG -> {
xOffset += event.x - xpos
yOffset += event.y - ypos
xpos = event.x
ypos = event.y
}
SkikoPointerEventKind.SCROLL -> {
when (event.modifiers) {
SkikoInputModifiers.CONTROL -> {
rotate += if (event.deltaY < 0) -5.0 else 5.0
}
else -> {
if (event.y != 0.0) {
scale *= if (event.deltaY < 0) 0.9 else 1.1
}
}
}
}
else -> {}
}
}
override fun onInputEvent(event: SkikoInputEvent) {
if (event.input != "\b") {
inputText += event.input
}
}
override fun onKeyboardEvent(event: SkikoKeyboardEvent) {
if (event.kind == SkikoKeyboardEventKind.DOWN) {
when (event.key) {
SkikoKey.KEY_NUMPAD_ADD -> scale *= 1.1
SkikoKey.KEY_I -> {
if (event.modifiers == SkikoInputModifiers.CONTROL) {
scale *= 1.1
}
}
SkikoKey.KEY_NUMPAD_SUBTRACT -> scale *= 0.9
SkikoKey.KEY_O -> {
if (event.modifiers == SkikoInputModifiers.CONTROL) {
scale *= 0.9
}
}
SkikoKey.KEY_R -> {
if (event.modifiers == SkikoInputModifiers.SHIFT) {
rotate -= 5.0
} else if (event.modifiers == SkikoInputModifiers.CONTROL) {
rotate += 5.0
}
}
SkikoKey.KEY_NUMPAD_4,
SkikoKey.KEY_LEFT -> xOffset -= 5.0
SkikoKey.KEY_NUMPAD_8,
SkikoKey.KEY_UP -> yOffset -= 5.0
SkikoKey.KEY_NUMPAD_6,
SkikoKey.KEY_RIGHT -> xOffset += 5.0
SkikoKey.KEY_NUMPAD_2,
SkikoKey.KEY_DOWN -> yOffset += 5.0
SkikoKey.KEY_SPACE -> {
xOffset = 0.0
yOffset = 0.0
rotate = 0.0
scale = 1.0
}
SkikoKey.KEY_BACKSPACE -> {
if (inputText.isNotEmpty()) {
inputText = inputText.dropLast(1)
}
}
else -> {}
}
}
}
override fun onTouchEvent(events: Array<SkikoTouchEvent>) {
val event = events.first()
if (event.kind == SkikoTouchEventKind.STARTED) {
xpos = event.x
ypos = event.y
}
}
override fun onGestureEvent(event: SkikoGestureEvent) {
when (event.kind) {
SkikoGestureEventKind.TAP -> {
xpos = event.x
ypos = event.y
}
SkikoGestureEventKind.PINCH -> {
if (event.state == SkikoGestureEventState.STARTED) {
k = scale
}
scale = k * event.scale
}
SkikoGestureEventKind.PAN -> {
xOffset += event.x - xpos
yOffset += event.y - ypos
xpos = event.x
ypos = event.y
}
SkikoGestureEventKind.ROTATION -> {
rotate = event.rotation * 180.0 / PI
}
else -> {}
}
}
}
\ No newline at end of file
package org.jetbrains.skiko.sample
import android.app.Activity
import android.os.Bundle
import android.widget.LinearLayout
import org.jetbrains.skiko.GenericSkikoView
import org.jetbrains.skiko.SkiaLayer
class MainActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val layout = LinearLayout(this)
layout.orientation = LinearLayout.VERTICAL
layout.layoutParams =
LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT)
val holder = LinearLayout(this).apply {
//layoutParams = ViewGroup.LayoutParams(1000, 1200)
}
val skiaLayer = SkiaLayer()
skiaLayer.skikoView = GenericSkikoView(skiaLayer, Clocks(skiaLayer))
skiaLayer.attachTo(holder)
layout.addView(holder)
setContentView(layout, layout.layoutParams)
}
}
package org.jetbrains.skiko.sample
import org.jetbrains.skia.Canvas
import org.jetbrains.skia.Color
import org.jetbrains.skia.Paint
import org.jetbrains.skia.Rect
import org.jetbrains.skiko.SkikoInputEvent
import org.jetbrains.skiko.SkikoKeyboardEvent
import org.jetbrains.skiko.SkikoPointerEvent
import org.jetbrains.skiko.SkikoView
class RotatingSquare : SkikoView {
override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) {
val angleDeg = (nanoTime / 5_000_000) % 360
val paint = Paint().apply { color = Color.GREEN }
canvas.clear(Color.RED)
canvas.save();
canvas.translate(128.0f, 128.0f)
canvas.rotate(angleDeg.toFloat())
val rect = Rect.makeXYWH(-90.5f, -90.5f, 181.0f, 181.0f)
canvas.drawRect(rect, paint)
canvas.restore()
}
override fun onInputEvent(event: SkikoInputEvent) {
println("onInput: $event")
}
override fun onKeyboardEvent(event: SkikoKeyboardEvent) {
println("onKeyboard: $event")
}
override fun onPointerEvent(event: SkikoPointerEvent) {
println("onMouse: $event")
}
}
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108"
android:tint="#FFFFFF">
<group android:scaleX="2.61"
android:scaleY="2.61"
android:translateX="22.68"
android:translateY="22.68">
<path
android:fillColor="@android:color/white"
android:pathData="M9.4,16.6L4.8,12l4.6,-4.6L8,6l-6,6 6,6 1.4,-1.4zM14.6,16.6l4.6,-4.6 -4.6,-4.6L16,6l6,6 -6,6 -1.4,-1.4z"/>
</group>
</vector>
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#3C3F41</color>
</resources>
\ No newline at end of file
<resources>
<string name="app_name">Skiko Example</string>
</resources>
...@@ -603,8 +603,8 @@ val skikoAwtJar by project.tasks.registering(Jar::class) { ...@@ -603,8 +603,8 @@ val skikoAwtJar by project.tasks.registering(Jar::class) {
archiveBaseName.set("skiko-awt") archiveBaseName.set("skiko-awt")
from(kotlin.jvm("awt").compilations["main"].output.allOutputs) from(kotlin.jvm("awt").compilations["main"].output.allOutputs)
} }
val skikoJvmRuntimeJar = createSkikoJvmJarTask(hostOs, hostArch, skikoAwtJar) val skikoAwtRuntimeJar = createSkikoJvmJarTask(hostOs, hostArch, skikoAwtJar)
val skikoRuntimeDirForTests = skikoRuntimeDirForTestsTask(hostOs, hostArch, skikoJvmRuntimeJar) val skikoRuntimeDirForTests = skikoRuntimeDirForTestsTask(hostOs, hostArch, skikoAwtRuntimeJar)
if (supportAndroid) { if (supportAndroid) {
val os = OS.Android val os = OS.Android
...@@ -689,18 +689,17 @@ fun createObjcCompileTask( ...@@ -689,18 +689,17 @@ fun createObjcCompileTask(
) )
} }
fun androidHome() = when (hostOs) { fun androidHome(): File {
OS.MacOS -> File("${System.getProperty("user.home")}/Library/Android/sdk") val envPath = System.getenv("ANDROID_SDK_ROOT")
OS.Linux -> File("${System.getProperty("user.home")}/.android") return when {
else -> throw GradleException("unsupported $hostOs") envPath != null -> File(envPath)
hostOs == OS.MacOS -> File("${System.getProperty("user.home")}/Library/Android/sdk")
hostOs == OS.Linux -> File("${System.getProperty("user.home")}/.android")
else -> throw GradleException("unsupported $hostOs. Alternative is to define Android SDK in ANDROID_SDK_ROOT environment variable")
}
} }
fun androidClangFor(targetArch: Arch, version: String = "30"): String { fun androidClangFor(targetArch: Arch, version: String = "30"): String {
val androidHome = androidHome()
val ndkVersion =
arrayOf("ndk/23.0.7599858", "ndk-bundle").find {
androidHome.resolve(it).exists()
}!!
val androidArch = when (targetArch) { val androidArch = when (targetArch) {
Arch.Arm64 -> "aarch64" Arch.Arm64 -> "aarch64"
Arch.X64 -> "x86_64" Arch.X64 -> "x86_64"
...@@ -709,10 +708,25 @@ fun androidClangFor(targetArch: Arch, version: String = "30"): String { ...@@ -709,10 +708,25 @@ fun androidClangFor(targetArch: Arch, version: String = "30"): String {
val hostOsArch = when (hostOs) { val hostOsArch = when (hostOs) {
OS.MacOS -> "darwin-x86_64" OS.MacOS -> "darwin-x86_64"
OS.Linux -> "linux-x86_64" OS.Linux -> "linux-x86_64"
OS.Windows -> "windows-x86_64"
else -> throw GradleException("unsupported $hostOs") else -> throw GradleException("unsupported $hostOs")
} }
val ndkDir = File(androidHome, "/$ndkVersion/toolchains/llvm/prebuilt/$hostOsArch") val ndkHome = if (System.getenv("ANDROID_NDK_HOME").isNullOrEmpty()) {
return ndkDir.resolve("bin/$androidArch-linux-android$version-clang++").absolutePath val androidHome = androidHome()
val ndkVersion =
arrayOf(*(file("$androidHome/ndk").list().map { "ndk/$it" }.sortedDescending()).toTypedArray(), "ndk-bundle").find {
androidHome.resolve(it).exists()
} ?: throw GradleException("Cannot find NDK, is it installed (Tools/SDK Manager)?")
"$androidHome/$ndkVersion"
} else {
System.getenv("ANDROID_NDK_HOME")
}
val ndkDir = File(ndkHome, "/toolchains/llvm/prebuilt/$hostOsArch")
var clang = ndkDir.resolve("bin/$androidArch-linux-android$version-clang++").absolutePath
if (hostOs.isWindows) {
clang += ".cmd"
}
return clang
} }
fun androidJar(version: String = "30"): String { fun androidJar(version: String = "30"): String {
...@@ -901,7 +915,10 @@ fun createLinkJvmBindings( ...@@ -901,7 +915,10 @@ fun createLinkJvmBindings(
} }
OS.Android -> { OS.Android -> {
osFlags = arrayOf( osFlags = arrayOf(
"-shared" "-shared",
"-static-libstdc++",
"-lGLESv3",
"-lEGL"
) )
linker.set(androidClangFor(targetArch)) linker.set(androidClangFor(targetArch))
} }
...@@ -1024,11 +1041,11 @@ fun skikoRuntimeDirForTestsTask( ...@@ -1024,11 +1041,11 @@ fun skikoRuntimeDirForTestsTask(
tasks.withType<Test>().configureEach { tasks.withType<Test>().configureEach {
dependsOn(skikoRuntimeDirForTests) dependsOn(skikoRuntimeDirForTests)
dependsOn(skikoJvmRuntimeJar) dependsOn(skikoAwtRuntimeJar)
options { options {
val dir = skikoRuntimeDirForTests.map { it.destinationDir }.get() val dir = skikoRuntimeDirForTests.map { it.destinationDir }.get()
systemProperty("skiko.library.path", dir) systemProperty("skiko.library.path", dir)
val jar = skikoJvmRuntimeJar.get().outputs.files.files.single { it.name.endsWith(".jar")} val jar = skikoAwtRuntimeJar.get().outputs.files.files.single { it.name.endsWith(".jar")}
systemProperty("skiko.jar.path", jar.absolutePath) systemProperty("skiko.jar.path", jar.absolutePath)
systemProperty("skiko.test.screenshots.dir", File(project.projectDir, "src/jvmTest/screenshots").absolutePath) systemProperty("skiko.test.screenshots.dir", File(project.projectDir, "src/jvmTest/screenshots").absolutePath)
...@@ -1148,7 +1165,7 @@ publishing { ...@@ -1148,7 +1165,7 @@ publishing {
artifact(entry.value.map { it.archiveFile.get() }) artifact(entry.value.map { it.archiveFile.get() })
var jvmSourcesArtifact: Any? = null var jvmSourcesArtifact: Any? = null
// todo: use correct sources jar for each jvm source set // todo: use correct sources jar for each jvm source set
kotlin.jvm("awt").mavenPublication { kotlin.jvm(if (os == OS.Android) "android" else "awt").mavenPublication {
jvmSourcesArtifact = artifacts.find { it.classifier == "sources" } jvmSourcesArtifact = artifacts.find { it.classifier == "sources" }
} }
if (jvmSourcesArtifact == null) { if (jvmSourcesArtifact == null) {
......
...@@ -142,8 +142,9 @@ abstract class CompileSkikoCppTask() : AbstractSkikoNativeToolTask() { ...@@ -142,8 +142,9 @@ abstract class CompileSkikoCppTask() : AbstractSkikoNativeToolTask() {
submittedWorks.add(workId) submittedWorks.add(workId)
val workArgs = args.copy { val workArgs = args.copy {
arg("-o", outputFile) // Replace slash for Windows paths
arg(value = sourceFile) arg("-o", outputFile.absolutePath.replace("\\", "/"))
arg(value = sourceFile.absolutePath.replace("\\", "/"))
} }
val argFile = run { val argFile = run {
...@@ -260,7 +261,7 @@ abstract class CompileSkikoCppTask() : AbstractSkikoNativeToolTask() { ...@@ -260,7 +261,7 @@ abstract class CompileSkikoCppTask() : AbstractSkikoNativeToolTask() {
override fun configureArgs() = override fun configureArgs() =
super.configureArgs().apply { super.configureArgs().apply {
arg("-c") arg("-c")
repeatedArg("-I", headersDirs) repeatedArg("-I", headersDirs.map { it.absolutePath.replace("\\", "/") })
// todo: ensure that flags do not start with '-I' (all headers should be added via [headersDirs]) // todo: ensure that flags do not start with '-I' (all headers should be added via [headersDirs])
rawArgs(flags.get()) rawArgs(flags.get())
} }
......
...@@ -176,8 +176,10 @@ object SkikoArtifacts { ...@@ -176,8 +176,10 @@ object SkikoArtifacts {
val jsArtifactId = "skiko-js" val jsArtifactId = "skiko-js"
val jsWasmArtifactId = "skiko-js-wasm-runtime" val jsWasmArtifactId = "skiko-js-wasm-runtime"
fun jvmRuntimeArtifactIdFor(os: OS, arch: Arch) = fun jvmRuntimeArtifactIdFor(os: OS, arch: Arch) =
"skiko-awt-runtime-${targetId(os, arch)}" if (os == OS.Android)
"skiko-android-runtime-${arch.id}"
else
"skiko-awt-runtime-${targetId(os, arch)}"
// Using custom name like skiko-<Os>-<Arch> (with a dash) // Using custom name like skiko-<Os>-<Arch> (with a dash)
// does not seem possible (at least without adding a dash to a target's tasks), // does not seem possible (at least without adding a dash to a target's tasks),
// so we're using the default naming pattern instead. // so we're using the default naming pattern instead.
......
package org.jetbrains.skia.impl
import java.lang.ref.PhantomReference
import java.lang.ref.ReferenceQueue
import kotlin.concurrent.thread
// Android doesn't have Cleaner API, so use explicit phantom references + finalization queue.
// Consider using this on all JVM platforms eventually.
actual abstract class Managed actual constructor(
ptr: Long, finalizer: Long, managed: Boolean
) : Native(ptr), AutoCloseable {
actual override fun close() {
if (0L == _ptr)
throw RuntimeException("Object already closed: $javaClass, _ptr=$_ptr")
else if (null == cleanable)
throw RuntimeException("Object is not managed in JVM, can't close(): $javaClass, _ptr=$_ptr")
else {
cleanable!!.clean()
cleanable = null
_ptr = 0
}
}
actual open val isClosed: Boolean
get() = _ptr == 0L
class CleanerThunk(var className: String, var ptr: Long, var finalizerPtr: Long) : Runnable {
override fun run() {
Log.trace { "Cleaning $className ${java.lang.Long.toString(ptr, 16)}" }
Stats.onDeallocated(className)
Stats.onNativeCall()
_nInvokeFinalizer(finalizerPtr, ptr)
}
}
private var cleanable: Cleanable? = null
companion object {
private val CLEANER = Cleaner()
@JvmStatic
external fun _nInvokeFinalizer(finalizer: Long, ptr: Long)
}
init {
if (managed) {
assert(ptr != 0L) { "Managed ptr is 0" }
assert(finalizer != 0L) { "Managed finalizer is 0" }
val className = javaClass.simpleName
Stats.onAllocated(className)
cleanable = CLEANER.register(this, CleanerThunk(className, ptr, finalizer))
}
}
}
private interface Cleanable {
fun clean()
var prev: Cleanable?
var next: Cleanable?
}
private class CleanableImpl(managed: Managed, action: Runnable, cleaner: Cleaner) :
PhantomReference<Managed>(managed, cleaner.queue), Cleanable {
override var prev: Cleanable? = this
override var next: Cleanable? = this
private val list: Cleanable = cleaner.list
private var action: Runnable = action
init {
insert()
reachabilityFence(managed)
reachabilityFence(cleaner)
}
override fun clean() {
if (remove()) {
super.clear()
action.run()
}
}
override fun clear() {
throw UnsupportedOperationException("clear() unsupported")
}
private fun insert() {
synchronized(list) {
prev = list
next = list.next
next?.prev = this
list.next = this
}
}
private fun remove(): Boolean {
synchronized(list) {
if (next !== this) {
next?.prev = prev
prev?.next = next
prev = this
next = this
return true
}
return false
}
}
}
private class Cleaner {
val queue = ReferenceQueue<Managed>()
var list: Cleanable = object : Cleanable {
override fun clean() {
TODO("Must not be called")
}
override var prev: Cleanable? = null
override var next: Cleanable? = null
}
@Volatile
var stopped = false
init {
thread(start = true, isDaemon = true, name = "Reference Cleaner") {
while (!stopped) {
val ref = queue.remove(60 * 1000L) as Cleanable?
try {
ref?.clean()
} catch (t: Throwable) {
}
}
}
}
fun register(managed: Managed, action: Runnable): Cleanable {
return CleanableImpl(managed, action, this)
}
fun stop() {
stopped = true
}
}
package org.jetbrains.skiko package org.jetbrains.skiko
import android.content.Context
import android.content.res.Configuration
import org.jetbrains.skiko.redrawer.Redrawer import org.jetbrains.skiko.redrawer.Redrawer
actual fun setSystemLookAndFeel(): Unit = TODO() actual fun setSystemLookAndFeel(): Unit = TODO()
...@@ -27,3 +29,19 @@ internal actual fun makeDefaultRenderFactory(): RenderFactory { ...@@ -27,3 +29,19 @@ internal actual fun makeDefaultRenderFactory(): RenderFactory {
} }
} }
} }
private var defaultContext: Context? = null
internal fun initDefaultContext(context: Context) {
defaultContext = context
}
actual val currentSystemTheme: SystemTheme
get() {
if (defaultContext == null) return SystemTheme.UNKNOWN
return when (defaultContext!!.resources?.configuration?.uiMode?.and(Configuration.UI_MODE_NIGHT_MASK)) {
Configuration.UI_MODE_NIGHT_YES -> SystemTheme.DARK
Configuration.UI_MODE_NIGHT_NO -> SystemTheme.LIGHT
else -> SystemTheme.UNKNOWN
}
}
package org.jetbrains.skiko
import android.content.Context
import android.opengl.GLES30
import android.opengl.GLSurfaceView
import android.widget.LinearLayout
import kotlinx.coroutines.Dispatchers
import org.jetbrains.skia.*
import java.nio.IntBuffer
import javax.microedition.khronos.egl.EGLConfig
import javax.microedition.khronos.opengles.GL10
class SkikoSurfaceView(context: Context, layer: SkiaLayer) : GLSurfaceView(context) {
private val renderer = SkikoSurfaceRender(layer)
init {
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT)
setEGLConfigChooser (8, 8, 8, 0, 24, 8)
setEGLContextClientVersion(2)
// setRenderMode(RENDERMODE_WHEN_DIRTY)
setRenderer(renderer)
}
private val frameDispatcher = FrameDispatcher(Dispatchers.Main) {
renderer.update()
requestRender()
}
fun scheduleFrame() {
frameDispatcher.scheduleFrame()
}
}
private class SkikoSurfaceRender(private val layer: SkiaLayer) : GLSurfaceView.Renderer {
private var width: Int = 0
private var height: Int = 0
@Volatile
private var picture: PictureHolder? = null
private var pictureRecorder: PictureRecorder = PictureRecorder()
private val pictureLock = Any()
private fun <T : Any> lockPicture(action: (PictureHolder) -> T): T? {
return synchronized(pictureLock) {
val picture = picture
if (picture != null) {
action(picture)
} else {
null
}
}
}
// This method is called from the main thread.
fun update() {
layer.skikoView?.let {
val bounds = Rect.makeWH(width.toFloat(), width.toFloat())
val canvas = pictureRecorder.beginRecording(bounds)
try {
it.onRender(canvas, width, height, System.nanoTime())
} finally {
synchronized(pictureLock) {
picture?.instance?.close()
val picture = pictureRecorder.finishRecordingAsPicture()
this.picture = PictureHolder(picture, width, height)
}
}
}
}
// This method is called from GL rendering thread.
override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
gl!!
gl.glClearColor(0f, 0f, 0f, 0f)
gl.glClear(GL10.GL_COLOR_BUFFER_BIT)
}
// This method is called from GL rendering thread.
override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
gl!!
this.width = width
this.height = height
initCanvas(gl)
}
// This method is called from GL rendering thread, it shall render Skia picture.
override fun onDrawFrame(gl: GL10?) {
lockPicture {
canvas?.drawPicture(it.instance)
Unit
}
context?.flush()
}
private var context: DirectContext? = null
private var renderTarget: BackendRenderTarget? = null
private var surface: Surface? = null
private var canvas: Canvas? = null
private fun initCanvas(gl: GL10) {
disposeCanvas()
val intBuf1 = IntBuffer.allocate(1)
gl.glGetIntegerv(GLES30.GL_DRAW_FRAMEBUFFER_BINDING, intBuf1)
val fbId = intBuf1[0]
renderTarget = makeGLRenderTarget(
width,
height,
0,
8,
fbId,
FramebufferFormat.GR_GL_RGBA8
)
context = makeGLContext()
surface = Surface.makeFromBackendRenderTarget(
context!!,
renderTarget!!,
SurfaceOrigin.BOTTOM_LEFT,
SurfaceColorFormat.RGBA_8888,
ColorSpace.sRGB
)
canvas = surface!!.canvas
}
private fun disposeCanvas() {
surface?.close()
renderTarget?.close()
}
}
package org.jetbrains.skiko package org.jetbrains.skiko
import android.view.KeyEvent import android.view.*
import android.view.MotionEvent
import org.jetbrains.skia.Canvas import org.jetbrains.skia.Canvas
import org.jetbrains.skiko.redrawer.Redrawer
actual typealias SkikoGesturePlatformEvent = MotionEvent actual typealias SkikoGesturePlatformEvent = MotionEvent
actual typealias SkikoPlatformPointerEvent = MotionEvent actual typealias SkikoPlatformPointerEvent = MotionEvent
...@@ -13,14 +11,17 @@ actual typealias SkikoTouchPlatformEvent = Any ...@@ -13,14 +11,17 @@ actual typealias SkikoTouchPlatformEvent = Any
actual typealias SkikoPlatformKeyboardEvent = KeyEvent actual typealias SkikoPlatformKeyboardEvent = KeyEvent
actual open class SkiaLayer { actual open class SkiaLayer {
private var glView: SkikoSurfaceView? = null
private var container: ViewGroup? = null
actual var renderApi: GraphicsApi = GraphicsApi.OPENGL actual var renderApi: GraphicsApi = GraphicsApi.OPENGL
actual val contentScale: Float actual val contentScale: Float
get() = 1.0f get() = container?.context?.resources?.displayMetrics?.density?: 1.0f
actual var fullscreen: Boolean actual var fullscreen: Boolean
get() = false get() = true
set(value) { set(value) {
if (value) throw IllegalArgumentException("fullscreen unsupported") if (value) throw IllegalArgumentException("changing fullscreen is unsupported")
} }
actual var transparency: Boolean actual var transparency: Boolean
...@@ -29,24 +30,41 @@ actual open class SkiaLayer { ...@@ -29,24 +30,41 @@ actual open class SkiaLayer {
if (value) throw IllegalArgumentException("transparency unsupported") if (value) throw IllegalArgumentException("transparency unsupported")
} }
actual var skikoView: SkikoView? = null actual var skikoView: SkikoView? = null
actual fun attachTo(container: Any) { actual fun attachTo(container: Any) {
TODO("Implement attachTo()") when (container) {
is ViewGroup -> {
attachTo(container)
}
else -> error("Cannot attach to $container")
}
}
fun attachTo(container: ViewGroup) {
initDefaultContext(container.context)
val view = SkikoSurfaceView(container.context, this)
container.addView(view)
this.container = container
this.glView = view
needRedraw()
} }
actual fun detach() { actual fun detach() {
this.container?.let {
it.removeView(this.glView)
this.glView = null
}
} }
actual fun needRedraw() { actual fun needRedraw() {
TODO("Implement needRedraw()") glView?.apply {
scheduleFrame()
}
} }
internal var redrawer: Redrawer? = null
var width: Int = 0
var height: Int = 0
internal actual fun draw(canvas: Canvas): Unit = TODO() internal actual fun draw(canvas: Canvas): Unit = TODO()
} }
\ No newline at end of file
...@@ -2,17 +2,17 @@ package org.jetbrains.skia.impl ...@@ -2,17 +2,17 @@ package org.jetbrains.skia.impl
import java.lang.ref.Cleaner import java.lang.ref.Cleaner
actual abstract class Managed actual constructor(ptr: Long, finalizer: Long, managed: Boolean) : Native(ptr), actual abstract class Managed actual constructor(ptr: Long, finalizer: Long, managed: Boolean)
AutoCloseable { : Native(ptr), AutoCloseable {
private var _cleanable: Cleaner.Cleanable? = null private var cleanable: Cleaner.Cleanable? = null
actual override fun close() { actual override fun close() {
if (0L == _ptr) if (0L == _ptr)
throw RuntimeException("Object already closed: $javaClass, _ptr=$_ptr") throw RuntimeException("Object already closed: $javaClass, _ptr=$_ptr")
else if (null == _cleanable) else if (null == cleanable)
throw RuntimeException("Object is not managed in JVM, can't close(): $javaClass, _ptr=$_ptr") throw RuntimeException("Object is not managed in JVM, can't close(): $javaClass, _ptr=$_ptr")
else { else {
_cleanable!!.clean() cleanable!!.clean()
_cleanable = null cleanable = null
_ptr = 0 _ptr = 0
} }
} }
...@@ -20,17 +20,17 @@ actual abstract class Managed actual constructor(ptr: Long, finalizer: Long, man ...@@ -20,17 +20,17 @@ actual abstract class Managed actual constructor(ptr: Long, finalizer: Long, man
actual open val isClosed: Boolean actual open val isClosed: Boolean
get() = _ptr == 0L get() = _ptr == 0L
class CleanerThunk(var _className: String, var _ptr: Long, var _finalizerPtr: Long) : Runnable { class CleanerThunk(var className: String, var ptr: Long, var finalizerPtr: Long) : Runnable {
override fun run() { override fun run() {
Log.trace { "Cleaning " + _className + " " + java.lang.Long.toString(_ptr, 16) } Log.trace { "Cleaning $className ${java.lang.Long.toString(ptr, 16)}" }
Stats.onDeallocated(_className) Stats.onDeallocated(className)
Stats.onNativeCall() Stats.onNativeCall()
_nInvokeFinalizer(_finalizerPtr, _ptr) _nInvokeFinalizer(finalizerPtr, ptr)
} }
} }
companion object { companion object {
var _cleaner = Cleaner.create() var CLEANER = Cleaner.create()
@JvmStatic external fun _nInvokeFinalizer(finalizer: Long, ptr: Long) @JvmStatic external fun _nInvokeFinalizer(finalizer: Long, ptr: Long)
} }
...@@ -40,7 +40,7 @@ actual abstract class Managed actual constructor(ptr: Long, finalizer: Long, man ...@@ -40,7 +40,7 @@ actual abstract class Managed actual constructor(ptr: Long, finalizer: Long, man
assert(finalizer != 0L) { "Managed finalizer is 0" } assert(finalizer != 0L) { "Managed finalizer is 0" }
val className = javaClass.simpleName val className = javaClass.simpleName
Stats.onAllocated(className) Stats.onAllocated(className)
_cleanable = _cleaner.register(this, CleanerThunk(className, ptr, finalizer)) cleanable = CLEANER.register(this, CleanerThunk(className, ptr, finalizer))
} }
} }
} }
\ No newline at end of file
package org.jetbrains.skiko package org.jetbrains.skiko
val currentSystemTheme: SystemTheme actual val currentSystemTheme: SystemTheme
get() = when (getCurrentSystemTheme()) { get() = when (getCurrentSystemTheme()) {
0 -> SystemTheme.LIGHT 0 -> SystemTheme.LIGHT
1 -> SystemTheme.DARK 1 -> SystemTheme.DARK
...@@ -8,4 +8,4 @@ val currentSystemTheme: SystemTheme ...@@ -8,4 +8,4 @@ val currentSystemTheme: SystemTheme
} }
// Common // Common
external private fun getCurrentSystemTheme(): Int private external fun getCurrentSystemTheme(): Int
...@@ -22,3 +22,4 @@ expect open class SkiaLayer { ...@@ -22,3 +22,4 @@ expect open class SkiaLayer {
internal class PictureHolder(val instance: Picture, val width: Int, val height: Int) internal class PictureHolder(val instance: Picture, val width: Int, val height: Int)
...@@ -4,4 +4,6 @@ enum class SystemTheme { ...@@ -4,4 +4,6 @@ enum class SystemTheme {
DARK, DARK,
LIGHT, LIGHT,
UNKNOWN UNKNOWN
} }
\ No newline at end of file
expect val currentSystemTheme: SystemTheme
\ No newline at end of file
...@@ -3,7 +3,7 @@ package org.jetbrains.skiko ...@@ -3,7 +3,7 @@ package org.jetbrains.skiko
import platform.UIKit.* import platform.UIKit.*
import platform.UIKit.UIUserInterfaceStyle.* import platform.UIKit.UIUserInterfaceStyle.*
val currentSystemTheme: SystemTheme actual val currentSystemTheme: SystemTheme
get() = when (UITraitCollection.currentTraitCollection.userInterfaceStyle) { get() = when (UITraitCollection.currentTraitCollection.userInterfaceStyle) {
UIUserInterfaceStyleDark -> SystemTheme.DARK UIUserInterfaceStyleDark -> SystemTheme.DARK
UIUserInterfaceStyleLight -> SystemTheme.LIGHT UIUserInterfaceStyleLight -> SystemTheme.LIGHT
......
package org.jetbrains.skiko package org.jetbrains.skiko
val currentSystemTheme: SystemTheme actual val currentSystemTheme: SystemTheme
// TODO: getting actual OS system theme // TODO: getting actual OS/browser system theme
get() = SystemTheme.UNKNOWN get() = SystemTheme.UNKNOWN
\ No newline at end of file
...@@ -74,6 +74,11 @@ object Library { ...@@ -74,6 +74,11 @@ object Library {
val platformName = System.mapLibraryName(name) val platformName = System.mapLibraryName(name)
val icu = if (hostOs.isWindows) "icudtl.dat" else null val icu = if (hostOs.isWindows) "icudtl.dat" else null
if (hostOs == OS.Android) {
System.loadLibrary("skiko-$hostId")
return
}
// First try: system property is set. // First try: system property is set.
if (skikoLibraryPath != null) { if (skikoLibraryPath != null) {
val library = File(File(skikoLibraryPath), platformName) val library = File(File(skikoLibraryPath), platformName)
......
...@@ -37,3 +37,6 @@ actual typealias SkikoGesturePlatformEvent = Any ...@@ -37,3 +37,6 @@ actual typealias SkikoGesturePlatformEvent = Any
actual typealias SkikoPlatformInputEvent = Any actual typealias SkikoPlatformInputEvent = Any
actual typealias SkikoPlatformKeyboardEvent = Any actual typealias SkikoPlatformKeyboardEvent = Any
actual typealias SkikoPlatformPointerEvent = Any actual typealias SkikoPlatformPointerEvent = Any
actual val currentSystemTheme: SystemTheme
get() = SystemTheme.UNKNOWN
\ No newline at end of file
...@@ -2,7 +2,7 @@ package org.jetbrains.skiko ...@@ -2,7 +2,7 @@ package org.jetbrains.skiko
import platform.Foundation.NSUserDefaults import platform.Foundation.NSUserDefaults
val currentSystemTheme: SystemTheme actual val currentSystemTheme: SystemTheme
get() = when (NSUserDefaults.standardUserDefaults.stringForKey("AppleInterfaceStyle")) { get() = when (NSUserDefaults.standardUserDefaults.stringForKey("AppleInterfaceStyle")) {
"Dark" -> SystemTheme.DARK "Dark" -> SystemTheme.DARK
else -> SystemTheme.LIGHT else -> SystemTheme.LIGHT
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment