Browse Source

Put visual-effects sample to experimental as is. (#2572)

To add Android/iOS targets later
pull/2574/head
Nikita Lipsky 2 years ago committed by GitHub
parent
commit
c67820e07a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 15
      experimental/examples/visual-effects/.gitignore
  2. 23
      experimental/examples/visual-effects/.run/desktop.run.xml
  3. 7
      experimental/examples/visual-effects/README.md
  4. 62
      experimental/examples/visual-effects/build.gradle.kts
  5. 3
      experimental/examples/visual-effects/gradle.properties
  6. BIN
      experimental/examples/visual-effects/gradle/wrapper/gradle-wrapper.jar
  7. 5
      experimental/examples/visual-effects/gradle/wrapper/gradle-wrapper.properties
  8. 185
      experimental/examples/visual-effects/gradlew
  9. 89
      experimental/examples/visual-effects/gradlew.bat
  10. BIN
      experimental/examples/visual-effects/screenshots/desktop-run-configuration.png
  11. 13
      experimental/examples/visual-effects/settings.gradle.kts
  12. 430
      experimental/examples/visual-effects/src/main/kotlin/HappyNY.kt
  13. 162
      experimental/examples/visual-effects/src/main/kotlin/RotatingWords.kt
  14. 265
      experimental/examples/visual-effects/src/main/kotlin/WaveEffect.kt
  15. 32
      experimental/examples/visual-effects/src/main/kotlin/main.kt
  16. BIN
      experimental/examples/visual-effects/src/main/resources/compose-community-primary.png
  17. 19
      experimental/examples/visual-effects/src/main/resources/compose-community-primary.svg

15
experimental/examples/visual-effects/.gitignore vendored

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

23
experimental/examples/visual-effects/.run/desktop.run.xml

@ -0,0 +1,23 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="desktop" type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value=":run" />
</list>
</option>
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<method v="2" />
</configuration>
</component>

7
experimental/examples/visual-effects/README.md

@ -0,0 +1,7 @@
Several visual effects implmented with Compose Multiplatform, used in 1.0 release announce video.
### Running desktop application
* To run, launch command: `./gradlew run`
* Or choose **desktop** configuration in IDE and run it.
![desktop-run-configuration.png](screenshots/desktop-run-configuration.png)

62
experimental/examples/visual-effects/build.gradle.kts

@ -0,0 +1,62 @@
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
kotlin("jvm")
id("org.jetbrains.compose")
}
group = "me.user"
version = "1.0"
repositories {
mavenCentral()
google()
maven { url = uri("https://maven.pkg.jetbrains.space/public/p/compose/dev") }
}
dependencies {
implementation(compose.desktop.currentOs)
}
tasks.withType<KotlinCompile> {
kotlinOptions.jvmTarget = "11"
kotlinOptions.freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn"
}
compose.desktop {
application {
mainClass = "org.jetbrains.compose.demo.visuals.MainKt"
nativeDistributions {
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
packageName = "compose-demo"
packageVersion = "1.0.0"
}
}
}
afterEvaluate {
val additionalArguments = mutableListOf<String>()
val runTask = tasks.named<JavaExec>("run") {
this.args = additionalArguments
}
tasks.register("runWords") {
additionalArguments.add("words")
group = "compose desktop"
dependsOn(runTask)
}
tasks.register("runWave") {
additionalArguments.add("wave")
group = "compose desktop"
dependsOn(runTask)
}
tasks.register("runNewYear") {
additionalArguments.add("NY")
group = "compose desktop"
dependsOn(runTask)
}
}

3
experimental/examples/visual-effects/gradle.properties

@ -0,0 +1,3 @@
kotlin.code.style=official
kotlin.version=1.7.20
compose.version=1.2.2

BIN
experimental/examples/visual-effects/gradle/wrapper/gradle-wrapper.jar vendored

Binary file not shown.

5
experimental/examples/visual-effects/gradle/wrapper/gradle-wrapper.properties vendored

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

185
experimental/examples/visual-effects/gradlew vendored

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

89
experimental/examples/visual-effects/gradlew.bat vendored

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

BIN
experimental/examples/visual-effects/screenshots/desktop-run-configuration.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

13
experimental/examples/visual-effects/settings.gradle.kts

@ -0,0 +1,13 @@
pluginManagement {
repositories {
gradlePluginPortal()
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
}
plugins {
kotlin("jvm").version(extra["kotlin.version"] as String)
id("org.jetbrains.compose").version(extra["compose.version"] as String)
}
}
rootProject.name = "visual-effects"

430
experimental/examples/visual-effects/src/main/kotlin/HappyNY.kt

@ -0,0 +1,430 @@
package org.jetbrains.compose.demo.visuals
import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.*
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerMoveFilter
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.*
import java.lang.Math.random
import kotlin.math.*
import kotlin.random.Random
const val width = 1200
const val height = 800
const val snowCount = 80
const val starCount = 60
const val rocketPartsCount = 30
data class SnowFlake(
var x: Dp,
var y: Dp,
val scale: Float,
var v: Double,
var alpha: Float,
var angle: Float,
var rotate: Int,
var phase: Double
)
data class Star(val x: Dp, val y: Dp, val color: Color, val size: Dp)
const val HNYString = "Happy New Year!"
class DoubleRocket(val particle: Particle) {
private val STATE_ROCKET = 0
private val STATE_SMALL_ROCKETS = 1
var state = STATE_ROCKET
var rockets: Array<Rocket> = emptyArray()
private fun checkState(time: Long) {
if (particle.vy > -3.0 && state == STATE_ROCKET) {
explode(time)
}
if (state == STATE_SMALL_ROCKETS) {
var done = true
rockets.forEach {
if (!it.exploded) {
it.checkExplode(time)
}
if (!it.checkDone()) {
done = false
}
}
if (done) {
reset()
}
}
}
private fun reset() {
if (particle.vx < 0) return //to stop drawing after the second rocket. This could be commented out
state = STATE_ROCKET
particle.x = if (particle.vx > 0) width - 0.0 else 0.0
particle.y = 1000.0
particle.vx = -1 * particle.vx
particle.vy = -12.5
}
private fun explode(time: Long) {
val colors = arrayOf(Color(0xff, 0, 0), Color(192, 255, 192), Color(192, 212, 255))
rockets = Array(7) {
val v = 1.2f + 1.0 * random()
val angle = 2 * PI * random()
Rocket(
Particle(
particle.x,
particle.y,
v * sin(angle) + particle.vx,
v * cos(angle) + particle.vy - 0.5f,
colors[it % colors.size]
), colors[it % colors.size], time
)
}
state = STATE_SMALL_ROCKETS
}
fun move(time: Long, prevTime: Long) {
if (rocket.state == rocket.STATE_ROCKET) {
rocket.particle.move(time, prevTime)
rocket.particle.gravity(time, prevTime)
} else {
rocket.rockets.forEach {
it.move(time, prevTime)
}
}
rocket.checkState(time)
}
@Composable
fun draw() {
if (state == rocket.STATE_ROCKET) {
particle.draw()
} else {
rockets.forEach {
it.draw()
}
}
}
}
class Rocket(val particle: Particle, val color: Color, val startTime: Long = 0) {
var exploded = false
var parts: Array<Particle> = emptyArray()
fun checkExplode(time: Long) {
if (time - startTime > 1200000000) {
explode()
}
}
private fun explode() {
parts = Array(rocketPartsCount) {
val v = 0.5f + 1.5 * random()
val angle = 2 * PI * random()
Particle(particle.x, particle.y, v * sin(angle) + particle.vx, v * cos(angle) + particle.vy, color, 1)
}
exploded = true
}
fun checkDone(): Boolean {
if (!exploded) return false
parts.forEach {
if (it.y < 800) return false
}
return true
}
fun move(time: Long, prevTime: Long) {
if (!exploded) {
particle.move(time, prevTime)
particle.gravity(time, prevTime)
checkExplode(time)
} else {
parts.forEach {
it.move(time, prevTime)
it.gravity(time, prevTime)
}
}
}
@Composable
fun draw() {
if (!exploded) {
particle.draw()
} else {
parts.forEach {
it.draw()
}
}
}
}
class Particle(var x: Double, var y: Double, var vx: Double, var vy: Double, val color: Color, val type: Int = 0) {
fun move(time: Long, prevTime: Long) {
x = (x + vx * (time - prevTime) / 30000000)
y = (y + vy * (time - prevTime) / 30000000)
}
fun gravity(time: Long, prevTime: Long) {
vy = vy + 1.0f * (time - prevTime) / 300000000
}
@Composable
fun draw() {
val alphaFactor = if (type == 0) 1.0f else 1 / (1 + abs(vy / 5)).toFloat()
Box(Modifier.size(5.dp).offset(x.dp, y.dp).alpha(alphaFactor).clip(CircleShape).background(color))
for (i in 1..5) {
Box(
Modifier.size(4.dp).offset((x - vx / 2 * i).dp, (y - vy / 2 * i).dp)
.alpha(alphaFactor * (1 - 0.18f * i)).clip(CircleShape).background(color)
)
}
}
}
val rocket = DoubleRocket(Particle(0.0, 1000.0, 2.1, -12.5, Color.White))
@Composable
fun NYWindow(onCloseRequest: () -> Unit) {
val windowState = remember { WindowState(width = width.dp, height = height.dp) }
Window(onCloseRequest = onCloseRequest, undecorated = true, transparent = true, state = windowState) {
NYContent()
}
}
fun prepareStarsAndSnowFlakes(stars: SnapshotStateList<Star>, snowFlakes: SnapshotStateList<SnowFlake>) {
for (i in 0..snowCount) {
snowFlakes.add(
SnowFlake(
(50 + (width - 50) * random()).dp,
(height * random()).dp,
0.1f + 0.2f * random().toFloat(),
1.5 + 3 * random(),
(0.4f + 0.4 * random()).toFloat(),
60 * random().toFloat(),
Random.nextInt(1, 5) - 3,
random() * 2 * PI
)
)
}
val colors = arrayOf(Color.Red, Color.Yellow, Color.Green, Color.Yellow, Color.Cyan, Color.Magenta, Color.White)
for (i in 0..starCount) {
stars.add(
Star(
(width * random()).dp,
(height * random()).dp,
colors[Random.nextInt(colors.size)],
(3 + 5 * random()).dp
)
)
}
}
@OptIn(ExperimentalComposeUiApi::class)
@Composable
@Preview
fun NYContent() {
var time by remember { mutableStateOf(System.nanoTime()) }
var started by remember { mutableStateOf(false) }
var startTime = remember { System.nanoTime() }
var prevTime by remember { mutableStateOf(System.nanoTime()) }
val snowFlakes = remember { mutableStateListOf<SnowFlake>() }
val stars = remember { mutableStateListOf<Star>() }
var flickering2 by remember { mutableStateOf(true) }
remember { prepareStarsAndSnowFlakes(stars, snowFlakes) }
Surface(
modifier = Modifier.fillMaxSize().padding(5.dp).shadow(3.dp, RoundedCornerShape(20.dp)),
color = Color.Black,
shape = RoundedCornerShape(20.dp)
) {
LaunchedEffect(Unit) {
while (true) {
withFrameNanos {
prevTime = time
time = it
}
}
}
if (!started) { //animation starts with delay, so there is some time to start recording
if (time - startTime in 7000000001..7099999999) println("ready!")
if (time - startTime > 10000000000) {
startTime = time //restarting timer
started = true
}
}
if (flickering2) {
if (time - startTime > 15500000000) { //note, that startTime has been updated above
flickering2 = false
}
}
if (started) {
rocket.move(time, prevTime)
}
with(LocalDensity.current) {
Box(Modifier.fillMaxSize()) {
snow(time, prevTime, snowFlakes, startTime)
starrySky(stars)
Text(
"202",
Modifier.scale(10f).align(Alignment.Center).offset(-2.dp, 15.dp)
.alpha(if (flickering2) 0.8f else 1.0f),
color = Color.White
)
val alpha = if (flickering2) flickeringAlpha(time) else 1.0f
Text(
"2",
Modifier.scale(10f).align(Alignment.Center).offset(14.dp, 15.dp).alpha(alpha),
color = Color.White
)
if (started) { //delay to be able to start recording
//HNY
var i = 0
val angle = (HNYString.length / 2 * 5) * -1.0f
val color = colorHNY(time, startTime)
HNYString.forEach {
val alpha = alphaHNY(i, time, startTime)
Text(
it.toString(),
color = color,
modifier = Modifier.scale(5f).align(Alignment.Center).offset(0.dp, 85.dp)
.rotate((angle + 5.0f * i)).offset(0.dp, -90.dp).alpha(alpha)
)
i++
}
rocket.draw()
}
Text(
"Powered by Compose Multiplatform",
modifier = Modifier.align(Alignment.BottomEnd).offset(-20.dp, 0.dp),
color = Color.White
)
}
}
}
}
fun colorHNY(time: Long, startTime: Long): Color {
val periodLength = 60
val offset = ((time - startTime) / 80000000).toFloat() / periodLength
val color1 = Color.Red
val color2 = Color.Yellow
val color3 = Color.Magenta
if (offset < 1) return blend(color1, color2, offset)
if (offset < 2) return blend(color2, color3, offset - 1)
if (offset < 3) return blend(color3, color1, offset - 2)
return color1
}
fun blend(color1: Color, color2: Color, fraction: Float): Color {
if (fraction < 0) return color1
if (fraction > 1) return color2
return Color(
color2.red * fraction + color1.red * (1 - fraction),
color2.green * fraction + color1.green * (1 - fraction),
color2.blue * fraction + color1.blue * (1 - fraction)
)
}
fun alphaHNY(i: Int, time: Long, startTime: Long): Float {
val period = period(time, startTime, 200) - i
if (period < 0) return 0.0f
if (period > 10) return 1.0f
return 0.1f * period
}
fun period(time: Long, startTime: Long, periodLength: Int, speed: Int = 1): Int {
val period = 200000000 / speed
return (((time - startTime) / period) % periodLength).toInt()
}
fun flickeringAlpha(time: Long): Float {
val time = (time / 10000000) % 100
var result = 0.2f
if (time > 75) {
result += 0.6f * ((time - 75) % 3) / 3
}
return result
}
@Composable
fun starrySky(stars: SnapshotStateList<Star>) {
stars.forEach {
star(it.x, it.y, it.color, size = it.size)
}
}
@Composable
fun star(x: Dp, y: Dp, color: Color = Color.White, size: Dp) {
Box(Modifier.offset(x, y).scale(1.0f, 0.2f).rotate(45f).size(size).background(color))
Box(Modifier.offset(x, y).scale(0.2f, 1.0f).rotate(45f).size(size).background(color))
}
@Composable
fun snow(time: Long, prevTime: Long, snowFlakes: SnapshotStateList<SnowFlake>, startTime: Long) {
val deltaAngle = (time - startTime) / 100000000
with(LocalDensity.current) {
snowFlakes.forEach {
var y = it.y + ((it.v * (time - prevTime)) / 300000000).dp
if (y > (height + 20).dp) {
y = -20.dp
}
it.y = y
val x = it.x + (15 * sin(time.toDouble() / 3000000000 + it.phase)).dp
snowFlake(Modifier.offset(x, y).scale(it.scale).rotate(it.angle + deltaAngle * it.rotate), it.alpha)
}
}
}
@Composable
fun snowFlake(modifier: Modifier, alpha: Float = 0.8f) {
Box(modifier) {
snowFlakeInt(0, 0f, 30.dp, 0.dp, alpha)
snowFlakeInt(0, 60f, 15.dp, 25.dp, alpha)
snowFlakeInt(0, 120f, -15.dp, 25.dp, alpha)
snowFlakeInt(0, 180f, -30.dp, 0.dp, alpha)
snowFlakeInt(0, 240f, -15.dp, -25.dp, alpha)
snowFlakeInt(0, 300f, 15.dp, -25.dp, alpha)
}
}
@Composable
fun snowFlakeInt(level: Int, angle: Float, shiftX: Dp, shiftY: Dp, alpha: Float) {
if (level > 3) return
Box(
Modifier.offset(shiftX, shiftY).rotate(angle).width(100.dp).height(10.dp).scale(0.6f).alpha(1f)
.background(Color.White.copy(alpha = alpha))
) {
snowFlakeInt(level + 1, 30f, 12.dp, 20.dp, alpha * 0.8f)
snowFlakeInt(level + 1, -30f, 12.dp, -20.dp, alpha * 0.8f)
}
}

162
experimental/examples/visual-effects/src/main/kotlin/RotatingWords.kt

@ -0,0 +1,162 @@
package org.jetbrains.compose.demo.visuals
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.core.*
import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.loadSvgPainter
import androidx.compose.ui.res.useResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.WindowState
import androidx.compose.ui.window.singleWindowApplication
@OptIn(ExperimentalAnimationApi::class, ExperimentalMaterialApi::class)
@Preview
@Composable
fun Words() {
val density = LocalDensity.current
val duration = 5000
val infiniteTransition = rememberInfiniteTransition()
val angle by infiniteTransition.animateFloat(
initialValue = -50f,
targetValue = 30f,
animationSpec = infiniteRepeatable(
animation = tween(duration, easing = FastOutSlowInEasing),
repeatMode = RepeatMode.Reverse
)
)
val scale by infiniteTransition.animateFloat(
initialValue = 1f,
targetValue = 7f,
animationSpec = infiniteRepeatable(
animation = tween(duration, easing = FastOutSlowInEasing),
repeatMode = RepeatMode.Reverse
)
)
val logoSvg = remember {
useResource("compose-community-primary.svg") { loadSvgPainter(it, density) }
}
val baseLogo = DpOffset(350.dp, 270.dp)
val baseText = DpOffset(350.dp, 270.dp)
val baseRu = DpOffset(100.dp, 100.dp)
val baseEn = DpOffset(100.dp, 600.dp)
val baseCh = DpOffset(600.dp, 100.dp)
val baseJa = DpOffset(600.dp, 600.dp)
val color1 = Color(0x6B, 0x57, 0xFF)
val color2 = Color(0xFE, 0x28, 0x57)
val color3 = Color(0xFD, 0xB6, 0x0D)
val color4 = Color(0xFC, 0xF8, 0x4A)
Column(modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
Word(position = baseRu, angle = angle, scale = scale, text = "Ваш", color = color1)
Word(position = baseEn, angle = angle, scale = scale, text = "Your", color = color2)
Word(position = baseCh, angle = angle, scale = scale, text = "您的", color = color3)
Word(position = baseJa, angle = angle, scale = scale, text = "あなたの", color = color4)
Word(position = baseText, angle = 0f, scale = 6f, text = " Compose\nMultiplatform", color = Color(52, 67, 235),
alpha = 0.4f)
val size = 80.dp * scale
Image(logoSvg, contentDescription = "Logo",
modifier = Modifier
.offset(baseLogo.x - size / 2, baseLogo.y - size / 2)
.size(size)
.rotate(angle * 2f)
)
}
}
@Composable
fun Word(position: DpOffset, angle: Float, scale: Float, text: String,
color: Color, alpha: Float = 0.8f) {
Text(
modifier = Modifier
.offset(position.x, position.y)
.rotate(angle)
.scale(scale)
.alpha(alpha),
color = color,
fontWeight = FontWeight.Bold,
text = text,
)
}
@Composable
@Preview
fun FallingSnow() {
BoxWithConstraints(Modifier.fillMaxSize()) {
repeat(50) {
val size = remember { 20.dp + 10.dp * Math.random().toFloat() }
val alpha = remember { 0.10f + 0.15f * Math.random().toFloat() }
val sizePx = with(LocalDensity.current) { size.toPx() }
val x = remember { (constraints.maxWidth * Math.random()).toInt() }
val infiniteTransition = rememberInfiniteTransition()
val t by infiniteTransition.animateFloat(
initialValue = 0f,
targetValue = 1f,
animationSpec = infiniteRepeatable(
animation = tween(16000 + (16000 * Math.random()).toInt(), easing = LinearEasing),
repeatMode = RepeatMode.Restart
)
)
val initialT = remember { Math.random().toFloat() }
val actualT = (initialT + t) % 1f
val y = (-sizePx + (constraints.maxHeight + sizePx) * actualT).toInt()
Box(
Modifier
.offset { IntOffset(x, y) }
.clip(CircleShape)
.alpha(alpha)
.background(Color.White)
.size(size)
)
}
}
}
@Composable
@Preview
fun Background() = Box(
Modifier
.fillMaxSize()
.background(Color(0xFF6F97FF))
)
@Composable
@Preview
fun RotatingWords() {
Background()
FallingSnow()
Words()
}

265
experimental/examples/visual-effects/src/main/kotlin/WaveEffect.kt

@ -0,0 +1,265 @@
package org.jetbrains.compose.demo.visuals
import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.*
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.PointerEventType
import androidx.compose.ui.input.pointer.onPointerEvent
import androidx.compose.ui.input.pointer.pointerMoveFilter
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.WindowPosition
import androidx.compose.ui.window.WindowState
import kotlin.math.*
@Composable
fun WaveEffect(onCloseRequest: () -> Unit, showControls: Boolean) {
val windowState = remember { WindowState(width = 1200.dp, height = 800.dp) }
Window(onCloseRequest = {}, undecorated = true, transparent = true, state = windowState) {
Grid()
}
if (showControls) {
Window(
onCloseRequest = onCloseRequest,
state = WindowState(width = 200.dp, height = 400.dp, position = WindowPosition(1400.dp, 200.dp))
) {
Column {
SettingsPanel(State.red, "Red")
SettingsPanel(State.green, "Green")
SettingsPanel(State.blue, "Blue")
}
}
}
}
@OptIn(ExperimentalComposeUiApi::class)
@Composable
@Preview
fun Grid() {
var mouseX by remember { mutableStateOf(0) }
var mouseY by remember { mutableStateOf(0) }
var centerX by remember { mutableStateOf(1200) }
var centerY by remember { mutableStateOf(900) }
var vX by remember { mutableStateOf(0) }
var vY by remember { mutableStateOf(0) }
var time by remember { mutableStateOf(System.nanoTime()) }
var prevTime by remember { mutableStateOf(System.nanoTime()) }
if (State.mouseUsed) {
centerX = (centerX + vX * (time - prevTime) / 1000000000).toInt()
if (centerX < -100) centerX = -100
if (centerX > 2600) centerX = 2600
vX =
(vX * (1 - (time - prevTime).toDouble() / 500000000) + 10 * (mouseX - centerX) * (time - prevTime) / 1000000000).toInt()
centerY = (centerY + vY * (time - prevTime) / 1000000000).toInt()
if (centerY < -100) centerY = -100
if (centerY > 1800) centerY = 1800
vY =
(vY * (1 - (time - prevTime).toDouble() / 500000000) + 5 * (mouseY - centerY) * (time - prevTime) / 1000000000).toInt()
prevTime = time
}
Surface(
modifier = Modifier.fillMaxSize().padding(5.dp).shadow(3.dp, RoundedCornerShape(20.dp))
.onPointerEvent(PointerEventType.Move) {
mouseX = it.changes.first().position.x.toInt()
mouseY = it.changes.first().position.y.toInt()
}
.onPointerEvent(PointerEventType.Enter) {
State.mouseUsed = true
}
.onPointerEvent(PointerEventType.Exit) {
State.mouseUsed = false
},
color = Color(0, 0, 0),
shape = RoundedCornerShape(20.dp)
) {
Box(Modifier.fillMaxSize()) {
var x = 10 // initial position
var y = 10 // initial position
val shift = 25
var evenRow = false
val pointerOffsetX = (centerX / 2)
val pointerOffsety = (centerY / 2)
while (y < 790) {
x = if (evenRow) 10 + shift else 10
while (x < 1190) {
val size: Int = size(x, y, pointerOffsetX, pointerOffsety)
val color = boxColor(x, y, time, pointerOffsetX, pointerOffsety)
Dot(size, Modifier.offset(x.dp, y.dp), color, time)
x += shift * 2
}
y += shift
evenRow = !evenRow
}
HighPanel(pointerOffsetX, pointerOffsety)
}
LaunchedEffect(Unit) {
while (true) {
withFrameNanos {
time = it
}
}
}
}
}
@Composable
fun HighPanel(mouseX: Int, mouseY: Int) {
Text(
"Compose",
Modifier.offset(270.dp, 600.dp).scale(7.0f).alpha(alpha(mouseX, mouseY, 270, 700)),
color = colorMouse(mouseX, mouseY, 270, 700),
fontWeight = FontWeight.Bold
)
Text(
"Multiplatform",
Modifier.offset(350.dp, 700.dp).scale(7.0f).alpha(alpha(mouseX, mouseY, 550, 800)),
color = colorMouse(mouseX, mouseY, 550, 800),
fontWeight = FontWeight.Bold
)
Text(
"1.0",
Modifier.offset(800.dp, 700.dp).scale(7.0f).alpha(alpha(mouseX, mouseY, 800, 800)),
color = colorMouse(mouseX, mouseY, 800, 800),
fontWeight = FontWeight.Bold
)
}
private fun alpha(mouseX: Int, mouseY: Int, x: Int, y: Int): Float {
var d = distance(mouseX, mouseY, x, y)
if (d > 450) return 0.0f
d = d / 450 - 0.1
return (1 - d * d).toFloat()
}
private fun colorMouse(mouseX: Int, mouseY: Int, x: Int, y: Int): Color {
val d = distance(mouseX, mouseY, x, y) / 450
val color1 = Color(0x6B, 0x57, 0xFF)
val color2 = Color(0xFE, 0x28, 0x57)
val color3 = Color(0xFD, 0xB6, 0x0D)
val color4 = Color(0xFC, 0xF8, 0x4A)
if (d > 1) return color1
if (d > 0.66) return balancedColor(3 * d - 2, color1, color2)
if (d > 0.33) return balancedColor(3 * d - 1, color2, color3)
return balancedColor(3 * d, color3, color4)
}
private fun balancedColor(d: Double, color1: Color, color2: Color): Color {
if (d > 1) return color1
if (d < 0) return color2
val red = ((color1.red * d + color2.red * (1 - d)) * 255).toInt()
val green = ((color1.green * d + color2.green * (1 - d)) * 255).toInt()
val blue = ((color1.blue * d + color2.blue * (1 - d)) * 255).toInt()
return Color(red, green, blue)
}
private fun distance(x1: Int, y1: Int, x2: Int, y2: Int): Double {
return sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2).toDouble())
}
@Composable
fun Dot(size: Int, modifier: Modifier, color: Color, time: Long) {
Box(
modifier.rotate(time.toFloat() / (15 * 10000000)).clip(RoundedCornerShape((3 + size / 20).dp))
.size(width = size.dp, height = size.dp)
) {
Box(modifier = Modifier.fillMaxSize().background(color)) {
}
}
}
private fun size(x: Int, y: Int, mouseX: Int, mouseY: Int): Int {
val addSize = 3
var result = 5
if (y > 550 && x < 550) return result
if (y > 650 && x < 900) return result
val distance2 = sqrt((x - mouseX) * (x - mouseX) + (y - mouseY) * (y - mouseY).toDouble()) / 200
val scale: Double = (if (distance2 < 1) {
addSize * (1 - distance2)
} else 0.toDouble())
result += (if (State.mouseUsed) round(7.5 * scale).toInt() else 0)
return result
}
private fun boxColor(x: Int, y: Int, time: Long, mouseX: Int, mouseY: Int): Color {
if (!State.mouseUsed) return Color.White
val color1 = Color(0x6B, 0x57, 0xFF)
val color2 = Color(0xFE, 0x28, 0x57)
val color3 = Color(0xFC, 0xF8, 0x4A)
val distance = sqrt(((x - mouseX) * (x - mouseX) + (y - mouseY) * (y - mouseY)).toDouble())
val fade = exp(-1 * distance * distance / 150000)
var c1 = sin(12 * distance / 450 - (time.toDouble() / (5 * 100000000)))
if (c1 < 0) c1 = 0.0
var c2 = sin(2 + 12 * distance / 450 - (time.toDouble() / (5 * 100000000)))
if (c2 < 0) c2 = 0.0
var c3 = sin(4 + 12 * distance / 450 - (time.toDouble() / (5 * 100000000)))
if (c3 < 0) c3 = 0.0
var color = Color.White
if (c1 <= 0) {
val d = c2 / (c2 + c3)
color = balancedColor(d, color2, color3)
} else if (c2 <= 0) {
val d = c3 / (c1 + c3)
color = balancedColor(d, color3, color1)
} else if (c3 <= 0) {
val d = c1 / (c1 + c2)
color = balancedColor(d, color1, color2)
}
return balancedColor(fade, color, Color.White)
}
internal class ColorSettings {
var enabled by mutableStateOf(true)
var waveLength by mutableStateOf(30.0)
var simple by mutableStateOf(true)
var period by mutableStateOf(80.0)
}
private class State {
companion object {
var red by mutableStateOf(ColorSettings())
var green by mutableStateOf(ColorSettings())
var blue by mutableStateOf(ColorSettings())
var mouseUsed by mutableStateOf(false)
}
}
@Composable
internal fun SettingsPanel(settings: ColorSettings, name: String) {
Row {
Text(name)
Checkbox(settings.enabled, onCheckedChange = { settings.enabled = it })
Checkbox(settings.simple, onCheckedChange = { settings.simple = it })
Slider(
(settings.waveLength.toFloat() - 10) / 90,
{ settings.waveLength = 10 + 90 * it.toDouble() },
Modifier.width(100.dp)
)
Slider(
(settings.period.toFloat() - 10) / 90,
{ settings.period = 10 + 90 * it.toDouble() },
Modifier.width(100.dp)
)
}
}

32
experimental/examples/visual-effects/src/main/kotlin/main.kt

@ -0,0 +1,32 @@
package org.jetbrains.compose.demo.visuals
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.WindowState
import androidx.compose.ui.window.application
import androidx.compose.ui.window.singleWindowApplication
fun mainWords() = singleWindowApplication(
title = "Compose Demo", state = WindowState(size = DpSize(830.dp, 830.dp))
) {
RotatingWords()
}
fun mainWave(controls: Boolean) = application {
WaveEffect(::exitApplication, controls)
}
fun mainNY() = application {
NYWindow(::exitApplication)
}
fun main(args: Array<String>) {
if (args.isEmpty()) return mainWords()
when (val effect = args[0]) {
"words" -> mainWords()
"wave" -> mainWave(false)
"wave-controls" -> mainWave(true)
"NY" -> mainNY()
else -> throw Error("Unknown effect: $effect")
}
}

BIN
experimental/examples/visual-effects/src/main/resources/compose-community-primary.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

19
experimental/examples/visual-effects/src/main/resources/compose-community-primary.svg

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 600 600" version="1.1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve"
style="fill-rule:evenodd;clip-rule:evenodd;"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<path d="M301.214,418.529C300.972,418.54 300.73,418.556 300.488,418.558C297.091,418.587 293.745,417.722 290.787,416.051L222.6,377.537C220.635,376.43 218.996,374.823 217.85,372.88C216.703,370.937 216.088,368.727 216.067,366.471L216.066,288.156C216.058,287.32 216.093,286.49 216.172,285.673C216.377,283.54 216.909,281.503 217.707,279.602L199.294,268.265L177.742,256.191C175.719,260.427 174.734,265.233 174.784,270.219L174.785,387.053C174.848,393.894 178.571,400.201 184.531,403.561L286.256,461.02C290.669,463.512 295.661,464.802 300.729,464.76C300.911,464.758 301.092,464.742 301.274,464.738C301.245,449.844 301.217,439.234 301.217,439.234L301.214,418.529Z"
style="fill:rgb(4,22,25);fill-rule:nonzero;"/>
<path d="M409.451,242.913L312.64,188.233C303.643,183.15 292.581,183.256 283.683,188.511L187.919,245.004C183.313,247.725 179.93,251.616 177.75,256.173L177.742,256.191L199.295,268.265L217.707,279.602C217.827,279.317 217.922,279.019 218.055,278.741C218.238,278.359 218.433,277.984 218.641,277.617C219.056,276.883 219.523,276.179 220.04,275.511C221.365,273.798 223.005,272.353 224.871,271.254L289.062,233.387C290.422,232.587 291.874,231.955 293.387,231.507C295.529,230.865 297.765,230.598 299.998,230.72C302.976,230.885 305.876,231.734 308.473,233.201L373.366,269.853C375.543,271.084 377.493,272.678 379.134,274.565C379.676,275.19 380.182,275.847 380.647,276.531C380.855,276.837 381.049,277.151 381.24,277.467L397.794,266.392L420.344,252.925L420.313,252.876C417.547,248.799 413.767,245.354 409.451,242.913Z"
style="fill:rgb(55,191,110);fill-rule:nonzero;"/>
<path d="M381.24,277.467C381.513,277.917 381.773,278.375 382.008,278.844C382.208,279.243 382.395,279.648 382.566,280.059C382.909,280.881 383.193,281.726 383.412,282.588C383.74,283.881 383.921,285.214 383.935,286.568L383.934,361.095C383.956,363.946 383.351,366.768 382.162,369.36C381.934,369.859 381.693,370.35 381.424,370.827C379.75,373.793 377.322,376.267 374.388,377.997L310.197,415.871C307.466,417.48 304.381,418.393 301.214,418.529L301.217,439.234C301.217,439.234 301.245,449.844 301.274,464.738C306.103,464.608 310.907,463.296 315.213,460.75L410.976,404.249C419.875,398.997 425.314,389.366 425.216,379.033L425.218,267.849C425.165,262.483 423.34,257.343 420.344,252.925L397.794,266.392L381.24,277.467Z"
style="fill:rgb(56,112,178);fill-rule:nonzero;"/>
<path d="M177.75,256.173C179.93,251.616 183.313,247.725 187.919,245.004L283.683,188.511C292.581,183.256 303.643,183.149 312.64,188.233L409.451,242.913C413.767,245.353 417.547,248.799 420.313,252.876L420.344,252.925L498.594,206.193C494.029,199.463 487.79,193.776 480.666,189.747L320.864,99.49C306.013,91.099 287.753,91.274 273.066,99.95L114.994,193.199C107.391,197.691 101.807,204.112 98.209,211.634L177.742,256.191L177.75,256.173ZM301.274,464.738C301.092,464.742 300.911,464.758 300.729,464.76C295.661,464.802 290.669,463.512 286.256,461.02L184.531,403.562C178.572,400.202 174.848,393.894 174.785,387.053L174.784,270.219C174.734,265.233 175.719,260.427 177.742,256.191L98.209,211.635C94.86,218.633 93.229,226.578 93.312,234.821L93.314,427.671C93.424,438.973 99.544,449.374 109.402,454.922L277.313,549.765C284.598,553.879 292.837,556.008 301.203,555.938L301.205,555.803C301.393,543.776 301.333,495.262 301.274,464.738Z"
style="fill:none;fill-rule:nonzero;stroke:rgb(8,48,66);stroke-width:10px;"/>
<path d="M498.594,206.194L420.344,252.925C423.341,257.343 425.165,262.483 425.218,267.849L425.216,379.033C425.314,389.366 419.875,398.997 410.976,404.249L315.213,460.751C310.907,463.296 306.103,464.609 301.274,464.738C301.333,495.262 301.393,543.776 301.205,555.802L301.203,555.938C309.475,555.871 317.736,553.68 325.111,549.321L483.183,456.056C497.872,447.387 506.85,431.489 506.688,414.433L506.691,230.908C506.604,222.018 503.573,213.503 498.594,206.194Z"
style="fill:none;fill-rule:nonzero;stroke:rgb(8,48,66);stroke-width:10px;"/>
<path d="M301.203,555.938C292.837,556.007 284.598,553.878 277.313,549.764L109.402,454.921C99.545,449.374 93.424,438.973 93.314,427.671L93.312,234.821C93.23,226.578 94.86,218.633 98.209,211.634C101.807,204.112 107.391,197.691 114.994,193.199L273.066,99.949C287.753,91.274 306.013,91.098 320.864,99.49L480.666,189.747C487.79,193.776 494.029,199.463 498.594,206.193C503.573,213.503 506.604,222.018 506.691,230.908L506.688,414.433C506.85,431.489 497.872,447.387 483.183,456.056L325.111,549.32C317.736,553.68 309.475,555.871 301.203,555.938Z"
style="fill:none;fill-rule:nonzero;stroke:rgb(8,48,66);stroke-width:10px;"/>
</svg>

After

Width:  |  Height:  |  Size: 4.9 KiB

Loading…
Cancel
Save