Nikolay Igotti
3 years ago
committed by
GitHub
14 changed files with 812 additions and 1 deletions
@ -1 +1 @@
|
||||
Subproject commit e2a3108b92d4c54b5780f9eeceb1712845961cd7 |
||||
Subproject commit f1e595b90421e4896a9164c3ae0a652920020b77 |
@ -0,0 +1 @@
|
||||
Several visual effects implmented with Compose Multiplatform, used in 1.0 release announce video. |
@ -0,0 +1,57 @@
|
||||
import org.jetbrains.compose.compose |
||||
import org.jetbrains.compose.desktop.application.dsl.TargetFormat |
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile |
||||
|
||||
plugins { |
||||
// __KOTLIN_COMPOSE_VERSION__ |
||||
kotlin("jvm") version "1.5.31" |
||||
// __LATEST_COMPOSE_RELEASE_VERSION__ |
||||
id("org.jetbrains.compose") version "1.0.0" |
||||
} |
||||
|
||||
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") |
||||
dependsOn(runTask) |
||||
} |
||||
|
||||
tasks.register("runWave") { |
||||
additionalArguments.add("wave") |
||||
dependsOn(runTask) |
||||
} |
||||
} |
@ -0,0 +1 @@
|
||||
kotlin.code.style=official |
Binary file not shown.
@ -0,0 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME |
||||
distributionPath=wrapper/dists |
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.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 |
@ -0,0 +1,10 @@
|
||||
pluginManagement { |
||||
repositories { |
||||
gradlePluginPortal() |
||||
mavenCentral() |
||||
maven { url = uri("https://maven.pkg.jetbrains.space/public/p/compose/dev") } |
||||
} |
||||
|
||||
} |
||||
rootProject.name = "visual-effects" |
||||
|
@ -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,255 @@
|
||||
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.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) { |
||||
var 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)) |
||||
.pointerMoveFilter(onMove = { mouseX = it.x.toInt(); mouseY = it.y.toInt(); false; }, |
||||
onEnter = { State.mouseUsed = true; false; }, onExit = { State.mouseUsed = false; 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 |
||||
var pointerOffsetX = (centerX / 2) |
||||
var pointerOffsety = (centerY / 2) |
||||
while (y < 790) { |
||||
x = if (evenRow) 10 + shift else 10 |
||||
while (x < 1190) { |
||||
var size: Int = size(x, y, pointerOffsetX, pointerOffsety) |
||||
var 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", |
||||
androidx.compose.ui.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", |
||||
androidx.compose.ui.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", |
||||
androidx.compose.ui.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 { |
||||
var 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 |
||||
var 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) { |
||||
var d = c2 / (c2 + c3) |
||||
color = balancedColor(d, color2, color3) |
||||
} else if (c2 <= 0) { |
||||
var d = c3 / (c1 + c3) |
||||
color = balancedColor(d, color3, color1) |
||||
} else if (c3 <= 0) { |
||||
var 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,27 @@
|
||||
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 main(args: Array<String>) { |
||||
if (args.isEmpty()) return mainWords() |
||||
when (val effect = args[0]) { |
||||
"words" -> mainWords() |
||||
"wave" -> mainWave(false) |
||||
"wave-controls" -> mainWave(true) |
||||
else -> throw Error("Unknown effect: $effect") |
||||
} |
||||
} |
After Width: | Height: | Size: 17 KiB |
Loading…
Reference in new issue