Nikita Lipsky
2 years ago
committed by
GitHub
17 changed files with 1310 additions and 0 deletions
@ -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 |
@ -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> |
@ -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) |
||||||
|
|
@ -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) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,3 @@ |
|||||||
|
kotlin.code.style=official |
||||||
|
kotlin.version=1.7.20 |
||||||
|
compose.version=1.2.2 |
Binary file not shown.
@ -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 |
@ -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" "$@" |
@ -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 |
After Width: | Height: | Size: 2.5 KiB |
@ -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" |
@ -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) |
||||||
|
} |
||||||
|
} |
||||||
|
|
@ -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() |
||||||
|
} |
@ -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) |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
|
@ -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") |
||||||
|
} |
||||||
|
} |
After Width: | Height: | Size: 17 KiB |
Loading…
Reference in new issue