├── .gitignore ├── .idea ├── .gitignore ├── gradle.xml ├── icon.png ├── inspectionProfiles │ └── Project_Default.xml ├── runConfigurations │ ├── Gallery__Android_.xml │ ├── Gallery__JS_Build_.xml │ ├── Gallery__JVM_.xml │ └── Gallery__WASM_Build_.xml └── vcs.xml ├── LICENSE ├── README.en-US.md ├── README.md ├── build.gradle.kts ├── gallery ├── build.gradle.kts └── src │ ├── androidMain │ ├── AndroidManifest.xml │ ├── kotlin │ │ └── vip │ │ │ └── cdms │ │ │ └── orecompose │ │ │ └── gallery │ │ │ └── MainActivity.kt │ └── res │ │ └── mipmap │ │ └── ic_launcher.png │ ├── commonMain │ ├── composeResources │ │ ├── drawable │ │ │ └── orecompose_logo.png │ │ └── font │ │ │ └── HarmonyOS_Sans_SC_Regular.ttf │ └── kotlin │ │ └── vip │ │ └── cdms │ │ └── orecompose │ │ └── gallery │ │ ├── App.kt │ │ └── ui │ │ └── Theme.kt │ ├── jsMain │ ├── kotlin │ │ └── vip │ │ │ └── cdms │ │ │ └── orecompose │ │ │ └── gallery │ │ │ └── main.kt │ └── resources │ │ └── index.html │ ├── jvmMain │ └── kotlin │ │ └── vip │ │ └── cdms │ │ └── orecompose │ │ └── gallery │ │ └── main.kt │ ├── wasmJsMain │ ├── kotlin │ │ └── vip │ │ │ └── cdms │ │ │ └── orecompose │ │ │ └── gallery │ │ │ └── main.kt │ └── resources │ │ └── index.html │ └── webMain │ └── resources │ └── main.css ├── gradle.properties ├── gradle ├── kotlin-js-store │ └── yarn.lock ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── oreui-panorama ├── build.gradle.kts └── src │ ├── androidMain │ └── kotlin │ │ └── vip │ │ └── cdms │ │ └── orecompose │ │ └── layout │ │ └── panorama │ │ ├── GlPanoramaView.kt │ │ └── Panorama.android.kt │ ├── commonMain │ ├── composeResources │ │ └── drawable │ │ │ └── panorama_default.png │ └── kotlin │ │ └── vip │ │ └── cdms │ │ └── orecompose │ │ └── layout │ │ └── panorama │ │ ├── FastMath.kt │ │ ├── OrePanoramaModule.kt │ │ ├── Panorama.kt │ │ └── PanoramaSoft.kt │ ├── jvmMain │ └── kotlin │ │ └── vip │ │ └── cdms │ │ └── orecompose │ │ └── layout │ │ └── panorama │ │ └── Panorama.jvm.kt │ ├── nonWebMain │ └── kotlin │ │ └── vip │ │ └── cdms │ │ └── orecompose │ │ └── layout │ │ └── panorama │ │ └── OrePanoramaModule.nonWeb.kt │ ├── skikoMain │ └── kotlin │ │ └── vip │ │ └── cdms │ │ └── orecompose │ │ └── layout │ │ └── panorama │ │ ├── PanoramaSkiko.kt │ │ └── SkShader.kt │ └── webMain │ └── kotlin │ └── vip │ └── cdms │ └── orecompose │ └── layout │ └── panorama │ ├── OrePanoramaModule.web.kt │ └── Panorama.web.kt ├── oreui ├── build.gradle.kts └── src │ ├── androidMain │ └── kotlin │ │ └── vip │ │ └── cdms │ │ └── orecompose │ │ ├── style │ │ └── OreTheme.android.kt │ │ └── utils │ │ └── PlatformUtils.android.kt │ ├── commonMain │ ├── composeResources │ │ └── font │ │ │ └── Minecraft.ttf │ └── kotlin │ │ └── vip │ │ └── cdms │ │ └── orecompose │ │ ├── components │ │ ├── Input.kt │ │ └── Label.kt │ │ ├── effect │ │ ├── Outline.kt │ │ ├── Pixel.kt │ │ └── Sound.kt │ │ ├── style │ │ ├── McFonts.kt │ │ ├── OreColors.kt │ │ ├── OreTheme.kt │ │ └── ZIndices.kt │ │ └── utils │ │ ├── Accessibility.kt │ │ ├── ColorUtils.kt │ │ ├── ComposeNester.kt │ │ ├── DrawUtils.kt │ │ ├── FontsFallback.kt │ │ ├── LocalUtils.kt │ │ ├── McFormat.kt │ │ ├── McFormatter.kt │ │ ├── PlatformUtils.kt │ │ └── UnitUtils.kt │ ├── jsMain │ └── kotlin │ │ └── vip │ │ └── cdms │ │ └── orecompose │ │ └── utils │ │ ├── PlatformUtils.js.kt │ │ └── ResourcePreloader.js.kt │ ├── jvmMain │ └── kotlin │ │ └── vip │ │ └── cdms │ │ └── orecompose │ │ ├── style │ │ └── OreTheme.jvm.kt │ │ └── utils │ │ └── PlatformUtils.jvm.kt │ ├── wasmJsMain │ └── kotlin │ │ └── vip │ │ └── cdms │ │ └── orecompose │ │ └── utils │ │ ├── PlatformUtils.wasmJs.kt │ │ └── ResourcePreloader.wasmJs.kt │ └── webMain │ └── kotlin │ └── vip │ └── cdms │ └── orecompose │ ├── style │ └── OreTheme.web.kt │ └── utils │ └── ResourcePreloader.kt └── settings.gradle.kts /.gitignore: -------------------------------------------------------------------------------- 1 | ### IntelliJ IDEA ### 2 | .idea/modules 3 | .idea/modules.xml 4 | .idea/jarRepositories.xml 5 | .idea/compiler.xml 6 | .idea/kotlinc.xml 7 | .idea/libraries/ 8 | .idea/artifacts 9 | .idea/*.iml 10 | .idea/misc.xml 11 | 12 | ### Kotlin ### 13 | /.kotlin 14 | 15 | ### Gradle ### 16 | .gradle 17 | build/ 18 | local.properties 19 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 18 | 20 | -------------------------------------------------------------------------------- /.idea/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cdm2883/OreCompose/8536db1e8e694556716b7462ad20475eb9099242/.idea/icon.png -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 44 | -------------------------------------------------------------------------------- /.idea/runConfigurations/Gallery__Android_.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 39 | -------------------------------------------------------------------------------- /.idea/runConfigurations/Gallery__JS_Build_.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 16 | 18 | true 19 | true 20 | false 21 | false 22 | 23 | 24 | -------------------------------------------------------------------------------- /.idea/runConfigurations/Gallery__JVM_.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 16 | 18 | false 19 | true 20 | false 21 | false 22 | 23 | 24 | -------------------------------------------------------------------------------- /.idea/runConfigurations/Gallery__WASM_Build_.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 16 | 18 | true 19 | true 20 | false 21 | false 22 | 23 | 24 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.en-US.md: -------------------------------------------------------------------------------- 1 | ![OreCompose](https://socialify.git.ci/Cdm2883/OreCompose/image?custom_description=Compose+Multiplatform+for+Minecraft+Bedrock+Edition%27s+New+UI+%E2%9C%A8&description=1&font=Inter&forks=1&issues=1&logo=data%3Aimage%2Fpng%3Bbase64%2CiVBORw0KGgoAAAANSUhEUgAAAE4AAABXCAYAAACqc3NOAAAAAXNSR0IArs4c6QAAAh5JREFUeJzt3bFKw1AYxXEjASuR2kUEcSsOgtCCPkHxAVxEcPEZfBJdfQRx8gXEFxBaEBzETVxcarGoINbtGiQf7T1prmn8%2F6YkTZNw%2BM6Ym2iuYLV2J%2FN40kzc9vBhOPK5ZtJMotR%2FM8957175XNLbfKFXrzCCE0XjT5lMnkq2Tra87tU7vrXuFazCTJyI4ETeVQ1ZSV8hK8zEiQhOZFa1zJX0VUSFmTgRwYmiKlXSV54KM3EighNFq0d7buelN6hkJX1ZFV5u1V2FmTgRwYmiWrvj6vnfKukrXWEmTkRwoji9c3NwkXnS9vl%2BkIcpi%2Bhy1%2FiFquZGcKJ4%2FCnVrbBdyfGYOBHBiSaqqmVWKpynkhYmTkRwolxVtfxVhYuopIWJExGcqJCqWqZV4ZCVtDBxIoITBa2qxarwzuFZ4CeZHBMnIjgRwYkITkRwIoITEZyI4EQEJyI4EcGJCE5EcCKCExGciOBEBCciOBHBiQhORHAighMRnIjgRAQnIjgRwYkITkRwIoITEZyI4ETx19vArWwQr21mrgLx%2BXQX7olKoHt9mnm8sbLBKhB5EZwonl%2Bsp%2FfdKI4%2BftYUqmqFrUouNdZdDq%2F9R3e8%2F3zvtpk4EcGJYmtFvl%2FLo810hX0rmd5mxcIpIziR%2BWbNLFa4iEpamDgRwYm8X4IrQ4VDVtLCxIkITjS1D2FYrHWG0xWOFhKvb9ZYlUzjmzUlRXCib4RCv1Gei%2FJ7AAAAAElFTkSuQmCC&name=1&owner=1&pattern=Brick+Wall&pulls=1&stargazers=1&theme=Auto) 2 | # OreCompose 3 | [![zh-CN](https://img.shields.io/badge/README-%E7%AE%80%E4%BD%93%E4%B8%AD%E6%96%87-50a0fe?style=for-the-badge)](README.md) 4 | > [Compose Multiplatform](https://www.jetbrains.com/lp/compose-multiplatform/) for Minecraft Bedrock Edition's **New UI** Style ✨ 5 | 6 | **High** Decoupling, **Strong** Compatibility, **Pixel-Perfect** Restoration ⚡⚡ 7 | 8 | ``` 9 | Copyright 2024 Cdm2883 10 | 11 | Licensed under the Apache License, Version 2.0 (the "License"); 12 | you may not use this file except in compliance with the License. 13 | You may obtain a copy of the License at 14 | 15 | http://www.apache.org/licenses/LICENSE-2.0 16 | 17 | Unless required by applicable law or agreed to in writing, software 18 | distributed under the License is distributed on an "AS IS" BASIS, 19 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20 | See the License for the specific language governing permissions and 21 | limitations under the License. 22 | ``` 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![OreCompose](https://socialify.git.ci/Cdm2883/OreCompose/image?description=1&font=Inter&forks=1&issues=1&logo=data%3Aimage%2Fpng%3Bbase64%2CiVBORw0KGgoAAAANSUhEUgAAAE4AAABXCAYAAACqc3NOAAAAAXNSR0IArs4c6QAAAh5JREFUeJzt3bFKw1AYxXEjASuR2kUEcSsOgtCCPkHxAVxEcPEZfBJdfQRx8gXEFxBaEBzETVxcarGoINbtGiQf7T1prmn8%2F6YkTZNw%2BM6Ym2iuYLV2J%2FN40kzc9vBhOPK5ZtJMotR%2FM8957175XNLbfKFXrzCCE0XjT5lMnkq2Tra87tU7vrXuFazCTJyI4ETeVQ1ZSV8hK8zEiQhOZFa1zJX0VUSFmTgRwYmiKlXSV54KM3EighNFq0d7buelN6hkJX1ZFV5u1V2FmTgRwYmiWrvj6vnfKukrXWEmTkRwoji9c3NwkXnS9vl%2BkIcpi%2Bhy1%2FiFquZGcKJ4%2FCnVrbBdyfGYOBHBiSaqqmVWKpynkhYmTkRwolxVtfxVhYuopIWJExGcqJCqWqZV4ZCVtDBxIoITBa2qxarwzuFZ4CeZHBMnIjgRwYkITkRwIoITEZyI4EQEJyI4EcGJCE5EcCKCExGciOBEBCciOBHBiQhORHAighMRnIjgRAQnIjgRwYkITkRwIoITEZyI4ETx19vArWwQr21mrgLx%2BXQX7olKoHt9mnm8sbLBKhB5EZwonl%2Bsp%2FfdKI4%2BftYUqmqFrUouNdZdDq%2F9R3e8%2F3zvtpk4EcGJYmtFvl%2FLo810hX0rmd5mxcIpIziR%2BWbNLFa4iEpamDgRwYm8X4IrQ4VDVtLCxIkITjS1D2FYrHWG0xWOFhKvb9ZYlUzjmzUlRXCib4RCv1Gei%2FJ7AAAAAElFTkSuQmCC&name=1&owner=1&pattern=Brick+Wall&pulls=1&stargazers=1&theme=Auto) 2 | # OreCompose 3 | [![en-US](https://img.shields.io/badge/README-English%20%28United%20States%29-50a0fe?style=for-the-badge)](README.en-US.md) 4 | [![QQ Group](https://img.shields.io/badge/QQ_Group-Cdm's_group-0099ff?style=for-the-badge&logo=data%3Aimage%2Fpng%3Bbase64%2CiVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAYAAADimHc4AAAACXBIWXMAACE4AAAhOAFFljFgAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAARcSURBVHgB7Z2NUdswFMf%2F6XWAdALEBNAJaiZomQAzQekEJBOUTsAIzQawQekEdicgnUDVO8uHa0icSHqyJb%2Ffnc6Q5GxLT%2B9DlvwECIIgCIIgzJEFJo7WujCHc1PO7HFpiur9rDZla4%2B%2FTXky5XGxWGwhHA81uin3pjxrPx5MKU1ZQhjGNlalw1OZcmuKgvAa3fT4J81PZUoJ4QXTIN91fB703LXBNMDSNsRYVHoCQhglCrIVf8DraCY2FCVdmGjpCSMRXQC6iUh%2BYfzGbxlVCO8Qn3tMp%2FEJ6hA%2FxzJHUQVgKnlrDl8wPRSajpEv1MP09FkhMtF8gKlchWmZnl2cGn9QIxJRTJBuBj8KaRDVFEXRgIR6fwtFRY%2BIALsGJNb7W24RCXYNSLD3t0TRAlYNMI1PIadCmkQJl7lN0BRj%2FkO50hHmEdhMkL35Z6QNuxni1IAC6cOuwZwCSNn8tHwGM5wCOEP60OMTBUZYBGDt%2FznyoAAjXBqQS%2BMTrHURAQyjwAiXABTygdWXcQkgBwfcosAIlwCyWonGGQmJCToMtg4lGnAYyQkgNxSYCC4ALSuRj4JDA0QARxBcADFXFOSA%2BICR4fABCvmRVBRUIj%2FY5gWCT0kmvApiCJbpyaAakPgqiCFYZvhCm6AS%2BcKySiKYAKzzZZ9DHRFq%2FBKBCakBJfIneAcL5oQzdr59gjrjIBqQufPtE9QZhzJBJeZDUGfsLYAZON8%2BQZ1xCA0oMD%2BCdThvJzwj59vnQ4h0OF4aYBqf1v8ozJMbBMDXBAW5iUT5hAD4CiDITSQKpdgp4ImzAOzFFeZNAU98NCCH9f%2B%2BeFsAHwHM2fy0FL4zgE4CsBfNaQW0D16WwFUDCggtBTxwFYDY%2Fxe8TLGrAHJafu7L0g5InThaADMf%2Fe6igCMuGqAg9HG2CC4CGFK3GnmyL6lfPBM0cLE18hXAtz3fKTjiIoCTHZ%2FX5vHsyhz%2FID%2B2dh74x47vl64DspA%2B4MIe96lqqrR1WmG3hjtNU7oI4K0LrTvL0nMUAO1JADsBc4km2WufaALos7Gmp4UE4D1TNDE27R82w%2B4agXARQLdxa%2FSck%2B0lOWlB3V8HZP6%2Fw25%2FcBQ%2BAqjRLFKq3%2FhNsB4yAR7f%2BtDU%2B6b3XY0YUHZZfUD%2Bfc2zE8YYqD11pOjnTjcLE6aFbrYjSR32JK6saSt1%2BktW2NMYc7%2Bkd410Wcd44zNG4laKGL4iLSjyOUUEYgiABii0XUkqU5gU5X2M9b4z%2B3vCndFjjTS4zi59va0QPSuqMW2o8TfIFd3solHpaVJiBN4jErpZSXeF6SbzuDL3SMdNzE1AuccB1NgUAZVIZzxQo3nEsF6knHhEN%2FtDVjpt2DdyYNEAe%2BMr5AHtS3wBJjhyReSQtr7PJVd0xBWG1sgLNqfMZYIUmpFvgWYSv7sN%2BRL7I6Et%2Fq%2Fwtlf%2B4mX7cnTOe9I7t%2BqcY%2Bia6JyvPTcdaSpyI1nABEEQBEEQQvMP82DW%2Bu24kOQAAAAASUVORK5CYII%3D)](https://qm.qq.com/cgi-bin/qm/qr?k=Vcspr10ZnFcPd29hgPUgmxcBPsPni6pC&jump_from=webapi&authKey=uKMeRLS8aWCJwEXlYmkKeg1aBg4xmt/UbdXvEejPDOqCxhIKpqLK8Q+qG4ZFl0Nn) 5 | > [Compose Multiplatform](https://www.jetbrains.com/lp/compose-multiplatform/) 类《我的世界》基岩版**新 UI 风格**组件库 ✨ 6 | 7 | 高解耦,强兼容,像素级还原 ⚡⚡ 8 | 9 | ``` 10 | Copyright 2024 Cdm2883 11 | 12 | Licensed under the Apache License, Version 2.0 (the "License"); 13 | you may not use this file except in compliance with the License. 14 | You may obtain a copy of the License at 15 | 16 | http://www.apache.org/licenses/LICENSE-2.0 17 | 18 | Unless required by applicable law or agreed to in writing, software 19 | distributed under the License is distributed on an "AS IS" BASIS, 20 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 21 | See the License for the specific language governing permissions and 22 | limitations under the License. 23 | ``` 24 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnPlugin 2 | import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnRootExtension 3 | 4 | plugins { 5 | // this is necessary to avoid the plugins to be loaded multiple times 6 | // in each subproject's classloader 7 | alias(libs.plugins.android.application) apply false 8 | alias(libs.plugins.android.library) apply false 9 | alias(libs.plugins.compose.multiplatform) apply false 10 | alias(libs.plugins.compose.compiler) apply false 11 | alias(libs.plugins.kotlin.multiplatform) apply false 12 | } 13 | 14 | version = libs.versions.orecompose.lib.version.get() 15 | 16 | plugins.withType { 17 | the().lockFileDirectory = 18 | rootDir.resolve("gradle/kotlin-js-store") 19 | } 20 | -------------------------------------------------------------------------------- /gallery/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.compose.desktop.application.dsl.TargetFormat 2 | import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl 3 | import org.jetbrains.kotlin.gradle.dsl.JvmTarget 4 | import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig 5 | 6 | plugins { 7 | alias(libs.plugins.kotlin.multiplatform) 8 | alias(libs.plugins.android.application) 9 | alias(libs.plugins.compose.multiplatform) 10 | alias(libs.plugins.compose.compiler) 11 | } 12 | 13 | kotlin { 14 | androidTarget { 15 | compilerOptions { 16 | jvmTarget.set(JvmTarget.JVM_11) 17 | } 18 | } 19 | 20 | jvm() 21 | 22 | @OptIn(ExperimentalWasmDsl::class) 23 | wasmJs { 24 | moduleName = "composeApp" 25 | browser { 26 | val rootDirPath = project.rootDir.path 27 | val projectDirPath = project.projectDir.path 28 | commonWebpackConfig { 29 | outputFileName = "composeApp.js" 30 | devServer = (devServer ?: KotlinWebpackConfig.DevServer()).apply { 31 | static = (static ?: mutableListOf()).apply { 32 | // Serve sources to debug inside browser 33 | add(rootDirPath) 34 | add(projectDirPath) 35 | } 36 | } 37 | } 38 | } 39 | binaries.executable() 40 | } 41 | js(IR) { 42 | moduleName = "composeApp" 43 | browser { 44 | commonWebpackConfig { 45 | outputFileName = "composeApp.js" 46 | } 47 | } 48 | binaries.executable() 49 | } 50 | 51 | applyDefaultHierarchyTemplate() 52 | 53 | sourceSets { 54 | commonMain.dependencies { 55 | implementation(compose.runtime) 56 | implementation(compose.ui) 57 | implementation(compose.foundation) 58 | implementation(compose.material) 59 | implementation(compose.components.resources) 60 | implementation(compose.components.uiToolingPreview) 61 | implementation(projects.oreui) 62 | implementation(projects.oreuiPanorama) 63 | } 64 | androidMain.dependencies { 65 | implementation(libs.androidx.activity.compose) 66 | } 67 | jvmMain.dependencies { 68 | implementation(compose.desktop.currentOs) 69 | implementation(libs.mayakapps.compose.window.styler) 70 | } 71 | 72 | val webMain by creating 73 | webMain.dependsOn(commonMain.get()) 74 | jsMain.get().dependsOn(webMain) 75 | wasmJsMain.get().dependsOn(webMain) 76 | } 77 | } 78 | 79 | android { 80 | namespace = "vip.cdms.orecompose.gallery" 81 | compileSdk = libs.versions.android.sdk.compile.get().toInt() 82 | 83 | sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml") 84 | sourceSets["main"].res.srcDirs("src/androidMain/res") 85 | sourceSets["main"].resources.srcDirs("src/commonMain/resources") 86 | 87 | defaultConfig { 88 | applicationId = "vip.cdms.orecompose.gallery" 89 | minSdk = libs.versions.android.sdk.min.get().toInt() 90 | targetSdk = libs.versions.android.sdk.target.get().toInt() 91 | } 92 | packaging { 93 | resources { 94 | excludes += "/META-INF/{AL2.0,LGPL2.1}" 95 | } 96 | } 97 | compileOptions { 98 | sourceCompatibility = JavaVersion.VERSION_11 99 | targetCompatibility = JavaVersion.VERSION_11 100 | } 101 | dependencies { 102 | debugImplementation(compose.uiTooling) 103 | } 104 | } 105 | 106 | compose.desktop { 107 | application { 108 | mainClass = "vip.cdms.orecompose.gallery.MainKt" 109 | 110 | nativeDistributions { 111 | targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb) 112 | packageName = "OreCompose Gallery" 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /gallery/src/androidMain/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /gallery/src/androidMain/kotlin/vip/cdms/orecompose/gallery/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package vip.cdms.orecompose.gallery 2 | 3 | import android.os.Bundle 4 | import androidx.activity.ComponentActivity 5 | import androidx.activity.compose.setContent 6 | import androidx.activity.enableEdgeToEdge 7 | 8 | class MainActivity : ComponentActivity() { 9 | override fun onCreate(savedInstanceState: Bundle?) { 10 | super.onCreate(savedInstanceState) 11 | 12 | enableEdgeToEdge() 13 | setContent { 14 | App() 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /gallery/src/androidMain/res/mipmap/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cdm2883/OreCompose/8536db1e8e694556716b7462ad20475eb9099242/gallery/src/androidMain/res/mipmap/ic_launcher.png -------------------------------------------------------------------------------- /gallery/src/commonMain/composeResources/drawable/orecompose_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cdm2883/OreCompose/8536db1e8e694556716b7462ad20475eb9099242/gallery/src/commonMain/composeResources/drawable/orecompose_logo.png -------------------------------------------------------------------------------- /gallery/src/commonMain/composeResources/font/HarmonyOS_Sans_SC_Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cdm2883/OreCompose/8536db1e8e694556716b7462ad20475eb9099242/gallery/src/commonMain/composeResources/font/HarmonyOS_Sans_SC_Regular.ttf -------------------------------------------------------------------------------- /gallery/src/commonMain/kotlin/vip/cdms/orecompose/gallery/App.kt: -------------------------------------------------------------------------------- 1 | package vip.cdms.orecompose.gallery 2 | 3 | import androidx.compose.foundation.LocalIndication 4 | import androidx.compose.foundation.background 5 | import androidx.compose.foundation.clickable 6 | import androidx.compose.foundation.interaction.MutableInteractionSource 7 | import androidx.compose.foundation.layout.* 8 | import androidx.compose.runtime.* 9 | import androidx.compose.ui.Alignment 10 | import androidx.compose.ui.Modifier 11 | import androidx.compose.ui.graphics.Color 12 | import androidx.compose.ui.layout.boundsInParent 13 | import androidx.compose.ui.layout.onGloballyPositioned 14 | import androidx.compose.ui.semantics.Role 15 | import androidx.compose.ui.semantics.role 16 | import androidx.compose.ui.semantics.semantics 17 | import androidx.compose.ui.unit.dp 18 | import vip.cdms.orecompose.components.Label 19 | import vip.cdms.orecompose.effect.LocalOutlineColor 20 | import vip.cdms.orecompose.effect.LocalOutlineWidth 21 | import vip.cdms.orecompose.effect.outline 22 | import vip.cdms.orecompose.gallery.ui.GalleryTheme 23 | import vip.cdms.orecompose.layout.panorama.Panorama 24 | import vip.cdms.orecompose.utils.accessibilityIndicator 25 | import vip.cdms.orecompose.utils.toPx 26 | 27 | @Composable 28 | fun App() { 29 | GalleryTheme { 30 | CompositionLocalProvider( 31 | LocalOutlineColor provides Color.LightGray, 32 | ) { 33 | var screenWidth by remember { mutableStateOf(Float.MIN_VALUE) } 34 | Box( 35 | Modifier 36 | .fillMaxSize() 37 | .onGloballyPositioned { screenWidth = it.boundsInParent().width }, 38 | contentAlignment = Alignment.Center 39 | ) { 40 | Panorama() 41 | Column { 42 | Label( 43 | "OreCompose! 你好世界!", 44 | color = Color.White, 45 | fontSize = (screenWidth / 16).toPx() 46 | ) 47 | Row(horizontalArrangement = Arrangement.spacedBy(-LocalOutlineWidth.current.toDp())) { 48 | @Composable 49 | fun Modifier.buttonLike(onClick: () -> Unit) = remember { MutableInteractionSource() }.let { interactionSource -> 50 | semantics { role = Role.Button } 51 | .accessibilityIndicator(interactionSource) 52 | .clickable( 53 | interactionSource = interactionSource, 54 | indication = LocalIndication.current, 55 | onClick = onClick 56 | ) 57 | } 58 | Spacer(Modifier.size(40.dp) 59 | .background(Color.Yellow) 60 | .buttonLike {}) 61 | Spacer(Modifier.size(10.dp)) 62 | Spacer(Modifier.size(40.dp) 63 | .background(Color.Red) 64 | .outline() 65 | .buttonLike {}) 66 | Spacer(Modifier.size(40.dp) 67 | .background(Color.Green) 68 | .outline() 69 | .buttonLike {}) 70 | Spacer(Modifier.size(40.dp) 71 | .background(Color.Blue) 72 | .outline() 73 | .buttonLike {}) 74 | } 75 | } 76 | } 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /gallery/src/commonMain/kotlin/vip/cdms/orecompose/gallery/ui/Theme.kt: -------------------------------------------------------------------------------- 1 | package vip.cdms.orecompose.gallery.ui 2 | 3 | import androidx.compose.material.ProvideTextStyle 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.ui.text.TextStyle 6 | import androidx.compose.ui.text.font.FontFamily 7 | import orecompose.gallery.generated.resources.HarmonyOS_Sans_SC_Regular 8 | import orecompose.gallery.generated.resources.Res 9 | import org.jetbrains.compose.resources.Font 10 | import vip.cdms.orecompose.layout.panorama.OrePanoramaModule 11 | import vip.cdms.orecompose.style.OreModule 12 | import vip.cdms.orecompose.style.OreTheme 13 | 14 | private val TextFontModule = OreModule.wrap { 15 | val fontFamily = FontFamily(Font(Res.font.HarmonyOS_Sans_SC_Regular)) 16 | ProvideTextStyle(TextStyle(fontFamily = fontFamily), content = it) 17 | } 18 | 19 | @Composable 20 | fun GalleryTheme(content: @Composable () -> Unit) { 21 | OreTheme( 22 | modules = arrayOf( 23 | OrePanoramaModule, 24 | TextFontModule, 25 | ), 26 | content = content 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /gallery/src/jsMain/kotlin/vip/cdms/orecompose/gallery/main.kt: -------------------------------------------------------------------------------- 1 | package vip.cdms.orecompose.gallery 2 | 3 | import androidx.compose.ui.ExperimentalComposeUiApi 4 | import androidx.compose.ui.window.ComposeViewport 5 | import kotlinx.browser.document 6 | import orecompose.gallery.generated.resources.HarmonyOS_Sans_SC_Regular 7 | import orecompose.gallery.generated.resources.Res 8 | import org.jetbrains.skiko.wasm.onWasmReady 9 | import vip.cdms.orecompose.utils.PreloadResources 10 | import vip.cdms.orecompose.utils.ResourceUrl.Companion.resourceUrl 11 | import vip.cdms.orecompose.utils.ResourceUrl.Companion.ttf 12 | 13 | @OptIn(ExperimentalComposeUiApi::class) 14 | fun main() { 15 | onWasmReady { 16 | ComposeViewport(document.body!!) { 17 | val packaging = "orecompose.gallery.generated.resources" 18 | PreloadResources( 19 | Res.font.resourceUrl(packaging) { ::HarmonyOS_Sans_SC_Regular.ttf }, 20 | ) { 21 | App() 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /gallery/src/jsMain/resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | OreCompose Gallery (JS) 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /gallery/src/jvmMain/kotlin/vip/cdms/orecompose/gallery/main.kt: -------------------------------------------------------------------------------- 1 | package vip.cdms.orecompose.gallery 2 | 3 | import androidx.compose.foundation.isSystemInDarkTheme 4 | import androidx.compose.ui.window.Window 5 | import androidx.compose.ui.window.application 6 | import com.mayakapps.compose.windowstyler.WindowBackdrop 7 | import com.mayakapps.compose.windowstyler.WindowStyle 8 | import orecompose.gallery.generated.resources.Res 9 | import orecompose.gallery.generated.resources.orecompose_logo 10 | import org.jetbrains.compose.resources.painterResource 11 | 12 | fun main() = application { 13 | Window( 14 | ::exitApplication, 15 | title = "OreCompose Gallery", 16 | icon = painterResource(Res.drawable.orecompose_logo) 17 | ) { 18 | WindowStyle( 19 | isDarkTheme = isSystemInDarkTheme(), 20 | backdropType = WindowBackdrop.Mica, 21 | ) 22 | App() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /gallery/src/wasmJsMain/kotlin/vip/cdms/orecompose/gallery/main.kt: -------------------------------------------------------------------------------- 1 | package vip.cdms.orecompose.gallery 2 | 3 | import androidx.compose.ui.ExperimentalComposeUiApi 4 | import androidx.compose.ui.window.ComposeViewport 5 | import kotlinx.browser.document 6 | import orecompose.gallery.generated.resources.HarmonyOS_Sans_SC_Regular 7 | import orecompose.gallery.generated.resources.Res 8 | import vip.cdms.orecompose.utils.PreloadResources 9 | import vip.cdms.orecompose.utils.ResourceUrl.Companion.resourceUrl 10 | import vip.cdms.orecompose.utils.ResourceUrl.Companion.ttf 11 | 12 | @OptIn(ExperimentalComposeUiApi::class) 13 | fun main() { 14 | ComposeViewport(document.body!!) { 15 | val packaging = "orecompose.gallery.generated.resources" 16 | PreloadResources( 17 | Res.font.resourceUrl(packaging) { ::HarmonyOS_Sans_SC_Regular.ttf }, 18 | ) { 19 | App() 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /gallery/src/wasmJsMain/resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | OreCompose Gallery (WASM) 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /gallery/src/webMain/resources/main.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | width: 100%; 3 | height: 100%; 4 | margin: 0; 5 | padding: 0; 6 | overflow: hidden; 7 | } 8 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Kotlin 2 | kotlin.code.style=official 3 | kotlin.daemon.jvmargs=-Xmx2048M 4 | kotlin.compiler.preciseCompilationResultsBackup=true 5 | 6 | # Compose 7 | org.jetbrains.compose.experimental.jscanvas.enabled=true 8 | 9 | # Gradle 10 | org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8 11 | org.gradle.caching=true 12 | org.gradle.parallel=true 13 | 14 | # Android 15 | android.nonTransitiveRClass=true 16 | android.useAndroidX=true 17 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | orecompose-lib-version = "0.0.1-alpha" 3 | 4 | android-gradle-plugin = "8.5.2" 5 | android-sdk-compile = "35" 6 | android-sdk-min = "24" 7 | android-sdk-target = "35" 8 | compose-multiplatform = "1.7.3" 9 | kotlin = "2.1.10" 10 | 11 | androidx-activity-compose = "1.10.0" 12 | kilua = "0.0.16" 13 | jetbrains-annotations = "26.0.2" 14 | 15 | mayakapps-compose-window-styler = "0.3.3-SNAPSHOT" 16 | 17 | [libraries] 18 | androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity-compose" } 19 | kilua-dom = { module = "dev.kilua:kilua-dom", version.ref = "kilua" } 20 | jetbrains-annotations = { module = "org.jetbrains:annotations", version.ref = "jetbrains-annotations" } 21 | 22 | # gallery 23 | mayakapps-compose-window-styler = { module = "com.mayakapps.compose:window-styler", version.ref = "mayakapps-compose-window-styler" } 24 | 25 | [plugins] 26 | android-application = { id = "com.android.application", version.ref = "android-gradle-plugin" } 27 | android-library = { id = "com.android.library", version.ref = "android-gradle-plugin" } 28 | compose-multiplatform = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" } 29 | compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } 30 | kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } 31 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cdm2883/OreCompose/8536db1e8e694556716b7462ad20475eb9099242/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-8.9-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original 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 POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 87 | APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit 88 | 89 | # Use the maximum available, or set MAX_FD != -1 to use that value. 90 | MAX_FD=maximum 91 | 92 | warn () { 93 | echo "$*" 94 | } >&2 95 | 96 | die () { 97 | echo 98 | echo "$*" 99 | echo 100 | exit 1 101 | } >&2 102 | 103 | # OS specific support (must be 'true' or 'false'). 104 | cygwin=false 105 | msys=false 106 | darwin=false 107 | nonstop=false 108 | case "$( uname )" in #( 109 | CYGWIN* ) cygwin=true ;; #( 110 | Darwin* ) darwin=true ;; #( 111 | MSYS* | MINGW* ) msys=true ;; #( 112 | NONSTOP* ) nonstop=true ;; 113 | esac 114 | 115 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 116 | 117 | 118 | # Determine the Java command to use to start the JVM. 119 | if [ -n "$JAVA_HOME" ] ; then 120 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 121 | # IBM's JDK on AIX uses strange locations for the executables 122 | JAVACMD=$JAVA_HOME/jre/sh/java 123 | else 124 | JAVACMD=$JAVA_HOME/bin/java 125 | fi 126 | if [ ! -x "$JAVACMD" ] ; then 127 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 128 | 129 | Please set the JAVA_HOME variable in your environment to match the 130 | location of your Java installation." 131 | fi 132 | else 133 | JAVACMD=java 134 | if ! command -v java >/dev/null 2>&1 135 | then 136 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | fi 142 | 143 | # Increase the maximum file descriptors if we can. 144 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 145 | case $MAX_FD in #( 146 | max*) 147 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 148 | # shellcheck disable=SC2039,SC3045 149 | MAX_FD=$( ulimit -H -n ) || 150 | warn "Could not query maximum file descriptor limit" 151 | esac 152 | case $MAX_FD in #( 153 | '' | soft) :;; #( 154 | *) 155 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 156 | # shellcheck disable=SC2039,SC3045 157 | ulimit -n "$MAX_FD" || 158 | warn "Could not set maximum file descriptor limit to $MAX_FD" 159 | esac 160 | fi 161 | 162 | # Collect all arguments for the java command, stacking in reverse order: 163 | # * args from the command line 164 | # * the main class name 165 | # * -classpath 166 | # * -D...appname settings 167 | # * --module-path (only if needed) 168 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 169 | 170 | # For Cygwin or MSYS, switch paths to Windows format before running java 171 | if "$cygwin" || "$msys" ; then 172 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 173 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 174 | 175 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 176 | 177 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 178 | for arg do 179 | if 180 | case $arg in #( 181 | -*) false ;; # don't mess with options #( 182 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 183 | [ -e "$t" ] ;; #( 184 | *) false ;; 185 | esac 186 | then 187 | arg=$( cygpath --path --ignore --mixed "$arg" ) 188 | fi 189 | # Roll the args list around exactly as many times as the number of 190 | # args, so each arg winds up back in the position where it started, but 191 | # possibly modified. 192 | # 193 | # NB: a `for` loop captures its iteration list before it begins, so 194 | # changing the positional parameters here affects neither the number of 195 | # iterations, nor the values presented in `arg`. 196 | shift # remove old arg 197 | set -- "$@" "$arg" # push replacement arg 198 | done 199 | fi 200 | 201 | 202 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 203 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 204 | 205 | # Collect all arguments for the java command: 206 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 207 | # and any embedded shellness will be escaped. 208 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 209 | # treated as '${Hostname}' itself on the command line. 210 | 211 | set -- \ 212 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 213 | -classpath "$CLASSPATH" \ 214 | org.gradle.wrapper.GradleWrapperMain \ 215 | "$@" 216 | 217 | # Stop when "xargs" is not available. 218 | if ! command -v xargs >/dev/null 2>&1 219 | then 220 | die "xargs is not available" 221 | fi 222 | 223 | # Use "xargs" to parse quoted args. 224 | # 225 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 226 | # 227 | # In Bash we could simply go: 228 | # 229 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 230 | # set -- "${ARGS[@]}" "$@" 231 | # 232 | # but POSIX shell has neither arrays nor command substitution, so instead we 233 | # post-process each arg (as a line of input to sed) to backslash-escape any 234 | # character that might be a shell metacharacter, then use eval to reverse 235 | # that process (while maintaining the separation between arguments), and wrap 236 | # the whole thing up as a single "set" statement. 237 | # 238 | # This will of course break if any of these variables contains a newline or 239 | # an unmatched quote. 240 | # 241 | 242 | eval "set -- $( 243 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 244 | xargs -n1 | 245 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 246 | tr '\n' ' ' 247 | )" '"$@"' 248 | 249 | exec "$JAVACMD" "$@" 250 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /oreui-panorama/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl 2 | import org.jetbrains.kotlin.gradle.dsl.JvmTarget 3 | 4 | plugins { 5 | alias(libs.plugins.kotlin.multiplatform) 6 | alias(libs.plugins.android.library) 7 | alias(libs.plugins.compose.multiplatform) 8 | alias(libs.plugins.compose.compiler) 9 | } 10 | 11 | kotlin { 12 | androidTarget { 13 | compilerOptions { 14 | jvmTarget.set(JvmTarget.JVM_11) 15 | } 16 | } 17 | 18 | jvm() 19 | 20 | @OptIn(ExperimentalWasmDsl::class) 21 | wasmJs { 22 | browser() 23 | } 24 | 25 | js { 26 | browser() 27 | } 28 | 29 | applyDefaultHierarchyTemplate() 30 | 31 | sourceSets { 32 | commonMain.dependencies { 33 | implementation(compose.runtime) 34 | implementation(compose.ui) 35 | implementation(compose.foundation) 36 | implementation(compose.components.resources) 37 | compileOnly(libs.jetbrains.annotations) 38 | implementation(projects.oreui) 39 | } 40 | 41 | val nonWebMain by creating { 42 | dependsOn(commonMain.get()) 43 | } 44 | 45 | androidMain { 46 | dependsOn(nonWebMain) 47 | dependencies { 48 | api(libs.jetbrains.annotations) 49 | //noinspection UseTomlInstead 50 | implementation("com.github.androidZzT:VRPanoramaView:1.0.1") 51 | } 52 | } 53 | 54 | val skikoMain by creating { 55 | dependsOn(commonMain.get()) 56 | } 57 | jvmMain { 58 | dependsOn(nonWebMain) 59 | dependsOn(skikoMain) 60 | dependencies { 61 | implementation(compose.desktop.common) 62 | } 63 | } 64 | 65 | val webMain by creating { 66 | dependsOn(skikoMain) 67 | dependencies { 68 | api(libs.jetbrains.annotations) 69 | implementation(libs.kilua.dom) 70 | } 71 | } 72 | wasmJsMain { 73 | dependsOn(webMain) 74 | } 75 | jsMain { 76 | dependsOn(webMain) 77 | } 78 | } 79 | 80 | compilerOptions.freeCompilerArgs.addAll( 81 | "-Xexpect-actual-classes", 82 | ) 83 | } 84 | 85 | android { 86 | namespace = "vip.cdms.orecompose.layout.panorama" 87 | compileSdk = libs.versions.android.sdk.compile.get().toInt() 88 | defaultConfig { 89 | minSdk = libs.versions.android.sdk.min.get().toInt() 90 | } 91 | compileOptions { 92 | sourceCompatibility = JavaVersion.VERSION_11 93 | targetCompatibility = JavaVersion.VERSION_11 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /oreui-panorama/src/androidMain/kotlin/vip/cdms/orecompose/layout/panorama/GlPanoramaView.kt: -------------------------------------------------------------------------------- 1 | package vip.cdms.orecompose.layout.panorama 2 | 3 | import android.content.Context 4 | import android.graphics.Bitmap 5 | import android.graphics.SurfaceTexture 6 | import android.util.AttributeSet 7 | import android.view.TextureView 8 | import android.widget.FrameLayout 9 | import com.zzt.panorama.cg.GLProducerThread 10 | import com.zzt.panorama.sphere.SphereRenderer 11 | import java.util.concurrent.atomic.AtomicBoolean 12 | 13 | class GlPanoramaView @JvmOverloads constructor( 14 | context: Context, attrs: AttributeSet? = null 15 | ) : FrameLayout(context, attrs), TextureView.SurfaceTextureListener { 16 | var onGLThreadReady = {} 17 | lateinit var bitmap: Bitmap 18 | 19 | private val view by lazy { TextureView(context) } 20 | val renderer by lazy { SphereRenderer(context) } 21 | 22 | private val rendererBiasMatrixField by lazy { 23 | SphereRenderer::class.java.getDeclaredField("mBiasMatrix") 24 | .apply { isAccessible = true } 25 | } 26 | val rendererBiasMatrix 27 | get() = rendererBiasMatrixField.get(renderer) as FloatArray 28 | 29 | // val camera by lazy { 30 | // SphereRenderer::class.java.getDeclaredField("mCamera") 31 | // .apply { isAccessible = true } 32 | // .get(renderer) as Camera 33 | // } 34 | 35 | private var isGLThreadAvailable = false 36 | var glThread: GLProducerThread? = null 37 | 38 | init { 39 | renderer.enableGyroTracking(false) 40 | view.surfaceTextureListener = this 41 | addView(view) 42 | } 43 | 44 | override fun onAttachedToWindow() { 45 | super.onAttachedToWindow() 46 | renderer.onAttached() 47 | } 48 | 49 | override fun onDetachedFromWindow() { 50 | super.onDetachedFromWindow() 51 | renderer.onDetached() 52 | glThread?.enqueueEvent { glThread!!.releaseEglContext() } 53 | } 54 | 55 | override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) { 56 | if (!isGLThreadAvailable) { 57 | isGLThreadAvailable = true 58 | glThread = GLProducerThread(surface, renderer, AtomicBoolean(true)) 59 | glThread!!.start() 60 | onGLThreadReady() 61 | glThread!!.enqueueEvent { renderer.onSurfaceChanged(width, height) } 62 | glThread!!.enqueueEvent { renderer.loadBitmap(bitmap) } 63 | } else { 64 | glThread!!.refreshSurfaceTexture(surface) 65 | glThread!!.enqueueEvent { renderer.changeTextureBitmap(bitmap) } 66 | glThread!!.onResume() 67 | } 68 | } 69 | 70 | override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture, width: Int, height: Int) { 71 | glThread?.enqueueEvent { renderer.onSurfaceChanged(width, height) } 72 | } 73 | 74 | override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean { 75 | glThread?.onPause() 76 | return true 77 | } 78 | 79 | override fun onSurfaceTextureUpdated(surface: SurfaceTexture) {} 80 | } 81 | -------------------------------------------------------------------------------- /oreui-panorama/src/androidMain/kotlin/vip/cdms/orecompose/layout/panorama/Panorama.android.kt: -------------------------------------------------------------------------------- 1 | package vip.cdms.orecompose.layout.panorama 2 | 3 | import android.app.ActivityManager 4 | import android.content.Context 5 | import android.opengl.Matrix 6 | import android.widget.FrameLayout 7 | import androidx.compose.runtime.* 8 | import androidx.compose.ui.Modifier 9 | import androidx.compose.ui.graphics.ImageBitmap 10 | import androidx.compose.ui.graphics.asAndroidBitmap 11 | import androidx.compose.ui.platform.LocalContext 12 | import androidx.compose.ui.viewinterop.AndroidView 13 | import com.zzt.panorama.cg.GLProducerThread 14 | import kotlinx.coroutines.Dispatchers 15 | import kotlinx.coroutines.delay 16 | import kotlinx.coroutines.isActive 17 | import kotlinx.coroutines.withContext 18 | import kotlin.time.Duration.Companion.milliseconds 19 | 20 | @Composable 21 | actual fun Panorama(equirectangular: ImageBitmap, modifier: Modifier, rotating: Boolean) { 22 | if ( 23 | (LocalContext.current 24 | .getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager) 25 | .deviceConfigurationInfo.reqGlEsVersion >= 0x20000 26 | ) { // support OpenGLES 2.0 27 | var view by remember { mutableStateOf(null) } 28 | AndroidView( 29 | factory = { context -> 30 | GlPanoramaView(context).apply { 31 | layoutParams = FrameLayout.LayoutParams( 32 | FrameLayout.LayoutParams.MATCH_PARENT, 33 | FrameLayout.LayoutParams.MATCH_PARENT 34 | ) 35 | onGLThreadReady = { 36 | glThread!!.setRenderMode(if (rotating) GLProducerThread.RENDERMODE_CONTINUOUSLY else GLProducerThread.RENDERMODE_WHEN_DIRTY) 37 | // renderer.reCenter() 38 | } 39 | bitmap = equirectangular.asAndroidBitmap() 40 | }.also { view = it } 41 | }, 42 | modifier, 43 | ) 44 | if (rotating && view != null) LaunchedEffect(view) { 45 | withContext(Dispatchers.Unconfined) { 46 | while (isActive) { 47 | Matrix.rotateM(view!!.rendererBiasMatrix, 0, .1f, 0f, 1f, 0f) 48 | delay(30.milliseconds) 49 | } 50 | } 51 | } 52 | } else PanoramaSoft(equirectangular, modifier, rotating) // fallback to software rendering 53 | } 54 | -------------------------------------------------------------------------------- /oreui-panorama/src/commonMain/composeResources/drawable/panorama_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cdm2883/OreCompose/8536db1e8e694556716b7462ad20475eb9099242/oreui-panorama/src/commonMain/composeResources/drawable/panorama_default.png -------------------------------------------------------------------------------- /oreui-panorama/src/commonMain/kotlin/vip/cdms/orecompose/layout/panorama/FastMath.kt: -------------------------------------------------------------------------------- 1 | package vip.cdms.orecompose.layout.panorama 2 | 3 | import kotlin.math.PI 4 | import kotlin.math.abs 5 | import kotlin.math.max 6 | import kotlin.math.min 7 | 8 | fun fastTan(x: Float): Float { 9 | val x2 = x * x 10 | val x3 = x * x2 11 | val x5 = x3 * x2 12 | 13 | return x + x3 / 3 + (2 * x5 / 15) 14 | } 15 | 16 | fun fastAtan2(y: Float, x: Float): Float { 17 | val absX = abs(x) 18 | val absY = abs(y) 19 | val a = min(absX, absY) / max(absX, absY) 20 | val s = a * a 21 | val angle = (((-0.046496473f * s + 0.15931422f) * s - 0.32762277f) * s * a + a) 22 | 23 | return if (absY > absX) { 24 | (PI / 2).toFloat() - angle 25 | } else { 26 | angle 27 | } * if (x < 0) -1 else 1 * if (y < 0) -1 else 1 28 | } 29 | 30 | fun fastAtan(x: Float, terms: Int = 5): Float { 31 | var result = 0f 32 | var power = x 33 | var sign = 1 34 | for (i in 1..terms step 2) { 35 | result += sign * power / i 36 | power *= x * x 37 | sign = -sign 38 | } 39 | return result 40 | } 41 | -------------------------------------------------------------------------------- /oreui-panorama/src/commonMain/kotlin/vip/cdms/orecompose/layout/panorama/OrePanoramaModule.kt: -------------------------------------------------------------------------------- 1 | package vip.cdms.orecompose.layout.panorama 2 | 3 | import androidx.compose.runtime.Composable 4 | import vip.cdms.orecompose.style.OreModule 5 | 6 | /** 7 | * A common [OreModule] for preloading default panorama image. 8 | * 9 | * Attach this module if you use default panorama image 10 | * could resolve some problem like panorama disappearing. 11 | * (Only works for Web) 12 | */ 13 | expect object OrePanoramaModule : OreModule { 14 | @Composable 15 | override operator fun invoke(content: @Composable () -> Unit) 16 | } 17 | -------------------------------------------------------------------------------- /oreui-panorama/src/commonMain/kotlin/vip/cdms/orecompose/layout/panorama/Panorama.kt: -------------------------------------------------------------------------------- 1 | package vip.cdms.orecompose.layout.panorama 2 | 3 | import androidx.compose.foundation.layout.fillMaxSize 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.ui.Modifier 6 | import androidx.compose.ui.draw.drawWithContent 7 | import androidx.compose.ui.graphics.Color 8 | import androidx.compose.ui.graphics.ImageBitmap 9 | import orecompose.oreui_panorama.generated.resources.Res 10 | import orecompose.oreui_panorama.generated.resources.panorama_default 11 | import org.jetbrains.compose.resources.imageResource 12 | import vip.cdms.orecompose.utils.argb 13 | 14 | object PanoramaDefaults { 15 | val Equirectangular = Res.drawable.panorama_default 16 | val Modifier = androidx.compose.ui.Modifier 17 | .fillMaxSize() 18 | .foreground(0x55000000.argb) // WTF, different platforms with different color ?! 19 | } 20 | 21 | fun Modifier.foreground(color: Color) = drawWithContent { 22 | drawContent() 23 | drawRect(color) 24 | } 25 | 26 | /** 27 | * A panorama viewer with auto rotating like Minecraft UI background. 28 | * 29 | * Could use [PanoramaBE](https://github.com/MineBuilders/PanoramaBE) 30 | * to generate the equirectangular. 31 | */ 32 | @Composable 33 | expect fun Panorama( 34 | equirectangular: ImageBitmap = imageResource(PanoramaDefaults.Equirectangular), 35 | modifier: Modifier = PanoramaDefaults.Modifier, 36 | rotating: Boolean = true, 37 | ) 38 | -------------------------------------------------------------------------------- /oreui-panorama/src/commonMain/kotlin/vip/cdms/orecompose/layout/panorama/PanoramaSoft.kt: -------------------------------------------------------------------------------- 1 | package vip.cdms.orecompose.layout.panorama 2 | 3 | import androidx.compose.foundation.Image 4 | import androidx.compose.runtime.* 5 | import androidx.compose.ui.Modifier 6 | import androidx.compose.ui.graphics.* 7 | import androidx.compose.ui.layout.ContentScale 8 | import kotlinx.coroutines.Dispatchers 9 | import kotlinx.coroutines.delay 10 | import kotlinx.coroutines.isActive 11 | import kotlinx.coroutines.withContext 12 | import org.jetbrains.compose.resources.imageResource 13 | import vip.cdms.orecompose.utils.Platform 14 | import vip.cdms.orecompose.utils.RuntimePlatform 15 | import vip.cdms.orecompose.utils.drawPoint 16 | import vip.cdms.orecompose.utils.pixelPaint 17 | import kotlin.math.PI 18 | import kotlin.math.sqrt 19 | import kotlin.time.Duration 20 | import kotlin.time.Duration.Companion.milliseconds 21 | import kotlin.time.Duration.Companion.nanoseconds 22 | 23 | /** 24 | * Panorama fallback with CPU calculation directly. 25 | * 26 | * @see Panorama 27 | */ 28 | @Composable 29 | fun PanoramaSoft( 30 | equirectangular: ImageBitmap = imageResource(PanoramaDefaults.Equirectangular), 31 | modifier: Modifier = PanoramaDefaults.Modifier, 32 | rotating: Boolean = true, 33 | period: Duration = if (RuntimePlatform is Platform.Web) 100.milliseconds else 1.nanoseconds, 34 | width: Int = 1500, // 50 35 | height: Int = 750, // 25 36 | ) { 37 | var image by remember { mutableStateOf(null) } 38 | 39 | val origin = remember { 35f * PI.toFloat() / 180 } // magic number 40 | var direction by remember { mutableIntStateOf(1) } 41 | var yaw by remember { mutableFloatStateOf(origin) } 42 | val deg1 = remember { PI.toFloat() / 180 / 5 } 43 | val deg360 = remember { 2 * PI.toFloat() - origin } 44 | 45 | LaunchedEffect(equirectangular) { 46 | val pixels = equirectangular.toPixelMap() 47 | val paint = pixelPaint() 48 | withContext(Dispatchers.Unconfined) { 49 | do { 50 | val bitmap = ImageBitmap(width, height) 51 | val canvas = Canvas(bitmap) 52 | canvas.viewPanoramaSoft( 53 | pixels, 54 | paint, 55 | yaw, 56 | pitch = 0f, 57 | fov = 90f * PI.toFloat() / 180f, 58 | width, 59 | height, 60 | ) 61 | canvas.save() 62 | image = bitmap 63 | 64 | val next = yaw + direction * deg1 65 | if (next > deg360 || next < origin) direction *= -1 66 | else yaw = next 67 | delay(period) 68 | } while (rotating && isActive) 69 | } 70 | } 71 | 72 | if (image != null) Image( 73 | bitmap = image!!, 74 | contentDescription = null, 75 | modifier = modifier, 76 | contentScale = ContentScale.Crop, 77 | ) 78 | } 79 | 80 | fun Canvas.viewPanoramaSoft( 81 | equirectangular: PixelMap, 82 | paint: Paint, 83 | yaw: Float, 84 | pitch: Float, 85 | fov: Float, 86 | width: Int, 87 | height: Int, 88 | ) { 89 | val halfWidth = width / 2 90 | val halfHeight = height / 2 91 | val focalLength = halfWidth / fastTan(fov / 2) 92 | 93 | for (x in 0 until width) { 94 | for (y in 0 until height) { 95 | val dx = x - halfWidth.toFloat() 96 | val dy = y - halfHeight.toFloat() 97 | 98 | val theta = fastAtan2(dx, focalLength) + yaw 99 | val phi = fastAtan(dy / sqrt(dx * dx + focalLength * focalLength)) + pitch 100 | 101 | val u = theta / (2 * PI) 102 | val v = phi / PI + 0.5 103 | 104 | val srcX = (u * equirectangular.width).toInt().coerceIn(0, equirectangular.width - 1) 105 | val srcY = (v * equirectangular.height).toInt().coerceIn(0, equirectangular.height - 1) 106 | val color = equirectangular[srcX, srcY] 107 | 108 | drawPoint(x.toFloat(), y.toFloat(), color, paint) 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /oreui-panorama/src/jvmMain/kotlin/vip/cdms/orecompose/layout/panorama/Panorama.jvm.kt: -------------------------------------------------------------------------------- 1 | package vip.cdms.orecompose.layout.panorama 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.Modifier 5 | import androidx.compose.ui.graphics.ImageBitmap 6 | 7 | @Composable 8 | actual fun Panorama(equirectangular: ImageBitmap, modifier: Modifier, rotating: Boolean) { 9 | PanoramaSkiko(equirectangular, modifier, rotating) 10 | } 11 | -------------------------------------------------------------------------------- /oreui-panorama/src/nonWebMain/kotlin/vip/cdms/orecompose/layout/panorama/OrePanoramaModule.nonWeb.kt: -------------------------------------------------------------------------------- 1 | package vip.cdms.orecompose.layout.panorama 2 | 3 | import androidx.compose.runtime.Composable 4 | import vip.cdms.orecompose.style.OreModule 5 | 6 | actual object OrePanoramaModule : OreModule { 7 | @Composable 8 | actual override operator fun invoke(content: @Composable () -> Unit) { 9 | content() 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /oreui-panorama/src/skikoMain/kotlin/vip/cdms/orecompose/layout/panorama/PanoramaSkiko.kt: -------------------------------------------------------------------------------- 1 | package vip.cdms.orecompose.layout.panorama 2 | 3 | import androidx.compose.foundation.Canvas 4 | import androidx.compose.runtime.* 5 | import androidx.compose.ui.Modifier 6 | import androidx.compose.ui.graphics.ImageBitmap 7 | import androidx.compose.ui.graphics.asSkiaBitmap 8 | import androidx.compose.ui.graphics.drawscope.drawIntoCanvas 9 | import androidx.compose.ui.graphics.nativeCanvas 10 | import kotlinx.coroutines.Dispatchers 11 | import kotlinx.coroutines.delay 12 | import kotlinx.coroutines.isActive 13 | import kotlinx.coroutines.withContext 14 | import org.jetbrains.skia.Paint 15 | import org.jetbrains.skia.Rect 16 | import org.jetbrains.skia.RuntimeEffect 17 | import org.jetbrains.skia.RuntimeShaderBuilder 18 | import vip.cdms.orecompose.utils.Platform 19 | import vip.cdms.orecompose.utils.RuntimePlatform 20 | import kotlin.math.PI 21 | import kotlin.time.Duration 22 | import kotlin.time.Duration.Companion.milliseconds 23 | 24 | /** 25 | * Panorama implement with Skiko. 26 | * 27 | * @see Panorama 28 | */ 29 | @Composable 30 | fun PanoramaSkiko( 31 | equirectangular: ImageBitmap, 32 | modifier: Modifier, 33 | rotating: Boolean, 34 | period: Duration = if (RuntimePlatform is Platform.Web) 100.milliseconds else 1.milliseconds, 35 | yawDelta: Float = if (RuntimePlatform is Platform.Web) .001f else .0003f, 36 | // fpsCounter: FPSCounter? = null, 37 | ) { 38 | // val skiaImage = remember(equirectangular) { equirectangular.toAwtImage().toImage() } // JVM ONLY 39 | val skiaImage = remember(equirectangular) { /*Image.makeFromBitmap(*/equirectangular.asSkiaBitmap()/*)*/ } 40 | val imageShader = remember(skiaImage) { skiaImage.makeShader() } 41 | 42 | val effect = remember { RuntimeEffect.makeForShader(Sksl) } 43 | val shaderBuilder = remember { RuntimeShaderBuilder(effect) } 44 | 45 | val paint = remember { Paint()/*.apply { isAntiAlias = true }*/ } 46 | 47 | var yaw by remember { mutableFloatStateOf(0f) } 48 | LaunchedEffect(rotating) { 49 | withContext(Dispatchers.Unconfined) { 50 | while (rotating && isActive) { 51 | yaw += yawDelta 52 | delay(period) 53 | } 54 | } 55 | } 56 | 57 | Canvas(modifier = modifier) { 58 | drawIntoCanvas { canvas -> 59 | val skiaCanvas = canvas.nativeCanvas 60 | val viewportSize = this@Canvas.size 61 | 62 | shaderBuilder.uniform("iResolution", viewportSize.width, viewportSize.height) 63 | shaderBuilder.child("panorama", imageShader) 64 | shaderBuilder.uniform("panoramaSize", skiaImage.width.toFloat(), skiaImage.height.toFloat()) 65 | shaderBuilder.uniform("yaw", yaw) 66 | shaderBuilder.uniform("pitch", 0f) 67 | shaderBuilder.uniform("fov", (130 * PI / 180).toFloat()) 68 | 69 | paint.shader = shaderBuilder.makeShader() 70 | skiaCanvas.drawRect(Rect.makeXYWH(0f, 0f, viewportSize.width, viewportSize.height), paint) 71 | 72 | // fpsCounter?.tick() 73 | // if (fpsCounter != null) ParagraphBuilder( 74 | // ParagraphStyle(), 75 | // FontCollection().setDefaultFontManager(FontMgr.default) 76 | // ) 77 | // .pushStyle(TextStyle().setColor(Color.RED).setFontSize(24f)) 78 | // .addText("FPS: ${fpsCounter.average}") 79 | // .popStyle() 80 | // .build() 81 | // .layout(Float.POSITIVE_INFINITY) 82 | // .paint(skiaCanvas, 0f, 0f) 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /oreui-panorama/src/skikoMain/kotlin/vip/cdms/orecompose/layout/panorama/SkShader.kt: -------------------------------------------------------------------------------- 1 | package vip.cdms.orecompose.layout.panorama 2 | 3 | import org.intellij.lang.annotations.Language 4 | 5 | @Language("AGSL") // IDEA has no support for sksl :( 6 | @Suppress("SpellCheckingInspection") 7 | internal val Sksl = """ 8 | uniform float2 iResolution; 9 | uniform shader panorama; 10 | uniform float2 panoramaSize; 11 | uniform float yaw; 12 | uniform float pitch; 13 | uniform float fov; 14 | 15 | const float PI = 3.141592653589793; 16 | 17 | half4 main(float2 fragCoord) { 18 | float2 uv = (fragCoord - 0.5 * iResolution) / iResolution.y; 19 | 20 | float fovScale = tan(fov * 0.5); 21 | float3 rd = normalize(float3(uv.x * fovScale, uv.y * fovScale, 1.0)); 22 | 23 | float cosPitch = cos(pitch); 24 | float sinPitch = sin(pitch); 25 | float3 rd1 = float3(rd.x, 26 | rd.y * cosPitch - rd.z * sinPitch, 27 | rd.y * sinPitch + rd.z * cosPitch); 28 | 29 | float cosYaw = cos(yaw); 30 | float sinYaw = sin(yaw); 31 | float3 rdRot = float3(rd1.x * cosYaw + rd1.z * sinYaw, 32 | rd1.y, 33 | -rd1.x * sinYaw + rd1.z * cosYaw); 34 | 35 | float theta = atan(rdRot.z, rdRot.x); 36 | float phi = asin(clamp(rdRot.y, -1.0, 1.0)); 37 | 38 | float u = (theta + PI) / (2.0 * PI); 39 | float v = (phi + 0.5 * PI) / PI; 40 | // float v = 1.0 - ((phi + 0.5 * PI) / PI); // flip Y 41 | 42 | float2 panoramaCoord = float2(u * panoramaSize.x, v * panoramaSize.y); 43 | return panorama.eval(panoramaCoord); 44 | } 45 | """.trimIndent() 46 | -------------------------------------------------------------------------------- /oreui-panorama/src/webMain/kotlin/vip/cdms/orecompose/layout/panorama/OrePanoramaModule.web.kt: -------------------------------------------------------------------------------- 1 | package vip.cdms.orecompose.layout.panorama 2 | 3 | import androidx.compose.runtime.Composable 4 | import orecompose.oreui_panorama.generated.resources.Res 5 | import orecompose.oreui_panorama.generated.resources.panorama_default 6 | import vip.cdms.orecompose.style.OreModule 7 | import vip.cdms.orecompose.utils.PreloadResources 8 | import vip.cdms.orecompose.utils.ResourceUrl.Companion.png 9 | import vip.cdms.orecompose.utils.ResourceUrl.Companion.resourceUrl 10 | 11 | actual object OrePanoramaModule : OreModule { 12 | @Composable 13 | actual override operator fun invoke(content: @Composable () -> Unit) { 14 | val packaging = "orecompose.oreui_panorama.generated.resources" 15 | PreloadResources( 16 | Res.drawable.resourceUrl(packaging) { ::panorama_default.png }, 17 | ) { 18 | content() 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /oreui-panorama/src/webMain/kotlin/vip/cdms/orecompose/layout/panorama/Panorama.web.kt: -------------------------------------------------------------------------------- 1 | package vip.cdms.orecompose.layout.panorama 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.Modifier 5 | import androidx.compose.ui.graphics.ImageBitmap 6 | 7 | @Composable 8 | actual fun Panorama(equirectangular: ImageBitmap, modifier: Modifier, rotating: Boolean) { 9 | PanoramaSkiko(equirectangular, modifier, rotating) 10 | } 11 | -------------------------------------------------------------------------------- /oreui/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl 2 | import org.jetbrains.kotlin.gradle.dsl.JvmTarget 3 | 4 | plugins { 5 | alias(libs.plugins.kotlin.multiplatform) 6 | alias(libs.plugins.android.library) 7 | alias(libs.plugins.compose.multiplatform) 8 | alias(libs.plugins.compose.compiler) 9 | } 10 | 11 | kotlin { 12 | androidTarget { 13 | compilerOptions { 14 | jvmTarget.set(JvmTarget.JVM_11) 15 | } 16 | } 17 | 18 | jvm() 19 | 20 | @OptIn(ExperimentalWasmDsl::class) 21 | wasmJs { 22 | browser() 23 | } 24 | 25 | js { 26 | browser() 27 | } 28 | 29 | applyDefaultHierarchyTemplate() 30 | 31 | sourceSets { 32 | commonMain.dependencies { 33 | implementation(compose.runtime) 34 | implementation(compose.ui) 35 | implementation(compose.foundation) 36 | implementation(compose.material) 37 | implementation(compose.components.resources) 38 | } 39 | 40 | jvmMain.dependencies { 41 | implementation(compose.desktop.common) 42 | } 43 | 44 | val webMain by creating { 45 | dependsOn(commonMain.get()) 46 | dependencies { 47 | implementation(libs.kilua.dom) 48 | } 49 | } 50 | 51 | wasmJsMain { 52 | dependsOn(webMain) 53 | } 54 | 55 | jsMain { 56 | dependsOn(webMain) 57 | } 58 | } 59 | } 60 | 61 | android { 62 | namespace = "vip.cdms.orecompose" 63 | compileSdk = libs.versions.android.sdk.compile.get().toInt() 64 | defaultConfig { 65 | minSdk = libs.versions.android.sdk.min.get().toInt() 66 | } 67 | compileOptions { 68 | sourceCompatibility = JavaVersion.VERSION_11 69 | targetCompatibility = JavaVersion.VERSION_11 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /oreui/src/androidMain/kotlin/vip/cdms/orecompose/style/OreTheme.android.kt: -------------------------------------------------------------------------------- 1 | package vip.cdms.orecompose.style 2 | 3 | import androidx.compose.runtime.Composable 4 | 5 | @Composable 6 | internal actual fun PlatformOreTheme(content: @Composable () -> Unit) { 7 | content() 8 | } 9 | -------------------------------------------------------------------------------- /oreui/src/androidMain/kotlin/vip/cdms/orecompose/utils/PlatformUtils.android.kt: -------------------------------------------------------------------------------- 1 | package vip.cdms.orecompose.utils 2 | 3 | private object CurrentPlatform : Platform.Android 4 | actual fun getPlatform(): Platform = CurrentPlatform 5 | -------------------------------------------------------------------------------- /oreui/src/commonMain/composeResources/font/Minecraft.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cdm2883/OreCompose/8536db1e8e694556716b7462ad20475eb9099242/oreui/src/commonMain/composeResources/font/Minecraft.ttf -------------------------------------------------------------------------------- /oreui/src/commonMain/kotlin/vip/cdms/orecompose/components/Input.kt: -------------------------------------------------------------------------------- 1 | package vip.cdms.orecompose.components 2 | 3 | // TODO(oreui): text input 4 | -------------------------------------------------------------------------------- /oreui/src/commonMain/kotlin/vip/cdms/orecompose/components/Label.kt: -------------------------------------------------------------------------------- 1 | package vip.cdms.orecompose.components 2 | 3 | import androidx.compose.foundation.text.InlineTextContent 4 | import androidx.compose.material.LocalTextStyle 5 | import androidx.compose.material.Text 6 | import androidx.compose.runtime.* 7 | import androidx.compose.ui.Modifier 8 | import androidx.compose.ui.graphics.Color 9 | import androidx.compose.ui.text.AnnotatedString 10 | import androidx.compose.ui.text.TextLayoutResult 11 | import androidx.compose.ui.text.TextStyle 12 | import androidx.compose.ui.text.font.FontFamily 13 | import androidx.compose.ui.text.font.FontStyle 14 | import androidx.compose.ui.text.font.FontWeight 15 | import androidx.compose.ui.text.style.TextAlign 16 | import androidx.compose.ui.text.style.TextDecoration 17 | import androidx.compose.ui.text.style.TextOverflow 18 | import vip.cdms.orecompose.effect.Pixel 19 | import vip.cdms.orecompose.effect.Px 20 | import vip.cdms.orecompose.utils.* 21 | 22 | object LocalLabel { 23 | val AutoFontsFallbackEnabled = staticCompositionLocalOf { false } 24 | } 25 | 26 | @Composable 27 | fun Label( 28 | text: AnnotatedString, 29 | modifier: Modifier = Modifier, 30 | color: Color = Color.Unspecified, 31 | fontSize: Px = Pixel.Unspecified, 32 | fontStyle: FontStyle? = null, 33 | fontWeight: FontWeight? = null, 34 | fontFamily: FontFamily? = null, 35 | letterSpacing: Px = Pixel.Unspecified, 36 | textDecoration: TextDecoration? = null, 37 | textAlign: TextAlign? = null, 38 | lineHeight: Px = Pixel.Unspecified, 39 | overflow: TextOverflow = TextOverflow.Clip, 40 | softWrap: Boolean = true, 41 | maxLines: Int = Int.MAX_VALUE, 42 | minLines: Int = 1, 43 | inlineContent: Map = mapOf(), 44 | onTextLayout: (TextLayoutResult) -> Unit = {}, 45 | style: TextStyle = LocalTextStyle.current 46 | ) { 47 | @Composable 48 | fun Text(text: AnnotatedString) = Text( 49 | text, 50 | modifier, 51 | color, 52 | fontSize.toSp(), 53 | fontStyle, 54 | fontWeight, 55 | fontFamily, 56 | letterSpacing.toSp(), 57 | textDecoration, 58 | textAlign, 59 | lineHeight.toSp(), 60 | overflow, 61 | softWrap, 62 | maxLines, 63 | minLines, 64 | inlineContent, 65 | onTextLayout, 66 | style 67 | ) 68 | 69 | // FIXME(oreui/web): cache label text 70 | if (RuntimePlatform is Platform.Web) 71 | // weird, localFontsFallback will be not working with another scope outer (like LaunchedEffect & if-else) 72 | return Text(if (LocalLabel.AutoFontsFallbackEnabled.current) text.localFontsFallback() else text) 73 | 74 | var texted by remember { mutableStateOf(null) } 75 | val localFontsFallback = LocalFontsFallback.current 76 | if (!localFontsFallback.isNullOrEmpty() && LocalLabel.AutoFontsFallbackEnabled.current) LaunchedEffect(text) { 77 | texted = text.fontsFallback(*localFontsFallback) // !!! TIME-CONSUMING !!! 78 | } 79 | Text(texted ?: text) 80 | } 81 | 82 | @Composable 83 | fun Label( 84 | text: String, 85 | modifier: Modifier = Modifier, 86 | color: Color = Color.Unspecified, 87 | fontSize: Px = Pixel.Unspecified, 88 | fontStyle: FontStyle? = null, 89 | fontWeight: FontWeight? = null, 90 | fontFamily: FontFamily? = null, 91 | letterSpacing: Px = Pixel.Unspecified, 92 | textDecoration: TextDecoration? = null, 93 | textAlign: TextAlign? = null, 94 | lineHeight: Px = Pixel.Unspecified, 95 | overflow: TextOverflow = TextOverflow.Clip, 96 | softWrap: Boolean = true, 97 | maxLines: Int = Int.MAX_VALUE, 98 | minLines: Int = 1, 99 | onTextLayout: ((TextLayoutResult) -> Unit)? = null, 100 | style: TextStyle = LocalTextStyle.current 101 | ) = Label( 102 | AnnotatedString(text), 103 | modifier, 104 | color, 105 | fontSize, 106 | fontStyle, 107 | fontWeight, 108 | fontFamily, 109 | letterSpacing, 110 | textDecoration, 111 | textAlign, 112 | lineHeight, 113 | overflow, 114 | softWrap, 115 | maxLines, 116 | minLines, 117 | onTextLayout = onTextLayout ?: {}, 118 | style = style, 119 | ) 120 | -------------------------------------------------------------------------------- /oreui/src/commonMain/kotlin/vip/cdms/orecompose/effect/Outline.kt: -------------------------------------------------------------------------------- 1 | package vip.cdms.orecompose.effect 2 | 3 | import androidx.compose.foundation.layout.padding 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.runtime.staticCompositionLocalOf 6 | import androidx.compose.ui.Modifier 7 | import androidx.compose.ui.geometry.Offset 8 | import androidx.compose.ui.geometry.Size 9 | import androidx.compose.ui.graphics.Color 10 | import androidx.compose.ui.graphics.drawscope.ContentDrawScope 11 | import androidx.compose.ui.node.DrawModifierNode 12 | import androidx.compose.ui.node.ModifierNodeElement 13 | import androidx.compose.ui.platform.InspectorInfo 14 | import androidx.compose.ui.unit.Dp 15 | import androidx.compose.ui.unit.dp 16 | import androidx.compose.ui.unit.isUnspecified 17 | import vip.cdms.orecompose.style.OreColors 18 | 19 | val LocalOutlineColor = staticCompositionLocalOf { OreColors.Outline } 20 | val LocalOutlineWidth = staticCompositionLocalOf { 1.px } 21 | 22 | @Composable 23 | fun Modifier.outline( 24 | color: Color = LocalOutlineColor.current, 25 | width: Px = LocalOutlineWidth.current, 26 | padding: Boolean = true 27 | ) = outline( 28 | color, 29 | width.toDp(), 30 | padding 31 | ) 32 | 33 | fun Modifier.outline(color: Color, width: Dp, padding: Boolean = true) = 34 | if (padding) padding(width).outline(color, width, Dp.Unspecified) 35 | else outline(color, width, accInnerOutlinesWidth()) 36 | 37 | fun Modifier.outline(color: Color, width: Dp, padding: Dp) = 38 | if (width.value > 0) this then OutlineElement(color, width, padding) else this 39 | 40 | fun Modifier.hasOutlined() = any { it is OutlineElement } 41 | 42 | internal fun Modifier.accInnerOutlinesWidth() = foldIn(0.dp) { acc, element -> 43 | acc + if (element is OutlineElement) element.width else 0.dp 44 | } 45 | 46 | private data class OutlineElement( 47 | val color: Color, 48 | val width: Dp, 49 | val padding: Dp, 50 | ) : ModifierNodeElement() { 51 | override fun create() = OutlineModifier(color, width, padding.validPadding()) 52 | 53 | override fun update(node: OutlineModifier) { 54 | node.color = color 55 | node.width = width 56 | node.padding = padding.validPadding() 57 | } 58 | 59 | override fun InspectorInfo.inspectableProperties() { 60 | name = "outline" 61 | properties["color"] = color 62 | properties["width"] = width 63 | properties["padding"] = padding 64 | } 65 | 66 | fun Dp.validPadding() = if (isUnspecified) 0.dp else this 67 | } 68 | 69 | private class OutlineModifier( 70 | var color: Color, 71 | var width: Dp, 72 | var padding: Dp, 73 | ) : Modifier.Node(), DrawModifierNode { 74 | override fun ContentDrawScope.draw() { 75 | val px = width.toPx() 76 | val pd = padding.toPx() 77 | val pd2 = pd * 2 78 | drawRect(color, Offset(x = -pd - px, y = -pd - px), Size(width = size.width + pd2 + px, height = px)) 79 | drawRect(color, Offset(x = size.width + pd, y = -pd - px), Size(width = px, height = size.height + pd2 + px)) 80 | drawRect(color, Offset(x = -pd, y = size.height + pd), Size(width = size.width + pd2 + px, height = px)) 81 | drawRect(color, Offset(x = -pd - px, y = -pd), Size(width = px, height = size.height + pd2 + px)) 82 | drawContent() 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /oreui/src/commonMain/kotlin/vip/cdms/orecompose/effect/Pixel.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("NOTHING_TO_INLINE") 2 | 3 | package vip.cdms.orecompose.effect 4 | 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.runtime.Immutable 7 | import androidx.compose.runtime.Stable 8 | import androidx.compose.runtime.staticCompositionLocalOf 9 | import androidx.compose.ui.unit.Dp 10 | import androidx.compose.ui.unit.dp 11 | import androidx.compose.ui.unit.isSpecified 12 | import androidx.compose.ui.unit.times 13 | import kotlin.jvm.JvmInline 14 | 15 | val DefaultPixelSize = 2.dp 16 | val LocalPixelSize = staticCompositionLocalOf { DefaultPixelSize } 17 | 18 | typealias Px = Pixel 19 | 20 | /** 21 | * Dimension value that is used to ensure that 22 | * the relative size of each place is strictly consistent. 23 | * 24 | * Just like the real pixel means, but it can be scaled. 25 | */ 26 | @Immutable 27 | @JvmInline 28 | value class Pixel(val value: Float) : Comparable { 29 | // 30 | // Maybe it should be a single fixed multiple instead of 31 | // a multiple of [LocalPixelSize] which can only be got in [Composable] scope 32 | // (just like the contagiousness of coroutines & js async)? 33 | // 34 | // The [Density] is already a modifiable scale value, 35 | // is it necessary to add one more factor that is more difficult to use? 36 | // 37 | // If we don't use [LocalPixelSize], it's easier to 38 | // use in a draw function that only gets [Density] without additional wrapping. 39 | // 40 | // But now there is still a chance to modify it thanks to the excellent program decoupling design! 41 | // Save that for later when more related issues arise. 42 | // 43 | @Composable inline fun toDp() = if (isSpecified) value * LocalPixelSize.current else Dp.Unspecified 44 | 45 | @Stable inline val isSpecified get() = !value.isNaN() 46 | @Stable inline val isUnspecified get() = value.isNaN() 47 | 48 | @Stable inline operator fun plus(other: Pixel) = Pixel(value + other.value) 49 | @Stable inline operator fun minus(other: Pixel) = Pixel(value - other.value) 50 | @Stable inline operator fun unaryMinus() = Pixel(-value) 51 | @Stable inline operator fun times(other: Int) = Pixel(value * other) 52 | @Stable inline operator fun times(other: Float) = Pixel(value * other) 53 | @Stable inline operator fun div(other: Int) = Pixel(value / other) 54 | @Stable inline operator fun div(other: Float) = Pixel(value / other) 55 | @Stable inline operator fun div(other: Pixel) = value / other.value 56 | @Stable override fun compareTo(other: Pixel) = value.compareTo(other.value) 57 | @Stable override fun toString() = if (isUnspecified) "Pixel.Unspecified" else "$value.px" 58 | 59 | companion object { 60 | @Composable inline fun Dp.toPx() = if (isSpecified) Pixel(this / LocalPixelSize.current) else Unspecified 61 | 62 | @Stable val Zero = Pixel(0f) 63 | @Stable val Unspecified = Pixel(Float.NaN) 64 | } 65 | } 66 | 67 | @Stable inline val Int.px get() = Pixel(toFloat()) 68 | @Stable inline val Double.px get() = Pixel(toFloat()) 69 | @Stable inline val Float.px get() = Pixel(this) 70 | 71 | @Stable inline operator fun Int.times(other: Pixel) = Pixel(this * other.value) 72 | @Stable inline operator fun Float.times(other: Pixel) = Pixel(this * other.value) 73 | @Stable inline operator fun Double.times(other: Pixel) = Pixel(toFloat() * other.value) 74 | -------------------------------------------------------------------------------- /oreui/src/commonMain/kotlin/vip/cdms/orecompose/effect/Sound.kt: -------------------------------------------------------------------------------- 1 | package vip.cdms.orecompose.effect 2 | 3 | -------------------------------------------------------------------------------- /oreui/src/commonMain/kotlin/vip/cdms/orecompose/style/McFonts.kt: -------------------------------------------------------------------------------- 1 | package vip.cdms.orecompose.style 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.text.font.FontFamily 5 | import orecompose.oreui.generated.resources.Minecraft 6 | import orecompose.oreui.generated.resources.Res 7 | import org.jetbrains.compose.resources.Font 8 | import vip.cdms.orecompose.utils.FontFamilyFallback 9 | 10 | object McFonts { 11 | 12 | // https://github.com/IdreesInc/Minecraft-Font 13 | // OFL-1.1 license 14 | object Ascii { 15 | val Resource = Res.font.Minecraft 16 | val FontFamily 17 | @Composable inline get() = FontFamily(Font(Resource)) 18 | val Fallback: FontFamilyFallback 19 | @Composable inline get() = FontFamily to ::hasGlyph 20 | val Glyphs = arrayOf(33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 21 | 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 22 | 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 23 | 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 24 | 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 25 | 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 26 | 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 27 | 121, 122, 123, 124, 125, 126, 161, 162, 163, 164, 165, 28 | 166, 167, 168, 169, 170, 171, 172, 174, 175, 176, 177, 29 | 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 30 | 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 31 | 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 32 | 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 33 | 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 34 | 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 35 | 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 8364, 36 | 9786, 9787, 9829) 37 | @Suppress("NOTHING_TO_INLINE") 38 | inline fun hasGlyph(c: Char) = c.code in Glyphs 39 | } 40 | 41 | // TODO(oreui): bold font (e.g. title) 42 | } 43 | -------------------------------------------------------------------------------- /oreui/src/commonMain/kotlin/vip/cdms/orecompose/style/OreColors.kt: -------------------------------------------------------------------------------- 1 | package vip.cdms.orecompose.style 2 | 3 | import androidx.compose.ui.graphics.Color 4 | import vip.cdms.orecompose.utils.rgb 5 | 6 | object OreColors { 7 | val PureWhite = Color.White 8 | val PureBlack = Color.Black 9 | 10 | val Outline = 0x1E1E1F.rgb 11 | val OutlineDisabled = 0x8C8D90.rgb 12 | } 13 | -------------------------------------------------------------------------------- /oreui/src/commonMain/kotlin/vip/cdms/orecompose/style/OreTheme.kt: -------------------------------------------------------------------------------- 1 | package vip.cdms.orecompose.style 2 | 3 | import androidx.compose.material.MaterialTheme 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.runtime.CompositionLocalProvider 6 | import androidx.compose.ui.unit.Dp 7 | import vip.cdms.orecompose.components.LocalLabel 8 | import vip.cdms.orecompose.effect.LocalPixelSize 9 | import vip.cdms.orecompose.utils.ComposeNester 10 | import vip.cdms.orecompose.utils.LocalFontsFallback 11 | import vip.cdms.orecompose.utils.providesMore 12 | 13 | typealias OreModule = ComposeNester 14 | 15 | @Composable 16 | fun OreTheme( 17 | pixelSize: Dp? = null, 18 | fallbackMcFonts: Boolean = true, 19 | modules: Array? = null, 20 | content: @Composable () -> Unit 21 | ) = OreModule.Apply(modules, content) { 22 | val locals = buildList { 23 | if (pixelSize != null) this += LocalPixelSize provides pixelSize 24 | if (fallbackMcFonts) { 25 | this += LocalFontsFallback providesMore arrayOf(McFonts.Ascii.Fallback) 26 | this += LocalLabel.AutoFontsFallbackEnabled provides true 27 | } 28 | } 29 | 30 | CompositionLocalProvider(*locals.toTypedArray()) { 31 | PlatformOreTheme { MaterialTheme(content = it) } 32 | } 33 | } 34 | 35 | @Composable 36 | internal expect fun PlatformOreTheme(content: @Composable () -> Unit) 37 | -------------------------------------------------------------------------------- /oreui/src/commonMain/kotlin/vip/cdms/orecompose/style/ZIndices.kt: -------------------------------------------------------------------------------- 1 | package vip.cdms.orecompose.style 2 | 3 | object ZIndices { 4 | const val ACCESSIBILITY_INDICATOR = 2f 5 | } 6 | -------------------------------------------------------------------------------- /oreui/src/commonMain/kotlin/vip/cdms/orecompose/utils/Accessibility.kt: -------------------------------------------------------------------------------- 1 | package vip.cdms.orecompose.utils 2 | 3 | import androidx.compose.foundation.interaction.InteractionSource 4 | import androidx.compose.foundation.interaction.collectIsFocusedAsState 5 | import androidx.compose.foundation.interaction.collectIsHoveredAsState 6 | import androidx.compose.runtime.* 7 | import androidx.compose.ui.Modifier 8 | import androidx.compose.ui.composed 9 | import androidx.compose.ui.graphics.Color 10 | import androidx.compose.ui.input.key.* 11 | import androidx.compose.ui.zIndex 12 | import vip.cdms.orecompose.effect.LocalOutlineWidth 13 | import vip.cdms.orecompose.style.ZIndices 14 | import vip.cdms.orecompose.effect.accInnerOutlinesWidth 15 | import vip.cdms.orecompose.effect.outline 16 | import vip.cdms.orecompose.style.OreColors 17 | 18 | val LocalAccessibilityIndicatorColor = staticCompositionLocalOf { OreColors.PureWhite } 19 | 20 | fun Modifier.accessibilityIndicator(interactionSource: InteractionSource) = accInnerOutlinesWidth().let { padding -> 21 | // WARNING: using `composed` will cause all the padding nodes after this node 22 | // calculated a wrong padding (ignore this). Because foreach modifier chain in this case 23 | // will be a ComposedModifier rather than OutlineElement. We cannot get the element inside 24 | // before it actually drawing. 25 | // 26 | // But for accessibility indicator, maybe it's not a big problem... 27 | // Because the indicator usually be the last element of modifiers. 28 | composed { 29 | val color = LocalAccessibilityIndicatorColor.current ?: return@composed this 30 | 31 | var isTab by remember { mutableStateOf(false) } 32 | val focused = interactionSource.collectIsFocusedAsState().value 33 | val hovered = interactionSource.collectIsHoveredAsState().value 34 | remember(hovered) { isTab = false } 35 | 36 | onKeyEvent { 37 | if (it.type == KeyEventType.Unknown) return@onKeyEvent false 38 | isTab = it.key == Key.Tab 39 | false 40 | }.run { 41 | if (isTab && focused && !hovered) zIndex(ZIndices.ACCESSIBILITY_INDICATOR) 42 | .outline(color, LocalOutlineWidth.current.toDp(), padding) 43 | else this 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /oreui/src/commonMain/kotlin/vip/cdms/orecompose/utils/ColorUtils.kt: -------------------------------------------------------------------------------- 1 | package vip.cdms.orecompose.utils 2 | 3 | import androidx.compose.ui.graphics.Color 4 | 5 | val Int.argb 6 | inline get() = Color(this) 7 | val Long.argb 8 | inline get() = Color(this) 9 | 10 | val Int.rgb 11 | inline get() = (0xFF000000 or this.toLong()).argb 12 | val Long.rgb 13 | inline get() = (0xFF000000L or this).argb 14 | -------------------------------------------------------------------------------- /oreui/src/commonMain/kotlin/vip/cdms/orecompose/utils/ComposeNester.kt: -------------------------------------------------------------------------------- 1 | package vip.cdms.orecompose.utils 2 | 3 | import androidx.compose.runtime.Composable 4 | 5 | private typealias Component = @Composable () -> Unit 6 | private typealias ModuleWrapper = @Composable (Component) -> Unit 7 | 8 | interface ComposeNester { 9 | @Composable 10 | operator fun invoke(content: @Composable () -> Unit) 11 | 12 | companion object { 13 | inline fun wrap(crossinline wrapper: ModuleWrapper) = object : ComposeNester { 14 | @Composable 15 | override operator fun invoke(content: Component) = wrapper(content) 16 | } 17 | 18 | /** 19 | * ```kt 20 | * Apply( 21 | * modules = arrayOf( 22 | * wrap { print("1"); it() }, 23 | * wrap { print("2"); it() }, 24 | * ), 25 | * core = wrap { print("3"); it() } 26 | * ) { print("4") } // prints 1234 27 | * ``` 28 | */ 29 | @Composable 30 | inline fun Apply(modules: Array?, core: T, noinline content: Component) = 31 | // ((modules ?: emptyArray()) + core).foldRight(content) { module, acc -> { module(acc) } }() 32 | if (modules == null) core { content() } 33 | else (modules + core).foldRight(content) { module, acc -> { module(acc) } }() 34 | 35 | @Composable 36 | inline fun Apply(modules: Array?, noinline content: Component, crossinline core: ModuleWrapper) = 37 | Apply(modules, wrap(core) as T, content) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /oreui/src/commonMain/kotlin/vip/cdms/orecompose/utils/DrawUtils.kt: -------------------------------------------------------------------------------- 1 | package vip.cdms.orecompose.utils 2 | 3 | import androidx.compose.ui.graphics.Canvas 4 | import androidx.compose.ui.graphics.Color 5 | import androidx.compose.ui.graphics.Paint 6 | import androidx.compose.ui.graphics.PointMode 7 | 8 | fun pixelPaint() = Paint().apply { 9 | isAntiAlias = false 10 | } 11 | 12 | @Suppress("NOTHING_TO_INLINE") 13 | inline fun Canvas.drawPoint(x: Float, y: Float, color: Color, paint: Paint = pixelPaint()) { 14 | paint.color = color 15 | drawRawPoints(PointMode.Points, floatArrayOf(x, y), paint) 16 | } 17 | -------------------------------------------------------------------------------- /oreui/src/commonMain/kotlin/vip/cdms/orecompose/utils/FontsFallback.kt: -------------------------------------------------------------------------------- 1 | package vip.cdms.orecompose.utils 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.staticCompositionLocalOf 5 | import androidx.compose.ui.text.AnnotatedString 6 | import androidx.compose.ui.text.SpanStyle 7 | import androidx.compose.ui.text.buildAnnotatedString 8 | import androidx.compose.ui.text.font.FontFamily 9 | import androidx.compose.ui.text.withStyle 10 | 11 | typealias FontFamilyFallback = Pair Boolean> 12 | val LocalFontsFallback = staticCompositionLocalOf?> { null } 13 | 14 | @Composable 15 | fun CharSequence.localFontsFallback() = LocalFontsFallback.current 16 | ?.takeIf { isNotEmpty() }?.let { fontsFallback(*it) } 17 | ?: if (this is AnnotatedString) this else AnnotatedString(this.toString()) 18 | 19 | fun CharSequence.fontsFallback(vararg fonts: FontFamilyFallback) = 20 | charStyle(*Array(fonts.size) { i -> SpanStyle(fontFamily = fonts[i].first) to fonts[i].second }) 21 | 22 | fun CharSequence.charStyle(vararg styles: Pair Boolean>) = buildAnnotatedString { 23 | val text = this@charStyle 24 | var i = 0 25 | // too slow... but there's no another way to fallback multi-fonts 26 | // unless rewrite the whole compose text. 27 | while (i < text.length) { 28 | val char = text[i] 29 | if (char.code in 0xd800..0xdbff) { 30 | append(text.subSequence(i, i + if (text.getOrNull(i + 1) != null) 2 else 1)) 31 | i += 2 32 | continue 33 | } 34 | styles.asSequence() 35 | .filter { (_, filter) -> filter(char) } 36 | .map { (style) -> style } 37 | .reduceOrNull { acc, next -> acc.merge(next) } 38 | ?.let { withStyle(it) { append(text.subSequence(i, i + 1)) } } 39 | ?: append(text.subSequence(i, i + 1)) 40 | i++ 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /oreui/src/commonMain/kotlin/vip/cdms/orecompose/utils/LocalUtils.kt: -------------------------------------------------------------------------------- 1 | package vip.cdms.orecompose.utils 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.ProvidableCompositionLocal 5 | import kotlin.jvm.JvmName 6 | 7 | @Composable 8 | inline infix fun , R> T.providesCompute(block: @Composable T.() -> R) = 9 | provides(block(this)) // providesComputed do not allow access value itself :( 10 | 11 | @JvmName("providesNullableMore") 12 | @Composable 13 | inline infix fun ProvidableCompositionLocal?>.providesMore(items: Array) = 14 | // providesCompute { (current ?: emptyArray()) + items } 15 | providesCompute { current?.let { it + items } ?: items } 16 | 17 | @Composable 18 | inline infix fun ProvidableCompositionLocal>.providesMore(items: Array) = 19 | providesCompute { current + items } 20 | -------------------------------------------------------------------------------- /oreui/src/commonMain/kotlin/vip/cdms/orecompose/utils/McFormat.kt: -------------------------------------------------------------------------------- 1 | package vip.cdms.orecompose.utils 2 | 3 | @Suppress("MemberVisibilityCanBePrivate", "ConstPropertyName", "unused", "SpellCheckingInspection", "NOTHING_TO_INLINE") 4 | object McFormatBuilderScope { 5 | inline infix fun String.with(format: Char) = s(format) + this 6 | fun String.attach(vararg format: Char) = SS + format.joinToString(SS.toString()) + this 7 | infix fun String.with(format: String) = attach(*format.toCharArray()) 8 | 9 | inline fun s(format: Char) = SS + format.toString() 10 | 11 | const val black = '0' 12 | const val dark_blue = '1' 13 | const val dark_green = '2' 14 | const val dark_aqua = '3' 15 | const val dark_red = '4' 16 | const val dark_purple = '5' 17 | const val gold = '6' 18 | const val gray = '7' 19 | const val dark_gray = '8' 20 | const val blue = '9' 21 | const val green = 'a' 22 | const val aqua = 'b' 23 | const val red = 'c' 24 | const val light_purple = 'd' 25 | const val yellow = 'e' 26 | const val white = 'f' 27 | const val minecoin_gold = 'g' 28 | const val material_quartz = 'h' 29 | const val material_iron = 'i' 30 | const val material_netherite = 'j' 31 | // const val material_redstone = 'm' 32 | // const val material_copper = 'n' 33 | const val material_gold = 'p' 34 | const val material_emerald = 'q' 35 | const val material_diamond = 's' 36 | const val material_lapis = 't' 37 | const val material_amethyst = 'u' 38 | 39 | const val obfuscated = 'k' 40 | const val bold = 'l' 41 | const val strikethrough = 'm' 42 | const val underline = 'n' 43 | const val italic = 'o' 44 | const val clear = 'r' 45 | 46 | fun String.clear() = with(clear) 47 | val String.black inline get() = with(this@McFormatBuilderScope.black) 48 | val String.dark_blue inline get() = with(this@McFormatBuilderScope.dark_blue) 49 | val String.dark_green inline get() = with(this@McFormatBuilderScope.dark_green) 50 | val String.dark_aqua inline get() = with(this@McFormatBuilderScope.dark_aqua) 51 | val String.dark_red inline get() = with(this@McFormatBuilderScope.dark_red) 52 | val String.dark_purple inline get() = with(this@McFormatBuilderScope.dark_purple) 53 | val String.gold inline get() = with(this@McFormatBuilderScope.gold) 54 | val String.gray inline get() = with(this@McFormatBuilderScope.gray) 55 | val String.dark_gray inline get() = with(this@McFormatBuilderScope.dark_gray) 56 | val String.blue inline get() = with(this@McFormatBuilderScope.blue) 57 | val String.green inline get() = with(this@McFormatBuilderScope.green) 58 | val String.aqua inline get() = with(this@McFormatBuilderScope.aqua) 59 | val String.red inline get() = with(this@McFormatBuilderScope.red) 60 | val String.light_purple inline get() = with(this@McFormatBuilderScope.light_purple) 61 | val String.yellow inline get() = with(this@McFormatBuilderScope.yellow) 62 | val String.white inline get() = with(this@McFormatBuilderScope.white) 63 | val String.minecoin_gold inline get() = with(this@McFormatBuilderScope.minecoin_gold) 64 | val String.material_quartz inline get() = with(this@McFormatBuilderScope.material_quartz) 65 | val String.material_iron inline get() = with(this@McFormatBuilderScope.material_iron) 66 | val String.material_netherite inline get() = with(this@McFormatBuilderScope.material_netherite) 67 | // val String.material_redstone inline get() = with(this@McFormatBuilderScope.material_redstone) 68 | // val String.material_copper inline get() = with(this@McFormatBuilderScope.material_copper) 69 | val String.material_gold inline get() = with(this@McFormatBuilderScope.material_gold) 70 | val String.material_emerald inline get() = with(this@McFormatBuilderScope.material_emerald) 71 | val String.material_diamond inline get() = with(this@McFormatBuilderScope.material_diamond) 72 | val String.material_lapis inline get() = with(this@McFormatBuilderScope.material_lapis) 73 | val String.material_amethyst inline get() = with(this@McFormatBuilderScope.material_amethyst) 74 | 75 | val String.obfuscated inline get() = with(this@McFormatBuilderScope.obfuscated) 76 | val String.bold inline get() = with(this@McFormatBuilderScope.bold) 77 | val String.strikethrough inline get() = with(this@McFormatBuilderScope.strikethrough) 78 | val String.underline inline get() = with(this@McFormatBuilderScope.underline) 79 | val String.italic inline get() = with(this@McFormatBuilderScope.italic) 80 | 81 | // mimic connecting symbols (e.g. PHP) 82 | inline operator fun String.rangeTo(string: String) = this + SS + clear + string 83 | } 84 | 85 | const val SS = '§' 86 | 87 | typealias McFormat = McFormatBuilderScope 88 | 89 | inline fun mcFormat(builder: McFormatBuilderScope.() -> T) = builder(McFormatBuilderScope) 90 | -------------------------------------------------------------------------------- /oreui/src/commonMain/kotlin/vip/cdms/orecompose/utils/McFormatter.kt: -------------------------------------------------------------------------------- 1 | package vip.cdms.orecompose.utils 2 | 3 | @Suppress("SpellCheckingInspection") 4 | val McFormattingColors = with(McFormat) { 5 | mapOf( 6 | black to 0x000000.rgb, 7 | dark_blue to 0x0000AA.rgb, 8 | dark_green to 0x00AA00.rgb, 9 | dark_aqua to 0x00AAAA.rgb, 10 | dark_red to 0xAA0000.rgb, 11 | dark_purple to 0xAA00AA.rgb, 12 | gold to 0xFFAA00.rgb, 13 | gray to 0xAAAAAA.rgb, 14 | dark_gray to 0x555555.rgb, 15 | blue to 0x5555FF.rgb, // #5455FF 16 | green to 0x55FF55.rgb, // #55FF56 17 | aqua to 0x55FFFF.rgb, 18 | red to 0xFF5555.rgb, 19 | light_purple to 0xFF55FE.rgb, 20 | yellow to 0xFFFF55.rgb, 21 | white to 0xFFFFFF.rgb, 22 | minecoin_gold to 0xEECF15.rgb, 23 | material_quartz to 0xE3D4D1.rgb, 24 | material_iron to 0xCECACA.rgb, 25 | material_netherite to 0x443A3B.rgb, 26 | // material_redstone to 0x971607.rgb, 27 | // material_copper to 0xB4684D.rgb, 28 | material_gold to 0xDEB12D.rgb, 29 | material_emerald to 0x47A036.rgb, 30 | material_diamond to 0x2CBAA8.rgb, 31 | material_lapis to 0x21497B.rgb, 32 | material_amethyst to 0x9A5CC6.rgb, 33 | ) 34 | } 35 | 36 | // TODO(oreui): text color renderer (support input & rich text edit) 37 | -------------------------------------------------------------------------------- /oreui/src/commonMain/kotlin/vip/cdms/orecompose/utils/PlatformUtils.kt: -------------------------------------------------------------------------------- 1 | package vip.cdms.orecompose.utils 2 | 3 | sealed interface Platform { 4 | interface Jvm : Platform 5 | interface Android : Platform 6 | interface Web : Platform 7 | interface Js : Web 8 | interface WasmJs : Web 9 | } 10 | 11 | internal expect fun getPlatform(): Platform 12 | val RuntimePlatform = getPlatform() 13 | -------------------------------------------------------------------------------- /oreui/src/commonMain/kotlin/vip/cdms/orecompose/utils/UnitUtils.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("NOTHING_TO_INLINE") 2 | 3 | package vip.cdms.orecompose.utils 4 | 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.runtime.Stable 7 | import androidx.compose.ui.platform.LocalDensity 8 | import androidx.compose.ui.unit.Dp 9 | import androidx.compose.ui.unit.TextUnit 10 | import vip.cdms.orecompose.effect.Pixel 11 | import vip.cdms.orecompose.effect.Pixel.Companion.toPx 12 | 13 | // so confusing :( 14 | 15 | private typealias IntDevicePixel = Int 16 | private typealias DevicePixel = Float 17 | private typealias OrePixel = Pixel 18 | 19 | @[Stable Composable] 20 | inline fun IntDevicePixel.toDp(): Dp = with(LocalDensity.current) { toDp() } 21 | @[Stable Composable] 22 | inline fun DevicePixel.toDp(): Dp = with(LocalDensity.current) { toDp() } 23 | @[Stable Composable] 24 | inline fun TextUnit.toDp(): Dp = with(LocalDensity.current) { toDp() } 25 | 26 | @[Stable Composable] 27 | inline fun IntDevicePixel.toSp(): TextUnit = with(LocalDensity.current) { toSp() } 28 | @[Stable Composable] 29 | inline fun DevicePixel.toSp(): TextUnit = with(LocalDensity.current) { toSp() } 30 | @[Stable Composable] 31 | inline fun Dp.toSp(): TextUnit = with(LocalDensity.current) { toSp() } 32 | @Composable 33 | inline fun OrePixel.toSp(): TextUnit = if (isSpecified) toDp().toSp() else TextUnit.Unspecified 34 | 35 | @[Stable Composable] 36 | inline fun TextUnit.toDPx(): DevicePixel = with(LocalDensity.current) { toPx() } 37 | @[Stable Composable] 38 | inline fun Dp.toDPx(): DevicePixel = with(LocalDensity.current) { toPx() } 39 | @Composable 40 | inline fun OrePixel.toDPx(): DevicePixel = toDp().toDPx() 41 | 42 | @Composable 43 | inline fun IntDevicePixel.toPx(): OrePixel = toDp().toPx() 44 | @Composable 45 | inline fun DevicePixel.toPx(): OrePixel = toDp().toPx() 46 | @Composable 47 | inline fun TextUnit.toPx(): OrePixel = toDp().toPx() 48 | -------------------------------------------------------------------------------- /oreui/src/jsMain/kotlin/vip/cdms/orecompose/utils/PlatformUtils.js.kt: -------------------------------------------------------------------------------- 1 | package vip.cdms.orecompose.utils 2 | 3 | private object CurrentPlatform : Platform.Js 4 | actual fun getPlatform(): Platform = CurrentPlatform 5 | -------------------------------------------------------------------------------- /oreui/src/jsMain/kotlin/vip/cdms/orecompose/utils/ResourcePreloader.js.kt: -------------------------------------------------------------------------------- 1 | package vip.cdms.orecompose.utils 2 | 3 | import androidx.compose.ui.text.font.FontFamily 4 | import androidx.compose.ui.text.platform.Font 5 | import kotlinx.browser.window 6 | import kotlinx.coroutines.await 7 | import org.khronos.webgl.ArrayBuffer 8 | import org.khronos.webgl.Uint8Array 9 | import org.khronos.webgl.get 10 | 11 | actual suspend fun loadResource(fontFamilyResolver: FontFamily.Resolver, resource: ResourceUrl) { 12 | val response = window.fetch(resource.url).await() 13 | if (!resource.isFont) return 14 | val fontBytes = response.arrayBuffer().await().toByteArray() 15 | val fontFamily = FontFamily(Font(resource.fileName.split(".").first(), fontBytes)) 16 | fontFamilyResolver.preload(fontFamily) 17 | } 18 | 19 | fun ArrayBuffer.toByteArray(): ByteArray { 20 | val uint8Array = Uint8Array(this) // k/js pretty simple :) 21 | return ByteArray(uint8Array.length) { i -> uint8Array[i] } 22 | } 23 | -------------------------------------------------------------------------------- /oreui/src/jvmMain/kotlin/vip/cdms/orecompose/style/OreTheme.jvm.kt: -------------------------------------------------------------------------------- 1 | package vip.cdms.orecompose.style 2 | 3 | import androidx.compose.runtime.Composable 4 | 5 | @Composable 6 | internal actual fun PlatformOreTheme(content: @Composable () -> Unit) { 7 | content() 8 | } 9 | -------------------------------------------------------------------------------- /oreui/src/jvmMain/kotlin/vip/cdms/orecompose/utils/PlatformUtils.jvm.kt: -------------------------------------------------------------------------------- 1 | package vip.cdms.orecompose.utils 2 | 3 | private object CurrentPlatform : Platform.Jvm 4 | actual fun getPlatform(): Platform = CurrentPlatform 5 | 6 | -------------------------------------------------------------------------------- /oreui/src/wasmJsMain/kotlin/vip/cdms/orecompose/utils/PlatformUtils.wasmJs.kt: -------------------------------------------------------------------------------- 1 | package vip.cdms.orecompose.utils 2 | 3 | private object CurrentPlatform : Platform.WasmJs 4 | actual fun getPlatform(): Platform = CurrentPlatform 5 | -------------------------------------------------------------------------------- /oreui/src/wasmJsMain/kotlin/vip/cdms/orecompose/utils/ResourcePreloader.wasmJs.kt: -------------------------------------------------------------------------------- 1 | package vip.cdms.orecompose.utils 2 | 3 | import androidx.compose.ui.text.font.FontFamily 4 | import androidx.compose.ui.text.platform.Font 5 | import kotlinx.browser.window 6 | import kotlinx.coroutines.await 7 | import org.khronos.webgl.ArrayBuffer 8 | import org.khronos.webgl.Int8Array 9 | import org.w3c.fetch.Response 10 | import kotlin.wasm.unsafe.UnsafeWasmMemoryApi 11 | import kotlin.wasm.unsafe.withScopedMemoryAllocator 12 | 13 | actual suspend fun loadResource(fontFamilyResolver: FontFamily.Resolver, resource: ResourceUrl) { 14 | val response = window.fetch(resource.url).await() 15 | if (!resource.isFont) return 16 | val fontBytes = response.arrayBuffer().await().toByteArray() 17 | val fontFamily = FontFamily(Font(resource.fileName.split(".").first(), fontBytes)) 18 | fontFamilyResolver.preload(fontFamily) 19 | } 20 | 21 | // https://github.com/JetBrains/compose-multiplatform-core/pull/1400/files#diff-9d3f03189881301ef36eabc7a053f7f1184e4cc9b831c80a0d56424ed8a0c548R62 22 | // https://github.com/JetBrains/compose-multiplatform-core/blob/7eabcc8e1bf32f1b0f5723def9576c7123b519bf/compose/mpp/demo/src/wasmJsMain/kotlin/androidx/compose/mpp/demo/main.js.kt#L83 23 | 24 | fun ArrayBuffer.toByteArray(): ByteArray { 25 | val source = Int8Array(this, 0, byteLength) 26 | return jsInt8ArrayToKotlinByteArray(source) 27 | } 28 | 29 | internal fun jsInt8ArrayToKotlinByteArray(x: Int8Array): ByteArray { 30 | val size = x.length 31 | @OptIn(UnsafeWasmMemoryApi::class) 32 | return withScopedMemoryAllocator { allocator -> 33 | val memBuffer = allocator.allocate(size) 34 | val dstAddress = memBuffer.address.toInt() 35 | jsExportInt8ArrayToWasm(x, size, dstAddress) 36 | ByteArray(size) { i -> (memBuffer + i).loadByte() } 37 | } 38 | } 39 | 40 | //language=JavaScript 41 | @JsFun(""" 42 | (src, size, dstAddr) => { 43 | // noinspection JSUnresolvedReference 44 | const mem8 = new Int8Array(wasmExports.memory.buffer, dstAddr, size); 45 | mem8.set(src); 46 | } 47 | """) 48 | internal external fun jsExportInt8ArrayToWasm(src: Int8Array, size: Int, dstAddr: Int) 49 | -------------------------------------------------------------------------------- /oreui/src/webMain/kotlin/vip/cdms/orecompose/style/OreTheme.web.kt: -------------------------------------------------------------------------------- 1 | package vip.cdms.orecompose.style 2 | 3 | import androidx.compose.runtime.Composable 4 | import orecompose.oreui.generated.resources.Minecraft 5 | import orecompose.oreui.generated.resources.Res 6 | import vip.cdms.orecompose.utils.PreloadResources 7 | import vip.cdms.orecompose.utils.ResourcePreloader 8 | import vip.cdms.orecompose.utils.ResourceUrl.Companion.resourceUrl 9 | import vip.cdms.orecompose.utils.ResourceUrl.Companion.ttf 10 | 11 | @Composable 12 | internal actual fun PlatformOreTheme(content: @Composable () -> Unit) { 13 | val packaging = "orecompose.oreui.generated.resources" 14 | PreloadResources( 15 | Res.font.resourceUrl(packaging) { ::Minecraft.ttf }, 16 | ) { 17 | ResourcePreloader { 18 | content() 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /oreui/src/webMain/kotlin/vip/cdms/orecompose/utils/ResourcePreloader.kt: -------------------------------------------------------------------------------- 1 | package vip.cdms.orecompose.utils 2 | 3 | import androidx.compose.runtime.* 4 | import androidx.compose.ui.platform.LocalFontFamilyResolver 5 | import androidx.compose.ui.text.font.FontFamily 6 | import kotlinx.coroutines.async 7 | import kotlinx.coroutines.awaitAll 8 | import org.jetbrains.compose.resources.DrawableResource 9 | import org.jetbrains.compose.resources.FontResource 10 | import kotlin.reflect.KProperty0 11 | 12 | val LocalPreloadResources = staticCompositionLocalOf?> { null } 13 | /*value*/ class ResourceUrl(val url: String) { 14 | constructor(packaging: String, type: String, file: String) : this("$ROOT/$packaging/$type/$file") 15 | val fileName get() = url.split("/").last() 16 | val isFont get() = url.contains("/font/") 17 | companion object { 18 | const val ROOT = "./composeResources" 19 | 20 | val KProperty0.png inline get() = "$name.png" 21 | val KProperty0.jpg inline get() = "$name.jpg" 22 | val KProperty0.bmp inline get() = "$name.bmp" 23 | val KProperty0.webp inline get() = "$name.webp" 24 | val KProperty0.xml inline get() = "$name.xml" 25 | 26 | val KProperty0.ttf inline get() = "$name.ttf" 27 | val KProperty0.otf inline get() = "$name.otf" 28 | 29 | // stupid kmp reflect metadata, we need real qualifiedName >:( 30 | inline fun T.resourceUrl(packaging: String, suffix: String = "", file: T.() -> String) = 31 | ResourceUrl(packaging, T::class.simpleName!! + suffix, file(this)) 32 | } 33 | } 34 | 35 | private val DisablePreloadResources = arrayOf() 36 | @Deprecated("Do NOT use this unless you know what are you doing now!", ReplaceWith("content()")) 37 | @Composable 38 | fun DisablePreloadResources(content: @Composable () -> Unit) = CompositionLocalProvider(LocalPreloadResources provides DisablePreloadResources, content) 39 | 40 | @Composable 41 | fun PreloadResources(vararg urls: /*don't allow value class*/ ResourceUrl, content: @Composable () -> Unit) { 42 | var preloadResources = LocalPreloadResources.current ?: emptyArray() 43 | if (preloadResources !== DisablePreloadResources) preloadResources += urls 44 | CompositionLocalProvider(LocalPreloadResources provides preloadResources, content) 45 | } 46 | 47 | val LocalPreloadResourcePlaceholder = staticCompositionLocalOf<(@Composable () -> Unit)?> { null } 48 | @Composable 49 | fun ResourcePreloader(content: @Composable () -> Unit) { 50 | val urls = LocalPreloadResources.current 51 | val fontFamilyResolver = LocalFontFamilyResolver.current 52 | 53 | var loaded by remember { mutableStateOf(urls == null || urls === DisablePreloadResources) } 54 | if (loaded) return content() 55 | else LocalPreloadResourcePlaceholder.current?.let { it() } 56 | 57 | LaunchedEffect(Unit) { 58 | urls!!.map { async { loadResource(fontFamilyResolver, it) } }.awaitAll() 59 | loaded = true 60 | } 61 | } 62 | 63 | expect suspend fun loadResource(fontFamilyResolver: FontFamily.Resolver, resource: ResourceUrl) 64 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "OreCompose" 2 | enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") 3 | 4 | pluginManagement { 5 | @Suppress("UnstableApiUsage") 6 | repositories { 7 | google { 8 | mavenContent { 9 | includeGroupAndSubgroups("androidx") 10 | includeGroupAndSubgroups("com.android") 11 | includeGroupAndSubgroups("com.google") 12 | } 13 | } 14 | mavenCentral() 15 | gradlePluginPortal() 16 | } 17 | } 18 | 19 | dependencyResolutionManagement { 20 | @Suppress("UnstableApiUsage") 21 | repositories { 22 | google { 23 | mavenContent { 24 | includeGroupAndSubgroups("androidx") 25 | includeGroupAndSubgroups("com.android") 26 | includeGroupAndSubgroups("com.google") 27 | } 28 | } 29 | mavenCentral() 30 | maven("https://jitpack.io") 31 | maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") 32 | } 33 | } 34 | 35 | include(":oreui") 36 | include(":oreui-panorama") 37 | include(":gallery") 38 | --------------------------------------------------------------------------------