fieldType,
36 | @NotNull String fieldName) {
37 | Field field = findField(objectClass, fieldName, fieldType);
38 | if (field != null) {
39 | try {
40 | @SuppressWarnings("unchecked") T t = (T) field.get(object);
41 | return t;
42 | } catch (IllegalAccessException ignored) {
43 | }
44 | }
45 |
46 | return null;
47 | }
48 |
49 | @Nullable
50 | private static Field findField(Class> clazz, String fieldName, @Nullable Class> fieldType) {
51 | for (Field field : clazz.getDeclaredFields()) {
52 | if (fieldName.equals(field.getName()) && (fieldType == null || fieldType.isAssignableFrom(field.getType()))) {
53 | field.setAccessible(true);
54 | return field;
55 | }
56 | }
57 |
58 | Class> superClass = clazz.getSuperclass();
59 | if (superClass != null) {
60 | Field result = findField(superClass, fieldName, fieldType);
61 | if (result != null) return result;
62 | }
63 |
64 | for (Class> each : clazz.getInterfaces()) {
65 | Field result = findField(each, fieldName, fieldType);
66 | if (result != null) return result;
67 | }
68 |
69 | return null;
70 | }
71 |
72 | private static final class MySecurityManager extends SecurityManager {
73 | private static final MySecurityManager INSTANCE = new MySecurityManager();
74 |
75 | Class>[] getStack() {
76 | return getClassContext();
77 | }
78 | }
79 |
80 | /**
81 | * Returns the class this method was called 'framesToSkip' frames up the caller hierarchy.
82 | *
83 | * NOTE:
84 | * Extremely expensive!
85 | * Please consider not using it.
86 | * These aren't the droids you're looking for!
87 | */
88 | @Nullable
89 | public static Class> findCallerClass(int framesToSkip) {
90 | try {
91 | Class>[] stack = MySecurityManager.INSTANCE.getStack();
92 | int indexFromTop = 1 + framesToSkip;
93 | return stack.length > indexFromTop ? stack[indexFromTop] : null;
94 | } catch (Exception e) {
95 | // LoggerRt.getInstance(ReflectionUtilRt.class).warn(e);
96 | return null;
97 | }
98 | }
99 | }
--------------------------------------------------------------------------------
/clipboard/src/main/java/com/intellij/SystemInfo.java:
--------------------------------------------------------------------------------
1 | // Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 | package com.intellij;
3 |
4 | import org.jetbrains.annotations.NotNull;
5 |
6 | import java.io.File;
7 | import java.util.List;
8 |
9 | /**
10 | * Provides information about operating system, system-wide settings, and Java Runtime.
11 | */
12 | public final class SystemInfo {
13 | public static final String OS_NAME = SystemInfoRt.OS_NAME;
14 | public static final String OS_VERSION = SystemInfoRt.OS_VERSION;
15 | public static final String OS_ARCH = System.getProperty("os.arch");
16 | public static final String JAVA_VERSION = System.getProperty("java.version");
17 | public static final String JAVA_RUNTIME_VERSION = getRtVersion(JAVA_VERSION);
18 | public static final String JAVA_VENDOR = System.getProperty("java.vm.vendor", "Unknown");
19 |
20 | private static String getRtVersion(@SuppressWarnings("SameParameterValue") String fallback) {
21 | String rtVersion = System.getProperty("java.runtime.version");
22 | return Character.isDigit(rtVersion.charAt(0)) ? rtVersion : fallback;
23 | }
24 |
25 | public static final boolean isWindows = SystemInfoRt.isWindows;
26 | public static final boolean isMac = SystemInfoRt.isMac;
27 | public static final boolean isLinux = SystemInfoRt.isLinux;
28 | public static final boolean isFreeBSD = SystemInfoRt.isFreeBSD;
29 | public static final boolean isSolaris = SystemInfoRt.isSolaris;
30 | public static final boolean isUnix = SystemInfoRt.isUnix;
31 | public static final boolean isChromeOS = isLinux && isCrostini();
32 |
33 | public static final boolean isOracleJvm = Strings.indexOfIgnoreCase(JAVA_VENDOR, "Oracle", 0) >= 0;
34 | public static final boolean isIbmJvm = Strings.indexOfIgnoreCase(JAVA_VENDOR, "IBM", 0) >= 0;
35 | public static final boolean isAzulJvm = Strings.indexOfIgnoreCase(JAVA_VENDOR, "Azul", 0) >= 0;
36 | public static final boolean isJetBrainsJvm = Strings.indexOfIgnoreCase(JAVA_VENDOR, "JetBrains", 0) >= 0;
37 |
38 | @SuppressWarnings("SpellCheckingInspection")
39 | private static boolean isCrostini() {
40 | return new File("/dev/.cros_milestone").exists();
41 | }
42 |
43 | public static boolean isOsVersionAtLeast(@NotNull String version) {
44 | return StringUtil.compareVersionNumbers(OS_VERSION, version) >= 0;
45 | }
46 |
47 | public static final boolean isWin7OrNewer = isWindows && isOsVersionAtLeast("6.1");
48 | public static final boolean isWin8OrNewer = isWindows && isOsVersionAtLeast("6.2");
49 | public static final boolean isWin10OrNewer = isWindows && isOsVersionAtLeast("10.0");
50 |
51 | public static final boolean isXWindow = SystemInfoRt.isXWindow;
52 | public static final boolean isWayland, isGNOME, isKDE, isXfce, isI3;
53 |
54 | static {
55 | // http://askubuntu.com/questions/72549/how-to-determine-which-window-manager-is-running/227669#227669
56 | // https://userbase.kde.org/KDE_System_Administration/Environment_Variables#KDE_FULL_SESSION
57 | if (isXWindow) {
58 | isWayland = System.getenv("WAYLAND_DISPLAY") != null;
59 | String desktop = System.getenv("XDG_CURRENT_DESKTOP"), gdmSession = System.getenv("GDMSESSION");
60 | isGNOME = desktop != null && desktop.contains("GNOME") || gdmSession != null && gdmSession.contains("gnome");
61 | isKDE = !isGNOME && (desktop != null && desktop.contains("KDE") || System.getenv("KDE_FULL_SESSION") != null);
62 | isXfce = !isGNOME && !isKDE && (desktop != null && desktop.contains("XFCE"));
63 | isI3 = !isGNOME && !isKDE && !isXfce && (desktop != null && desktop.contains("i3"));
64 | } else {
65 | isWayland = isGNOME = isKDE = isXfce = isI3 = false;
66 | }
67 | }
68 |
69 | public static final boolean isMacSystemMenu = isMac && "true".equals(System.getProperty("apple.laf.useScreenMenuBar"));
70 |
71 | public static final boolean isFileSystemCaseSensitive = SystemInfoRt.isFileSystemCaseSensitive;
72 |
73 | public static final boolean isMacOSYosemite = isMac && isOsVersionAtLeast("10.10");
74 | public static final boolean isMacOSElCapitan = isMac && isOsVersionAtLeast("10.11");
75 | public static final boolean isMacOSSierra = isMac && isOsVersionAtLeast("10.12");
76 | public static final boolean isMacOSHighSierra = isMac && isOsVersionAtLeast("10.13");
77 | public static final boolean isMacOSMojave = isMac && isOsVersionAtLeast("10.14");
78 | public static final boolean isMacOSCatalina = isMac && isOsVersionAtLeast("10.15");
79 | public static final boolean isMacOSBigSur = isMac && isOsVersionAtLeast("10.16");
80 | public static final boolean isMacOSMonterey = isMac && isOsVersionAtLeast("12.0");
81 |
82 | public static @NotNull
83 | String getMacOSMajorVersion() {
84 | return getMacOSMajorVersion(OS_VERSION);
85 | }
86 |
87 | public static String getMacOSMajorVersion(String version) {
88 | int[] parts = getMacOSVersionParts(version);
89 | return String.format("%d.%d", parts[0], parts[1]);
90 | }
91 |
92 | public static @NotNull
93 | String getMacOSVersionCode() {
94 | return getMacOSVersionCode(OS_VERSION);
95 | }
96 |
97 | public static @NotNull
98 | String getMacOSMajorVersionCode() {
99 | return getMacOSMajorVersionCode(OS_VERSION);
100 | }
101 |
102 | public static @NotNull
103 | String getMacOSMinorVersionCode() {
104 | return getMacOSMinorVersionCode(OS_VERSION);
105 | }
106 |
107 | public static @NotNull
108 | String getMacOSVersionCode(@NotNull String version) {
109 | int[] parts = getMacOSVersionParts(version);
110 | return String.format("%02d%d%d", parts[0], normalize(parts[1]), normalize(parts[2]));
111 | }
112 |
113 | public static @NotNull
114 | String getMacOSMajorVersionCode(@NotNull String version) {
115 | int[] parts = getMacOSVersionParts(version);
116 | return String.format("%02d%d%d", parts[0], normalize(parts[1]), 0);
117 | }
118 |
119 | public static @NotNull
120 | String getMacOSMinorVersionCode(@NotNull String version) {
121 | int[] parts = getMacOSVersionParts(version);
122 | return String.format("%02d%02d", parts[1], parts[2]);
123 | }
124 |
125 | private static int[] getMacOSVersionParts(@NotNull String version) {
126 | List parts = StringUtil.split(version, ".");
127 | while (parts.size() < 3) {
128 | parts.add("0");
129 | }
130 | return new int[]{toInt(parts.get(0)), toInt(parts.get(1)), toInt(parts.get(2))};
131 | }
132 |
133 | public static String getOsNameAndVersion() {
134 | return (isMac ? "macOS" : OS_NAME) + ' ' + OS_VERSION;
135 | }
136 |
137 | private static int normalize(int number) {
138 | return Math.min(number, 9);
139 | }
140 |
141 | private static int toInt(String string) {
142 | try {
143 | return Integer.parseInt(string);
144 | } catch (NumberFormatException e) {
145 | return 0;
146 | }
147 | }
148 |
149 | public static final boolean isIntel64 = CpuArch.isIntel64();
150 | public static final boolean isMacIntel64 = isMac && isIntel64;
151 |
152 | /**
153 | * @deprecated always false
154 | */
155 | @Deprecated
156 | public static final boolean isAppleJvm = false;
157 |
158 | /**
159 | * @deprecated always false
160 | */
161 | @Deprecated
162 | public static final boolean isSunJvm = false;
163 |
164 | /**
165 | * @deprecated always true (Java 8 requires macOS 10.9+)
166 | */
167 | @Deprecated
168 | public static final boolean isMacOSTiger = isMac;
169 |
170 | /**
171 | * @deprecated always true (Java 8 requires macOS 10.9+)
172 | */
173 | @Deprecated
174 | public static final boolean isMacOSLeopard = isMac;
175 |
176 | /**
177 | * @deprecated always true (Java 8 requires macOS 10.9+)
178 | */
179 | @Deprecated
180 | public static final boolean isMacOSSnowLeopard = isMac;
181 |
182 | /**
183 | * @deprecated always true (Java 8 requires macOS 10.9+)
184 | */
185 | @Deprecated
186 | public static final boolean isMacOSLion = isMac;
187 |
188 | /**
189 | * @deprecated always true (Java 8 requires macOS 10.9+)
190 | */
191 | @Deprecated
192 | public static final boolean isMacOSMountainLion = isMac;
193 |
194 | /**
195 | * @deprecated always true (Java 8 requires macOS 10.9+)
196 | */
197 | @Deprecated
198 | public static final boolean isMacOSMavericks = isMac;
199 |
200 | /**
201 | * @deprecated always true (Java 8 requires Windows Vista / Server 2008)
202 | */
203 | @Deprecated
204 | public static final boolean isWin2kOrNewer = isWindows;
205 |
206 | /**
207 | * @deprecated always true (Java 8 requires Windows Vista / Server 2008)
208 | */
209 | @Deprecated
210 | public static final boolean isWinVistaOrNewer = isWindows;
211 |
212 | /**
213 | * @deprecated always true
214 | */
215 | @Deprecated
216 | public static final boolean areSymLinksSupported = isUnix || isWindows;
217 | //
218 | }
219 |
--------------------------------------------------------------------------------
/clipboard/src/main/java/com/intellij/SystemInfoRt.java:
--------------------------------------------------------------------------------
1 | // Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 | package com.intellij;
3 |
4 | import java.util.Locale;
5 |
6 | public final class SystemInfoRt {
7 | public static final String OS_NAME = System.getProperty("os.name");
8 | public static final String OS_VERSION = System.getProperty("os.version").toLowerCase(Locale.ENGLISH);
9 |
10 | private static final String _OS_NAME = OS_NAME.toLowerCase(Locale.ENGLISH);
11 | public static final boolean isWindows = _OS_NAME.startsWith("windows");
12 | public static final boolean isMac = _OS_NAME.startsWith("mac");
13 | public static final boolean isLinux = _OS_NAME.startsWith("linux");
14 | public static final boolean isFreeBSD = _OS_NAME.startsWith("freebsd");
15 | public static final boolean isSolaris = _OS_NAME.startsWith("sunos");
16 | public static final boolean isUnix = !isWindows;
17 | public static final boolean isXWindow = isUnix && !isMac;
18 |
19 | public static final boolean isFileSystemCaseSensitive =
20 | isUnix && !isMac || "true".equalsIgnoreCase(System.getProperty("idea.case.sensitive.fs"));
21 |
22 | private static final String ARCH_DATA_MODEL = System.getProperty("sun.arch.data.model");
23 | /**
24 | * @deprecated inexact, please use {@code com.intellij.util.system.CpuArch} instead
25 | */
26 | @Deprecated
27 |
28 | public static final boolean is64Bit = !(ARCH_DATA_MODEL == null || ARCH_DATA_MODEL.equals("32"));
29 | }
30 |
--------------------------------------------------------------------------------
/editor-run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | ./gradlew usage:run --no-daemon --offline
3 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | kotlin.code.style=official
2 | org.gradle.daemon=false
3 | org.gradle.jvmargs=-Xmx500m -XX:TieredStopAtLevel=1
4 | org.gradle.parallel=true
5 | #todo check:
6 | kotlin.parallel.tasks.in.project=true
7 | android.useAndroidX=true
8 | android.enableJetifier=false
9 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avdim/compose-vector/e507f6946d389e8e320b8db736319d783596516f/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | #distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.2-all.zip
4 | #distributionUrl=https\://services.gradle.org/distributions/gradle-6.9-all.zip
5 | #distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip
6 | #distributionUrl=https\://services.gradle.org/distributions/gradle-7.1-rc-1-all.zip
7 | #distributionUrl=https\://services.gradle.org/distributions/gradle-7.1-all.zip
8 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip
9 | zipStoreBase=GRADLE_USER_HOME
10 | zipStorePath=wrapper/dists
11 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 | # Determine the Java command to use to start the JVM.
86 | if [ -n "$JAVA_HOME" ] ; then
87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
88 | # IBM's JDK on AIX uses strange locations for the executables
89 | JAVACMD="$JAVA_HOME/jre/sh/java"
90 | else
91 | JAVACMD="$JAVA_HOME/bin/java"
92 | fi
93 | if [ ! -x "$JAVACMD" ] ; then
94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
95 |
96 | Please set the JAVA_HOME variable in your environment to match the
97 | location of your Java installation."
98 | fi
99 | else
100 | JAVACMD="java"
101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
102 |
103 | Please set the JAVA_HOME variable in your environment to match the
104 | location of your Java installation."
105 | fi
106 |
107 | # Increase the maximum file descriptors if we can.
108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
109 | MAX_FD_LIMIT=`ulimit -H -n`
110 | if [ $? -eq 0 ] ; then
111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
112 | MAX_FD="$MAX_FD_LIMIT"
113 | fi
114 | ulimit -n $MAX_FD
115 | if [ $? -ne 0 ] ; then
116 | warn "Could not set maximum file descriptor limit: $MAX_FD"
117 | fi
118 | else
119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
120 | fi
121 | fi
122 |
123 | # For Darwin, add options to specify how the application appears in the dock
124 | if $darwin; then
125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
126 | fi
127 |
128 | # For Cygwin or MSYS, switch paths to Windows format before running java
129 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
132 | JAVACMD=`cygpath --unix "$JAVACMD"`
133 |
134 | # We build the pattern for arguments to be converted via cygpath
135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
136 | SEP=""
137 | for dir in $ROOTDIRSRAW ; do
138 | ROOTDIRS="$ROOTDIRS$SEP$dir"
139 | SEP="|"
140 | done
141 | OURCYGPATTERN="(^($ROOTDIRS))"
142 | # Add a user-defined pattern to the cygpath arguments
143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
145 | fi
146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
147 | i=0
148 | for arg in "$@" ; do
149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
151 |
152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
154 | else
155 | eval `echo args$i`="\"$arg\""
156 | fi
157 | i=`expr $i + 1`
158 | done
159 | case $i in
160 | 0) set -- ;;
161 | 1) set -- "$args0" ;;
162 | 2) set -- "$args0" "$args1" ;;
163 | 3) set -- "$args0" "$args1" "$args2" ;;
164 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
165 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
166 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
167 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
168 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
169 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
170 | esac
171 | fi
172 |
173 | # Escape application args
174 | save () {
175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
176 | echo " "
177 | }
178 | APP_ARGS=`save "$@"`
179 |
180 | # Collect all arguments for the java command, following the shell quoting and substitution rules
181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
182 |
183 | exec "$JAVACMD" "$@"
184 |
--------------------------------------------------------------------------------
/happy-new-year/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | kotlin("multiplatform") // kotlin("jvm") doesn't work well in IDEA/AndroidStudio (https://github.com/JetBrains/compose-jb/issues/22)
3 | id("org.jetbrains.compose")
4 | }
5 |
6 | java {
7 | sourceCompatibility = JavaVersion.VERSION_11
8 | targetCompatibility = JavaVersion.VERSION_11
9 | }
10 |
11 | kotlin {
12 | jvm {
13 | withJava()
14 | }
15 | sourceSets {
16 | named("commonMain") {
17 | dependencies {
18 | api(project(":lib"))
19 | // api(compose.runtime)
20 | // api(compose.foundation)
21 | // api(compose.material)
22 | }
23 | }
24 | named("jvmMain") {
25 | dependencies {
26 | implementation(project(":clipboard"))
27 | implementation(compose.desktop.currentOs)
28 | implementation("com.squareup:kotlinpoet:1.10.2")
29 | implementation("org.pushing-pixels:radiance-animation:${RADIANCE_VERSION}")
30 | implementation("org.pushing-pixels:radiance-animation-ktx:${RADIANCE_VERSION}")
31 | }
32 | }
33 | named("jvmTest") {
34 | dependencies {
35 | implementation(kotlin("test"))
36 | }
37 | }
38 | }
39 | }
40 |
41 | compose.desktop {
42 | application {
43 | mainClass = "com.usage.MainKt"
44 | }
45 | }
46 |
47 | tasks.withType {
48 | kotlinOptions.jvmTarget = "11"
49 | }
50 |
--------------------------------------------------------------------------------
/happy-new-year/src/commonMain/kotlin/com/usage/BackgroundHills.kt:
--------------------------------------------------------------------------------
1 | package com.usage
2 |
3 | import androidx.compose.runtime.*
4 | import lib.vector.DisplayMode
5 | import lib.vector.Pt
6 | import kotlin.random.Random
7 |
8 | @Composable
9 | fun BackgroundHills() {
10 | val color = remember {
11 | val c = (180 + 60).toULong()
12 | 0xff00000000000000uL + (c shl 32) + (c shl 40) + (c shl 48)
13 | }
14 | val middleY = 300f
15 | val leftX = -3000f
16 | val rightX = 2000f
17 | val bottomY = 800f
18 | val bottomLeft = Pt(leftX, bottomY)
19 | val bottomRight = Pt(rightX, bottomY)
20 | val pointsCount = 15
21 | val stepWidth = (rightX - leftX) / pointsCount
22 | val speed = 1.3f
23 | var curvePoints: List by remember {
24 | mutableStateOf(
25 | List(pointsCount) {
26 | Pt(leftX + stepWidth * it, middleY + Random.nextInt(0, 80))
27 | }
28 | )
29 | }
30 | LaunchedEffect(Unit) {
31 | while (true) {
32 | withFrameNanos { it }
33 | curvePoints = curvePoints
34 | .map { Pt(it.x + speed, it.y) }
35 | .toMutableList().apply {
36 | indices.reversed().forEach { i ->
37 | if (this[i].x > rightX) {
38 | val moveMe = removeAt(i)
39 | add(0, Pt(leftX, moveMe.y))
40 | }
41 | }
42 | }
43 | }
44 | }
45 | DisplayMode() {
46 | drawCurve(color, listOf(bottomLeft) + curvePoints + listOf(bottomRight), emptyMap(), fillPath = true)
47 | }
48 | }
--------------------------------------------------------------------------------
/happy-new-year/src/commonMain/kotlin/com/usage/Cat.kt:
--------------------------------------------------------------------------------
1 | package com.usage
2 |
3 | import androidx.compose.animation.core.*
4 | import androidx.compose.runtime.*
5 | import androidx.compose.ui.Modifier
6 | import lib.vector.*
7 | import kotlin.math.absoluteValue
8 | import kotlin.random.Random
9 |
10 | @Composable
11 | fun Cat() {
12 | val points1 = listOf(Pt(205, 211),Pt(201, 242),Pt(169, 259),Pt(147, 292),Pt(155, 311),Pt(173, 324),Pt(205, 338),Pt(223, 407),Pt(214, 471),Pt(162, 518),Pt(132, 572),Pt(203, 533),Pt(283, 492),Pt(365, 478),Pt(446, 473),Pt(583, 473),Pt(667, 502),Pt(713, 536),Pt(732, 572),Pt(765, 577),Pt(765, 533),Pt(733, 490),Pt(701, 445),Pt(658, 379),Pt(626, 340),Pt(605, 265),Pt(581, 208),Pt(596, 166),Pt(560, 179),Pt(559, 243),Pt(580, 297),Pt(562, 326),Pt(489, 329),Pt(385, 336),Pt(304, 305),Pt(257, 256),Pt(239, 214),Pt(225, 237),Pt(160, 575),Pt(111, 532),Pt(232, 519),Pt(167, 285),Pt(232, 228),Pt(247, 233),Pt(607, 190),Pt(641, 486),)
13 | val points2 = listOf(Pt(205, 211),Pt(201, 242),Pt(169, 259),Pt(147, 292),Pt(155, 311),Pt(173, 324),Pt(205, 338),Pt(245, 431),Pt(310, 474),Pt(331, 538),Pt(309, 597),Pt(368, 564),Pt(377, 509),Pt(392, 472),Pt(454, 468),Pt(501, 475),Pt(535, 510),Pt(519, 548),Pt(487, 575),Pt(520, 592),Pt(552, 571),Pt(581, 536),Pt(598, 484),Pt(624, 425),Pt(631, 342),Pt(605, 265),Pt(581, 208),Pt(596, 166),Pt(560, 179),Pt(559, 243),Pt(580, 297),Pt(562, 326),Pt(489, 329),Pt(385, 336),Pt(304, 305),Pt(257, 256),Pt(239, 214),Pt(225, 237),Pt(365, 601),Pt(289, 568),Pt(385, 536),Pt(167, 285),Pt(232, 228),Pt(247, 233),Pt(607, 190),Pt(528, 488),)
14 |
15 | val head1 = listOf(Pt(205, 211), Pt(196, 243), Pt(169, 259), Pt(147, 292), Pt(155, 311), Pt(173, 324), Pt(205, 338), Pt(489, 329), Pt(385, 336), Pt(304, 305), Pt(257, 256), Pt(239, 214), Pt(225, 237), Pt(167, 285), Pt(229, 227), Pt(247, 233),)
16 | val head2 = listOf(Pt(177, 256), Pt(175, 282), Pt(151, 297), Pt(129, 330), Pt(137, 349), Pt(155, 362), Pt(187, 376), Pt(454, 322), Pt(355, 352), Pt(284, 333), Pt(241, 292), Pt(206, 251), Pt(203, 275), Pt(149, 323), Pt(207, 265), Pt(230, 271))
17 | val tail1 = listOf(Pt(605, 265), Pt(581, 208), Pt(596, 166), Pt(560, 179), Pt(559, 243), Pt(580, 297), Pt(553, 325), Pt(607, 190))
18 | val tail2 = listOf(Pt(657, 308), Pt(696, 280), Pt(747, 264), Pt(714, 241), Pt(651, 267), Pt(601, 296), Pt(553, 325), Pt(716, 271))
19 |
20 | val infiniteTransition = rememberInfiniteTransition()
21 | val legsAnimationRadio by infiniteTransition.animateFloat(
22 | initialValue = 0f,
23 | targetValue = 1f,
24 | animationSpec = infiniteRepeatable(
25 | animation = keyframes {
26 | durationMillis = 1000
27 | 0.2f at 300
28 | 0.8f at 700
29 | },
30 | repeatMode = RepeatMode.Reverse
31 | )
32 | )
33 | val headAnimationRadio by infiniteTransition.animateFloat(
34 | initialValue = 0f,
35 | targetValue = 1f,
36 | animationSpec = infiniteRepeatable(
37 | animation = keyframes {
38 | durationMillis = 1500
39 | 0.3f at 300
40 | 0.7f at 700
41 | },
42 | repeatMode = RepeatMode.Reverse
43 | )
44 | )
45 |
46 | var tailAnimationRatio by remember { mutableStateOf(0f) }
47 | var targetTailRatio by remember { mutableStateOf(0f) }
48 |
49 | LaunchedEffect(Unit) {
50 | while (true) {
51 | withFrameNanos { it }
52 | if ((tailAnimationRatio - targetTailRatio).absoluteValue < 0.01f) {
53 | targetTailRatio = Random.nextDouble(0.0, 1.0).toFloat()
54 | }
55 | tailAnimationRatio += (targetTailRatio - tailAnimationRatio) / 50
56 | }
57 | }
58 |
59 | val animatedHead:List by derivedStateOf {
60 | val f = headAnimationRadio
61 | head1.mapIndexed { i, pt -> pt + (head2[i] - pt) * f }
62 | }
63 |
64 | val animatedTail:List by derivedStateOf {
65 | val f = tailAnimationRatio
66 | tail2.mapIndexed { i, pt -> pt + (tail1[i] - pt) * f }
67 | }
68 |
69 | val animatedPointsA: List by derivedStateOf {
70 | val f = legsAnimationRadio
71 | points1.mapIndexed { i, pt -> pt + (points2[i] - pt) * f }
72 | }
73 | val animatedPointsB: List by derivedStateOf {
74 | val f = legsAnimationRadio
75 | points2.mapIndexed { i, pt -> pt + (points1[i] - pt) * f }
76 | }
77 | // CatBitmap()
78 |
79 | DisplayMode(Modifier) {
80 | val h00 = animatedHead[0]
81 | val h01 = animatedHead[1]
82 | val h02 = animatedHead[2]
83 | val h03 = animatedHead[3]
84 | val h04 = animatedHead[4]
85 | val h05 = animatedHead[5]
86 | val h06 = animatedHead[6]
87 | val h32 = animatedHead[7]
88 | val h33 = animatedHead[8]
89 | val h34 = animatedHead[9]
90 | val h35 = animatedHead[10]
91 | val h36 = animatedHead[11]
92 | val h37 = animatedHead[12]
93 | val h41 = animatedHead[13]
94 | val h42 = animatedHead[14]
95 | val h43 = animatedHead[15]
96 |
97 | val t25 by mkPt(animatedTail[0])
98 | val t26 by mkPt(animatedTail[1])
99 | val t27 by mkPt(animatedTail[2])
100 | val t28 by mkPt(animatedTail[3])
101 | val t29 by mkPt(animatedTail[4])
102 | val t30 by mkPt(animatedTail[5])
103 | val t31 by mkPt(animatedTail[6])
104 | val t44 by mkPt(animatedTail[7])
105 |
106 | val a = animatedPointsA
107 | drawCurve(
108 | 0xff44444400000000uL,
109 | listOf(h00, h01, h02, h03, h04, h05, h06, a[7], a[8], a[9], a[10], a[11], a[12], a[13], a[14], a[15], a[16], a[17], a[18], a[19], a[20], a[21], a[22], a[23], a[24], t25, t26, t27, t28, t29, t30, t31, h32, h33, h34, h35, h36, h37, h00,),
110 | mapOf(
111 | a[10] to BR(a[38], a[39]),
112 | a[11] to BR(a[40], null),
113 | h02 to BR(h41, null),
114 | h36 to BR(h42, h43),
115 | t27 to BR(null, t44),
116 | a[15] to BR(a[45], null),
117 | ),
118 | true
119 | )
120 | val b = animatedPointsB
121 | drawCurve(
122 | 0xff55555500000000uL,
123 | listOf(h00,h01,h02,h03,h04,h05,h06,b[7],b[8],b[9],b[10],b[11],b[12],b[13],b[14],b[15],b[16],b[17],b[18],b[19],b[20],b[21],b[22],b[23],b[24],t25,t26,t27,t28,t29,t30,t31,h32,h33,h34,h35,h36,h37,h00,),
124 | mapOf(
125 | b[10] to BR(b[38], b[39]),
126 | b[11] to BR(b[40], null),
127 | h02 to BR(h41, null),
128 | h36 to BR(h42, h43),
129 | t27 to BR(null, t44),
130 | b[15] to BR(b[45], null),
131 | ),
132 | true
133 | )
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/happy-new-year/src/commonMain/kotlin/com/usage/ComposeShader.kt:
--------------------------------------------------------------------------------
1 | package com.usage
2 |
3 | import androidx.compose.foundation.layout.Box
4 | import androidx.compose.foundation.layout.fillMaxSize
5 | import androidx.compose.runtime.*
6 | import androidx.compose.ui.Modifier
7 | import androidx.compose.ui.draw.drawBehind
8 | import androidx.compose.ui.geometry.Offset
9 | import androidx.compose.ui.geometry.Size
10 | import androidx.compose.ui.graphics.ShaderBrush
11 | import org.jetbrains.skia.Data
12 | import org.jetbrains.skia.RuntimeEffect
13 | import java.nio.ByteBuffer
14 | import java.nio.ByteOrder
15 |
16 | // https://github.com/Mishkun/ataman-intellij
17 | // https://www.pushing-pixels.org/2021/09/22/skia-shaders-in-compose-desktop.html
18 | // https://shaders.skia.org/?id=%40iMouse
19 |
20 | @Composable
21 | fun StarsAndSky(size:Size) {
22 |
23 | size.height
24 | val sksl =
25 | """
26 | uniform float iTime;
27 | float2 iResolution = float2(${size.width}, ${size.height});
28 |
29 | // The iResolution uniform is always present and provides
30 | // the canvas size in pixels.
31 | // The iResolution uniform is always present and provides
32 | // the canvas size in pixels.
33 |
34 | // The iResolution uniform is always present and provides
35 | // the canvas size in pixels.
36 |
37 | float rnd (vec2 uv) {
38 | return fract(sin(dot(uv.xy , vec2(12.9898,78.233))) * 43758.5453);
39 | }
40 |
41 | half4 main(float2 fragCoord) {
42 | float fx = fragCoord.x;
43 | float fy = fragCoord.y;
44 | vec2 r = vec2(0,0);//random seed
45 | float brightness = 0;
46 | float3 result = float3(0,0,0);
47 | for (int i = 0; i < 30; i++) {
48 | r = r + 1.0;//next random
49 | float2 p = float2(iResolution.x*rnd(r + 0), iResolution.y*rnd(r + 1));
50 | float distanceX = length(fragCoord.x - p.x);
51 | float distanceY = length(fragCoord.y - p.y);
52 | float distance = length(fragCoord - p);
53 | float pulse = 1.0 + 0.45*sin(rnd(r+3)*100 + iTime*(4 + 2*rnd(r+4)));
54 | brightness = 2.1 * pulse * 1/((distanceX+0.1)*(distanceY+0.1)*distance);
55 |
56 | float3 color = float3(0.4 + rnd(r + 5), 0.3 + rnd(r+6), 1.0 + rnd(r+7));
57 | result = result + color * brightness;
58 | }
59 |
60 | float ANIMATION_SPEED1 = 3.0;
61 | float bottomY = 400;
62 | float yellowY = 360 + 40*sin(fx*0.05 + iTime*0.9*ANIMATION_SPEED1 + 3.0) + 40*sin(-fx*0.03 + iTime*0.4*ANIMATION_SPEED1 + 2.0);
63 | float greenY = 300 + 40*sin(-fx*0.02 - iTime*0.3*ANIMATION_SPEED1 + 2.0) + 40*sin(fx*0.01 - iTime*0.7*ANIMATION_SPEED1 + 1.1);
64 | float blueY = 200 + 50*sin(-fx*0.04 + iTime*0.5*ANIMATION_SPEED1 + 2.5) + 50*sin(fx*0.02 + iTime*0.5*ANIMATION_SPEED1 + 0.3);
65 |
66 | for(int i = 1; i < 8; i++) {
67 | r = r + 1.0;//next random
68 | float power = 0.4*(1.0 + rnd(r+10));
69 | float2 pt = float2(iResolution.x*rnd(r+11),bottomY);
70 | float dy = (pt.y - fragCoord.y);
71 | dy = dy / (1 + sign(dy));
72 | float dx = length(fragCoord.x - pt.x + 10*sin(fy*0.04 + iTime*(1+2*rnd(r+9))));
73 |
74 | float3 yellow = float3(0.7, 1.0, 0.0) * (1 - length(yellowY - fy)/yellowY);
75 | float3 green = float3(0.0, 1.0, 0.0) * (1 - length(greenY - fy)/greenY);
76 | float3 blue = float3(0.0, 0.0, 1.0) * (1 - length(blueY - fy)/blueY);
77 | float3 northenLightColor = (yellow+green+blue)/(0.1 + sqrt(dy))/(2.0 + pow(dx, 0.25));
78 | northenLightColor = northenLightColor * power;
79 | result = result + northenLightColor;
80 | }
81 |
82 | return half4(3.0 * result, 1.0);
83 | }
84 |
85 | """
86 |
87 | val runtimeEffect = RuntimeEffect.makeForShader(sksl)
88 | val byteBuffer = remember { ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN) }
89 | var timeUniform by remember { mutableStateOf(0.0f) }
90 | var previousNanos by remember { mutableStateOf(0L) }
91 |
92 | val timeBits = byteBuffer.clear().putFloat(timeUniform).array()
93 | val shader = runtimeEffect.makeShader(
94 | uniforms = Data.makeFromBytes(timeBits),
95 | children = null,
96 | localMatrix = null,
97 | isOpaque = false
98 | )
99 | val brush = ShaderBrush(shader)
100 |
101 | Box(modifier = Modifier.fillMaxSize().drawBehind {
102 | drawRect(
103 | brush = brush, topLeft = Offset(0f, 0f), size = size
104 | )
105 | })
106 |
107 | LaunchedEffect(null) {
108 | while (true) {
109 | withFrameNanos { frameTimeNanos ->
110 | val nanosPassed = frameTimeNanos - previousNanos
111 | val delta = nanosPassed / 1_000_000_000f
112 | if (previousNanos > 0.0f) {
113 | timeUniform -= delta
114 | }
115 | previousNanos = frameTimeNanos
116 | }
117 | }
118 | }
119 |
120 | }
121 |
--------------------------------------------------------------------------------
/happy-new-year/src/commonMain/kotlin/com/usage/HappyNewYear.kt:
--------------------------------------------------------------------------------
1 | @file:OptIn(ExperimentalStdlibApi::class)
2 |
3 | package com.usage
4 |
5 | import androidx.compose.runtime.*
6 | import androidx.compose.ui.Modifier
7 | import androidx.compose.ui.geometry.Size
8 |
9 | @Composable
10 | fun HappyNewYear() {
11 | StarsAndSky(Size(800f, 400f))
12 | BackgroundHills()
13 | ManyChristmasTrees()
14 | Snow(0.7f, 1.0f, 40)
15 | Cat()
16 | Snow(1.0f, 1.0f, 40)
17 | SnowDrifts()
18 | Snow(1.5f, 2f, 50)
19 | HappyNewYearText()
20 | }
21 |
--------------------------------------------------------------------------------
/happy-new-year/src/commonMain/kotlin/com/usage/HappyNewYearText.kt:
--------------------------------------------------------------------------------
1 | package com.usage
2 |
3 | import androidx.compose.animation.core.*
4 | import androidx.compose.foundation.background
5 | import androidx.compose.foundation.layout.Box
6 | import androidx.compose.foundation.layout.Column
7 | import androidx.compose.foundation.layout.fillMaxSize
8 | import androidx.compose.foundation.layout.padding
9 | import androidx.compose.material.Text
10 | import androidx.compose.runtime.*
11 | import androidx.compose.ui.Alignment
12 | import androidx.compose.ui.Modifier
13 | import androidx.compose.ui.graphics.Color
14 | import androidx.compose.ui.graphics.graphicsLayer
15 | import androidx.compose.ui.text.TextStyle
16 | import androidx.compose.ui.text.font.FontStyle
17 | import androidx.compose.ui.text.style.TextGeometricTransform
18 | import androidx.compose.ui.unit.dp
19 | import androidx.compose.ui.unit.sp
20 |
21 | @Composable
22 | fun HappyNewYearText() {
23 | val infiniteTransition = rememberInfiniteTransition()
24 | val animationRatio by infiniteTransition.animateFloat(
25 | initialValue = 0f,
26 | targetValue = 1f,
27 | animationSpec = infiniteRepeatable(
28 | animation = keyframes {
29 | durationMillis = 1000
30 | 0.2f at 300
31 | 0.8f at 700
32 | },
33 | repeatMode = RepeatMode.Reverse
34 | )
35 | )
36 |
37 | Box(Modifier.fillMaxSize().padding(top = 30.dp)) {
38 | Column(
39 | modifier = Modifier.align(Alignment.TopCenter)
40 | .background(color = Color(0x99000000))
41 | .graphicsLayer {
42 | this.shadowElevation = 5f
43 | }
44 | ) {
45 | Text(
46 | text = "@Composable",
47 | style = TextStyle(
48 | fontStyle = FontStyle.Italic,
49 | color = Color(0xFFffFFff),
50 | fontSize = 36.sp,
51 | textGeometricTransform = TextGeometricTransform(scaleX = 2f, skewX = animationRatio * 10)
52 | )
53 | )
54 | Text(
55 | text = "fun HappyNewYear() {",
56 | style = TextStyle(
57 | fontStyle = FontStyle.Normal,
58 | color = Color(0xFFffFFff),
59 | fontSize = 36.sp,
60 | textGeometricTransform = TextGeometricTransform(scaleX = 2f, skewX = animationRatio * 10)
61 | )
62 | )
63 |
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/happy-new-year/src/commonMain/kotlin/com/usage/ManyChristmasTrees.kt:
--------------------------------------------------------------------------------
1 | package com.usage
2 |
3 | import androidx.compose.runtime.*
4 | import lib.vector.DisplayMode
5 | import lib.vector.Pt
6 | import kotlin.random.Random
7 |
8 | @Composable
9 | fun ManyChristmasTrees() {
10 | val leftX = -3000f
11 | val rightX = 2000f
12 | val pointsCount = 40
13 | val stepWidth = (rightX - leftX) / pointsCount
14 | val speed = 1.3f
15 |
16 | data class TreeData(
17 | val x: Float,
18 | val y: Float,
19 | val color: ULong
20 | )
21 |
22 | var trees: List by remember {
23 | mutableStateOf(
24 | List(pointsCount) {
25 | val x = leftX + it * stepWidth + Random.nextFloat() * stepWidth / 3
26 | val y = Random.nextInt(0, 50).toFloat() + 80
27 | val baseGreen = 0x55
28 | val diffGray = (0..25).random()
29 | val c = (baseGreen + diffGray).toULong()
30 | val color: ULong = 0xff00000000000000uL + (c shl 40)
31 | TreeData(x, y, color)
32 | }.shuffled()
33 | )
34 | }
35 | LaunchedEffect(Unit) {
36 | while (true) {
37 | withFrameNanos { it }
38 | trees = trees
39 | .map { it.copy(x = it.x + speed) }
40 | .toMutableList().apply {
41 | indices.reversed().forEach { i ->
42 | if (this[i].x > rightX) {
43 | val moveMe = removeAt(i)
44 | add(Random.nextInt(0, 4), moveMe.copy(x = leftX))
45 | }
46 | }
47 | }
48 | }
49 | }
50 | trees.forEach { data ->
51 | ChristmasTree(color = data.color, x = data.x, y = data.y)
52 | }
53 | }
54 |
55 | @Composable
56 | fun ChristmasTree(color:ULong = 0xff00990000000000uL, x:Float, y:Float) {
57 | DisplayMode {
58 | val left = 0
59 | val right = 80
60 | val centerX = (right + left) / 2
61 | val top = 240
62 | val bottom = 300
63 | val stepHeight = 40
64 | val stepNarrowWidth = 8
65 | val repeatCount = 3
66 | val trunkWidth = 30
67 | val trunkHeight = 40
68 | val trunkY = bottom + repeatCount - 20
69 | drawRect(
70 | 0xff65432100000000uL,
71 | Pt(x + centerX - trunkWidth / 2, y + trunkY),
72 | Pt(x + centerX + trunkWidth / 2, y + trunkY + trunkHeight)
73 | )
74 | repeat(repeatCount) {
75 | val topPt = Pt(x + centerX, y + top - it * stepHeight)
76 | val bottomLeft = Pt(x + left + it * stepNarrowWidth, y + bottom - it * stepHeight)
77 | val bottomRight = Pt(x + right - it * stepNarrowWidth, y + bottom - it * stepHeight)
78 | drawCurve(color, listOf(topPt, bottomLeft, bottomRight, topPt), mapOf(), fillPath = true)
79 | }
80 |
81 | }
82 |
83 | }
--------------------------------------------------------------------------------
/happy-new-year/src/commonMain/kotlin/com/usage/Snow.kt:
--------------------------------------------------------------------------------
1 | package com.usage
2 |
3 | import androidx.compose.foundation.Canvas
4 | import androidx.compose.foundation.layout.fillMaxSize
5 | import androidx.compose.foundation.layout.wrapContentSize
6 | import androidx.compose.runtime.*
7 | import androidx.compose.ui.Alignment
8 | import androidx.compose.ui.Modifier
9 | import androidx.compose.ui.geometry.Offset
10 | import androidx.compose.ui.graphics.Color
11 | import kotlin.random.Random
12 |
13 | @Composable
14 | fun Snow(speed: Float, snowFlakeSize: Float = 1.0f, count: Int) {
15 | val leftX = -10f
16 | val topY = -10f
17 | val speedX = 1.1f * speed
18 | val speedY = 2.2f * speed
19 | val width = 800 + 20
20 | val height = 800 + 20
21 | val rightX = leftX + width
22 | val bottomY = topY + height
23 |
24 | data class SnowFlake(val size: Float, val x: Float, val y: Float)
25 |
26 | var snowFlakes: List by remember {
27 | mutableStateOf(
28 | List(count) {
29 | SnowFlake(
30 | snowFlakeSize * (1f + 1f * Random.nextFloat()),
31 | Random.nextFloat() * width,
32 | Random.nextFloat() * height
33 | )
34 | }
35 | )
36 | }
37 | LaunchedEffect(Unit) {
38 | while (true) {
39 | withFrameNanos { it }
40 | snowFlakes = snowFlakes
41 | .map { it.copy(x = it.x + speedX, y = it.y + speedY) }
42 | .toMutableList().apply {
43 | indices.reversed().forEach { i ->
44 | if (this[i].x > rightX || this[i].y > bottomY) {
45 | val moveMe = removeAt(i)
46 | if (Random.nextBoolean()) {
47 | add(moveMe.copy(x = leftX, y = topY + height * Random.nextFloat()))
48 | } else {
49 | add(moveMe.copy(x = leftX + width * Random.nextFloat(), y = topY))
50 | }
51 |
52 | }
53 | }
54 | }
55 | }
56 | }
57 |
58 | Canvas(Modifier.wrapContentSize(Alignment.Center).fillMaxSize()) {
59 | snowFlakes.forEach {
60 | drawCircle(Color.White, it.size, Offset(it.x, it.y))
61 | }
62 | }
63 | }
--------------------------------------------------------------------------------
/happy-new-year/src/commonMain/kotlin/com/usage/SnowDrifts.kt:
--------------------------------------------------------------------------------
1 | package com.usage
2 |
3 | import androidx.compose.runtime.*
4 | import lib.vector.DisplayMode
5 | import lib.vector.Pt
6 | import kotlin.random.Random
7 |
8 | @Composable
9 | fun SnowDrifts() {
10 | val SNOW_DRIFT_DY = -25f
11 | val leftX = -3000f
12 | val rightX = 2000f
13 | val pointsCount = 20
14 | val stepWidth = (rightX - leftX) / pointsCount
15 | val speed = 3f
16 |
17 | data class SnowDriftData(
18 | val dx: Float,
19 | val pathPoints: List,
20 | val color: ULong
21 | )
22 |
23 | var snowDrifts: List by remember {
24 | mutableStateOf(
25 | List(pointsCount) {
26 | val x = leftX + it * stepWidth
27 | val pathPoints = listOf(
28 | listOf(Pt(11, 755), Pt(135, 630), Pt(358, 578), Pt(535, 623), Pt(663, 755)),
29 | listOf(Pt(37, 755), Pt(110, 663), Pt(225, 617), Pt(378, 660), Pt(445, 755)),
30 | listOf(Pt(97, 755), Pt(204, 623), Pt(359, 572), Pt(523, 630), Pt(647, 755)),
31 | listOf(Pt(10, 755), Pt(192, 606), Pt(352, 598), Pt(458, 663), Pt(507, 717), Pt(557, 755)),
32 | listOf(Pt(65, 755), Pt(166, 641), Pt(258, 629), Pt(342, 575), Pt(431, 635), Pt(495, 755)),
33 | ).random()
34 |
35 | val baseGray = 195
36 | val diffGray = (0..25).random()
37 | val c = (baseGray + diffGray).toULong()
38 | val color: ULong = 0xff00000000000000uL + (c shl 32) + (c shl 40) + (c shl 48)
39 | SnowDriftData(x, pathPoints, color)
40 | }.shuffled()
41 | )
42 | }
43 | LaunchedEffect(Unit) {
44 | while (true) {
45 | withFrameNanos { it }
46 | snowDrifts = snowDrifts
47 | .map { it.copy(dx = it.dx + speed) }
48 | .toMutableList().apply {
49 | indices.reversed().forEach { i ->
50 | if (this[i].dx > rightX) {
51 | val moveMe = removeAt(i)
52 | add(Random.nextInt(0, 4), moveMe.copy(dx = leftX))
53 | }
54 | }
55 | }
56 | }
57 | }
58 | DisplayMode() {
59 | snowDrifts.forEach { data ->
60 | drawCurve(data.color, data.pathPoints.map { Pt(it.x + data.dx, it.y + SNOW_DRIFT_DY) }, fillPath = true)
61 | }
62 | }
63 | }
--------------------------------------------------------------------------------
/happy-new-year/src/jvmMain/kotlin/com/usage/main.kt:
--------------------------------------------------------------------------------
1 | package com.usage
2 |
3 | import androidx.compose.foundation.ExperimentalFoundationApi
4 | import androidx.compose.foundation.background
5 | import androidx.compose.foundation.layout.Box
6 | import androidx.compose.foundation.layout.fillMaxSize
7 | import androidx.compose.ui.ExperimentalComposeUiApi
8 | import androidx.compose.ui.Modifier
9 | import androidx.compose.ui.graphics.Color
10 | import androidx.compose.ui.input.key.Key
11 | import androidx.compose.ui.input.key.key
12 | import androidx.compose.ui.input.key.onKeyEvent
13 | import androidx.compose.ui.unit.dp
14 | import androidx.compose.ui.window.Window
15 | import androidx.compose.ui.window.application
16 | import androidx.compose.ui.window.rememberWindowState
17 | import kotlinx.coroutines.GlobalScope
18 | import kotlinx.coroutines.flow.collect
19 | import kotlinx.coroutines.launch
20 | import lib.vector.globalKeyListener
21 |
22 | @ExperimentalFoundationApi
23 | @ExperimentalComposeUiApi
24 | fun main() {
25 | application {
26 | Window(
27 | onCloseRequest = ::exitApplication,
28 | state = rememberWindowState(width = 800.dp, height = 750.dp),
29 | onKeyEvent = {
30 | GlobalScope.launch {
31 | globalKeyListener.emit(it.key)
32 | }
33 | false
34 | }
35 | ) {
36 | Box(Modifier.fillMaxSize().background(Color.Black)) {
37 | HappyNewYear()
38 | }
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/happy-new-year/src/jvmMain/kotlin/com/usage/run_simple_window.kt:
--------------------------------------------------------------------------------
1 | package com.usage
2 |
3 | import androidx.compose.foundation.layout.Column
4 | import androidx.compose.runtime.*
5 | import androidx.compose.ui.window.Window
6 | import androidx.compose.ui.window.application
7 | import lib.vector.TxtButton
8 |
9 | fun runSimpleComposableWindow(content: @Composable () -> Unit): Unit {
10 | application {
11 | Window(
12 | onCloseRequest = ::exitApplication,
13 | // state = rememberWindowState(width = 800.dp, height = 800.dp)
14 | ) {
15 | content()
16 | }
17 | }
18 |
19 | }
20 |
21 | fun runSimpleClickerWindow(content: @Composable (clicksCount: Int) -> Unit): Unit {
22 | runSimpleComposableWindow {
23 | var clicksCount by remember { mutableStateOf(0) }
24 | Column {
25 | TxtButton("Increment $clicksCount") {
26 | clicksCount++
27 | }
28 | content(clicksCount)
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/happy-new-year/src/jvmTest/README.md:
--------------------------------------------------------------------------------
1 | variables used in an effect should be added as a parameter of the effect composable, or use rememberUpdatedState.
2 |
3 |
4 | High level
5 | ```Kotlin
6 | val color = animateColorAsState(if (condition) Color.Green else Color.Red)
7 | ```
8 | Low level
9 | ```Kotlin
10 | val color = remember { Animatable(Color.Gray) }
11 | LaunchedEffect(condition) {
12 | color.animateTo(if (condition) Color.Green else Color.Red)
13 | }
14 | ```
15 |
--------------------------------------------------------------------------------
/happy-new-year/src/jvmTest/kotlin/TestAnimations.kt:
--------------------------------------------------------------------------------
1 | import androidx.compose.animation.animateColorAsState
2 | import androidx.compose.animation.core.*
3 | import androidx.compose.material.Text
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.runtime.*
6 | import androidx.compose.ui.Modifier
7 | import androidx.compose.ui.draw.alpha
8 | import androidx.compose.ui.graphics.Color
9 | import com.usage.runSimpleComposableWindow
10 |
11 | @Composable
12 | fun todoAnimations() {
13 | val backgroundColor by animateColorAsState(if (true) Color.Gray else Color.Yellow)
14 |
15 | }
16 |
17 | @Composable
18 | fun todoInfiniteAnimation() {
19 | val infiniteTransition = rememberInfiniteTransition()
20 | val alpha by infiniteTransition.animateFloat(
21 | initialValue = 0f,
22 | targetValue = 1f,
23 | animationSpec = infiniteRepeatable(
24 | animation = keyframes {
25 | durationMillis = 1000
26 | 0.7f at 500
27 | },
28 | repeatMode = RepeatMode.Reverse
29 | )
30 | )
31 | Text("Hello", modifier = Modifier.alpha(alpha))
32 | }
33 |
34 | fun main() {
35 | runSimpleComposableWindow() {
36 |
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/happy-new-year/src/jvmTest/kotlin/TestDerivedStateOf.kt:
--------------------------------------------------------------------------------
1 | import androidx.compose.runtime.*
2 |
3 | @Composable
4 | fun TestDerivedStateOf() {
5 | val myMutableState: MutableState = remember { mutableStateOf("State") }
6 | val myUpdatedState: State = remember {
7 | derivedStateOf {
8 | myMutableState.value + " Derived"
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/happy-new-year/src/jvmTest/kotlin/TestDisney.kt:
--------------------------------------------------------------------------------
1 | import androidx.compose.animation.core.FastOutLinearInEasing
2 | import androidx.compose.animation.core.animateFloatAsState
3 | import androidx.compose.animation.core.tween
4 | import androidx.compose.foundation.background
5 | import androidx.compose.foundation.layout.Box
6 | import androidx.compose.foundation.layout.Row
7 | import androidx.compose.foundation.layout.fillMaxSize
8 | import androidx.compose.foundation.layout.size
9 | import androidx.compose.runtime.*
10 | import androidx.compose.ui.Alignment
11 | import androidx.compose.ui.Modifier
12 | import androidx.compose.ui.draw.drawBehind
13 | import androidx.compose.ui.graphics.Brush
14 | import androidx.compose.ui.graphics.Color
15 | import androidx.compose.ui.graphics.StrokeCap
16 | import androidx.compose.ui.graphics.drawscope.Stroke
17 | import androidx.compose.ui.unit.dp
18 | import com.usage.runSimpleComposableWindow
19 |
20 | fun main() = runSimpleComposableWindow {
21 | DisneyLogoAnimation()
22 | }
23 |
24 | @Composable
25 | fun DisneyLogoAnimation() {
26 | val sweepAngle = 135f
27 | val animationDuration = 1000
28 | val plusAnimationDuration = 300
29 | val animationDelay = 100
30 | var animationPlayed by remember {
31 | mutableStateOf(false)
32 | }
33 | var plusAnimationPlayed by remember {
34 | mutableStateOf(false)
35 | }
36 |
37 | val currentPercent = animateFloatAsState(
38 | targetValue = if (animationPlayed) sweepAngle else 0f,
39 | animationSpec = tween(
40 | durationMillis = animationDuration,
41 | delayMillis = animationDelay,
42 | easing = FastOutLinearInEasing
43 | ),
44 | finishedListener = {
45 | plusAnimationPlayed = true
46 | }
47 | )
48 |
49 | val scalePercent = animateFloatAsState(
50 | targetValue = if (plusAnimationPlayed) 1f else 0f,
51 | animationSpec = tween(
52 | durationMillis = plusAnimationDuration,
53 | delayMillis = 0
54 | )
55 | )
56 |
57 | LaunchedEffect(key1 = true) {
58 | animationPlayed = true
59 | }
60 |
61 | Box(
62 | modifier = Modifier
63 | .fillMaxSize()
64 | .background(Background),
65 | contentAlignment = Alignment.Center
66 | ) {
67 | Box(modifier = Modifier
68 | .size(200.dp)
69 | .drawBehind {
70 | drawArc(
71 | brush = Brush.linearGradient(
72 | 0f to GradientColor1,
73 | 0.2f to GradientColor2,
74 | 0.35f to GradientColor3,
75 | 0.45f to GradientColor4,
76 | 0.75f to GradientColor5,
77 | ),
78 | startAngle = -152f,
79 | sweepAngle = currentPercent.value,
80 | useCenter = false,
81 | style = Stroke(width = 10f, cap = StrokeCap.Round)
82 | )
83 | }) { }
84 | Row {
85 | // Image(
86 | // painter = painterResource(id = R.drawable.ic_disney_logo_text),
87 | // contentDescription = "Disney Logo Text",
88 | // colorFilter = ColorFilter.tint(Color.White),
89 | // modifier = Modifier.size(200.dp)
90 | // )
91 | // Image(
92 | // painter = painterResource(id = R.drawable.ic_plus),
93 | // contentDescription = "Plus Image",
94 | // colorFilter = ColorFilter.tint(Color.White),
95 | // modifier = Modifier
96 | // .size(50.dp)
97 | // .align(Alignment.CenterVertically)
98 | // .scale(scalePercent.value)
99 | // )
100 | }
101 | }
102 | }
103 |
104 | private val Purple200 = Color(0xFFBB86FC)
105 | private val Purple500 = Color(0xFF6200EE)
106 | private val Purple700 = Color(0xFF3700B3)
107 | private val Teal200 = Color(0xFF03DAC5)
108 | private val Background = Color(0xFF111D52)
109 | private val GradientColor1 = Color(0xFF0E1956)
110 | private val GradientColor2 = Color(0xFF092474)
111 | private val GradientColor3 = Color(0xFF0170B6)
112 | private val GradientColor4 = Color(0xFF19FAFF)
113 | private val GradientColor5 = Color(0xFFFDFFF8)
114 |
--------------------------------------------------------------------------------
/happy-new-year/src/jvmTest/kotlin/TestDisposableEffect.kt:
--------------------------------------------------------------------------------
1 | import androidx.compose.foundation.layout.Column
2 | import androidx.compose.material.Button
3 | import androidx.compose.material.Text
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.runtime.*
6 | import com.usage.runSimpleComposableWindow
7 |
8 | @Composable
9 | fun TestDisposableEffect(someArg:Int) {
10 | Text("TestDisposableEffect $someArg")
11 | DisposableEffect(key1 = someArg, key2 = Unit) {
12 | // lifecycle.addObserver(lifecycleObserver)
13 | println("add subsciption")
14 | onDispose {
15 | println("remove subsciption")
16 | // lifecycle.removeObserver(lifecycleObserver)
17 | }
18 | }
19 | }
20 |
21 | fun main() = runSimpleComposableWindow {
22 | var counter by remember { mutableStateOf(0) }
23 | Column {
24 | TestDisposableEffect(counter)
25 | Button(onClick = {
26 | counter++
27 | }) {
28 | Text("click $counter")
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/happy-new-year/src/jvmTest/kotlin/TestMutableStateListOf.kt:
--------------------------------------------------------------------------------
1 | import androidx.compose.runtime.*
2 |
3 | @Composable
4 | fun TestMutableStateListOf() {
5 | var someIndex by remember { mutableStateOf(0) }
6 | val listState = mutableStateListOf("a", "b", "c")
7 | listState[someIndex] = "aa"
8 | }
9 |
--------------------------------------------------------------------------------
/happy-new-year/src/jvmTest/kotlin/TestPointerInput.kt:
--------------------------------------------------------------------------------
1 | import androidx.compose.animation.core.Animatable
2 | import androidx.compose.animation.core.calculateTargetValue
3 | import androidx.compose.animation.splineBasedDecay
4 | import androidx.compose.foundation.background
5 | import androidx.compose.foundation.gestures.awaitFirstDown
6 | import androidx.compose.foundation.gestures.horizontalDrag
7 | import androidx.compose.foundation.layout.Box
8 | import androidx.compose.foundation.layout.offset
9 | import androidx.compose.foundation.layout.size
10 | import androidx.compose.material.Text
11 | import androidx.compose.runtime.remember
12 | import androidx.compose.ui.Modifier
13 | import androidx.compose.ui.composed
14 | import androidx.compose.ui.graphics.Color
15 | import androidx.compose.ui.input.pointer.consumePositionChange
16 | import androidx.compose.ui.input.pointer.pointerInput
17 | import androidx.compose.ui.input.pointer.positionChange
18 | import androidx.compose.ui.input.pointer.util.VelocityTracker
19 | import androidx.compose.ui.unit.IntOffset
20 | import androidx.compose.ui.unit.dp
21 | import com.usage.runSimpleComposableWindow
22 | import kotlinx.coroutines.coroutineScope
23 | import kotlinx.coroutines.launch
24 | import kotlin.math.absoluteValue
25 | import kotlin.math.roundToInt
26 |
27 | private fun Modifier.swipeToDismiss(
28 | onDismissed: () -> Unit
29 | ): Modifier = composed {
30 | // This `Animatable` stores the horizontal offset for the element.
31 | val offsetX = remember { Animatable(0f) }
32 | pointerInput(Unit) {
33 | // Used to calculate a settling position of a fling animation.
34 | val decay = splineBasedDecay(this)
35 | // Wrap in a coroutine scope to use suspend functions for touch events and animation.
36 | coroutineScope {
37 | while (true) {
38 | // Wait for a touch down event.
39 | val pointerId = awaitPointerEventScope { awaitFirstDown().id }
40 | // Interrupt any ongoing animation.
41 | offsetX.stop()
42 | // Prepare for drag events and record velocity of a fling.
43 | val velocityTracker = VelocityTracker()
44 | // Wait for drag events.
45 | awaitPointerEventScope {
46 | horizontalDrag(pointerId) { change ->
47 | // Record the position after offset
48 | val horizontalDragOffset = offsetX.value + change.positionChange().x
49 | launch {
50 | // Overwrite the `Animatable` value while the element is dragged.
51 | offsetX.snapTo(horizontalDragOffset)
52 | }
53 | // Record the velocity of the drag.
54 | velocityTracker.addPosition(change.uptimeMillis, change.position)
55 | // Consume the gesture event, not passed to external
56 | change.consumePositionChange()
57 | }
58 | }
59 | // Dragging finished. Calculate the velocity of the fling.
60 | val velocity = velocityTracker.calculateVelocity().x
61 | // Calculate where the element eventually settles after the fling animation.
62 | val targetOffsetX = decay.calculateTargetValue(offsetX.value, velocity)
63 | // The animation should end as soon as it reaches these bounds.
64 | offsetX.updateBounds(
65 | lowerBound = -size.width.toFloat(),
66 | upperBound = size.width.toFloat()
67 | )
68 | launch {
69 | if (targetOffsetX.absoluteValue <= size.width) {
70 | // Not enough velocity; Slide back to the default position.
71 | offsetX.animateTo(targetValue = 0f, initialVelocity = velocity)
72 | } else {
73 | // Enough velocity to slide away the element to the edge.
74 | offsetX.animateDecay(velocity, decay)
75 | // The element was swiped away.
76 | onDismissed()
77 | }
78 | }
79 | }
80 | }
81 | }
82 | // Apply the horizontal offset to the element.
83 | .offset { IntOffset(offsetX.value.roundToInt(), 0) }
84 | }
85 |
86 | fun main() {
87 | runSimpleComposableWindow() {
88 | Box(
89 | Modifier.size(200.dp, 50.dp)
90 | .background(Color.Gray)
91 | .swipeToDismiss {
92 | println("swiped")
93 | }
94 | ) {
95 | Text("Swipe me")
96 | }
97 | }
98 | }
99 |
100 |
--------------------------------------------------------------------------------
/happy-new-year/src/jvmTest/kotlin/TestProduceState.kt:
--------------------------------------------------------------------------------
1 | import androidx.compose.runtime.Composable
2 | import androidx.compose.runtime.State
3 | import androidx.compose.runtime.produceState
4 | import kotlinx.coroutines.delay
5 |
6 | //Composables with a return type should be named the way you'd name a normal Kotlin function,
7 | // starting with a lowercase letter.
8 | @Composable
9 | fun testProduceState(): State {
10 | val key = Unit
11 | return produceState("Init", key) {
12 | value = "Second"
13 | delay(1)
14 | value = "Third"
15 | awaitDispose {
16 | println("on dispose")
17 | } // return's Nothing
18 | println("Unreachable code")
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/happy-new-year/src/jvmTest/kotlin/TestRecomposition.kt:
--------------------------------------------------------------------------------
1 | package com.usage
2 |
3 | import androidx.compose.foundation.layout.Column
4 | import androidx.compose.material.Text
5 | import androidx.compose.runtime.mutableStateOf
6 | import androidx.compose.runtime.*
7 | import androidx.compose.ui.unit.dp
8 | import androidx.compose.ui.window.Window
9 | import androidx.compose.ui.window.application
10 | import androidx.compose.ui.window.rememberWindowState
11 | import lib.vector.TxtButton
12 | import kotlin.random.Random
13 |
14 | fun main() {
15 | runSimpleComposableWindow {
16 | Column {
17 | println("recompose main")
18 | var counter by remember { mutableStateOf(0) }
19 | NotRecomposed(counter / 5) {
20 | println("lambda")
21 | }
22 | TxtButton("Increment $counter") {
23 | counter++
24 | }
25 | }
26 | }
27 | }
28 |
29 | @Composable
30 | fun NotRecomposed(arg1:Int, lambda: () -> Unit) {
31 | println("recompose NotRecomposed, arg1: $arg1")
32 | lambda()
33 | Text("NotRecomposed + ${Random.nextInt()}")
34 | }
35 |
--------------------------------------------------------------------------------
/happy-new-year/src/jvmTest/kotlin/TestRecompositionKey.kt:
--------------------------------------------------------------------------------
1 | import androidx.compose.foundation.layout.Column
2 | import androidx.compose.material.Text
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.key
5 | import com.usage.runSimpleComposableWindow
6 |
7 | private data class Movie(
8 | val id: Int,
9 | val name: String
10 | )
11 |
12 | @Composable
13 | private fun MoviesScreen(movies: List) {
14 | Column {
15 | for (movie in movies) {
16 | key(movie.id) { // Unique ID for this movie
17 | MovieOverview(movie)
18 | }
19 | }
20 | }
21 | }
22 |
23 | @Composable
24 | private fun MovieOverview(movie: Movie) {
25 | Text("Movie ${movie.name}")
26 | }
27 |
28 | fun main() {
29 | runSimpleComposableWindow {
30 | MoviesScreen(
31 | listOf(
32 | Movie(1, "Godzila"),
33 | Movie(2, "Kong"),
34 | )
35 | )
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/happy-new-year/src/jvmTest/kotlin/TestRememberUpdatedState.kt:
--------------------------------------------------------------------------------
1 | import androidx.compose.foundation.layout.Box
2 | import androidx.compose.foundation.layout.fillMaxSize
3 | import androidx.compose.runtime.*
4 | import androidx.compose.ui.Alignment
5 | import androidx.compose.ui.Modifier
6 | import kotlinx.coroutines.delay
7 |
8 | @Composable
9 | fun LandingScreen(modifier: Modifier = Modifier, onTimeout: () -> Unit) {
10 | Box(modifier = modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
11 | // This will always refer to the latest onTimeout function that
12 | // LandingScreen was recomposed with
13 | val currentOnTimeout by rememberUpdatedState(onTimeout) // IMPORTANT HERE
14 |
15 | // Create an effect that matches the lifecycle of LandingScreen.
16 | // If LandingScreen recomposes or onTimeout changes,
17 | // the delay shouldn't start again.
18 | LaunchedEffect(true) {
19 | delay(500)
20 | currentOnTimeout()
21 | }
22 |
23 | // Image(painterResource(id = R.drawable.ic_crane_drawer), contentDescription = null)
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/happy-new-year/src/jvmTest/kotlin/TestSimpleSideEffect.kt:
--------------------------------------------------------------------------------
1 |
2 | import androidx.compose.material.Text
3 | import androidx.compose.runtime.SideEffect
4 | import com.usage.runSimpleClickerWindow
5 |
6 |
7 | fun main() = runSimpleClickerWindow { clicksCount ->
8 | Text("clicksCount: $clicksCount")
9 | SideEffect {
10 | //On every successful composition
11 | println("side effect, clicksCount: $clicksCount")
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/happy-new-year/src/jvmTest/kotlin/TestSnapshotFlow.kt:
--------------------------------------------------------------------------------
1 | import androidx.compose.foundation.layout.Column
2 | import androidx.compose.runtime.*
3 | import com.usage.runSimpleComposableWindow
4 | import kotlinx.coroutines.InternalCoroutinesApi
5 | import kotlinx.coroutines.flow.FlowCollector
6 | import kotlinx.coroutines.flow.collect
7 | import kotlinx.coroutines.flow.filter
8 | import lib.vector.TxtButton
9 |
10 | @OptIn(InternalCoroutinesApi::class)
11 | @Composable
12 | fun testSnapshotFlow() {
13 | val someState: MutableState = remember { mutableStateOf(1) }
14 | LaunchedEffect(Unit) {
15 | snapshotFlow { someState.value }
16 | .filter { it % 2 == 0 }
17 | .collect(object : FlowCollector {
18 | override suspend fun emit(value: Int) {
19 | println("collect $value")
20 | }
21 | })
22 | }
23 |
24 | }
25 |
26 | fun main() {
27 | runSimpleComposableWindow {
28 | var clicksCount by remember { mutableStateOf(0) }
29 | Column {
30 | TxtButton("Increment $clicksCount") {
31 | clicksCount++
32 | }
33 | }
34 | LaunchedEffect(Unit) {
35 | snapshotFlow { clicksCount }
36 | .filter { it % 2 == 0 }
37 | .collect {
38 | println("flow $it")
39 | }
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/happy-new-year/src/jvmTest/kotlin/TestTouchInput.kt:
--------------------------------------------------------------------------------
1 | import androidx.compose.animation.core.Animatable
2 | import androidx.compose.animation.core.VectorConverter
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.gestures.awaitFirstDown
5 | import androidx.compose.foundation.layout.Box
6 | import androidx.compose.foundation.layout.fillMaxSize
7 | import androidx.compose.foundation.layout.offset
8 | import androidx.compose.foundation.layout.size
9 | import androidx.compose.foundation.shape.CircleShape
10 | import androidx.compose.material.Text
11 | import androidx.compose.runtime.Composable
12 | import androidx.compose.runtime.remember
13 | import androidx.compose.ui.Alignment
14 | import androidx.compose.ui.Modifier
15 | import androidx.compose.ui.geometry.Offset
16 | import androidx.compose.ui.graphics.Color
17 | import androidx.compose.ui.input.pointer.pointerInput
18 | import androidx.compose.ui.unit.IntOffset
19 | import androidx.compose.ui.unit.dp
20 | import com.usage.runSimpleComposableWindow
21 | import kotlinx.coroutines.coroutineScope
22 | import kotlinx.coroutines.launch
23 | import kotlin.math.roundToInt
24 |
25 | fun main() {
26 | runSimpleComposableWindow {
27 | MoveBoxWhereTapped()
28 | }
29 | }
30 |
31 | @Composable
32 | fun MoveBoxWhereTapped() {
33 | // Creates an `Animatable` to animate Offset and `remember` it.
34 | val animatedOffset = remember {
35 | Animatable(Offset(0f, 0f), androidx.compose.ui.geometry.Offset.VectorConverter)
36 | }
37 |
38 | Box(
39 | // The pointerInput modifier takes a suspend block of code
40 | Modifier.fillMaxSize().pointerInput(Unit) {
41 | // Create a new CoroutineScope to be able to create new
42 | // coroutines inside a suspend function
43 | coroutineScope {
44 | while (true) {
45 | // Wait for the user to tap on the screen
46 | val offset = awaitPointerEventScope {
47 | awaitFirstDown().position
48 | }
49 | // Launch a new coroutine to asynchronously animate to where
50 | // the user tapped on the screen
51 | launch {
52 | // Animate to the pressed position
53 | animatedOffset.animateTo(offset)
54 | }
55 | }
56 | }
57 | }
58 | ) {
59 | Text("Tap anywhere", Modifier.align(Alignment.Center))
60 | Box(
61 | Modifier
62 | .offset {
63 | // Use the animated offset as the offset of this Box
64 | IntOffset(
65 | animatedOffset.value.x.roundToInt(),
66 | animatedOffset.value.y.roundToInt()
67 | )
68 | }
69 | .size(40.dp)
70 | .background(Color(0xff3c1361), CircleShape)
71 | )
72 | }
73 | }
74 |
75 |
--------------------------------------------------------------------------------
/lib/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.android.library")
3 | kotlin("multiplatform") // kotlin("jvm") doesn't work well in IDEA/AndroidStudio (https://github.com/JetBrains/compose-jb/issues/22)
4 | id("org.jetbrains.compose")
5 | }
6 |
7 | kotlin {
8 | jvm()
9 | android()
10 | sourceSets {
11 | named("commonMain") {
12 | dependencies {
13 | api(compose.runtime)
14 | api(compose.foundation)
15 | api(compose.material)
16 | }
17 | }
18 | named("jvmMain") {
19 | dependencies {
20 | implementation(project(":clipboard"))
21 | implementation(compose.desktop.currentOs)
22 | implementation("com.squareup:kotlinpoet:1.10.2")
23 | }
24 | }
25 | named("androidMain") {
26 | dependencies {
27 | api("androidx.appcompat:appcompat:1.3.1")//todo move to buildSrc
28 | api("androidx.core:core-ktx:1.3.1")
29 | }
30 | }
31 | named("jvmTest") {
32 | dependencies {
33 | implementation(kotlin("test"))
34 | }
35 | }
36 | }
37 | }
38 |
39 | android {
40 | compileSdk = 31
41 |
42 | defaultConfig {
43 | minSdk = 21
44 | targetSdk = 31
45 | }
46 |
47 | compileOptions {
48 | sourceCompatibility = JavaVersion.VERSION_1_8
49 | targetCompatibility = JavaVersion.VERSION_1_8
50 | }
51 |
52 | sourceSets {
53 | named("main") {
54 | manifest.srcFile("src/androidMain/AndroidManifest.xml")
55 | res.srcDirs("src/androidMain/res")
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/lib/src/androidMain/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/lib/src/androidMain/kotlin/lib/vector/GeneratedLayer.kt:
--------------------------------------------------------------------------------
1 | package lib.vector
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.ui.Modifier
5 | import lib.vector.DisplayMode
6 | import lib.vector.GeneratedScope
7 |
8 | @Composable
9 | actual fun GeneratedLayer(modifier: Modifier, lambda: GeneratedScope.() -> Unit) {
10 | DisplayMode(modifier, lambda = lambda)
11 | }
12 |
--------------------------------------------------------------------------------
/lib/src/androidMain/kotlin/lib/vector/utils/utils_android.kt:
--------------------------------------------------------------------------------
1 | package lib.vector.utils
2 |
3 | import android.graphics.Bitmap
4 | import android.graphics.BitmapFactory
5 | import androidx.compose.ui.graphics.ImageBitmap
6 | import androidx.compose.ui.graphics.asAndroidBitmap
7 | import androidx.compose.ui.graphics.asImageBitmap
8 | import java.io.ByteArrayOutputStream
9 |
10 | actual fun ImageBitmap.toByteArray(): ByteArray = asAndroidBitmap().toByteArray()
11 | actual fun ByteArray.toImageBitmap(): ImageBitmap = toAndroidBitmap().asImageBitmap()
12 |
13 | fun Bitmap.toByteArray(): ByteArray {
14 | val baos = ByteArrayOutputStream()
15 | this.compress(Bitmap.CompressFormat.PNG, 100, baos)
16 | return baos.toByteArray()
17 | }
18 |
19 | fun ByteArray.toAndroidBitmap(): Bitmap {
20 | return BitmapFactory.decodeByteArray(this, 0, size);
21 | }
22 |
--------------------------------------------------------------------------------
/lib/src/androidMain/res/mipmap-anydpi-v26/ic_imageviewer.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/lib/src/androidMain/res/mipmap-anydpi-v26/ic_imageviewer_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/lib/src/androidMain/res/mipmap-hdpi/ic_imageviewer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avdim/compose-vector/e507f6946d389e8e320b8db736319d783596516f/lib/src/androidMain/res/mipmap-hdpi/ic_imageviewer.png
--------------------------------------------------------------------------------
/lib/src/androidMain/res/mipmap-hdpi/ic_imageviewer_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avdim/compose-vector/e507f6946d389e8e320b8db736319d783596516f/lib/src/androidMain/res/mipmap-hdpi/ic_imageviewer_background.png
--------------------------------------------------------------------------------
/lib/src/androidMain/res/mipmap-hdpi/ic_imageviewer_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avdim/compose-vector/e507f6946d389e8e320b8db736319d783596516f/lib/src/androidMain/res/mipmap-hdpi/ic_imageviewer_foreground.png
--------------------------------------------------------------------------------
/lib/src/androidMain/res/mipmap-hdpi/ic_imageviewer_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avdim/compose-vector/e507f6946d389e8e320b8db736319d783596516f/lib/src/androidMain/res/mipmap-hdpi/ic_imageviewer_round.png
--------------------------------------------------------------------------------
/lib/src/androidMain/res/mipmap-mdpi/ic_imageviewer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avdim/compose-vector/e507f6946d389e8e320b8db736319d783596516f/lib/src/androidMain/res/mipmap-mdpi/ic_imageviewer.png
--------------------------------------------------------------------------------
/lib/src/androidMain/res/mipmap-mdpi/ic_imageviewer_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avdim/compose-vector/e507f6946d389e8e320b8db736319d783596516f/lib/src/androidMain/res/mipmap-mdpi/ic_imageviewer_background.png
--------------------------------------------------------------------------------
/lib/src/androidMain/res/mipmap-mdpi/ic_imageviewer_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avdim/compose-vector/e507f6946d389e8e320b8db736319d783596516f/lib/src/androidMain/res/mipmap-mdpi/ic_imageviewer_foreground.png
--------------------------------------------------------------------------------
/lib/src/androidMain/res/mipmap-mdpi/ic_imageviewer_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avdim/compose-vector/e507f6946d389e8e320b8db736319d783596516f/lib/src/androidMain/res/mipmap-mdpi/ic_imageviewer_round.png
--------------------------------------------------------------------------------
/lib/src/androidMain/res/mipmap-xhdpi/ic_imageviewer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avdim/compose-vector/e507f6946d389e8e320b8db736319d783596516f/lib/src/androidMain/res/mipmap-xhdpi/ic_imageviewer.png
--------------------------------------------------------------------------------
/lib/src/androidMain/res/mipmap-xhdpi/ic_imageviewer_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avdim/compose-vector/e507f6946d389e8e320b8db736319d783596516f/lib/src/androidMain/res/mipmap-xhdpi/ic_imageviewer_background.png
--------------------------------------------------------------------------------
/lib/src/androidMain/res/mipmap-xhdpi/ic_imageviewer_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avdim/compose-vector/e507f6946d389e8e320b8db736319d783596516f/lib/src/androidMain/res/mipmap-xhdpi/ic_imageviewer_foreground.png
--------------------------------------------------------------------------------
/lib/src/androidMain/res/mipmap-xhdpi/ic_imageviewer_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avdim/compose-vector/e507f6946d389e8e320b8db736319d783596516f/lib/src/androidMain/res/mipmap-xhdpi/ic_imageviewer_round.png
--------------------------------------------------------------------------------
/lib/src/androidMain/res/mipmap-xxhdpi/ic_imageviewer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avdim/compose-vector/e507f6946d389e8e320b8db736319d783596516f/lib/src/androidMain/res/mipmap-xxhdpi/ic_imageviewer.png
--------------------------------------------------------------------------------
/lib/src/androidMain/res/mipmap-xxhdpi/ic_imageviewer_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avdim/compose-vector/e507f6946d389e8e320b8db736319d783596516f/lib/src/androidMain/res/mipmap-xxhdpi/ic_imageviewer_background.png
--------------------------------------------------------------------------------
/lib/src/androidMain/res/mipmap-xxhdpi/ic_imageviewer_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avdim/compose-vector/e507f6946d389e8e320b8db736319d783596516f/lib/src/androidMain/res/mipmap-xxhdpi/ic_imageviewer_foreground.png
--------------------------------------------------------------------------------
/lib/src/androidMain/res/mipmap-xxhdpi/ic_imageviewer_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avdim/compose-vector/e507f6946d389e8e320b8db736319d783596516f/lib/src/androidMain/res/mipmap-xxhdpi/ic_imageviewer_round.png
--------------------------------------------------------------------------------
/lib/src/androidMain/res/mipmap-xxxhdpi/ic_imageviewer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avdim/compose-vector/e507f6946d389e8e320b8db736319d783596516f/lib/src/androidMain/res/mipmap-xxxhdpi/ic_imageviewer.png
--------------------------------------------------------------------------------
/lib/src/androidMain/res/mipmap-xxxhdpi/ic_imageviewer_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avdim/compose-vector/e507f6946d389e8e320b8db736319d783596516f/lib/src/androidMain/res/mipmap-xxxhdpi/ic_imageviewer_background.png
--------------------------------------------------------------------------------
/lib/src/androidMain/res/mipmap-xxxhdpi/ic_imageviewer_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avdim/compose-vector/e507f6946d389e8e320b8db736319d783596516f/lib/src/androidMain/res/mipmap-xxxhdpi/ic_imageviewer_foreground.png
--------------------------------------------------------------------------------
/lib/src/androidMain/res/mipmap-xxxhdpi/ic_imageviewer_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avdim/compose-vector/e507f6946d389e8e320b8db736319d783596516f/lib/src/androidMain/res/mipmap-xxxhdpi/ic_imageviewer_round.png
--------------------------------------------------------------------------------
/lib/src/androidMain/res/raw/back.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avdim/compose-vector/e507f6946d389e8e320b8db736319d783596516f/lib/src/androidMain/res/raw/back.png
--------------------------------------------------------------------------------
/lib/src/androidMain/res/raw/blur_off.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avdim/compose-vector/e507f6946d389e8e320b8db736319d783596516f/lib/src/androidMain/res/raw/blur_off.png
--------------------------------------------------------------------------------
/lib/src/androidMain/res/raw/blur_on.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avdim/compose-vector/e507f6946d389e8e320b8db736319d783596516f/lib/src/androidMain/res/raw/blur_on.png
--------------------------------------------------------------------------------
/lib/src/androidMain/res/raw/dots.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avdim/compose-vector/e507f6946d389e8e320b8db736319d783596516f/lib/src/androidMain/res/raw/dots.png
--------------------------------------------------------------------------------
/lib/src/androidMain/res/raw/empty.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avdim/compose-vector/e507f6946d389e8e320b8db736319d783596516f/lib/src/androidMain/res/raw/empty.png
--------------------------------------------------------------------------------
/lib/src/androidMain/res/raw/filter_unknown.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avdim/compose-vector/e507f6946d389e8e320b8db736319d783596516f/lib/src/androidMain/res/raw/filter_unknown.png
--------------------------------------------------------------------------------
/lib/src/androidMain/res/raw/grayscale_off.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avdim/compose-vector/e507f6946d389e8e320b8db736319d783596516f/lib/src/androidMain/res/raw/grayscale_off.png
--------------------------------------------------------------------------------
/lib/src/androidMain/res/raw/grayscale_on.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avdim/compose-vector/e507f6946d389e8e320b8db736319d783596516f/lib/src/androidMain/res/raw/grayscale_on.png
--------------------------------------------------------------------------------
/lib/src/androidMain/res/raw/pixel_off.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avdim/compose-vector/e507f6946d389e8e320b8db736319d783596516f/lib/src/androidMain/res/raw/pixel_off.png
--------------------------------------------------------------------------------
/lib/src/androidMain/res/raw/pixel_on.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avdim/compose-vector/e507f6946d389e8e320b8db736319d783596516f/lib/src/androidMain/res/raw/pixel_on.png
--------------------------------------------------------------------------------
/lib/src/androidMain/res/raw/refresh.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/avdim/compose-vector/e507f6946d389e8e320b8db736319d783596516f/lib/src/androidMain/res/raw/refresh.png
--------------------------------------------------------------------------------
/lib/src/androidMain/res/values-ru/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | ImageViewer
4 | Загружаем изображения...
5 | Репозиторий пуст.
6 | Нет доступа в интернет.
7 | Список изображений в репозитории пуст или имеет неверный формат.
8 | Невозможно обновить изображения.
9 | Невозможно загузить полное изображение.
10 | Это последнее изображение.
11 | Это первое изображение.
12 | Изображение:
13 | Размеры:
14 | пикселей.
15 |
--------------------------------------------------------------------------------
/lib/src/androidMain/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | ImageViewer
3 | Loading images...
4 | Repository is empty.
5 | No internet access.
6 | List of images in current repository is invalid or empty.
7 | Cannot refresh images.
8 | Cannot load full size image.
9 | This is last image.
10 | This is first image.
11 | Picture:
12 | Size:
13 | pixels.
14 |
--------------------------------------------------------------------------------
/lib/src/commonMain/kotlin/lib/vector/DisplayMode.kt:
--------------------------------------------------------------------------------
1 | package lib.vector
2 |
3 | import androidx.compose.foundation.Canvas
4 | import androidx.compose.foundation.layout.fillMaxSize
5 | import androidx.compose.foundation.layout.wrapContentSize
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.ui.Alignment
8 | import androidx.compose.ui.Modifier
9 | import androidx.compose.ui.geometry.Offset
10 | import androidx.compose.ui.graphics.Color
11 | import androidx.compose.ui.graphics.Path
12 | import androidx.compose.ui.graphics.drawscope.*
13 | import lib.vector.utils.toImageBitmap
14 |
15 | const val DEFAULT_BEZIER_SCALE = 0.5f
16 | const val FILL_PATH = true
17 |
18 | @Composable
19 | fun DisplayMode(modifier: Modifier = Modifier, lambda: GeneratedScope.() -> Unit) {
20 | Canvas(modifier.wrapContentSize(Alignment.Center).fillMaxSize()) {
21 | val generatedScope = object : GeneratedScope {
22 | override fun mkPt(x: Float, y: Float): MakePt = MakePt { _, _ -> Pt(x, y) }
23 |
24 | override fun drawCurve(color: ULong, points: List, bezierRef: Map, fillPath:Boolean) {
25 | if (points.isNotEmpty()) {
26 | drawPath(
27 | path = Path().apply {
28 | val start = points[0]
29 | moveTo(start.x, start.y)
30 | points.toLineSegments().forEach { s ->
31 | val result = s.bezierSegment(bezierRef[s.start]?.startRef, bezierRef[s.end]?.endRef)
32 | with(result) {
33 | cubicTo(refStart.x, refStart.y, refEnd.x, refEnd.y, end.x, end.y)
34 | // lineTo(to.x, to.y)
35 | }
36 | }
37 | },
38 | color = Color(color),
39 | style = if (fillPath) Fill else Stroke(width = 2f)
40 | )
41 | }
42 | }
43 |
44 | override fun drawRect(color: ULong, start: Pt, end: Pt) {
45 | // rotate(degrees = 0f, pivot = start.offset) {
46 | drawRect(
47 | color = Color(color),
48 | topLeft = Offset(minOf(start.x, end.x), minOf(start.y, end.y)),
49 | size = (end - start).size
50 | )
51 | // }
52 | }
53 |
54 | override fun drawBitmap(pt: Pt, byteArray: ByteArray) {
55 | // scale(1f, pivot = pt.offset) {
56 | drawImage(image = byteArray.toImageBitmap(), topLeft = pt.offset)
57 | // }
58 | }
59 | }
60 | generatedScope.lambda()
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/lib/src/commonMain/kotlin/lib/vector/GeneratedLayer.kt:
--------------------------------------------------------------------------------
1 | package lib.vector
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.ui.Modifier
5 | import kotlin.jvm.JvmName
6 |
7 | @Composable
8 | expect fun GeneratedLayer(modifier: Modifier, lambda: GeneratedScope.() -> Unit)
9 |
--------------------------------------------------------------------------------
/lib/src/commonMain/kotlin/lib/vector/GeneratedScope.kt:
--------------------------------------------------------------------------------
1 | package lib.vector
2 |
3 | import lib.vector.utils.*
4 | import kotlin.reflect.KProperty
5 |
6 | interface GeneratedScope {
7 | fun mkPt(x: Float, y: Float): MakePt
8 | fun drawCurve(color: ULong, points: List, bezierRef: Map = emptyMap(), fillPath: Boolean = false)
9 | fun drawRect(color: ULong, start: Pt, end: Pt)
10 | fun drawBitmap(pt: Pt, byteArray: ByteArray)
11 |
12 | fun mkPt(x: Int, y: Int): MakePt = mkPt(x.toFloat(), y.toFloat())
13 | fun mkPt(pt:Pt): MakePt = mkPt(pt.x, pt.y)
14 | fun drawBitmap(pt: Pt, base64Str: String) = drawBitmap(pt, base64Str.fromBase64())
15 | }
16 |
17 | fun interface MakePt {
18 | operator fun getValue(noMatter: Any?, property: KProperty<*>): Pt
19 | }
20 |
--------------------------------------------------------------------------------
/lib/src/commonMain/kotlin/lib/vector/TxtButton.kt:
--------------------------------------------------------------------------------
1 | package lib.vector
2 |
3 | import androidx.compose.material.Button
4 | import androidx.compose.material.Text
5 | import androidx.compose.runtime.Composable
6 |
7 | @Composable
8 | fun TxtButton(text: String, onClick: () -> Unit) {
9 | Button(onClick = onClick) {
10 | Text(text)
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/lib/src/commonMain/kotlin/lib/vector/bezier.kt:
--------------------------------------------------------------------------------
1 | package lib.vector
2 |
3 | import androidx.compose.ui.graphics.Path
4 |
5 | data class BezierSegment(val start: Pt, val end: Pt, val refStart: Pt, val refEnd: Pt)
6 |
7 | val BezierSegment.p1 get():Pt = start
8 | val BezierSegment.p2 get():Pt = refStart
9 | val BezierSegment.p3 get():Pt = refEnd
10 | val BezierSegment.p4 get():Pt = end
11 |
12 | fun LineSegment.bezierSegment(startRef: Pt?, endRef: Pt?): BezierSegment {
13 | val defaultBezierReferences by lazy(mode = LazyThreadSafetyMode.NONE) {
14 | calcDefaultBezierReferences(before = before, start = start, end = end, after = after)
15 | }
16 | return BezierSegment(
17 | start = start, end = end, refStart = startRef ?: defaultBezierReferences.refStart, refEnd = endRef ?: defaultBezierReferences.refEnd
18 | )
19 | }
20 |
21 | private class InTriangleResult(val refFrom: Pt, val refTo: Pt)
22 |
23 | private fun calcDefaultBezierInTriangle(a: Pt, b: Pt, c: Pt): InTriangleResult {
24 | val ab = a.distance(b)
25 | val bc = b.distance(c)
26 | val scaleA = ab / (ab + bc + 0.001)
27 | val scaleC = bc / (ab + bc + 0.001)
28 | val direction = (c - a)
29 |
30 | val refFrom =
31 | if (scaleA == 0.0) {
32 | b + direction * scaleC * (DEFAULT_BEZIER_SCALE / 3)
33 | } else {
34 | b + direction * scaleA * DEFAULT_BEZIER_SCALE
35 | }
36 |
37 | val refTo =
38 | if (scaleC == 0.0) {
39 | b - direction * scaleA * (DEFAULT_BEZIER_SCALE / 3)
40 | } else {
41 | b - direction * scaleC * DEFAULT_BEZIER_SCALE
42 | }
43 | return InTriangleResult(
44 | refFrom = refFrom,
45 | refTo = refTo,
46 | )
47 | }
48 |
49 | private class BezierReferences(val refStart: Pt, val refEnd: Pt)
50 |
51 | private fun calcDefaultBezierReferences(before: Pt, start: Pt, end: Pt, after: Pt): BezierReferences {
52 | return BezierReferences(
53 | refStart = calcDefaultBezierInTriangle(before, start, end).refFrom, refEnd = calcDefaultBezierInTriangle(start, end, after).refTo
54 | )
55 | }
56 |
57 | fun BezierSegment.points(count: Int): List = (0..count).map { it.toFloat() / count }.map { point(it) }
58 |
59 | fun BezierSegment.point(t: Float):Pt =
60 | calcBezier3Pt(t, start, refStart, refEnd, end)
61 |
62 | fun calcBezier3Pt(t: Float, p0: Pt, p1: Pt, p2: Pt, p3: Pt): Pt {
63 | val t2 = t * t
64 | val t3 = t2 * t
65 | val mt = 1 - t
66 | val mt2 = mt * mt
67 | val mt3 = mt2 * mt
68 | return p0 * mt3 + 3 * p1 * mt2 * t + 3 * p2 * mt * t2 + p3 * t3
69 | }
70 |
71 | fun calcBezier2Pt(t: Float, p0: Pt, p1: Pt, p2: Pt): Pt {
72 | val t2 = t * t
73 | val mt = 1 - t
74 | val mt2 = mt * mt
75 | return p0 * mt2 + p1 * 2f * mt * t + p2 * t2
76 | }
77 |
78 | fun BezierSegment.point2(t: Float): Pt {
79 | var currentPoints: List = listOf(start, refStart, refEnd, end)
80 | while (currentPoints.size > 1) {
81 | currentPoints = currentPoints.windowed(2).map { (a, b) -> a * (1 - t) + b * t }
82 | }
83 | return currentPoints.first()
84 | }
85 |
86 | // p0(1-t)^3 + p1*3*(1-t)^2*t + p2*3(1-t)t^2 + p3*t^3
87 | // производная в точках p0 и p3 совпадают с оригиналом
88 |
89 | fun BezierSegment.derivative(t: Float): Pt {
90 | //3(B-A), 3(C-B), 3(D-C)
91 | return calcBezier2Pt(t, 3 * (p2 - p1), 3 * (p3 - p2), 3 * (p4 - p3))
92 | }
93 |
94 | fun BezierSegment.subSegment(t1:Float, t2:Float):BezierSegment {
95 | val A = point(t1)
96 | val D = point(t2)
97 |
98 | fun tArrCalc(t:Float): List {
99 | val mt = 1 - t
100 | return listOf(
101 | mt * mt * mt,
102 | 3 * mt * mt * t,
103 | 3 * mt * t * t,
104 | t * t * t,
105 | t1 * mt + t2 * t
106 | )
107 | }
108 | val arr50 = tArrCalc(0.5f)
109 | val arr75 = tArrCalc(0.75f)
110 | // A * arr50[0] + B * arr50[1] + C * arr50[2] + D * arr50[3] = point(arr50[4])
111 | // A * arr75[0] + B * arr75[1] + C * arr75[2] + D * arr75[3] = point(arr75[4])
112 | val up = point(arr75[4]) - A * arr75[0] - D * arr75[3] - (point(arr50[4]) - A * arr50[0] - D * arr50[3]) * arr75[1] / arr50[1]
113 | val down = (arr75[2] - arr50[2] * arr75[1] / arr50[1])
114 | val C = up / down
115 | val B = (point(arr50[4]) - A * arr50[0] - D * arr50[3] - C * arr50[2]) / arr50[1]
116 |
117 | return BezierSegment(
118 | start = A,
119 | end = D,
120 | refStart = B,
121 | refEnd = C
122 | )
123 | }
124 |
125 | fun BezierSegment.split(t: Float): Pair {
126 | // https://pomax.github.io/bezierinfo/index.html#splitting
127 | val left = mutableListOf()
128 | val right = mutableListOf()
129 |
130 | fun drawCurvePoint(points: List, t: Float) {
131 | if (points.size == 1) {
132 | left.add(points.first())
133 | right.add(points.last())
134 | // draw(points[0])
135 | } else if (points.size > 1) {
136 | left.add(points.first())
137 | right.add(points.last())
138 | val newpoints = (0 until (points.size - 1)).map { i ->
139 | (1 - t) * points[i] + t * points[i + 1]
140 | }
141 | drawCurvePoint(newpoints, t)
142 | }
143 | }
144 | drawCurvePoint(listOf(start, refStart, refEnd, end), t)
145 | fun MutableList.toSegment() =
146 | BezierSegment(start = get(0), end = get(3), refStart = get(1), refEnd = get(2))
147 |
148 | return left.toSegment() to right.toSegment()
149 | }
150 |
151 | val BezierSegment.aabb: Rect
152 | get() {
153 | val p1 = start
154 | val p2 = refStart
155 | val p3 = refEnd
156 | val p4 = end
157 | val a = 3 * (3 * p2 - p1 - 3 * p3 + p4)
158 | val b = 6 * (p1 - 2 * p2 + p3)
159 | val c = 3 * (p2 - p1)
160 | val xExtremum = listOf(start.x, end.x) + solveRoots(a.x, b.x, c.x).filter { it > 0 && it < 1 }.map { calcBezier3Pt(it, p1, p2, p3, p4).x }
161 | val yExtremum = listOf(start.y, end.y) + solveRoots(a.y, b.y, c.y).filter { it > 0 && it < 1 }.map { calcBezier3Pt(it, p1, p2, p3, p4).y }
162 | val min = Pt(xExtremum.minOrNull()!!, yExtremum.minOrNull()!!)
163 | val max = Pt(xExtremum.maxOrNull()!!, yExtremum.maxOrNull()!!)
164 | val size = Size(max - min)
165 | return Rect(
166 | topLeft = min,
167 | size = size,
168 | )
169 | }
170 |
171 | fun BezierSegment.toPath(): Path =
172 | Path().apply {
173 | val start = start
174 | moveTo(start.x, start.y)
175 | val result = this@toPath
176 | with(result) {
177 | cubicTo(refStart.x, refStart.y, refEnd.x, refEnd.y, end.x, end.y)
178 | // lineTo(to.x, to.y)
179 | }
180 | }
181 |
182 | val BezierSegment.length:Double get() = points(10).windowed(2).sumOf { (a,b) -> a.distance(b) }
183 | val BezierSegment.lengthF:Float get() = length.toFloat()
184 |
--------------------------------------------------------------------------------
/lib/src/commonMain/kotlin/lib/vector/line.kt:
--------------------------------------------------------------------------------
1 | package lib.vector
2 |
3 | class LineSegment(val before: T, val start: T, val end: T, val after: T)
4 |
5 | fun LineSegment.map(lambda:(T)->R) = LineSegment(
6 | before = lambda(before),
7 | start = lambda(start),
8 | end = lambda(end),
9 | after = lambda(after),
10 | )
11 |
12 | fun List.toLineSegments(): List> =
13 | (this.takeOrSmaller(1) + this + this.takeLastOrSmaller(1)).windowed(4).map { (before, start, end, after) ->
14 | LineSegment(
15 | before = before, start = start, end = end, after = after
16 | )
17 | }
18 |
19 | fun List.takeOrSmaller(n: Int) = take(minOf(n, size))
20 | fun List.takeLastOrSmaller(n: Int) = takeLast(minOf(n, size))
21 |
--------------------------------------------------------------------------------
/lib/src/commonMain/kotlin/lib/vector/math.kt:
--------------------------------------------------------------------------------
1 | package lib.vector
2 |
3 | import androidx.compose.ui.geometry.Offset
4 | import androidx.compose.ui.geometry.Size
5 | import kotlin.math.absoluteValue
6 | import kotlin.math.sqrt
7 |
8 | val Pt.size: Size get() = Size(x.absoluteValue, y.absoluteValue)
9 | val Pt.offset: Offset get() = Offset(x, y)
10 | infix operator fun Pt.minus(other: Pt): Pt = Pt(x - other.x, y - other.y)
11 | infix operator fun Pt.plus(other: Pt): Pt = Pt(x + other.x, y + other.y)
12 | infix operator fun Pt.times(scale: Float): Pt = Pt(x * scale, y * scale)
13 | infix operator fun Pt.times(scale: Double): Pt = this * scale.toFloat()
14 | infix operator fun Float.times(pt: Pt): Pt = pt.times(this)
15 | infix operator fun Int.times(pt: Pt): Pt = pt.times(this.toFloat())
16 | infix operator fun Pt.div(divider: Float): Pt = this * (1f / divider)
17 |
18 | fun solveRoots(a: Float, b: Float, c: Float): List =
19 | if (a == 0f) {
20 | //bx+c = 0
21 | if (b != 0f) {
22 | listOf(-c / b)
23 | } else {
24 | emptyList()
25 | }
26 | } else {
27 | val D = b * b - 4 * a * c
28 | if (D == 0f) {
29 | listOf(-b / (2 * a))
30 | } else if (D > 0) {
31 | val top1 = -b + sqrt(D)
32 | val top2 = -b - sqrt(D)
33 | listOf(top1 / (2 * a), top2 / (2 * a))
34 | } else {
35 | emptyList()
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/lib/src/commonMain/kotlin/lib/vector/serializable.kt:
--------------------------------------------------------------------------------
1 | package lib.vector
2 |
3 | import lib.vector.utils.fromBase64
4 | import kotlin.jvm.JvmInline
5 | import kotlin.math.absoluteValue
6 | import kotlin.math.sqrt
7 |
8 | interface Pt {
9 | val x: Float
10 | val y: Float
11 | }
12 |
13 | fun Pt(x: Float, y: Float) = PtDisplay(x, y)
14 | fun Pt(x: Int, y: Int) = PtDisplay(x.toFloat(), y.toFloat())
15 | data class PtDisplay(override val x: Float, override val y: Float) : Pt
16 |
17 | @JvmInline
18 | value class Size(val pt: Pt)
19 |
20 | val Size.diagonal: Double get() = pt.distance(Pt(0, 0))
21 | val Size.diagonalF: Float get() = pt.distanceF(Pt(0, 0))
22 |
23 | data class Rect(val topLeft: Pt, val size: Size)
24 |
25 | val Rect.top get() = topLeft.y
26 | val Rect.bottom get() = topLeft.y + size.pt.y
27 | val Rect.left get() = topLeft.x
28 | val Rect.right get() = topLeft.x + size.pt.x
29 |
30 | data class Id(val value: Long, val name: String? = null)
31 | data class PtEdit(override val x: Float, override val y: Float, val id: Id) : Pt
32 |
33 | infix fun Pt.distance(other: Pt): Double {
34 | val dx = (other.x - x).absoluteValue.toDouble()
35 | val dy = (other.y - y).absoluteValue.toDouble()
36 | return sqrt(dx * dx + dy * dy)
37 | }
38 |
39 | infix fun Pt.distanceF(other: Pt): Float {
40 | val dx = (other.x - x).absoluteValue
41 | val dy = (other.y - y).absoluteValue
42 | return sqrt(dx * dx + dy * dy)
43 | }
44 |
45 | fun BR(startRef: Pt? = null, endRef: Pt? = null) = BezierRef(startRef, endRef)
46 | data class BezierRef(
47 | val startRef: Pt? = null,
48 | val endRef: Pt? = null
49 | )
50 |
51 | data class BezierRefEdit(
52 | val startRef: Id?,
53 | val endRef: Id?
54 | )
55 |
56 | sealed class Element {
57 | data class Curve(val color: ULong, val points: List, val bezierRef: Map, val fillPath:Boolean) : Element()
58 | data class Rectangle(val color: ULong, val start: Id, val end: Id) : Element()
59 | data class Bitmap(val topLeft: Id, val byteArray: ByteArray) : Element() {
60 | constructor(topLeft: Id, base64Str: String) : this(topLeft, base64Str.fromBase64())
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/lib/src/commonMain/kotlin/lib/vector/utils/Base64.kt:
--------------------------------------------------------------------------------
1 | package lib.vector.utils
2 |
3 | object Base64 {
4 | private val TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
5 | private val DECODE = IntArray(0x100).apply {
6 | for (n in 0..255) this[n] = -1
7 | for (n in 0 until TABLE.length) {
8 | this[TABLE[n].toInt()] = n
9 | }
10 | }
11 |
12 | operator fun invoke(v: String) = decodeIgnoringSpaces(v)
13 | operator fun invoke(v: ByteArray) = encode(v)
14 |
15 | fun decode(str: String): ByteArray {
16 | val src = ByteArray(str.length) { str[it].toByte() }
17 | val dst = ByteArray(src.size)
18 | return dst.copyOf(decode(src, dst))
19 | }
20 |
21 | fun decodeIgnoringSpaces(str: String): ByteArray {
22 | return decode(str.replace(" ", "").replace("\n", "").replace("\r", ""))
23 | }
24 |
25 | fun decode(src: ByteArray, dst: ByteArray): Int {
26 | var m = 0
27 |
28 | var n = 0
29 | while (n < src.size) {
30 | val d = DECODE[src.readU8(n)]
31 | if (d < 0) {
32 | n++
33 | continue // skip character
34 | }
35 |
36 | val b0 = DECODE[src.readU8(n++)]
37 | val b1 = DECODE[src.readU8(n++)]
38 | val b2 = DECODE[src.readU8(n++)]
39 | val b3 = DECODE[src.readU8(n++)]
40 | dst[m++] = (b0 shl 2 or (b1 shr 4)).toByte()
41 | if (b2 < 64) {
42 | dst[m++] = (b1 shl 4 or (b2 shr 2)).toByte()
43 | if (b3 < 64) {
44 | dst[m++] = (b2 shl 6 or b3).toByte()
45 | }
46 | }
47 | }
48 | return m
49 | }
50 |
51 | @Suppress("UNUSED_CHANGED_VALUE")
52 | fun encode(src: ByteArray): String {
53 | val out = StringBuilder((src.size * 4) / 3 + 4)
54 | var ipos = 0
55 | val extraBytes = src.size % 3
56 | while (ipos < src.size - 2) {
57 | val num = src.readU24BE(ipos)
58 | ipos += 3
59 |
60 | out.append(TABLE[(num ushr 18) and 0x3F])
61 | out.append(TABLE[(num ushr 12) and 0x3F])
62 | out.append(TABLE[(num ushr 6) and 0x3F])
63 | out.append(TABLE[(num ushr 0) and 0x3F])
64 | }
65 |
66 | if (extraBytes == 1) {
67 | val num = src.readU8(ipos++)
68 | out.append(TABLE[num ushr 2])
69 | out.append(TABLE[(num shl 4) and 0x3F])
70 | out.append('=')
71 | out.append('=')
72 | } else if (extraBytes == 2) {
73 | val tmp = (src.readU8(ipos++) shl 8) or src.readU8(ipos++)
74 | out.append(TABLE[tmp ushr 10])
75 | out.append(TABLE[(tmp ushr 4) and 0x3F])
76 | out.append(TABLE[(tmp shl 2) and 0x3F])
77 | out.append('=')
78 | }
79 |
80 | return out.toString()
81 | }
82 |
83 | private fun ByteArray.readU8(index: Int): Int = this[index].toInt() and 0xFF
84 | private fun ByteArray.readU24BE(index: Int): Int =
85 | (readU8(index + 0) shl 16) or (readU8(index + 1) shl 8) or (readU8(index + 2) shl 0)
86 | }
87 |
88 | fun String.fromBase64(ignoreSpaces: Boolean = false): ByteArray = if (ignoreSpaces) Base64.decodeIgnoringSpaces(this) else Base64.decode(this)
89 | val ByteArray.base64: String get() = Base64.encode(this)
90 |
--------------------------------------------------------------------------------
/lib/src/commonMain/kotlin/lib/vector/utils/utils_common.kt:
--------------------------------------------------------------------------------
1 | package lib.vector.utils
2 |
3 | import androidx.compose.ui.graphics.ImageBitmap
4 | import androidx.compose.ui.graphics.Path
5 |
6 | inline fun Path.moveTo(x: Int, y: Int) {
7 | moveTo(x.toFloat(), y.toFloat())
8 | }
9 |
10 | inline fun Path.lineTo(x: Int, y: Int) {
11 | lineTo(x.toFloat(), y.toFloat())
12 | }
13 |
14 | expect fun ImageBitmap.toByteArray(): ByteArray
15 | expect fun ByteArray.toImageBitmap(): ImageBitmap
16 |
17 | fun List.indexOfFirstOrNull(lambda:(T)->Boolean):Int? {
18 | val result = indexOfFirst(lambda)
19 | if (result == -1) {
20 | return null
21 | }
22 | return result
23 | }
24 |
--------------------------------------------------------------------------------
/lib/src/jvmMain/kotlin/lib/vector/ColorPicker.kt:
--------------------------------------------------------------------------------
1 | package lib.vector
2 |
3 | import androidx.compose.foundation.Canvas
4 | import androidx.compose.foundation.clickable
5 | import androidx.compose.foundation.layout.Column
6 | import androidx.compose.foundation.layout.Row
7 | import androidx.compose.foundation.layout.size
8 | import androidx.compose.material.Divider
9 | import androidx.compose.material.Text
10 | import androidx.compose.runtime.*
11 | import androidx.compose.ui.Alignment
12 | import androidx.compose.ui.Modifier
13 | import androidx.compose.ui.geometry.Offset
14 | import androidx.compose.ui.geometry.Size
15 | import androidx.compose.ui.graphics.Color
16 | import androidx.compose.ui.input.pointer.isPrimaryPressed
17 | import androidx.compose.ui.input.pointer.pointerInput
18 | import androidx.compose.ui.unit.dp
19 | import androidx.compose.ui.window.Dialog
20 | import androidx.compose.ui.window.DialogState
21 | import androidx.compose.ui.window.WindowPosition
22 |
23 | @Composable
24 | fun ColorPicker(currentColor: ULong, onChangeColor: (ULong) -> Unit) {
25 | var dialogOpen by remember { mutableStateOf(false) }
26 | fun select(color: ULong) {
27 | dialogOpen = false
28 | onChangeColor(color)
29 | }
30 | Row(modifier = Modifier.clickable {
31 | dialogOpen = !dialogOpen
32 | }) {
33 | Canvas(modifier = Modifier.size(30.dp)) {
34 | drawRect(color = Color(currentColor))
35 | }
36 | Text("color")
37 | }
38 | if (dialogOpen) {
39 | Dialog(
40 | state = DialogState(width = 500.dp, height = 500.dp, position = WindowPosition(Alignment.TopStart)),
41 | onCloseRequest = {
42 | dialogOpen = false
43 | }
44 | ) {
45 | ColorPallet(currentColor) {
46 | select(it)
47 | }
48 | }
49 | }
50 | }
51 |
52 | @Composable
53 | fun ColorPallet(initColor: ULong, onSelect: (ULong) -> Unit) {
54 | Column {
55 | Row {
56 | listOf(Color.Red, Color.Green, Color.Blue, Color.Black, Color.Gray, Color.Yellow, Color.Cyan).forEach {
57 | val width = 40f
58 | val height = 40f
59 | Canvas(Modifier.size(width.dp, height.dp).clickable {
60 | onSelect(it.value)
61 | }) {
62 | drawRect(color = it, size = Size(width, height))
63 | }
64 | }
65 | }
66 | Divider(Modifier.size(5.dp))
67 | var red: Int by remember { mutableStateOf((Color(initColor).red * 0xFF).toInt()) }
68 | var green: Int by remember { mutableStateOf((Color(initColor).green * 0xFF).toInt()) }
69 | var blue: Int by remember { mutableStateOf((Color(initColor).blue * 0xFF).toInt()) }
70 | val currentColor: ULong by derivedStateOf { Color(red, green, blue).value }
71 |
72 | Canvas(Modifier.size(50.dp, 50.dp).clickable {
73 | onSelect(currentColor)
74 | }) {
75 | drawRect(Color(currentColor), size = Size(50f, 50f))
76 | }
77 | Divider(Modifier.size(5.dp))
78 | Row {
79 | Canvas(Modifier.size(256.dp, 256.dp).pointerInput(Unit) {
80 | awaitPointerEventScope {
81 | while (true) {
82 | val event = awaitPointerEvent()
83 | if (event.buttons.isPrimaryPressed) {
84 | red = event.changes.first().position.x.toInt()
85 | green = event.changes.first().position.y.toInt()
86 | }
87 | }
88 | }
89 | }) {
90 | for (r in 0..0xFF) {
91 | for (g in 0..0xFF) {
92 | drawRect(
93 | color = Color(red = r, green = g, blue = blue),
94 | topLeft = Offset(r.toFloat(), g.toFloat()),
95 | size = Size(1f, 1f)
96 | )
97 | }
98 | }
99 | }
100 | val BAND_WIDTH = 40
101 | Canvas(Modifier.size(BAND_WIDTH.dp, 256.dp).pointerInput(Unit) {
102 | awaitPointerEventScope {
103 | while (true) {
104 | val event = awaitPointerEvent()
105 | if (event.buttons.isPrimaryPressed) {
106 | blue = event.changes.first().position.y.toInt()
107 | }
108 | }
109 | }
110 | }) {
111 | for (b in 0..0xFF) {
112 | drawRect(color = Color(0, 0, b), topLeft = Offset(0f, b.toFloat()), size = Size(BAND_WIDTH.toFloat(), 1f))
113 | }
114 | }
115 | }
116 | TxtButton("Done") {
117 | onSelect(currentColor)
118 | }
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/lib/src/jvmMain/kotlin/lib/vector/GenerateCode.kt:
--------------------------------------------------------------------------------
1 | package lib.vector
2 |
3 | import com.squareup.kotlinpoet.*
4 | import lib.vector.utils.base64
5 |
6 | private const val USE_LIST = true
7 |
8 | @OptIn(ExperimentalStdlibApi::class)
9 | fun generateCode(elements: List, mapIdToPoint: Map): String {
10 | // val file = FileSpec.builder("com.uni", "GeneratedCode").addFunction(
11 | val idToIndex: MutableMap = mutableMapOf()
12 | val genFun = FunSpec.builder("generatedCode")
13 | .receiver(typeNameOf())
14 | // .addParameter("args", String::class, KModifier.VARARG)
15 | .apply {
16 | mapIdToPoint.entries.forEach {
17 | val name = it.key.name
18 | if (name != null) {
19 | addStatement(buildString {
20 | append("val $name by mkPt(${it.value.x.toInt()}, ${it.value.y.toInt()})")
21 | })
22 | } else {
23 | if (!USE_LIST) {
24 | addStatement(buildString {
25 | append("val p${it.key.value} by mkPt(${it.value.x.toInt()}, ${it.value.y.toInt()})")
26 | })
27 | }
28 | }
29 | }
30 | if (USE_LIST) {
31 | addStatement(buildString {
32 | append("val l = listOf(")
33 | mapIdToPoint.entries.filter { it.key.name == null }
34 | .forEach {
35 | idToIndex[it.key] = idToIndex.size
36 | append("Pt(${it.value.x.toInt()}, ${it.value.y.toInt()}),")
37 | }
38 | append(")")
39 | })
40 | }
41 | elements.forEach { e ->
42 | addStatement(buildString {
43 | when (e) {
44 | is Element.Curve -> {
45 | append("drawCurve(")
46 | append("${e.color.literalStr},")
47 | append("listOf(")
48 | e.points.forEach {
49 | append("${it.constructorPtOrLink(mapIdToPoint, idToIndex)},")
50 | }
51 | append("),")
52 | append(" mapOf(")
53 | e.bezierRef.forEach {
54 | val keyStr = it.key.constructorPtOrLink(mapIdToPoint, idToIndex)
55 | val valueStr = it.value.constructorStr(mapIdToPoint, idToIndex)
56 | append("$keyStr to $valueStr,")
57 | }
58 | append("),")
59 | append(")")
60 | }
61 | is Element.Rectangle -> {
62 | append("drawRect(")
63 | append("${e.color.literalStr},")
64 | append("${e.start.constructorPtOrLink(mapIdToPoint, idToIndex)},")
65 | append("${e.end.constructorPtOrLink(mapIdToPoint, idToIndex)},")
66 | append(")")
67 | }
68 | is Element.Bitmap -> {
69 | append("drawBitmap(")
70 | append("${e.topLeft.constructorPtOrLink(mapIdToPoint, idToIndex)},")
71 | append("\"" + e.byteArray.base64 + "\"")
72 | append(")")
73 | }
74 | }
75 | })
76 | }
77 | }
78 | .build()
79 | // ).build()
80 |
81 | return genFun.body.toString()
82 | // return file.toString()
83 | }
84 |
85 | private fun Id.constructorPtOrLink(map: Map, idToIndex:Map): String =
86 | if (name != null) name else {
87 | if(USE_LIST) {
88 | "l[${idToIndex[this]}]"
89 | } else {
90 | "p${value}"
91 | }
92 | // pt(map).run { "Pt(${x.toInt()}, ${y.toInt()})" }
93 | }
94 |
95 | private fun BezierRefEdit.constructorStr(map: Map, idToIndex:Map): String {
96 | val startRefStr = startRef?.constructorPtOrLink(map, idToIndex)
97 | val endRefStr = endRef?.constructorPtOrLink(map, idToIndex)
98 | return "BR($startRefStr, $endRefStr)"
99 | }
100 |
101 | private val ULong.literalStr: String get() = "0x" + toString(radix = 16) + "uL"
102 |
--------------------------------------------------------------------------------
/lib/src/jvmMain/kotlin/lib/vector/GeneratedLayer.kt:
--------------------------------------------------------------------------------
1 | package lib.vector
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.ui.Modifier
5 | import lib.vector.GeneratedScope
6 |
7 | @Composable
8 | actual fun GeneratedLayer(modifier: Modifier, lambda: GeneratedScope.() -> Unit) {
9 | EditMode(modifier, lambda)
10 | // DisplayMode(modifier, lambda)
11 | }
12 |
--------------------------------------------------------------------------------
/lib/src/jvmMain/kotlin/lib/vector/InitializeByGeneratedScope.kt:
--------------------------------------------------------------------------------
1 | package lib.vector
2 |
3 | data class EditState(
4 | val mapIdToPoint: Map,
5 | val savedElements: List
6 | )
7 |
8 | @OptIn(ExperimentalStdlibApi::class)
9 | fun initializeByGeneratedScope(lambda: GeneratedScope.() -> Unit): EditState {
10 | // Init
11 | val generatedMapIdToPoint: MutableMap = mutableMapOf()
12 | val generatedElements: MutableList = mutableListOf()
13 | fun mapPtToId(pt: Pt): Id {
14 | if (pt is PtEdit) {
15 | return pt.id
16 | } else {
17 | val existedEntry = generatedMapIdToPoint.entries.firstOrNull {
18 | it.value.x == pt.x && it.value.y == pt.y
19 | }
20 | if (existedEntry != null) {
21 | return existedEntry.key
22 | } else {
23 | return getNextPointId().also {
24 | generatedMapIdToPoint[it] = pt
25 | }
26 | }
27 | }
28 | }
29 |
30 | val generatedScope = object : GeneratedScope {
31 | override fun mkPt(x: Float, y: Float): MakePt = MakePt { _, property ->
32 | val id = getNextPointId(property.name)
33 | val pt = PtEdit(x, y, id)
34 | generatedMapIdToPoint[id] = pt
35 | pt
36 | }
37 |
38 | override fun drawCurve(color: ULong, points: List, bezierRef: Map, fillPath: Boolean) {
39 | generatedElements.add(
40 | Element.Curve(
41 | color = color,
42 | points = points.map(::mapPtToId),
43 | buildMap {
44 | bezierRef.forEach {
45 | put(
46 | mapPtToId(it.key), BezierRefEdit(
47 | startRef = it.value.startRef?.let(::mapPtToId),
48 | endRef = it.value.endRef?.let(::mapPtToId)
49 | )
50 | )
51 | }
52 | },
53 | fillPath = fillPath
54 | )
55 | )
56 | }
57 |
58 | override fun drawRect(color: ULong, start: Pt, end: Pt) {
59 | generatedElements.add(Element.Rectangle(color, mapPtToId(start), mapPtToId(end)))
60 | }
61 |
62 | override fun drawBitmap(pt: Pt, byteArray: ByteArray) {
63 | generatedElements.add(Element.Bitmap(mapPtToId(pt), byteArray))
64 | }
65 | }
66 | generatedScope.lambda()
67 | return EditState(
68 | generatedMapIdToPoint, generatedElements
69 | )
70 | }
71 |
--------------------------------------------------------------------------------
/lib/src/jvmMain/kotlin/lib/vector/intercept/InterceptCubicBezier.kt:
--------------------------------------------------------------------------------
1 | package lib.vector.intercept
2 |
3 | import lib.vector.*
4 |
5 | data class InterceptedBezierPoint(val relativePointsA: List, val relativePointsB: List)
6 |
7 | fun InterceptedBezierPoint.mapA(lambda: (Float) -> Float) = copy(
8 | relativePointsA = relativePointsA.map(lambda)
9 | )
10 |
11 | fun InterceptedBezierPoint.mapB(lambda: (Float) -> Float) = copy(
12 | relativePointsB = relativePointsB.map(lambda)
13 | )
14 |
15 | operator fun InterceptedBezierPoint.plus(other: InterceptedBezierPoint) =
16 | InterceptedBezierPoint(
17 | relativePointsA = relativePointsA + other.relativePointsA,
18 | relativePointsB = relativePointsB + other.relativePointsB,
19 | )
20 |
21 | fun interceptCubicBezier(a: BezierSegment, b: BezierSegment): InterceptedBezierPoint {
22 | val precision = maxOf(
23 | minOf(a.lengthF, b.lengthF) / 1E4f,
24 | 1E-4f
25 | )
26 | return interceptCubicBezier(a, b, precision, 0f, 1f, 0f, 1f)
27 | }
28 |
29 | fun nearestBezierPoint(segment: BezierSegment, pt:Pt): Float {
30 | return 0.5f
31 | // val precision = maxOf(
32 | // minOf(a.lengthF, b.lengthF) / 1E4f,
33 | // 1E-4f
34 | // )
35 | // return interceptCubicBezier(a, b, precision, 0f, 1f, 0f, 1f)
36 | }
37 |
38 | fun interceptCubicBezier(
39 | a: BezierSegment, b: BezierSegment,
40 | precision: Float,
41 | ta1: Float, ta2: Float,
42 | tb1: Float, tb2: Float,
43 | recursion:Int = 0
44 | ): InterceptedBezierPoint {
45 | return if (a.subSegment(ta1, ta2).aabb intercepted b.subSegment(tb1, tb2).aabb) {
46 | val tam = (ta1 + ta2) / 2
47 | val tbm = (tb1 + tb2) / 2
48 | val diagonalF = a.subSegment(ta1, ta2).aabb.size.diagonalF
49 | val MAX_RECURSION = 100
50 | if (recursion > MAX_RECURSION) {
51 | println("recursion > MAX_RECURSION")
52 | }
53 | if (diagonalF < precision || recursion > MAX_RECURSION) {
54 | InterceptedBezierPoint(listOf(tam), listOf(tbm))
55 | } else {
56 | interceptCubicBezier(a, b, precision, ta1, tam, tb1, tbm, recursion+1) +
57 | interceptCubicBezier(a, b, precision, ta1, tam, tbm, tb2, recursion+1) +
58 | interceptCubicBezier(a, b, precision, tam, ta2, tb1, tbm, recursion+1) +
59 | interceptCubicBezier(a, b, precision, tam, ta2, tbm, tb2, recursion+1)
60 | }
61 | } else {
62 | InterceptedBezierPoint(emptyList(), emptyList())
63 | }
64 | }
65 |
66 | infix fun Rect.intercepted(b: Rect): Boolean {
67 | val a = this
68 | val notIntercepter = a.top >= b.bottom || b.top >= a.bottom || a.left >= b.right || b.left >= a.right
69 | return notIntercepter.not()
70 | }
71 |
--------------------------------------------------------------------------------
/lib/src/jvmMain/kotlin/lib/vector/intercept/InterceptLinear.kt:
--------------------------------------------------------------------------------
1 | package lib.vector.intercept
2 |
3 | import lib.vector.BezierSegment
4 | import lib.vector.Pt
5 |
6 | fun interceptLinear(a: BezierSegment, b: BezierSegment): List {
7 | val a1 = a.start
8 | val a2 = a.end
9 | val b1 = b.start
10 | val b2 = b.end
11 | fun intercept(x1: Float, y1: Float, x2: Float, y2: Float, x3: Float, y3: Float, x4: Float, y4: Float): Pt? {
12 | //https://habr.com/ru/post/523440/
13 | val n: Float;
14 | if (y2 - y1 != 0f) { // a(y)
15 | val q = (x2 - x1) / (y1 - y2);
16 | val sn = (x3 - x4) + (y3 - y4) * q;
17 | if (sn == 0f) {
18 | return null; // c(x) + c(y)*q
19 | }
20 | val fn = (x3 - x1) + (y3 - y1) * q; // b(x) + b(y)*q
21 | n = fn / sn;
22 | } else {
23 | if (y3 - y4 == 0f) {
24 | return null; // b(y)
25 | }
26 | n = (y3 - y1) / (y3 - y4); // c(y)/b(y)
27 | }
28 | val x = x3 + (x4 - x3) * n; // x3 + (-b(x))*n
29 | val y = y3 + (y4 - y3) * n; // y3 +(-b(y))*n
30 | val r = Pt(x, y)
31 | if (true
32 | && r.x > minOf(x1, x2) && r.x > minOf(x3, x4)
33 | && r.x < maxOf(x1, x2) && r.x < maxOf(x3, x4)
34 | && r.y > minOf(y1, y2) && r.y > minOf(y3, y4)
35 | && r.y < maxOf(y1, y2) && r.y < maxOf(y3, y4)
36 | ) {
37 | return r
38 | } else {
39 | return null
40 | }
41 | }
42 |
43 | val interceptedPoint = intercept(a1.x, a1.y, a2.x, a2.y, b1.x, b1.y, b2.x, b2.y)
44 | if (interceptedPoint != null) {
45 | return listOf(interceptedPoint)
46 | } else {
47 | return emptyList()
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/lib/src/jvmMain/kotlin/lib/vector/jvm_util.kt:
--------------------------------------------------------------------------------
1 | package lib.vector
2 |
3 | import java.awt.Toolkit
4 | import java.awt.datatransfer.Clipboard
5 | import java.awt.datatransfer.StringSelection
6 |
7 | fun pasteToClipboard(result: String) {
8 | println(result)
9 | val stringSelection = StringSelection(result)
10 | val clipboard: Clipboard = Toolkit.getDefaultToolkit().getSystemClipboard()
11 | clipboard.setContents(stringSelection, null)
12 | }
13 |
--------------------------------------------------------------------------------
/lib/src/jvmMain/kotlin/lib/vector/point_id.kt:
--------------------------------------------------------------------------------
1 | package lib.vector
2 |
3 | import java.util.concurrent.atomic.AtomicLong
4 | import kotlin.random.Random
5 | import kotlin.random.nextULong
6 |
7 | private val nextPointId: AtomicLong = AtomicLong(0)
8 | fun getNextPointId(name: String? = null): Id {
9 | return Id(if(name != null) -1 else Random.nextLong() ushr 1 /*nextPointId.getAndIncrement()*/, name = name)
10 | }
11 |
12 |
--------------------------------------------------------------------------------
/lib/src/jvmMain/kotlin/lib/vector/utils/utils_desktop.kt:
--------------------------------------------------------------------------------
1 | package lib.vector.utils
2 |
3 | import androidx.compose.ui.graphics.ImageBitmap
4 | import androidx.compose.ui.graphics.asSkiaBitmap
5 | import androidx.compose.ui.graphics.toAwtImage
6 | import androidx.compose.ui.graphics.toComposeImageBitmap
7 | import org.jetbrains.skia.Image
8 | import java.awt.image.BufferedImage
9 | import java.io.ByteArrayOutputStream
10 | import javax.imageio.ImageIO
11 |
12 | actual fun ImageBitmap.toByteArray(): ByteArray = toAwtImage().toByteArray()
13 | actual fun ByteArray.toImageBitmap(): ImageBitmap = Image.makeFromEncoded(this).toComposeImageBitmap()
14 |
15 | fun BufferedImage.toByteArray(): ByteArray {
16 | val baos = ByteArrayOutputStream()
17 | ImageIO.write(this, "png", baos)
18 | return baos.toByteArray()
19 | }
20 |
21 | private fun todo(imageBitmap: ImageBitmap) {
22 | imageBitmap.asSkiaBitmap()
23 | imageBitmap.toAwtImage()
24 | }
25 |
--------------------------------------------------------------------------------
/lib/src/jvmTest/kotlin/lib/vector/BezierTest.kt:
--------------------------------------------------------------------------------
1 | package lib.vector
2 |
3 | import org.junit.Assert
4 | import org.junit.Test
5 |
6 | class BezierTest {
7 | @Test
8 | fun testSubSegment() {
9 | val segment = BezierSegment(Pt(0, 0), Pt(100, 100), Pt(0, 0), Pt(100, 100))
10 | val firstSplit = segment.split(0.5f).first
11 | val subSegment = segment.subSegment(0.0f, 0.5f)
12 | Assert.assertEquals(firstSplit, subSegment)
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/lib/src/jvmTest/kotlin/lib/vector/GenerateCodeKtTest.kt:
--------------------------------------------------------------------------------
1 | package lib.vector
2 |
3 | import org.junit.Assert.*
4 | import org.junit.Test
5 |
6 | class GenerateCodeKtTest {
7 | @Test
8 | fun testSimple() {
9 | val state = initializeByGeneratedScope {
10 | // val myPt by mkPt(140, 91)
11 | val p1 by mkPt(423, 167)
12 | val p2 by mkPt(427, 276)
13 | val p3 by mkPt(450, 211)
14 | drawCurve(
15 | 0xff0000ff00000000uL, listOf(p1, p2), mapOf(
16 | p1 to BR(p3, null),
17 | p2 to BR(null, Pt(458, 240))
18 | )
19 | )
20 | }
21 | assertEquals(
22 | """
23 | val p1 by mkPt(423, 167)
24 | val p2 by mkPt(427, 276)
25 | val p3 by mkPt(450, 211)
26 | drawCurve(0xff0000ff00000000uL,listOf(p1,p2,), mapOf(p1 to BezierRef(p3, null),p2 to BezierRef(null, Pt(458, 240)),),)
27 |
28 | """.trimIndent(),
29 | generateCode(state.savedElements, state.mapIdToPoint).also {
30 | println("------------------------------------")
31 | println(it)
32 | println("------------------------------------")
33 | }
34 | )
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | ./gradlew happy-new-year:run --no-daemon
3 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | gradlePluginPortal()//todo alternative?: maven { setUrl("https://plugins.gradle.org/m2/") }
4 | mavenCentral()
5 | // maven { setUrl("https://dl.bintray.com/kotlin/kotlinx") }
6 | maven(url = "https://maven.pkg.jetbrains.space/public/p/compose/dev")
7 | // maven { setUrl("https://dl.bintray.com/kotlin/kotlin-eap") }
8 | // maven { setUrl("https://dl.bintray.com/kotlin/kotlin-dev") }
9 | // maven(url = "https://oss.sonatype.org/content/repositories/snapshots/") // plugin id("org.jetbrains.intellij") SNAPSHOT
10 | }
11 |
12 | resolutionStrategy {
13 | eachPlugin {
14 | when (requested.id.id) {
15 | "org.jetbrains.compose" -> useModule("org.jetbrains.compose:compose-gradle-plugin:${requested.version}")
16 | // "kotlin-dce-js" -> useModule("org.jetbrains.kotlin:kotlin-gradle-plugin:${requested.version}")
17 | // "kotlinx-serialization" -> useModule("org.jetbrains.kotlin:kotlin-serialization:${requested.version}")
18 | // "org.jetbrains.kotlin.multiplatform" -> useModule("org.jetbrains.kotlin:kotlin-gradle-plugin:${target.version}")
19 | }
20 | }
21 | }
22 | }
23 | rootProject.name = "compose-vector"
24 | //enableFeaturePreview("GRADLE_METADATA")
25 | include("lib")
26 | include("clipboard")
27 | include("usage")
28 | include("happy-new-year")
29 | include("android")
30 |
--------------------------------------------------------------------------------
/usage/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | kotlin("multiplatform") // kotlin("jvm") doesn't work well in IDEA/AndroidStudio (https://github.com/JetBrains/compose-jb/issues/22)
3 | id("org.jetbrains.compose")
4 | }
5 |
6 | java {
7 | sourceCompatibility = JavaVersion.VERSION_11
8 | targetCompatibility = JavaVersion.VERSION_11
9 | }
10 |
11 | kotlin {
12 | jvm {
13 | withJava()
14 | }
15 | sourceSets {
16 | named("commonMain") {
17 | dependencies {
18 | api(project(":lib"))
19 | // api(compose.runtime)
20 | // api(compose.foundation)
21 | // api(compose.material)
22 | }
23 | }
24 | named("jvmMain") {
25 | dependencies {
26 | implementation(project(":clipboard"))
27 | implementation(compose.desktop.currentOs)
28 | implementation("com.squareup:kotlinpoet:1.10.2")
29 | }
30 | }
31 | named("jvmTest") {
32 | dependencies {
33 | implementation(kotlin("test"))
34 | }
35 | }
36 | }
37 | }
38 |
39 | compose.desktop {
40 | application {
41 | mainClass = "com.usage.MainKt"
42 | }
43 | }
44 |
45 | tasks.withType {
46 | kotlinOptions.jvmTarget = "11"
47 | }
48 |
--------------------------------------------------------------------------------
/usage/src/commonMain/kotlin/com/usage/UsageInCommon.kt:
--------------------------------------------------------------------------------
1 | @file:OptIn(ExperimentalStdlibApi::class)
2 |
3 | package com.usage
4 |
5 | import androidx.compose.runtime.*
6 | import androidx.compose.ui.Modifier
7 | import lib.vector.BR
8 | import lib.vector.GeneratedLayer
9 | import lib.vector.Pt
10 |
11 |
12 | @OptIn(ExperimentalStdlibApi::class)
13 | @Composable
14 | fun UsageInCommon(modifier: Modifier = Modifier) {
15 | GeneratedLayer(modifier) {
16 | val m637407704 by mkPt(163, 524)
17 | val m2782133651 by mkPt(67, 615)
18 | val m3101024460 by mkPt(65, 619)
19 | val m2429699421 by mkPt(89, 639)
20 | val m2617611882 by mkPt(143, 608)
21 | val m2902829938 by mkPt(203, 570)
22 | val m1900164015 by mkPt(248, 534)
23 |
24 |
25 | val l = listOf(Pt(171, 472),Pt(115, 465),Pt(88, 444),Pt(110, 385),Pt(113, 357),Pt(126, 363),Pt(121, 342),Pt(153, 368),Pt(230, 375),Pt(294, 371),Pt(346, 366),Pt(512, 353),Pt(605, 294),Pt(617, 213),Pt(637, 166),Pt(679, 153),Pt(661, 197),Pt(661, 252),Pt(654, 312),Pt(613, 353),Pt(581, 393),Pt(613, 438),Pt(647, 482),Pt(679, 529),Pt(700, 584),Pt(697, 619),Pt(682, 618),Pt(636, 543),Pt(564, 520),Pt(566, 560),Pt(544, 600),Pt(505, 631),Pt(460, 628),Pt(472, 608),Pt(501, 589),Pt(512, 563),Pt(463, 529),Pt(352, 511),Pt(366, 583),Pt(358, 624),Pt(323, 634),Pt(321, 605),Pt(300, 540),)
26 | drawCurve(0xff0000ff00000000uL,listOf(m2782133651,m637407704,l[0],l[1],l[2],l[3],l[4],l[5],l[6],l[7],l[8],l[9],l[10],l[11],l[12],l[13],l[14],l[15],l[16],l[17],l[18],l[19],l[20],l[21],l[22],l[23],l[24],l[25],l[26],l[27],l[28],l[29],l[30],l[31],l[32],l[33],l[34],l[35],l[36],l[37],l[38],l[39],l[40],l[41],l[42],m1900164015,m2902829938,m2617611882,m2429699421,m3101024460,), mapOf(),)
27 |
28 |
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/usage/src/jvmMain/kotlin/com/usage/main.kt:
--------------------------------------------------------------------------------
1 | package com.usage
2 |
3 | import androidx.compose.foundation.ExperimentalFoundationApi
4 | import androidx.compose.foundation.layout.Box
5 | import androidx.compose.foundation.layout.fillMaxSize
6 | import androidx.compose.ui.ExperimentalComposeUiApi
7 | import androidx.compose.ui.Modifier
8 | import androidx.compose.ui.input.key.key
9 | import androidx.compose.ui.unit.dp
10 | import androidx.compose.ui.window.Window
11 | import androidx.compose.ui.window.application
12 | import androidx.compose.ui.window.rememberWindowState
13 | import kotlinx.coroutines.GlobalScope
14 | import kotlinx.coroutines.launch
15 | import lib.vector.globalKeyListener
16 |
17 | @ExperimentalFoundationApi
18 | @ExperimentalComposeUiApi
19 | fun main() {
20 | application {
21 | Window(
22 | onCloseRequest = ::exitApplication,
23 | state = rememberWindowState(width = 800.dp, height = 750.dp),
24 | onKeyEvent = {
25 | GlobalScope.launch {
26 | globalKeyListener.emit(it.key)
27 | }
28 | false
29 | }
30 | ) {
31 | Box(Modifier.fillMaxSize()) {
32 | // val viewConfiguration = object : ViewConfiguration {
33 | // override val longPressTimeoutMillis: Long = 500
34 | // override val doubleTapTimeoutMillis: Long = 300
35 | // override val doubleTapMinTimeMillis: Long = 40
36 | // override val touchSlop: Float get() = 0.0f
37 | // }
38 | //
39 | // CompositionLocalProvider(
40 | // LocalViewConfiguration provides viewConfiguration
41 | // ) {
42 | UsageInCommon()
43 | // }
44 | }
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/usage/src/jvmMain/kotlin/com/usage/run_simple_window.kt:
--------------------------------------------------------------------------------
1 | package com.usage
2 |
3 | import androidx.compose.foundation.layout.Column
4 | import androidx.compose.runtime.*
5 | import androidx.compose.ui.window.Window
6 | import androidx.compose.ui.window.application
7 | import lib.vector.TxtButton
8 |
9 | fun runSimpleComposableWindow(content: @Composable () -> Unit): Unit {
10 | application {
11 | Window(
12 | onCloseRequest = ::exitApplication,
13 | // state = rememberWindowState(width = 800.dp, height = 800.dp)
14 | ) {
15 | content()
16 | }
17 | }
18 |
19 | }
20 |
21 | fun runSimpleClickerWindow(content: @Composable (clicksCount: Int) -> Unit): Unit {
22 | runSimpleComposableWindow {
23 | var clicksCount by remember { mutableStateOf(0) }
24 | Column {
25 | TxtButton("Increment $clicksCount") {
26 | clicksCount++
27 | }
28 | content(clicksCount)
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/usage/src/jvmTest/README.md:
--------------------------------------------------------------------------------
1 | variables used in an effect should be added as a parameter of the effect composable, or use rememberUpdatedState.
2 |
3 |
4 | High level
5 | ```Kotlin
6 | val color = animateColorAsState(if (condition) Color.Green else Color.Red)
7 | ```
8 | Low level
9 | ```Kotlin
10 | val color = remember { Animatable(Color.Gray) }
11 | LaunchedEffect(condition) {
12 | color.animateTo(if (condition) Color.Green else Color.Red)
13 | }
14 | ```
15 |
--------------------------------------------------------------------------------
/usage/src/jvmTest/kotlin/BitSortTest.kt:
--------------------------------------------------------------------------------
1 | import kotlinx.coroutines.*
2 | import org.junit.Test
3 | import javax.script.ScriptContext
4 | import kotlin.coroutines.CoroutineContext
5 | import kotlin.random.Random
6 | import kotlin.system.measureNanoTime
7 | import kotlin.test.assertEquals
8 | import kotlin.test.assertTrue
9 |
10 | class BitSortTest {
11 |
12 | companion object {
13 | const val BLOCKS = 8
14 | }
15 |
16 | @Test
17 | fun testRandom() {
18 | fun checkInRandomArray() {
19 | val input = Array(1024 * 1024) {
20 | Random.nextInt(0, 1000)
21 | }
22 | val inputCopy = input.copyOf()
23 | val t1 = measureNanoTime {
24 | inputCopy.sort()
25 | }.also {
26 | println("quickSort: $it")
27 | }
28 | val t2 = measureNanoTime {
29 | bitSort(input)
30 | }.also {
31 | println("bitSort: $it")
32 | }
33 | println("ratio: ${t1 * 100 / t2 * 0.01}")
34 | assertTrue(inputCopy contentDeepEquals input)
35 | }
36 | repeat(10) {
37 | checkInRandomArray()
38 | }
39 | }
40 |
41 | fun bitSort(arr: Array) {
42 | runBlocking(context = Dispatchers.Default) {
43 | var blocks = BLOCKS
44 | fun blockSize(): Int {
45 | val result = arr.size / blocks
46 | return result
47 | }
48 |
49 | fun merge(l1: Int, r1: Int, l2: Int, r2: Int) {
50 | val beginCopy = Array(r1 - l1) { arr[l1 + it] }
51 | var firstPointer = 0
52 | var secondPointer = l2
53 | var insert = l1
54 | while (firstPointer < beginCopy.size && secondPointer < r2) {
55 | val first = beginCopy[firstPointer]
56 | val second = arr[secondPointer]
57 | if (first < second) {
58 | arr[insert] = first
59 | firstPointer++
60 | } else {
61 | arr[insert] = second
62 | secondPointer++
63 | }
64 | insert++
65 | }
66 | while (firstPointer < beginCopy.size) {
67 | arr[insert] = beginCopy[firstPointer]
68 | firstPointer++
69 | insert++
70 | }
71 | }
72 | coroutineScope {
73 | repeat(blocks) { i ->
74 | launch {
75 | arr.sort(i * blockSize(), (i + 1) * blockSize())
76 | }
77 | // sort(i * blockSize(), (i + 1) * blockSize())
78 | }
79 | }
80 | while (blocks > 1) {
81 | coroutineScope {
82 | blocks /= 2
83 | repeat(blocks) { i ->
84 | val size = blockSize()
85 | val begin = i * size
86 | val middle = i * size + size / 2
87 | val end = (i + 1) * size
88 | launch {
89 | merge(begin, middle, middle, end)
90 | }
91 | }
92 | }
93 | }
94 | }
95 | }
96 |
97 | }
98 |
--------------------------------------------------------------------------------
/usage/src/jvmTest/kotlin/ReproduceBugScaleOffset.kt:
--------------------------------------------------------------------------------
1 | import androidx.compose.foundation.background
2 | import androidx.compose.foundation.layout.*
3 | import androidx.compose.ui.Modifier
4 | import androidx.compose.ui.draw.scale
5 | import androidx.compose.ui.graphics.Color
6 | import androidx.compose.ui.unit.dp
7 | import androidx.compose.ui.window.singleWindowApplication
8 |
9 | //https://github.com/JetBrains/compose-jb/issues/1559
10 | fun main() = singleWindowApplication {
11 | Box(modifier = Modifier.fillMaxSize()) {
12 | Box(
13 | modifier = Modifier
14 | .offset(-100.dp, 0.dp)
15 | .scale(0.95f)
16 | .offset(200.dp, 20.dp)
17 | .size(100.dp)
18 | .background(Color.Green)
19 | )
20 | Box(
21 | modifier = Modifier
22 | .offset(0.dp, 0.dp)
23 | .scale(0.95f)
24 | .offset(200.dp, 20.dp)
25 | .size(100.dp)
26 | .background(Color.Red)
27 | )
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/usage/src/jvmTest/kotlin/TestAnimations.kt:
--------------------------------------------------------------------------------
1 | import androidx.compose.animation.animateColorAsState
2 | import androidx.compose.animation.core.*
3 | import androidx.compose.material.Text
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.runtime.*
6 | import androidx.compose.ui.Modifier
7 | import androidx.compose.ui.draw.alpha
8 | import androidx.compose.ui.graphics.Color
9 | import com.usage.runSimpleComposableWindow
10 |
11 | @Composable
12 | fun todoAnimations() {
13 | val backgroundColor by animateColorAsState(if (true) Color.Gray else Color.Yellow)
14 |
15 | }
16 |
17 | @Composable
18 | fun todoInfiniteAnimation() {
19 | val infiniteTransition = rememberInfiniteTransition()
20 | val alpha by infiniteTransition.animateFloat(
21 | initialValue = 0f,
22 | targetValue = 1f,
23 | animationSpec = infiniteRepeatable(
24 | animation = keyframes {
25 | durationMillis = 1000
26 | 0.7f at 500
27 | },
28 | repeatMode = RepeatMode.Reverse
29 | )
30 | )
31 | Text("Hello", modifier = Modifier.alpha(alpha))
32 | }
33 |
34 | fun main() {
35 | runSimpleComposableWindow() {
36 |
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/usage/src/jvmTest/kotlin/TestDerivedStateOf.kt:
--------------------------------------------------------------------------------
1 | import androidx.compose.runtime.*
2 |
3 | @Composable
4 | fun TestDerivedStateOf() {
5 | val myMutableState: MutableState = remember { mutableStateOf("State") }
6 | val myUpdatedState: State = remember {
7 | derivedStateOf {
8 | myMutableState.value + " Derived"
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/usage/src/jvmTest/kotlin/TestDisney.kt:
--------------------------------------------------------------------------------
1 | import androidx.compose.animation.core.FastOutLinearInEasing
2 | import androidx.compose.animation.core.animateFloatAsState
3 | import androidx.compose.animation.core.tween
4 | import androidx.compose.foundation.background
5 | import androidx.compose.foundation.layout.Box
6 | import androidx.compose.foundation.layout.Row
7 | import androidx.compose.foundation.layout.fillMaxSize
8 | import androidx.compose.foundation.layout.size
9 | import androidx.compose.runtime.*
10 | import androidx.compose.ui.Alignment
11 | import androidx.compose.ui.Modifier
12 | import androidx.compose.ui.draw.drawBehind
13 | import androidx.compose.ui.graphics.Brush
14 | import androidx.compose.ui.graphics.Color
15 | import androidx.compose.ui.graphics.StrokeCap
16 | import androidx.compose.ui.graphics.drawscope.Stroke
17 | import androidx.compose.ui.unit.dp
18 | import com.usage.runSimpleComposableWindow
19 |
20 | fun main() = runSimpleComposableWindow {
21 | DisneyLogoAnimation()
22 | }
23 |
24 | @Composable
25 | fun DisneyLogoAnimation() {
26 | val sweepAngle = 135f
27 | val animationDuration = 1000
28 | val plusAnimationDuration = 300
29 | val animationDelay = 100
30 | var animationPlayed by remember {
31 | mutableStateOf(false)
32 | }
33 | var plusAnimationPlayed by remember {
34 | mutableStateOf(false)
35 | }
36 |
37 | val currentPercent = animateFloatAsState(
38 | targetValue = if (animationPlayed) sweepAngle else 0f,
39 | animationSpec = tween(
40 | durationMillis = animationDuration,
41 | delayMillis = animationDelay,
42 | easing = FastOutLinearInEasing
43 | ),
44 | finishedListener = {
45 | plusAnimationPlayed = true
46 | }
47 | )
48 |
49 | val scalePercent = animateFloatAsState(
50 | targetValue = if (plusAnimationPlayed) 1f else 0f,
51 | animationSpec = tween(
52 | durationMillis = plusAnimationDuration,
53 | delayMillis = 0
54 | )
55 | )
56 |
57 | LaunchedEffect(key1 = true) {
58 | animationPlayed = true
59 | }
60 |
61 | Box(
62 | modifier = Modifier
63 | .fillMaxSize()
64 | .background(Background),
65 | contentAlignment = Alignment.Center
66 | ) {
67 | Box(modifier = Modifier
68 | .size(200.dp)
69 | .drawBehind {
70 | drawArc(
71 | brush = Brush.linearGradient(
72 | 0f to GradientColor1,
73 | 0.2f to GradientColor2,
74 | 0.35f to GradientColor3,
75 | 0.45f to GradientColor4,
76 | 0.75f to GradientColor5,
77 | ),
78 | startAngle = -152f,
79 | sweepAngle = currentPercent.value,
80 | useCenter = false,
81 | style = Stroke(width = 10f, cap = StrokeCap.Round)
82 | )
83 | }) { }
84 | Row {
85 | // Image(
86 | // painter = painterResource(id = R.drawable.ic_disney_logo_text),
87 | // contentDescription = "Disney Logo Text",
88 | // colorFilter = ColorFilter.tint(Color.White),
89 | // modifier = Modifier.size(200.dp)
90 | // )
91 | // Image(
92 | // painter = painterResource(id = R.drawable.ic_plus),
93 | // contentDescription = "Plus Image",
94 | // colorFilter = ColorFilter.tint(Color.White),
95 | // modifier = Modifier
96 | // .size(50.dp)
97 | // .align(Alignment.CenterVertically)
98 | // .scale(scalePercent.value)
99 | // )
100 | }
101 | }
102 | }
103 |
104 | private val Purple200 = Color(0xFFBB86FC)
105 | private val Purple500 = Color(0xFF6200EE)
106 | private val Purple700 = Color(0xFF3700B3)
107 | private val Teal200 = Color(0xFF03DAC5)
108 | private val Background = Color(0xFF111D52)
109 | private val GradientColor1 = Color(0xFF0E1956)
110 | private val GradientColor2 = Color(0xFF092474)
111 | private val GradientColor3 = Color(0xFF0170B6)
112 | private val GradientColor4 = Color(0xFF19FAFF)
113 | private val GradientColor5 = Color(0xFFFDFFF8)
114 |
--------------------------------------------------------------------------------
/usage/src/jvmTest/kotlin/TestDisposableEffect.kt:
--------------------------------------------------------------------------------
1 | import androidx.compose.foundation.layout.Column
2 | import androidx.compose.material.Button
3 | import androidx.compose.material.Text
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.runtime.*
6 | import com.usage.runSimpleComposableWindow
7 |
8 | @Composable
9 | fun TestDisposableEffect(someArg:Int) {
10 | Text("TestDisposableEffect $someArg")
11 | DisposableEffect(key1 = someArg, key2 = Unit) {
12 | // lifecycle.addObserver(lifecycleObserver)
13 | println("add subsciption")
14 | onDispose {
15 | println("remove subsciption")
16 | // lifecycle.removeObserver(lifecycleObserver)
17 | }
18 | }
19 | }
20 |
21 | fun main() = runSimpleComposableWindow {
22 | var counter by remember { mutableStateOf(0) }
23 | Column {
24 | TestDisposableEffect(counter)
25 | Button(onClick = {
26 | counter++
27 | }) {
28 | Text("click $counter")
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/usage/src/jvmTest/kotlin/TestKeyboard.kt:
--------------------------------------------------------------------------------
1 | import androidx.compose.foundation.background
2 | import androidx.compose.ui.window.application
3 | import androidx.compose.ui.window.Window
4 | import androidx.compose.ui.window.WindowState
5 | import androidx.compose.material.MaterialTheme
6 | import androidx.compose.material.Text
7 | import androidx.compose.foundation.focusable
8 | import androidx.compose.foundation.interaction.collectIsFocusedAsState
9 | import androidx.compose.foundation.interaction.MutableInteractionSource
10 | import androidx.compose.foundation.layout.Box
11 | import androidx.compose.foundation.layout.Column
12 | import androidx.compose.foundation.layout.fillMaxSize
13 | import androidx.compose.foundation.layout.height
14 | import androidx.compose.foundation.layout.padding
15 | import androidx.compose.foundation.layout.size
16 | import androidx.compose.foundation.layout.Spacer
17 | import androidx.compose.foundation.shape.RoundedCornerShape
18 | import androidx.compose.runtime.Composable
19 | import androidx.compose.runtime.mutableStateOf
20 | import androidx.compose.runtime.remember
21 | import androidx.compose.ui.Alignment
22 | import androidx.compose.ui.graphics.Color
23 | import androidx.compose.ui.graphics.lerp
24 | import androidx.compose.ui.ExperimentalComposeUiApi
25 | import androidx.compose.ui.Modifier
26 | import androidx.compose.ui.draw.clip
27 | import androidx.compose.ui.input.key.*
28 | import androidx.compose.ui.input.pointer.PointerEventType
29 | import androidx.compose.ui.input.pointer.onPointerEvent
30 | import androidx.compose.ui.unit.dp
31 | import androidx.compose.ui.unit.IntSize
32 | import androidx.compose.ui.unit.DpSize
33 |
34 | fun main() = application {
35 | Window(
36 | state = WindowState(size = DpSize(350.dp, 450.dp)),
37 | onCloseRequest = ::exitApplication
38 | ) {
39 | val clicks = remember { mutableStateOf(0) }
40 | Column(
41 | modifier = Modifier.padding(40.dp)
42 | ) {
43 | Text(text = "Clicks: ${clicks.value}")
44 | Spacer(modifier = Modifier.height(20.dp))
45 | for (x in 1..5) {
46 | FocusableBox("Button $x", { clicks.value++ })
47 | Spacer(modifier = Modifier.height(20.dp))
48 | }
49 | }
50 | }
51 | }
52 |
53 | @OptIn(ExperimentalComposeUiApi::class)
54 | @Composable
55 | fun FocusableBox(
56 | text: String = "",
57 | onClick: () -> Unit = {},
58 | size: IntSize = IntSize(200, 35)
59 | ) {
60 | val keyPressedState = remember { mutableStateOf(false) }
61 | val interactionSource = remember { MutableInteractionSource() }
62 | val backgroundColor = if (interactionSource.collectIsFocusedAsState().value) {
63 | if (keyPressedState.value)
64 | lerp(MaterialTheme.colors.secondary, Color(64, 64, 64), 0.3f)
65 | else
66 | MaterialTheme.colors.secondary
67 | } else {
68 | MaterialTheme.colors.primary
69 | }
70 | Box(
71 | modifier = Modifier
72 | .clip(RoundedCornerShape(4.dp))
73 | .background(backgroundColor)
74 | .size(size.width.dp, size.height.dp)
75 | .onPointerEvent(PointerEventType.Press) { onClick() }
76 | .onPreviewKeyEvent {
77 | if (
78 | it.key == Key.Enter || it.key == Key.Spacebar || (it.isCtrlPressed && it.key == Key.Z)
79 | ) {
80 | when (it.type) {
81 | KeyEventType.KeyDown -> {
82 | keyPressedState.value = true
83 | }
84 | KeyEventType.KeyUp -> {
85 | keyPressedState.value = false
86 | onClick.invoke()
87 | }
88 | }
89 | }
90 | false
91 | }
92 | .focusable(interactionSource = interactionSource),
93 | contentAlignment = Alignment.Center
94 | ) {
95 | Text(text = text, color = Color.White)
96 | }
97 | }
--------------------------------------------------------------------------------
/usage/src/jvmTest/kotlin/TestMutableStateListOf.kt:
--------------------------------------------------------------------------------
1 | import androidx.compose.runtime.*
2 |
3 | @Composable
4 | fun TestMutableStateListOf() {
5 | var someIndex by remember { mutableStateOf(0) }
6 | val listState = mutableStateListOf("a", "b", "c")
7 | listState[someIndex] = "aa"
8 | }
9 |
--------------------------------------------------------------------------------
/usage/src/jvmTest/kotlin/TestPointerInput.kt:
--------------------------------------------------------------------------------
1 | import androidx.compose.animation.core.Animatable
2 | import androidx.compose.animation.core.calculateTargetValue
3 | import androidx.compose.animation.splineBasedDecay
4 | import androidx.compose.foundation.background
5 | import androidx.compose.foundation.gestures.awaitFirstDown
6 | import androidx.compose.foundation.gestures.horizontalDrag
7 | import androidx.compose.foundation.layout.Box
8 | import androidx.compose.foundation.layout.offset
9 | import androidx.compose.foundation.layout.size
10 | import androidx.compose.material.Text
11 | import androidx.compose.runtime.remember
12 | import androidx.compose.ui.Modifier
13 | import androidx.compose.ui.composed
14 | import androidx.compose.ui.graphics.Color
15 | import androidx.compose.ui.input.pointer.consumePositionChange
16 | import androidx.compose.ui.input.pointer.pointerInput
17 | import androidx.compose.ui.input.pointer.positionChange
18 | import androidx.compose.ui.input.pointer.util.VelocityTracker
19 | import androidx.compose.ui.unit.IntOffset
20 | import androidx.compose.ui.unit.dp
21 | import com.usage.runSimpleComposableWindow
22 | import kotlinx.coroutines.coroutineScope
23 | import kotlinx.coroutines.launch
24 | import kotlin.math.absoluteValue
25 | import kotlin.math.roundToInt
26 |
27 | private fun Modifier.swipeToDismiss(
28 | onDismissed: () -> Unit
29 | ): Modifier = composed {
30 | // This `Animatable` stores the horizontal offset for the element.
31 | val offsetX = remember { Animatable(0f) }
32 | pointerInput(Unit) {
33 | // Used to calculate a settling position of a fling animation.
34 | val decay = splineBasedDecay(this)
35 | // Wrap in a coroutine scope to use suspend functions for touch events and animation.
36 | coroutineScope {
37 | while (true) {
38 | // Wait for a touch down event.
39 | val pointerId = awaitPointerEventScope { awaitFirstDown().id }
40 | // Interrupt any ongoing animation.
41 | offsetX.stop()
42 | // Prepare for drag events and record velocity of a fling.
43 | val velocityTracker = VelocityTracker()
44 | // Wait for drag events.
45 | awaitPointerEventScope {
46 | horizontalDrag(pointerId) { change ->
47 | // Record the position after offset
48 | val horizontalDragOffset = offsetX.value + change.positionChange().x
49 | launch {
50 | // Overwrite the `Animatable` value while the element is dragged.
51 | offsetX.snapTo(horizontalDragOffset)
52 | }
53 | // Record the velocity of the drag.
54 | velocityTracker.addPosition(change.uptimeMillis, change.position)
55 | // Consume the gesture event, not passed to external
56 | change.consumePositionChange()
57 | }
58 | }
59 | // Dragging finished. Calculate the velocity of the fling.
60 | val velocity = velocityTracker.calculateVelocity().x
61 | // Calculate where the element eventually settles after the fling animation.
62 | val targetOffsetX = decay.calculateTargetValue(offsetX.value, velocity)
63 | // The animation should end as soon as it reaches these bounds.
64 | offsetX.updateBounds(
65 | lowerBound = -size.width.toFloat(),
66 | upperBound = size.width.toFloat()
67 | )
68 | launch {
69 | if (targetOffsetX.absoluteValue <= size.width) {
70 | // Not enough velocity; Slide back to the default position.
71 | offsetX.animateTo(targetValue = 0f, initialVelocity = velocity)
72 | } else {
73 | // Enough velocity to slide away the element to the edge.
74 | offsetX.animateDecay(velocity, decay)
75 | // The element was swiped away.
76 | onDismissed()
77 | }
78 | }
79 | }
80 | }
81 | }
82 | // Apply the horizontal offset to the element.
83 | .offset { IntOffset(offsetX.value.roundToInt(), 0) }
84 | }
85 |
86 | fun main() {
87 | runSimpleComposableWindow() {
88 | Box(
89 | Modifier.size(200.dp, 50.dp)
90 | .background(Color.Gray)
91 | .swipeToDismiss {
92 | println("swiped")
93 | }
94 | ) {
95 | Text("Swipe me")
96 | }
97 | }
98 | }
99 |
100 |
--------------------------------------------------------------------------------
/usage/src/jvmTest/kotlin/TestProduceState.kt:
--------------------------------------------------------------------------------
1 | import androidx.compose.runtime.Composable
2 | import androidx.compose.runtime.State
3 | import androidx.compose.runtime.produceState
4 | import kotlinx.coroutines.delay
5 |
6 | //Composables with a return type should be named the way you'd name a normal Kotlin function,
7 | // starting with a lowercase letter.
8 | @Composable
9 | fun testProduceState(): State {
10 | val key = Unit
11 | return produceState("Init", key) {
12 | value = "Second"
13 | delay(1)
14 | value = "Third"
15 | awaitDispose {
16 | println("on dispose")
17 | } // return's Nothing
18 | println("Unreachable code")
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/usage/src/jvmTest/kotlin/TestRainbow.kt:
--------------------------------------------------------------------------------
1 | import androidx.compose.ui.graphics.Color
2 | import com.usage.runSimpleComposableWindow
3 | import lib.vector.ColorPallet
4 |
5 | fun main() {
6 | runSimpleComposableWindow() {
7 | ColorPallet(initColor = Color.Red.value) { }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/usage/src/jvmTest/kotlin/TestRecomposition.kt:
--------------------------------------------------------------------------------
1 | package com.usage
2 |
3 | import androidx.compose.foundation.layout.Column
4 | import androidx.compose.material.Text
5 | import androidx.compose.runtime.mutableStateOf
6 | import androidx.compose.runtime.*
7 | import androidx.compose.ui.unit.dp
8 | import androidx.compose.ui.window.Window
9 | import androidx.compose.ui.window.application
10 | import androidx.compose.ui.window.rememberWindowState
11 | import lib.vector.TxtButton
12 | import kotlin.random.Random
13 |
14 | fun main() {
15 | runSimpleComposableWindow {
16 | Column {
17 | println("recompose main")
18 | var counter by remember { mutableStateOf(0) }
19 | NotRecomposed(counter / 5) {
20 | println("lambda")
21 | }
22 | TxtButton("Increment $counter") {
23 | counter++
24 | }
25 | }
26 | }
27 | }
28 |
29 | @Composable
30 | fun NotRecomposed(arg1:Int, lambda: () -> Unit) {
31 | println("recompose NotRecomposed, arg1: $arg1")
32 | lambda()
33 | Text("NotRecomposed + ${Random.nextInt()}")
34 | }
35 |
--------------------------------------------------------------------------------
/usage/src/jvmTest/kotlin/TestRecompositionKey.kt:
--------------------------------------------------------------------------------
1 | import androidx.compose.foundation.layout.Column
2 | import androidx.compose.material.Text
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.key
5 | import com.usage.runSimpleComposableWindow
6 |
7 | private data class Movie(
8 | val id: Int,
9 | val name: String
10 | )
11 |
12 | @Composable
13 | private fun MoviesScreen(movies: List) {
14 | Column {
15 | for (movie in movies) {
16 | key(movie.id) { // Unique ID for this movie
17 | MovieOverview(movie)
18 | }
19 | }
20 | }
21 | }
22 |
23 | @Composable
24 | private fun MovieOverview(movie: Movie) {
25 | Text("Movie ${movie.name}")
26 | }
27 |
28 | fun main() {
29 | runSimpleComposableWindow {
30 | MoviesScreen(
31 | listOf(
32 | Movie(1, "Godzila"),
33 | Movie(2, "Kong"),
34 | )
35 | )
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/usage/src/jvmTest/kotlin/TestRememberUpdatedState.kt:
--------------------------------------------------------------------------------
1 | import androidx.compose.foundation.layout.Box
2 | import androidx.compose.foundation.layout.fillMaxSize
3 | import androidx.compose.runtime.*
4 | import androidx.compose.ui.Alignment
5 | import androidx.compose.ui.Modifier
6 | import kotlinx.coroutines.delay
7 |
8 | @Composable
9 | fun LandingScreen(modifier: Modifier = Modifier, onTimeout: () -> Unit) {
10 | Box(modifier = modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
11 | // This will always refer to the latest onTimeout function that
12 | // LandingScreen was recomposed with
13 | val currentOnTimeout by rememberUpdatedState(onTimeout) // IMPORTANT HERE
14 |
15 | // Create an effect that matches the lifecycle of LandingScreen.
16 | // If LandingScreen recomposes or onTimeout changes,
17 | // the delay shouldn't start again.
18 | LaunchedEffect(true) {
19 | delay(500)
20 | currentOnTimeout()
21 | }
22 |
23 | // Image(painterResource(id = R.drawable.ic_crane_drawer), contentDescription = null)
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/usage/src/jvmTest/kotlin/TestScroll.kt:
--------------------------------------------------------------------------------
1 | import androidx.compose.foundation.gestures.calculateZoom
2 | import androidx.compose.foundation.layout.Box
3 | import androidx.compose.foundation.layout.fillMaxSize
4 | import androidx.compose.ui.ExperimentalComposeUiApi
5 | import androidx.compose.ui.Modifier
6 | import androidx.compose.ui.input.pointer.*
7 | import com.usage.runSimpleComposableWindow
8 |
9 | @OptIn(ExperimentalComposeUiApi::class)
10 | fun main() = runSimpleComposableWindow {
11 | Box(
12 | Modifier.fillMaxSize()
13 | .pointerInput(Unit) {
14 | while (true) {
15 | val event = awaitPointerEventScope {
16 | awaitPointerEvent()
17 | }
18 | if (event.type == PointerEventType.Scroll) {
19 | println("scroll $event")
20 | event.changes.first().scrollDelta.y
21 | event.keyboardModifiers.isShiftPressed//right
22 | event.keyboardModifiers.isCtrlPressed//zoom
23 | }
24 | }
25 | }
26 | )
27 | }
28 |
--------------------------------------------------------------------------------
/usage/src/jvmTest/kotlin/TestSimpleSideEffect.kt:
--------------------------------------------------------------------------------
1 |
2 | import androidx.compose.material.Text
3 | import androidx.compose.runtime.SideEffect
4 | import com.usage.runSimpleClickerWindow
5 |
6 |
7 | fun main() = runSimpleClickerWindow { clicksCount ->
8 | Text("clicksCount: $clicksCount")
9 | SideEffect {
10 | //On every successful composition
11 | println("side effect, clicksCount: $clicksCount")
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/usage/src/jvmTest/kotlin/TestSnapshotFlow.kt:
--------------------------------------------------------------------------------
1 | import androidx.compose.foundation.layout.Column
2 | import androidx.compose.runtime.*
3 | import com.usage.runSimpleComposableWindow
4 | import kotlinx.coroutines.InternalCoroutinesApi
5 | import kotlinx.coroutines.flow.FlowCollector
6 | import kotlinx.coroutines.flow.collect
7 | import kotlinx.coroutines.flow.filter
8 | import lib.vector.TxtButton
9 |
10 | @OptIn(InternalCoroutinesApi::class)
11 | @Composable
12 | fun testSnapshotFlow() {
13 | val someState: MutableState = remember { mutableStateOf(1) }
14 | LaunchedEffect(Unit) {
15 | snapshotFlow { someState.value }
16 | .filter { it % 2 == 0 }
17 | .collect(object : FlowCollector {
18 | override suspend fun emit(value: Int) {
19 | println("collect $value")
20 | }
21 | })
22 | }
23 |
24 | }
25 |
26 | fun main() {
27 | runSimpleComposableWindow {
28 | var clicksCount by remember { mutableStateOf(0) }
29 | Column {
30 | TxtButton("Increment $clicksCount") {
31 | clicksCount++
32 | }
33 | }
34 | LaunchedEffect(Unit) {
35 | snapshotFlow { clicksCount }
36 | .filter { it % 2 == 0 }
37 | .collect {
38 | println("flow $it")
39 | }
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/usage/src/jvmTest/kotlin/TestTouchInput.kt:
--------------------------------------------------------------------------------
1 | import androidx.compose.animation.core.Animatable
2 | import androidx.compose.animation.core.VectorConverter
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.gestures.awaitFirstDown
5 | import androidx.compose.foundation.layout.Box
6 | import androidx.compose.foundation.layout.fillMaxSize
7 | import androidx.compose.foundation.layout.offset
8 | import androidx.compose.foundation.layout.size
9 | import androidx.compose.foundation.shape.CircleShape
10 | import androidx.compose.material.Text
11 | import androidx.compose.runtime.Composable
12 | import androidx.compose.runtime.remember
13 | import androidx.compose.ui.Alignment
14 | import androidx.compose.ui.Modifier
15 | import androidx.compose.ui.geometry.Offset
16 | import androidx.compose.ui.graphics.Color
17 | import androidx.compose.ui.input.pointer.pointerInput
18 | import androidx.compose.ui.unit.IntOffset
19 | import androidx.compose.ui.unit.dp
20 | import com.usage.runSimpleComposableWindow
21 | import kotlinx.coroutines.coroutineScope
22 | import kotlinx.coroutines.launch
23 | import kotlin.math.roundToInt
24 |
25 | fun main() {
26 | runSimpleComposableWindow {
27 | MoveBoxWhereTapped()
28 | }
29 | }
30 |
31 | @Composable
32 | fun MoveBoxWhereTapped() {
33 | // Creates an `Animatable` to animate Offset and `remember` it.
34 | val animatedOffset = remember {
35 | Animatable(Offset(0f, 0f), androidx.compose.ui.geometry.Offset.VectorConverter)
36 | }
37 |
38 | Box(
39 | // The pointerInput modifier takes a suspend block of code
40 | Modifier.fillMaxSize().pointerInput(Unit) {
41 | // Create a new CoroutineScope to be able to create new
42 | // coroutines inside a suspend function
43 | coroutineScope {
44 | while (true) {
45 | // Wait for the user to tap on the screen
46 | val offset = awaitPointerEventScope {
47 | awaitFirstDown().position
48 | }
49 | // Launch a new coroutine to asynchronously animate to where
50 | // the user tapped on the screen
51 | launch {
52 | // Animate to the pressed position
53 | animatedOffset.animateTo(offset)
54 | }
55 | }
56 | }
57 | }
58 | ) {
59 | Text("Tap anywhere", Modifier.align(Alignment.Center))
60 | Box(
61 | Modifier
62 | .offset {
63 | // Use the animated offset as the offset of this Box
64 | IntOffset(
65 | animatedOffset.value.x.roundToInt(),
66 | animatedOffset.value.y.roundToInt()
67 | )
68 | }
69 | .size(40.dp)
70 | .background(Color(0xff3c1361), CircleShape)
71 | )
72 | }
73 | }
74 |
75 |
--------------------------------------------------------------------------------