├── .editorconfig ├── .gitignore ├── README.md ├── android ├── build.gradle.kts └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── example │ └── imageviewer │ └── MainActivity.kt ├── build.gradle.kts ├── buildSrc ├── build.gradle.kts ├── settings.gradle.kts └── src │ └── main │ └── kotlin │ └── const.kt ├── clipboard ├── build.gradle.kts └── src │ └── main │ └── java │ └── com │ └── intellij │ ├── ArrayUtilRt.java │ ├── CharArrayCharSequence.java │ ├── CharArrayUtil.java │ ├── CharSequenceBackedByArray.java │ ├── CharSequenceWithStringHash.java │ ├── ClientCopyPasteManager.kt │ ├── ClipboardConfig.kt │ ├── ClipboardSynchronizer.java │ ├── ClipboardUtil.java │ ├── Comparing.java │ ├── CopyPasteManager.java │ ├── CopyPasteManagerEx.java │ ├── CpuArch.java │ ├── DataContext.java │ ├── ImagePasteProvider.kt │ ├── LinkedListWithSum.java │ ├── LocalCopyPasteManager.java │ ├── Log.kt │ ├── Pair.java │ ├── PasteProvider.java │ ├── Patches.java │ ├── ReflectionUtil.java │ ├── ReflectionUtilRt.java │ ├── StringUtil.java │ ├── StringUtilRt.java │ ├── Strings.java │ ├── SystemInfo.java │ └── SystemInfoRt.java ├── editor-run.sh ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── happy-new-year ├── build.gradle.kts └── src │ ├── commonMain │ └── kotlin │ │ └── com │ │ └── usage │ │ ├── BackgroundHills.kt │ │ ├── Cat.kt │ │ ├── ComposeShader.kt │ │ ├── HappyNewYear.kt │ │ ├── HappyNewYearText.kt │ │ ├── ManyChristmasTrees.kt │ │ ├── Snow.kt │ │ └── SnowDrifts.kt │ ├── jvmMain │ └── kotlin │ │ └── com │ │ └── usage │ │ ├── main.kt │ │ └── run_simple_window.kt │ └── jvmTest │ ├── README.md │ └── kotlin │ ├── TestAnimations.kt │ ├── TestDerivedStateOf.kt │ ├── TestDisney.kt │ ├── TestDisposableEffect.kt │ ├── TestMutableStateListOf.kt │ ├── TestPointerInput.kt │ ├── TestProduceState.kt │ ├── TestRecomposition.kt │ ├── TestRecompositionKey.kt │ ├── TestRememberUpdatedState.kt │ ├── TestSimpleSideEffect.kt │ ├── TestSnapshotFlow.kt │ └── TestTouchInput.kt ├── lib ├── build.gradle.kts └── src │ ├── androidMain │ ├── AndroidManifest.xml │ ├── kotlin │ │ └── lib │ │ │ └── vector │ │ │ ├── GeneratedLayer.kt │ │ │ └── utils │ │ │ └── utils_android.kt │ └── res │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_imageviewer.xml │ │ └── ic_imageviewer_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_imageviewer.png │ │ ├── ic_imageviewer_background.png │ │ ├── ic_imageviewer_foreground.png │ │ └── ic_imageviewer_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_imageviewer.png │ │ ├── ic_imageviewer_background.png │ │ ├── ic_imageviewer_foreground.png │ │ └── ic_imageviewer_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_imageviewer.png │ │ ├── ic_imageviewer_background.png │ │ ├── ic_imageviewer_foreground.png │ │ └── ic_imageviewer_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_imageviewer.png │ │ ├── ic_imageviewer_background.png │ │ ├── ic_imageviewer_foreground.png │ │ └── ic_imageviewer_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_imageviewer.png │ │ ├── ic_imageviewer_background.png │ │ ├── ic_imageviewer_foreground.png │ │ └── ic_imageviewer_round.png │ │ ├── raw │ │ ├── back.png │ │ ├── blur_off.png │ │ ├── blur_on.png │ │ ├── dots.png │ │ ├── empty.png │ │ ├── filter_unknown.png │ │ ├── grayscale_off.png │ │ ├── grayscale_on.png │ │ ├── pixel_off.png │ │ ├── pixel_on.png │ │ └── refresh.png │ │ ├── values-ru │ │ └── strings.xml │ │ └── values │ │ └── strings.xml │ ├── commonMain │ └── kotlin │ │ └── lib │ │ └── vector │ │ ├── DisplayMode.kt │ │ ├── GeneratedLayer.kt │ │ ├── GeneratedScope.kt │ │ ├── TxtButton.kt │ │ ├── bezier.kt │ │ ├── line.kt │ │ ├── math.kt │ │ ├── serializable.kt │ │ └── utils │ │ ├── Base64.kt │ │ └── utils_common.kt │ ├── jvmMain │ └── kotlin │ │ └── lib │ │ └── vector │ │ ├── ColorPicker.kt │ │ ├── EditMode.kt │ │ ├── GenerateCode.kt │ │ ├── GeneratedLayer.kt │ │ ├── InitializeByGeneratedScope.kt │ │ ├── intercept │ │ ├── InterceptCubicBezier.kt │ │ └── InterceptLinear.kt │ │ ├── jvm_util.kt │ │ ├── point_id.kt │ │ └── utils │ │ └── utils_desktop.kt │ └── jvmTest │ └── kotlin │ └── lib │ └── vector │ ├── BezierTest.kt │ └── GenerateCodeKtTest.kt ├── run.sh ├── settings.gradle.kts └── usage ├── build.gradle.kts └── src ├── commonMain └── kotlin │ └── com │ └── usage │ ├── CatBitmap.kt.txt │ └── UsageInCommon.kt ├── jvmMain └── kotlin │ └── com │ └── usage │ ├── main.kt │ └── run_simple_window.kt └── jvmTest ├── README.md └── kotlin ├── BitSortTest.kt ├── ReproduceBugScaleOffset.kt ├── TestAnimations.kt ├── TestDerivedStateOf.kt ├── TestDisney.kt ├── TestDisposableEffect.kt ├── TestKeyboard.kt ├── TestMutableStateListOf.kt ├── TestPointerInput.kt ├── TestProduceState.kt ├── TestRainbow.kt ├── TestRecomposition.kt ├── TestRecompositionKey.kt ├── TestRememberUpdatedState.kt ├── TestScroll.kt ├── TestSimpleSideEffect.kt ├── TestSnapshotFlow.kt └── TestTouchInput.kt /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | # charset = utf-8 5 | # end_of_line = lf 6 | indent_size = 2 7 | # indent_style = space 8 | # insert_final_newline = false 9 | max_line_length = 150 10 | # tab_width = 4 11 | # ij_continuation_indent_size = 8 12 | # ij_formatter_off_tag = @formatter:off 13 | # ij_formatter_on_tag = @formatter:on 14 | # ij_formatter_tags_enabled = false 15 | # ij_smart_tabs = false 16 | # ij_wrap_on_typing = false 17 | 18 | [{jvm_app_jvm2.kt, *.kts, *.kt, *.gradle.kts}] 19 | # ij_kotlin_align_in_columns_case_branch = false 20 | # ij_kotlin_align_multiline_binary_operation = false 21 | # ij_kotlin_align_multiline_extends_list = false 22 | # ij_kotlin_align_multiline_method_parentheses = false 23 | # ij_kotlin_align_multiline_parameters = true 24 | # ij_kotlin_align_multiline_parameters_in_calls = false 25 | # ij_kotlin_assignment_wrap = off 26 | # ij_kotlin_blank_lines_after_class_header = 0 27 | # ij_kotlin_blank_lines_around_block_when_branches = 0 28 | # ij_kotlin_block_comment_at_first_column = true 29 | # ij_kotlin_call_parameters_new_line_after_left_paren = false 30 | # ij_kotlin_call_parameters_right_paren_on_new_line = false 31 | # ij_kotlin_call_parameters_wrap = off 32 | # ij_kotlin_catch_on_new_line = false 33 | # ij_kotlin_class_annotation_wrap = split_into_lines 34 | # ij_kotlin_continuation_indent_for_chained_calls = true 35 | # ij_kotlin_continuation_indent_for_expression_bodies = true 36 | # ij_kotlin_continuation_indent_in_argument_lists = true 37 | # ij_kotlin_continuation_indent_in_elvis = true 38 | # ij_kotlin_continuation_indent_in_if_conditions = true 39 | # ij_kotlin_continuation_indent_in_parameter_lists = true 40 | # ij_kotlin_continuation_indent_in_supertype_lists = true 41 | # ij_kotlin_else_on_new_line = false 42 | # ij_kotlin_enum_constants_wrap = off 43 | # ij_kotlin_extends_list_wrap = off 44 | # ij_kotlin_field_annotation_wrap = split_into_lines 45 | # ij_kotlin_finally_on_new_line = false 46 | # ij_kotlin_if_rparen_on_new_line = false 47 | # ij_kotlin_import_nested_classes = false 48 | # ij_kotlin_insert_whitespaces_in_simple_one_line_method = true 49 | # ij_kotlin_keep_blank_lines_before_right_brace = 2 50 | # ij_kotlin_keep_blank_lines_in_code = 2 51 | # ij_kotlin_keep_blank_lines_in_declarations = 2 52 | # ij_kotlin_keep_first_column_comment = true 53 | # ij_kotlin_keep_indents_on_empty_lines = false 54 | # ij_kotlin_keep_line_breaks = true 55 | # ij_kotlin_lbrace_on_next_line = false 56 | # ij_kotlin_line_comment_add_space = false 57 | # ij_kotlin_line_comment_at_first_column = true 58 | # ij_kotlin_method_annotation_wrap = split_into_lines 59 | # ij_kotlin_method_call_chain_wrap = off 60 | # ij_kotlin_method_parameters_new_line_after_left_paren = false 61 | # ij_kotlin_method_parameters_right_paren_on_new_line = false 62 | # ij_kotlin_method_parameters_wrap = off 63 | # ij_kotlin_name_count_to_use_star_import = 5 64 | # ij_kotlin_name_count_to_use_star_import_for_members = 3 65 | # ij_kotlin_parameter_annotation_wrap = off 66 | # ij_kotlin_space_after_comma = true 67 | # ij_kotlin_space_after_extend_colon = true 68 | # ij_kotlin_space_after_type_colon = true 69 | # ij_kotlin_space_before_catch_parentheses = true 70 | # ij_kotlin_space_before_comma = false 71 | # ij_kotlin_space_before_extend_colon = true 72 | # ij_kotlin_space_before_for_parentheses = true 73 | # ij_kotlin_space_before_if_parentheses = true 74 | # ij_kotlin_space_before_lambda_arrow = true 75 | # ij_kotlin_space_before_type_colon = false 76 | # ij_kotlin_space_before_when_parentheses = true 77 | # ij_kotlin_space_before_while_parentheses = true 78 | # ij_kotlin_spaces_around_additive_operators = true 79 | # ij_kotlin_spaces_around_assignment_operators = true 80 | # ij_kotlin_spaces_around_equality_operators = true 81 | # ij_kotlin_spaces_around_function_type_arrow = true 82 | # ij_kotlin_spaces_around_logical_operators = true 83 | # ij_kotlin_spaces_around_multiplicative_operators = true 84 | # ij_kotlin_spaces_around_range = false 85 | # ij_kotlin_spaces_around_relational_operators = true 86 | # ij_kotlin_spaces_around_unary_operator = false 87 | # ij_kotlin_spaces_around_when_arrow = true 88 | # ij_kotlin_variable_annotation_wrap = off 89 | # ij_kotlin_while_on_new_line = false 90 | # ij_kotlin_wrap_elvis_expressions = 1 91 | # ij_kotlin_wrap_expression_body_functions = 0 92 | # ij_kotlin_wrap_first_method_in_call_chain = false 93 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ###### Custom ############################# 2 | size_story.txt 3 | 4 | ###### Gradle ############################## 5 | build 6 | .gradle 7 | 8 | ###### Idea ############################### 9 | .idea 10 | *.ipr 11 | *.iws 12 | *.iml 13 | 14 | !hand-test-empty-project/* 15 | 16 | ###### Android ############################# 17 | out/ 18 | local.properties 19 | 20 | ##### OS Specific ########################## 21 | .DS_Store 22 | Thumbs.db 23 | 24 | temp_id_rsa 25 | .exclude 26 | .idea_system** 27 | .system** 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Compose Desktop vector graphics and animation editor with codegeneration 2 | To launch: 3 | `./run.sh` or `./gradlew happy-new-year:run` 4 | 5 | https://user-images.githubusercontent.com/2170973/146974854-e423e072-f9eb-4dd7-8769-a558baf1a397.mp4 6 | 7 | ### Addition 8 | Хочу сделать редактор 2D растровой и векторной графики. Причём вместо SVG или других форматов - будет кодогенерация на Kotlin. 9 | Этот проект ещё сырой, в идеале Я хочу оформить его как плагин к Intellij IDEA. 10 | Вот видео на русском про задумку: https://www.youtube.com/watch?v=dB9yjUBq8oQ 11 | 12 | This project is very early and may have a lot of bugs. 13 | I want to know we interest of community. 14 | Please start repo If you like it and what to have IDEA plugin with this Compose-Vector editor. 15 | 16 | 17 | Unfortunately have problems with MacOS high screens. I will try to fix it. 18 | For discusssions: https://github.com/avdim/compose-vector/discussions/1 19 | 20 | Вот тут можно поиграть с шейдерами https://shaders.skia.org/?id=%40iMouse 21 | Про шейдеры тут можно почитать https://www.pushing-pixels.org/2021/09/22/skia-shaders-in-compose-desktop.html 22 | -------------------------------------------------------------------------------- /android/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.application") 3 | kotlin("android") 4 | id("org.jetbrains.compose") 5 | } 6 | 7 | android { 8 | compileSdk = 31 9 | 10 | defaultConfig { 11 | minSdk = 21 12 | targetSdk = 31 13 | versionCode = 1 14 | versionName = "1.0" 15 | } 16 | 17 | compileOptions { 18 | sourceCompatibility = JavaVersion.VERSION_1_8 19 | targetCompatibility = JavaVersion.VERSION_1_8 20 | } 21 | } 22 | 23 | dependencies { 24 | implementation(project(":usage")) 25 | implementation("androidx.activity:activity-compose:1.3.1") 26 | } 27 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 17 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /android/src/main/java/example/imageviewer/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package example.imageviewer 2 | 3 | import android.os.Bundle 4 | import androidx.appcompat.app.AppCompatActivity 5 | import androidx.activity.compose.setContent 6 | import com.usage.* 7 | 8 | class MainActivity : AppCompatActivity() { 9 | override fun onCreate(savedInstanceState: Bundle?) { 10 | super.onCreate(savedInstanceState) 11 | 12 | setContent { 13 | UsageInCommon() 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("multiplatform") version KOTLIN_VERSION apply false 3 | // kotlin("plugin.serialization") version KOTLIN_VERSION apply false 4 | id("org.jetbrains.compose") version COMPOSE_VERSION apply false 5 | } 6 | 7 | buildscript { 8 | repositories { 9 | google() 10 | mavenCentral() 11 | maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") 12 | } 13 | 14 | dependencies { 15 | // classpath("org.jetbrains.compose:compose-gradle-plugin:$COMPOSE_VERSION") 16 | classpath("com.android.tools.build:gradle:4.1.3") 17 | } 18 | } 19 | 20 | allprojects { 21 | repositories { 22 | google() 23 | mavenCentral() 24 | maven(url = "https://maven.pkg.jetbrains.space/public/p/compose/dev") 25 | } 26 | tasks.withType(AbstractTestTask::class) { 27 | testLogging { 28 | showStandardStreams = true 29 | events("passed", "failed") 30 | } 31 | } 32 | tasks.withType { 33 | kotlinOptions.jvmTarget = "11" 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /buildSrc/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version embeddedKotlinVersion 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | dependencies { 10 | 11 | } 12 | 13 | java { 14 | sourceCompatibility = JavaVersion.VERSION_11 15 | targetCompatibility = JavaVersion.VERSION_11 16 | } 17 | 18 | tasks.withType().configureEach { 19 | kotlinOptions.jvmTarget = "11" 20 | } 21 | -------------------------------------------------------------------------------- /buildSrc/settings.gradle.kts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avdim/compose-vector/e507f6946d389e8e320b8db736319d783596516f/buildSrc/settings.gradle.kts -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/const.kt: -------------------------------------------------------------------------------- 1 | //val KOTLIN_VERSION = "1.5.31" 2 | val KOTLIN_VERSION = "1.6.10" 3 | //val COMPOSE_VERSION = "1.0.0-beta5" 4 | val COMPOSE_VERSION = "1.0.1" 5 | val RADIANCE_VERSION = "5.0.0" 6 | -------------------------------------------------------------------------------- /clipboard/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | java 3 | kotlin("jvm") 4 | } 5 | 6 | java { 7 | if (true) { 8 | toolchain { 9 | languageVersion.set(JavaLanguageVersion.of(11)) 10 | if (false) { 11 | vendor.set(JvmVendorSpec.BELLSOFT) 12 | implementation.set(JvmImplementation.VENDOR_SPECIFIC) 13 | } 14 | } 15 | } else { 16 | sourceCompatibility = JavaVersion.VERSION_11 17 | targetCompatibility = JavaVersion.VERSION_11 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /clipboard/src/main/java/com/intellij/ArrayUtilRt.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 org.jetbrains.annotations.Contract; 5 | import org.jetbrains.annotations.NotNull; 6 | import org.jetbrains.annotations.Nullable; 7 | 8 | import java.io.File; 9 | import java.util.Collection; 10 | 11 | /** 12 | * A stripped-down version of {@link com.intellij.util.ArrayUtil}. 13 | * Intended to use by external (out-of-IDE-process) runners and helpers so it should not contain any library dependencies. 14 | */ 15 | public final class ArrayUtilRt { 16 | public static final short[] EMPTY_SHORT_ARRAY = new short[0]; 17 | public static final char[] EMPTY_CHAR_ARRAY = new char[0]; 18 | public static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; 19 | public static final int[] EMPTY_INT_ARRAY = new int[0]; 20 | public static final boolean[] EMPTY_BOOLEAN_ARRAY = new boolean[0]; 21 | @SuppressWarnings("SSBasedInspection") 22 | public static final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; 23 | @SuppressWarnings("SSBasedInspection") 24 | public static final String[] EMPTY_STRING_ARRAY = new String[0]; 25 | @SuppressWarnings("SSBasedInspection") 26 | public static final Class[] EMPTY_CLASS_ARRAY = new Class[0]; 27 | public static final long[] EMPTY_LONG_ARRAY = new long[0]; 28 | public static final Collection[] EMPTY_COLLECTION_ARRAY = new Collection[0]; 29 | public static final File[] EMPTY_FILE_ARRAY = new File[0]; 30 | 31 | @NotNull 32 | @Contract(pure = true) 33 | public static String[] toStringArray(@Nullable Collection collection) { 34 | return collection == null || collection.isEmpty() 35 | ? EMPTY_STRING_ARRAY : collection.toArray(EMPTY_STRING_ARRAY); 36 | } 37 | 38 | /** 39 | * @param src source array. 40 | * @param obj object to be found. 41 | * @return index of {@code obj} in the {@code src} array. 42 | * Returns {@code -1} if passed object isn't found. This method uses 43 | * {@code equals} of arrays elements to compare {@code obj} with 44 | * these elements. 45 | */ 46 | @Contract(pure = true) 47 | public static int find(@NotNull T[] src, @Nullable T obj) { 48 | return indexOf(src, obj, 0, src.length); 49 | } 50 | 51 | @Contract(pure = true) 52 | public static int indexOf(@NotNull T[] src, @Nullable T obj, int start, int end) { 53 | if (obj == null) { 54 | for (int i = start; i < end; i++) { 55 | if (src[i] == null) return i; 56 | } 57 | } else { 58 | for (int i = start; i < end; i++) { 59 | if (obj.equals(src[i])) return i; 60 | } 61 | } 62 | return -1; 63 | } 64 | } -------------------------------------------------------------------------------- /clipboard/src/main/java/com/intellij/CharArrayCharSequence.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 org.jetbrains.annotations.NotNull; 5 | 6 | public class CharArrayCharSequence implements CharSequenceBackedByArray, CharSequenceWithStringHash { 7 | protected final char[] myChars; 8 | protected final int myStart; 9 | protected final int myEnd; 10 | 11 | public CharArrayCharSequence(char... chars) { 12 | this(chars, 0, chars.length); 13 | } 14 | 15 | public CharArrayCharSequence(char[] chars, int start, int end) { 16 | if (start < 0 || end > chars.length || start > end) { 17 | throw new IndexOutOfBoundsException("chars.length:" + chars.length + ", start:" + start + ", end:" + end); 18 | } 19 | myChars = chars; 20 | myStart = start; 21 | myEnd = end; 22 | } 23 | 24 | @Override 25 | public final int length() { 26 | return myEnd - myStart; 27 | } 28 | 29 | @Override 30 | public final char charAt(int index) { 31 | return myChars[index + myStart]; 32 | } 33 | 34 | @NotNull 35 | @Override 36 | public CharSequence subSequence(int start, int end) { 37 | return start == 0 && end == length() ? this : new CharArrayCharSequence(myChars, myStart + start, myStart + end); 38 | } 39 | 40 | @Override 41 | @NotNull 42 | public String toString() { 43 | return new String(myChars, myStart, myEnd - myStart); //TODO StringFactory 44 | } 45 | 46 | @Override 47 | public char[] getChars() { 48 | if (myStart == 0) return myChars; 49 | char[] chars = new char[length()]; 50 | getChars(chars, 0); 51 | return chars; 52 | } 53 | 54 | @Override 55 | public void getChars(char[] dst, int dstOffset) { 56 | System.arraycopy(myChars, myStart, dst, dstOffset, length()); 57 | } 58 | 59 | @Override 60 | public boolean equals(Object anObject) { 61 | if (this == anObject) { 62 | return true; 63 | } 64 | if (anObject == null || getClass() != anObject.getClass() || length() != ((CharSequence) anObject).length()) { 65 | return false; 66 | } 67 | return CharArrayUtil.regionMatches(myChars, myStart, myEnd, (CharSequence) anObject); 68 | } 69 | 70 | /** 71 | * See {@link java.io.Reader#read(char[], int, int)}; 72 | */ 73 | public int readCharsTo(int start, char[] cbuf, int off, int len) { 74 | final int readChars = Math.min(len, length() - start); 75 | if (readChars <= 0) return -1; 76 | 77 | System.arraycopy(myChars, myStart + start, cbuf, off, readChars); 78 | return readChars; 79 | } 80 | 81 | private transient int hash; 82 | 83 | @Override 84 | public int hashCode() { 85 | int h = hash; 86 | if (h == 0) { 87 | hash = h = Strings.stringHashCode(myChars, myStart, myEnd); 88 | } 89 | return h; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /clipboard/src/main/java/com/intellij/CharSequenceBackedByArray.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 | 3 | package com.intellij; 4 | 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | /** 8 | * A char sequence based on a char array. May be used for performance optimizations. 9 | * 10 | * @author Maxim.Mossienko 11 | * @see CharArrayUtil#fromSequenceWithoutCopying(CharSequence) 12 | */ 13 | public interface CharSequenceBackedByArray extends CharSequence { 14 | // NOT guaranteed to return the array of the length of the original charSequence.length() - may be more for performance reasons. 15 | char[] getChars(); 16 | 17 | void getChars(char[] dst, int dstOffset); 18 | } 19 | -------------------------------------------------------------------------------- /clipboard/src/main/java/com/intellij/CharSequenceWithStringHash.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 | public interface CharSequenceWithStringHash extends CharSequence { 5 | @Override 6 | int hashCode(); 7 | } 8 | -------------------------------------------------------------------------------- /clipboard/src/main/java/com/intellij/ClientCopyPasteManager.kt: -------------------------------------------------------------------------------- 1 | // Copyright 2000-2021 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.awt.datatransfer.ClipboardOwner 5 | import java.awt.datatransfer.DataFlavor 6 | import java.awt.datatransfer.Transferable 7 | 8 | /** 9 | * A per-client service managing clipboard. 10 | * Take a look at [CopyPasteManagerEx] 11 | */ 12 | 13 | interface ClientCopyPasteManager { 14 | companion object { 15 | @JvmStatic 16 | fun getCurrentInstance(): ClientCopyPasteManager = LocalCopyPasteManager() 17 | } 18 | 19 | fun areDataFlavorsAvailable(vararg flavors: DataFlavor): Boolean 20 | fun getContents(): Transferable? 21 | } -------------------------------------------------------------------------------- /clipboard/src/main/java/com/intellij/ClipboardConfig.kt: -------------------------------------------------------------------------------- 1 | package com.intellij 2 | 3 | object ClipboardConfig { 4 | @JvmStatic 5 | val useMacNativeClipboard: Boolean = true 6 | } -------------------------------------------------------------------------------- /clipboard/src/main/java/com/intellij/ClipboardUtil.java: -------------------------------------------------------------------------------- 1 | // Copyright 2000-2021 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 org.jetbrains.annotations.NotNull; 5 | import org.jetbrains.annotations.Nullable; 6 | 7 | import java.awt.datatransfer.DataFlavor; 8 | import java.util.function.Supplier; 9 | 10 | public final class ClipboardUtil { 11 | private static final Log LOG = new Log(); 12 | 13 | public static E handleClipboardSafely(@NotNull Supplier supplier, E defaultValue) { 14 | try { 15 | return supplier.get(); 16 | } catch (IllegalStateException e) { 17 | if (SystemInfo.isWindows) { 18 | LOG.debug("Clipboard is busy"); 19 | } else { 20 | LOG.warn(e); 21 | } 22 | } catch (NullPointerException e) { 23 | LOG.warn("Java bug #6322854", e); 24 | } catch (IllegalArgumentException e) { 25 | LOG.warn("Java bug #7173464", e); 26 | } 27 | 28 | return defaultValue; 29 | } 30 | 31 | // public static @Nullable String getTextInClipboard() { 32 | // return CopyPasteManager.getInstance().getContents(DataFlavor.stringFlavor); 33 | // } 34 | } 35 | -------------------------------------------------------------------------------- /clipboard/src/main/java/com/intellij/Comparing.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 org.jetbrains.annotations.Contract; 5 | import org.jetbrains.annotations.NotNull; 6 | import org.jetbrains.annotations.Nullable; 7 | 8 | import java.util.*; 9 | 10 | /** 11 | * Null-safe {@code equal} methods. 12 | */ 13 | public final class Comparing { 14 | private Comparing() { 15 | } 16 | 17 | @Contract(value = "null,!null -> false; !null,null -> false; null,null -> true", pure = true) 18 | public static boolean equal(@Nullable T arg1, @Nullable T arg2) { 19 | if (arg1 == arg2) return true; 20 | if (arg1 == null || arg2 == null) { 21 | return false; 22 | } 23 | if (arg1 instanceof Object[] && arg2 instanceof Object[]) { 24 | Object[] arr1 = (Object[]) arg1; 25 | Object[] arr2 = (Object[]) arg2; 26 | return Arrays.equals(arr1, arr2); 27 | } 28 | if (arg1 instanceof CharSequence && arg2 instanceof CharSequence) { 29 | return equal((CharSequence) arg1, (CharSequence) arg2, true); 30 | } 31 | return arg1.equals(arg2); 32 | } 33 | 34 | /** 35 | * @deprecated same as {@link Arrays#equals(Object[], Object[])} 36 | */ 37 | public static boolean equal(@Nullable T[] arr1, @Nullable T[] arr2) { 38 | return Arrays.equals(arr1, arr2); 39 | } 40 | 41 | @Contract(value = "null,!null -> false; !null,null -> false; null,null -> true", pure = true) 42 | public static boolean equal(CharSequence s1, CharSequence s2) { 43 | return StringUtilRt.equal(s1, s2, true); 44 | } 45 | 46 | @Contract(value = "null,!null,_ -> false; !null,null,_ -> false; null,null,_ -> true", pure = true) 47 | public static boolean equal(@Nullable CharSequence s1, @Nullable CharSequence s2, boolean caseSensitive) { 48 | return StringUtilRt.equal(s1, s2, caseSensitive); 49 | } 50 | 51 | /** 52 | * @deprecated Use {@link Objects#equals(Object, Object)} 53 | */ 54 | @Deprecated 55 | @Contract(value = "null,!null -> false; !null,null -> false; null,null -> true", pure = true) 56 | public static boolean equal(@Nullable String arg1, @Nullable String arg2) { 57 | return arg1 == null ? arg2 == null : arg1.equals(arg2); 58 | } 59 | 60 | @Contract(value = "null,!null,_ -> false; !null,null,_ -> false; null,null,_ -> true", pure = true) 61 | public static boolean equal(@Nullable String arg1, @Nullable String arg2, boolean caseSensitive) { 62 | return arg1 == null ? arg2 == null : caseSensitive ? arg1.equals(arg2) : arg1.equalsIgnoreCase(arg2); 63 | } 64 | 65 | /** 66 | * Unlike {@link Objects#equals(Object, Object)}, considers {@code null} and {@code ""} equal. 67 | */ 68 | public static boolean strEqual(@Nullable String arg1, @Nullable String arg2) { 69 | return strEqual(arg1, arg2, true); 70 | } 71 | 72 | /** 73 | * Unlike {@link #equal(String, String, boolean)}, considers {@code null} and {@code ""} equal. 74 | */ 75 | public static boolean strEqual(@Nullable String arg1, @Nullable String arg2, boolean caseSensitive) { 76 | return equal(arg1 == null ? "" : arg1, arg2 == null ? "" : arg2, caseSensitive); 77 | } 78 | 79 | public static boolean haveEqualElements(@NotNull Collection a, @NotNull Collection b) { 80 | if (a.size() != b.size()) { 81 | return false; 82 | } 83 | 84 | Set aSet = new HashSet(a); 85 | for (T t : b) { 86 | if (!aSet.contains(t)) { 87 | return false; 88 | } 89 | } 90 | 91 | return true; 92 | } 93 | 94 | public static boolean haveEqualElements(@Nullable T[] a, @Nullable T[] b) { 95 | if (a == null || b == null) { 96 | //noinspection ArrayEquality 97 | return a == b; 98 | } 99 | 100 | if (a.length != b.length) { 101 | return false; 102 | } 103 | 104 | Set aSet = new HashSet(Arrays.asList(a)); 105 | for (T t : b) { 106 | if (!aSet.contains(t)) { 107 | return false; 108 | } 109 | } 110 | 111 | return true; 112 | } 113 | 114 | @SuppressWarnings("MethodNamesDifferingOnlyByCase") 115 | public static int hashcode(@Nullable Object obj) { 116 | return obj == null ? 0 : obj.hashCode(); 117 | } 118 | 119 | public static int hashcode(Object obj1, Object obj2) { 120 | return hashcode(obj1) ^ hashcode(obj2); 121 | } 122 | 123 | /** 124 | * @see AbstractSet#hashCode() 125 | */ 126 | public static int unorderedHashcode(@NotNull Collection collection) { 127 | int h = 0; 128 | for (Object obj : collection) { 129 | if (obj != null) { 130 | h += obj.hashCode(); 131 | } 132 | } 133 | return h; 134 | } 135 | 136 | public static int compare(byte o1, byte o2) { 137 | return o1 < o2 ? -1 : o1 == o2 ? 0 : 1; 138 | } 139 | 140 | public static int compare(boolean o1, boolean o2) { 141 | return o1 == o2 ? 0 : o1 ? 1 : -1; 142 | } 143 | 144 | public static int compare(int o1, int o2) { 145 | return o1 < o2 ? -1 : o1 == o2 ? 0 : 1; 146 | } 147 | 148 | public static int compare(long o1, long o2) { 149 | return o1 < o2 ? -1 : o1 == o2 ? 0 : 1; 150 | } 151 | 152 | public static int compare(double o1, double o2) { 153 | return Double.compare(o1, o2); 154 | } 155 | 156 | public static int compare(@Nullable byte[] o1, @Nullable byte[] o2) { 157 | //noinspection ArrayEquality 158 | if (o1 == o2) return 0; 159 | if (o1 == null) return 1; 160 | if (o2 == null) return -1; 161 | 162 | if (o1.length > o2.length) return 1; 163 | if (o1.length < o2.length) return -1; 164 | 165 | for (int i = 0; i < o1.length; i++) { 166 | if (o1[i] > o2[i]) return 1; 167 | else if (o1[i] < o2[i]) return -1; 168 | } 169 | 170 | return 0; 171 | } 172 | 173 | public static > int compare(@Nullable T o1, @Nullable T o2) { 174 | if (o1 == o2) return 0; 175 | if (o1 == null) return -1; 176 | if (o2 == null) return 1; 177 | return o1.compareTo(o2); 178 | } 179 | 180 | public static int compare(@Nullable T o1, @Nullable T o2, @NotNull Comparator notNullComparator) { 181 | if (o1 == o2) return 0; 182 | if (o1 == null) return -1; 183 | if (o2 == null) return 1; 184 | return notNullComparator.compare(o1, o2); 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /clipboard/src/main/java/com/intellij/CopyPasteManager.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 org.jetbrains.annotations.Nullable; 5 | 6 | import java.awt.datatransfer.DataFlavor; 7 | import java.awt.datatransfer.Transferable; 8 | 9 | public abstract class CopyPasteManager { 10 | 11 | public static CopyPasteManager getInstance() { 12 | return new CopyPasteManagerEx(); 13 | } 14 | 15 | public abstract boolean areDataFlavorsAvailable(DataFlavor... flavors); 16 | 17 | @Nullable 18 | public abstract Transferable getContents(); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /clipboard/src/main/java/com/intellij/CopyPasteManagerEx.java: -------------------------------------------------------------------------------- 1 | // Copyright 2000-2021 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 org.jetbrains.annotations.Nullable; 5 | 6 | import java.awt.datatransfer.DataFlavor; 7 | import java.awt.datatransfer.Transferable; 8 | 9 | public class CopyPasteManagerEx extends CopyPasteManager { 10 | 11 | @Override 12 | public boolean areDataFlavorsAvailable(DataFlavor... flavors) { 13 | return ClientCopyPasteManager.getCurrentInstance().areDataFlavorsAvailable(flavors); 14 | } 15 | 16 | @Override 17 | public @Nullable 18 | Transferable getContents() { 19 | return ClientCopyPasteManager.getCurrentInstance().getContents(); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /clipboard/src/main/java/com/intellij/CpuArch.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.Nullable; 5 | 6 | public enum CpuArch { 7 | X86(32), X86_64(64), ARM64(64), OTHER(0), UNKNOWN(0); 8 | 9 | /** 10 | * Machine word size, in bits. 11 | */ 12 | public final int width; 13 | 14 | CpuArch(int width) { 15 | if (width == 0) { 16 | try { 17 | width = Integer.parseInt(System.getProperty("sun.arch.data.model", "32")); 18 | } catch (NumberFormatException ignored) { 19 | } 20 | } 21 | this.width = width; 22 | } 23 | 24 | /** 25 | *

A CPU architecture this Java VM is executed on. 26 | * Here, {@link CpuArch#OTHER} is an architecture not yet supported by JetBrains Runtime, 27 | * and {@link CpuArch#UNKNOWN} means the code was unable to detect an architecture.

28 | * 29 | *

Note: may not correspond to the actual hardware if a JVM is "virtualized" (e.g. WoW64 or Rosetta 2).

30 | */ 31 | public static final CpuArch CURRENT; 32 | 33 | static { 34 | String arch = System.getProperty("os.arch"); 35 | if ("x86_64".equals(arch) || "amd64".equals(arch)) { 36 | CURRENT = X86_64; 37 | } else if ("i386".equals(arch) || "x86".equals(arch)) { 38 | CURRENT = X86; 39 | } else if ("aarch64".equals(arch) || "arm64".equals(arch)) { 40 | CURRENT = ARM64; 41 | } else if (arch == null || arch.trim().isEmpty()) { 42 | CURRENT = UNKNOWN; 43 | } else { 44 | CURRENT = OTHER; 45 | } 46 | } 47 | 48 | public static boolean isIntel32() { 49 | return CURRENT == X86; 50 | } 51 | 52 | public static boolean isIntel64() { 53 | return CURRENT == X86_64; 54 | } 55 | 56 | public static boolean isArm64() { 57 | return CURRENT == ARM64; 58 | } 59 | 60 | public static boolean is32Bit() { 61 | return CURRENT.width == 32; 62 | } 63 | 64 | /** 65 | * The method tries to detect whether this JVM is executed in a known emulated environment - Rosetta 2, WoW64, etc. 66 | */ 67 | public static boolean isEmulated() { 68 | if (ourEmulated == null) { 69 | if (CURRENT == X86_64) { 70 | ourEmulated = SystemInfoRt.isMac && isUnderRosetta() || SystemInfoRt.isWindows && !matchesWindowsNativeArch(); 71 | } else if (CURRENT == X86) { 72 | ourEmulated = SystemInfoRt.isWindows && !matchesWindowsNativeArch(); 73 | } else { 74 | ourEmulated = Boolean.FALSE; 75 | } 76 | } 77 | 78 | return ourEmulated == Boolean.TRUE; 79 | } 80 | 81 | private static @Nullable 82 | Boolean ourEmulated; 83 | 84 | // 85 | // https://developer.apple.com/documentation/apple-silicon/about-the-rosetta-translation-environment 86 | private static boolean isUnderRosetta() { 87 | // try { 88 | // if (JnaLoader.isLoaded()) { 89 | // IntByReference p = new IntByReference(); 90 | // SystemB.size_t.ByReference size = new SystemB.size_t.ByReference(SystemB.INT_SIZE); 91 | // if (SystemB.INSTANCE.sysctlbyname("sysctl.proc_translated", p.getPointer(), size, null, SystemB.size_t.ZERO) != -1) { 92 | // return p.getValue() == 1; 93 | // } 94 | // } 95 | // } 96 | // catch (Throwable t) { 97 | // Logger.getInstance(CpuArch.class).error(t); 98 | // } 99 | 100 | return false; 101 | } 102 | 103 | // https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getnativesysteminfo 104 | private static boolean matchesWindowsNativeArch() { 105 | // try { 106 | // if (JnaLoader.isLoaded()) { 107 | // WinBase.SYSTEM_INFO systemInfo = new WinBase.SYSTEM_INFO(); 108 | // Kernel32.INSTANCE.GetNativeSystemInfo(systemInfo); 109 | // int arch = systemInfo.processorArchitecture.dwOemID.getLow().intValue(); 110 | // if (arch == 0) return CURRENT == X86; 111 | // if (arch == 9) return CURRENT == X86_64; 112 | // if (arch == 12) return CURRENT == ARM64; 113 | // } 114 | // } 115 | // catch (Throwable t) { 116 | // Logger.getInstance(CpuArch.class).error(t); 117 | // } 118 | 119 | return true; 120 | } 121 | // 122 | } 123 | -------------------------------------------------------------------------------- /clipboard/src/main/java/com/intellij/DataContext.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 org.jetbrains.annotations.NotNull; 5 | import org.jetbrains.annotations.Nullable; 6 | 7 | 8 | @FunctionalInterface 9 | public interface DataContext { 10 | /** 11 | * Returns the object corresponding to the specified data identifier. Some of the supported 12 | * data identifiers are defined in the {@link com.intellij.openapi.actionSystem.PlatformDataKeys} class. 13 | * 14 | * NOTE: For implementation only, prefer {@link DataContext#getData(DataKey)} in client code. 15 | * 16 | * @param dataId the data identifier for which the value is requested. 17 | * @return the value, or null if no value is available in the current context for this identifier. 18 | */ 19 | @Nullable 20 | Object getData(@NotNull String dataId); 21 | 22 | DataContext EMPTY_CONTEXT = dataId -> null; 23 | 24 | } 25 | -------------------------------------------------------------------------------- /clipboard/src/main/java/com/intellij/ImagePasteProvider.kt: -------------------------------------------------------------------------------- 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.awt.Image 5 | import java.awt.datatransfer.DataFlavor.imageFlavor 6 | import java.awt.image.BufferedImage 7 | import java.awt.image.MultiResolutionImage 8 | import java.io.IOException 9 | 10 | /** 11 | * Represents a basic paste provider that allows to paste screenshots (from clipboard) as PNG files. 12 | * 13 | * NOTE: If registered as `filePasteProvider` handles paste operations in project view. 14 | */ 15 | open class ImagePasteProvider : PasteProvider { 16 | final override fun isPastePossible(dataContext: DataContext): Boolean = true 17 | final override fun isPasteEnabled(dataContext: DataContext): Boolean = 18 | CopyPasteManager.getInstance().areDataFlavorsAvailable(imageFlavor) 19 | && isEnabledForDataContext(dataContext) 20 | // && dataContext.getData(CommonDataKeys.VIRTUAL_FILE) != null 21 | 22 | open fun isEnabledForDataContext(dataContext: DataContext): Boolean = true 23 | 24 | final override fun performPaste(dataContext: DataContext) { 25 | val pasteContents = CopyPasteManager.getInstance().contents ?: return 26 | // Step 1: Obtain image data from the clipboard 27 | val imageToPaste: BufferedImage? = try { 28 | pasteContents.getTransferData(imageFlavor) 29 | } catch (ioException: IOException) { 30 | //todo catch IOException 31 | ioException.printStackTrace() 32 | println("Failed to get data from the clipboard. Data is no longer available. Aborting operation.") 33 | return 34 | }.let { 35 | when (it) { 36 | is MultiResolutionImage -> it.resolutionVariants.firstOrNull()?.toBufferedImage() 37 | is BufferedImage -> it 38 | is Image -> it.toBufferedImage() 39 | else -> null 40 | } 41 | } 42 | 43 | } 44 | 45 | } 46 | 47 | fun Image.toBufferedImage() = let { img -> 48 | when (img) { 49 | is BufferedImage -> img 50 | else -> { 51 | // Create a buffered image with transparency 52 | val bufferedImage = BufferedImage(img.getWidth(null), img.getHeight(null), BufferedImage.TYPE_INT_ARGB) 53 | 54 | // Draw the image on to the buffered image 55 | val bGr = bufferedImage.createGraphics() 56 | bGr.drawImage(img, 0, 0, null) 57 | bGr.dispose() 58 | 59 | bufferedImage 60 | } 61 | } 62 | } 63 | 64 | fun getClipboardImage(): BufferedImage? { 65 | val pasteContents = CopyPasteManager.getInstance().contents ?: return null 66 | // Step 1: Obtain image data from the clipboard 67 | val imageToPaste: BufferedImage? = try { 68 | pasteContents.getTransferData(imageFlavor) 69 | } catch (ioException: IOException) { 70 | //todo catch IOException 71 | ioException.printStackTrace() 72 | println("Failed to get data from the clipboard. Data is no longer available. Aborting operation.") 73 | return null 74 | }.let { 75 | when (it) { 76 | is MultiResolutionImage -> it.resolutionVariants.firstOrNull()?.toBufferedImage() 77 | is BufferedImage -> it 78 | is Image -> it.toBufferedImage() 79 | else -> null 80 | } 81 | } 82 | return imageToPaste?.toBufferedImage() 83 | } 84 | -------------------------------------------------------------------------------- /clipboard/src/main/java/com/intellij/LinkedListWithSum.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 org.jetbrains.annotations.NotNull; 5 | 6 | import java.util.AbstractSequentialList; 7 | import java.util.LinkedList; 8 | import java.util.List; 9 | import java.util.function.ToIntFunction; 10 | 11 | /** 12 | * Linked list implementation, which maintains the sum of values associated with contained elements. The associated values are provided 13 | * by evaluator passed to {@link #LinkedListWithSum(ToIntFunction) constructor}. Current sum can be obtained via {@link #getSum()}. 14 | *

15 | * This class is not thread-safe. 16 | */ 17 | public class LinkedListWithSum extends AbstractSequentialList implements List { 18 | private final LinkedList> myList = new LinkedList<>(); 19 | private final ToIntFunction myEvaluator; 20 | private long mySum; 21 | 22 | public LinkedListWithSum(@NotNull ToIntFunction evaluator) { 23 | myEvaluator = evaluator; 24 | } 25 | 26 | private ItemWithValue createItem(E e) { 27 | return new ItemWithValue<>(e, myEvaluator.applyAsInt(e)); 28 | } 29 | 30 | public long getSum() { 31 | return mySum; 32 | } 33 | 34 | @Override 35 | public int size() { 36 | return myList.size(); 37 | } 38 | 39 | @NotNull 40 | @Override 41 | public ListIterator listIterator(int index) { 42 | return new ListIterator(myList.listIterator(index)); 43 | } 44 | 45 | private static final class ItemWithValue { 46 | private final E item; 47 | private final int value; 48 | 49 | private ItemWithValue(E item, int value) { 50 | this.item = item; 51 | this.value = value; 52 | } 53 | } 54 | 55 | public final class ListIterator implements java.util.ListIterator { 56 | private final java.util.ListIterator> it; 57 | private ItemWithValue lastItem; 58 | 59 | private ListIterator(java.util.ListIterator> it) { 60 | this.it = it; 61 | } 62 | 63 | @Override 64 | public boolean hasNext() { 65 | return it.hasNext(); 66 | } 67 | 68 | @Override 69 | public E next() { 70 | return (lastItem = it.next()).item; 71 | } 72 | 73 | @Override 74 | public boolean hasPrevious() { 75 | return it.hasPrevious(); 76 | } 77 | 78 | @Override 79 | public E previous() { 80 | return (lastItem = it.previous()).item; 81 | } 82 | 83 | @Override 84 | public int nextIndex() { 85 | return it.nextIndex(); 86 | } 87 | 88 | @Override 89 | public int previousIndex() { 90 | return it.previousIndex(); 91 | } 92 | 93 | @Override 94 | public void remove() { 95 | it.remove(); 96 | mySum -= lastItem.value; 97 | } 98 | 99 | @Override 100 | public void set(E e) { 101 | ItemWithValue item = createItem(e); 102 | it.set(item); 103 | mySum -= lastItem.value; 104 | mySum += item.value; 105 | } 106 | 107 | @Override 108 | public void add(E e) { 109 | ItemWithValue item = createItem(e); 110 | it.add(item); 111 | mySum += item.value; 112 | } 113 | 114 | /** 115 | * Returns the value associated with item last returned from {@link #previous()} or {@link #next()} call. 116 | */ 117 | public int getValue() { 118 | return lastItem.value; 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /clipboard/src/main/java/com/intellij/LocalCopyPasteManager.java: -------------------------------------------------------------------------------- 1 | // Copyright 2000-2021 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.awt.datatransfer.*; 5 | import java.io.IOException; 6 | import java.util.Iterator; 7 | import java.util.Objects; 8 | 9 | public class LocalCopyPasteManager implements ClientCopyPasteManager { 10 | 11 | public static final ClipboardSynchronizer CLIPBOARD_SYNCHRONIZER = new ClipboardSynchronizer(); 12 | 13 | @Override 14 | public boolean areDataFlavorsAvailable(DataFlavor... flavors) { 15 | return flavors.length > 0 && CLIPBOARD_SYNCHRONIZER.areDataFlavorsAvailable(flavors); 16 | } 17 | 18 | @Override 19 | public Transferable getContents() { 20 | return CLIPBOARD_SYNCHRONIZER.getContents(); 21 | } 22 | 23 | 24 | } 25 | -------------------------------------------------------------------------------- /clipboard/src/main/java/com/intellij/Log.kt: -------------------------------------------------------------------------------- 1 | package com.intellij 2 | 3 | import java.lang.Exception 4 | import java.lang.IllegalStateException 5 | import java.lang.NullPointerException 6 | 7 | class Log { 8 | fun debug(msg: String) { 9 | println("debug: $msg") 10 | } 11 | 12 | fun warn(e: Throwable) { 13 | println("warn exception: $e") 14 | } 15 | 16 | fun info(e: Throwable) { 17 | println("info exception: $e") 18 | } 19 | 20 | fun warn(s: String, e: Throwable) { 21 | println("warn s: $s, exception: $e") 22 | } 23 | 24 | fun error(s: String) { 25 | 26 | } 27 | 28 | fun debug(e: Throwable) { 29 | 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /clipboard/src/main/java/com/intellij/Pair.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 org.jetbrains.annotations.NotNull; 5 | import org.jetbrains.annotations.Nullable; 6 | 7 | import java.util.Comparator; 8 | 9 | /** 10 | * Generic wrapper around two related values. 11 | */ 12 | public class Pair { 13 | public final A first; 14 | public final B second; 15 | 16 | @NotNull 17 | public static Pair create(A first, B second) { 18 | //noinspection DontUsePairConstructor 19 | return new Pair(first, second); 20 | } 21 | 22 | @NotNull 23 | public static NonNull createNonNull(@NotNull A first, @NotNull B second) { 24 | return new NonNull(first, second); 25 | } 26 | 27 | @NotNull 28 | public static Pair pair(A first, B second) { 29 | //noinspection DontUsePairConstructor 30 | return new Pair(first, second); 31 | } 32 | 33 | public static T getFirst(@Nullable Pair pair) { 34 | return pair != null ? pair.first : null; 35 | } 36 | 37 | public static T getSecond(@Nullable Pair pair) { 38 | return pair != null ? pair.second : null; 39 | } 40 | 41 | @SuppressWarnings("rawtypes") 42 | private static final Pair EMPTY = create(null, null); 43 | 44 | @NotNull 45 | public static Pair empty() { 46 | //noinspection unchecked 47 | return EMPTY; 48 | } 49 | 50 | /** 51 | * @see #create(Object, Object) 52 | */ 53 | public Pair(A first, B second) { 54 | this.first = first; 55 | this.second = second; 56 | } 57 | 58 | public final A getFirst() { 59 | return first; 60 | } 61 | 62 | public final B getSecond() { 63 | return second; 64 | } 65 | 66 | @Override 67 | public final boolean equals(Object o) { 68 | return o instanceof Pair && Comparing.equal(first, ((Pair) o).first) && Comparing.equal(second, ((Pair) o).second); 69 | } 70 | 71 | @Override 72 | public int hashCode() { 73 | int result = first != null ? first.hashCode() : 0; 74 | result = 31 * result + (second != null ? second.hashCode() : 0); 75 | return result; 76 | } 77 | 78 | @Override 79 | public String toString() { 80 | return "<" + first + "," + second + ">"; 81 | } 82 | 83 | public static class NonNull extends Pair { 84 | public NonNull(@NotNull A first, @NotNull B second) { 85 | super(first, second); 86 | } 87 | } 88 | 89 | /** 90 | * @param first value type (Comparable) 91 | * @param second value type 92 | * @return comparator that compares pair values by first value 93 | */ 94 | public static , B> Comparator> comparingByFirst() { 95 | return new Comparator>() { 96 | @Override 97 | public int compare(Pair o1, Pair o2) { 98 | return o1.first.compareTo(o2.first); 99 | } 100 | }; 101 | } 102 | 103 | /** 104 | * @param first value type 105 | * @param second value type (Comparable) 106 | * @return comparator that compares pair values by second value 107 | */ 108 | public static > Comparator> comparingBySecond() { 109 | return new Comparator>() { 110 | @Override 111 | public int compare(Pair o1, Pair o2) { 112 | return o1.second.compareTo(o2.second); 113 | } 114 | }; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /clipboard/src/main/java/com/intellij/PasteProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2000-2009 JetBrains s.r.o. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.intellij; 18 | 19 | import org.jetbrains.annotations.NotNull; 20 | 21 | public interface PasteProvider { 22 | void performPaste(@NotNull DataContext dataContext); 23 | 24 | /** 25 | * Should perform fast and memory cheap negation. May return incorrect true. 26 | * See #12326 27 | */ 28 | boolean isPastePossible(@NotNull DataContext dataContext); 29 | 30 | boolean isPasteEnabled(@NotNull DataContext dataContext); 31 | } 32 | -------------------------------------------------------------------------------- /clipboard/src/main/java/com/intellij/Patches.java: -------------------------------------------------------------------------------- 1 | // Copyright 2000-2021 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 | public final class Patches { 5 | /** 6 | * See JDK-6322854. 7 | * java.lang.NullPointerException: Failed to retrieve atom name. 8 | */ 9 | public static final boolean SUN_BUG_ID_6322854 = SystemInfoRt.isXWindow; 10 | 11 | /** 12 | * IBM JVM 1.4.2 crashes if debugger uses ObjectReference.disableCollection() and ObjectReference.enableCollection(). 13 | */ 14 | public static final boolean IBM_JDK_DISABLE_COLLECTION_BUG = "false".equalsIgnoreCase(System.getProperty("idea.debugger.keep.temp.objects")); 15 | 16 | /** 17 | * See JDK-4818143. 18 | * The bug is marked as fixed but it actually isn't - {@link java.awt.datatransfer.Clipboard#getContents(Object)} call may hang 19 | * for up to 10 seconds if clipboard owner is not responding. 20 | */ 21 | public static final boolean SLOW_GETTING_CLIPBOARD_CONTENTS = SystemInfoRt.isUnix; 22 | 23 | /** 24 | * Desktop API support on X Window is limited to GNOME (and even there it may work incorrectly). 25 | * See JDK-6486393. 26 | */ 27 | public static final boolean SUN_BUG_ID_6486393 = SystemInfoRt.isXWindow; 28 | 29 | /** 30 | * Debugger hangs in trace mode with TRACE_SEND when method argument is a {@link com.sun.jdi.StringReference} 31 | */ 32 | public static final boolean JDK_BUG_ID_21275177 = true; 33 | 34 | /** 35 | * Debugger hangs in trace mode with TRACE_SEND when method argument is a {@link com.sun.jdi.ThreadReference} 36 | */ 37 | public static final boolean JDK_BUG_WITH_TRACE_SEND = true; 38 | 39 | /** 40 | * JDK on Mac detects font style for system fonts based only on their name (PostScript name). 41 | * This doesn't work for some fonts, which don't use recognizable style suffixes in their names. 42 | * Corresponding JDK request for enhancement - JDK-8139151. 43 | */ 44 | public static final boolean JDK_MAC_FONT_STYLE_DETECTION_WORKAROUND = SystemInfoRt.isMac; 45 | 46 | /** 47 | * Some HTTP connections lock class loaders: JDK-8032832 48 | * The issue claims to be fixed in 8u20, but the fix just replaces one lock with another (on a context class loader). 49 | */ 50 | public static final boolean JDK_BUG_ID_8032832 = true; 51 | 52 | /** 53 | * JDK-8220231 54 | */ 55 | } 56 | -------------------------------------------------------------------------------- /clipboard/src/main/java/com/intellij/ReflectionUtilRt.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 org.jetbrains.annotations.NotNull; 5 | import org.jetbrains.annotations.Nullable; 6 | 7 | import java.lang.reflect.Field; 8 | import java.util.ArrayList; 9 | import java.util.Arrays; 10 | import java.util.List; 11 | 12 | public final class ReflectionUtilRt { 13 | @NotNull 14 | public static List collectFields(@NotNull Class clazz) { 15 | List result = new ArrayList(); 16 | collectFields(clazz, result); 17 | return result; 18 | } 19 | 20 | private static void collectFields(Class clazz, List result) { 21 | result.addAll(Arrays.asList(clazz.getDeclaredFields())); 22 | 23 | Class superClass = clazz.getSuperclass(); 24 | if (superClass != null) { 25 | collectFields(superClass, result); 26 | } 27 | 28 | for (Class each : clazz.getInterfaces()) { 29 | collectFields(each, result); 30 | } 31 | } 32 | 33 | public static T getField(@NotNull Class objectClass, 34 | @Nullable Object object, 35 | @Nullable("null means any type") Class 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 | --------------------------------------------------------------------------------