diff --git a/Buildscripts/TactilitySDK/TactilitySDK.esp32.cmake b/Buildscripts/TactilitySDK/TactilitySDK.esp32.cmake index b1da6de34..0c0af1347 100644 --- a/Buildscripts/TactilitySDK/TactilitySDK.esp32.cmake +++ b/Buildscripts/TactilitySDK/TactilitySDK.esp32.cmake @@ -24,6 +24,12 @@ macro(tactility_project_post project_name) idf_build_set_property(COMPILE_OPTIONS "-fno-omit-frame-pointer" APPEND) endif() + # Binds the app's references to its own symbols at link time. The C++ standard library's templates + # (e.g. std::shared_ptr internals) keep default visibility despite -fvisibility=hidden, so without this + # the linker leaves symbol relocations for them, which the ELF loader only resolves against the + # firmware's exported symbols. On RISC-V that fails to load the app with "Can't find common ". + idf_build_set_property(COMPILE_OPTIONS "-Wl,-Bsymbolic" APPEND) + # ESP-IDF's own (esp_libc/platform_include) is found before app-module's, which adds # the terminal ioctls (TIOCGWINSZ) Tactility's ioctl() supports for app fds. # C and C++ only: assembly sources can't include a C header. diff --git a/Buildscripts/sdkconfig-default.properties b/Buildscripts/sdkconfig-default.properties index 3a6cdf62f..aa5e1c34c 100644 --- a/Buildscripts/sdkconfig-default.properties +++ b/Buildscripts/sdkconfig-default.properties @@ -37,6 +37,8 @@ CONFIG_LV_FS_STDIO_LETTER=65 CONFIG_LV_FS_STDIO_PATH="" CONFIG_LV_FS_STDIO_CACHE_SIZE=4096 CONFIG_LV_USE_LODEPNG=n +# 2D keyboard navigation in grids (lvgl_grid_navigation_add()) +CONFIG_LV_USE_GRIDNAV=y CONFIG_LV_USE_BUILTIN_MALLOC=n # Routes lv_malloc/realloc/free through Modules/lvgl-module/source/lv_mem_custom.c, which prefers # PSRAM (falls back to internal RAM automatically) instead of a fixed-size pool or plain malloc. diff --git a/CMakeLists.txt b/CMakeLists.txt index daec1f6c4..0a43f0afb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -115,13 +115,6 @@ if (DEFINED ENV{ESP_IDF_VERSION}) idf_build_set_property(LINK_OPTIONS "-Wl,--wrap=utime" APPEND) idf_build_set_property(LINK_OPTIONS "-Wl,--wrap=rmdir" APPEND) - idf_build_set_property(LINK_OPTIONS "-Wl,--wrap=lv_button_create" APPEND) - idf_build_set_property(LINK_OPTIONS "-Wl,--wrap=lv_dropdown_create" APPEND) - idf_build_set_property(LINK_OPTIONS "-Wl,--wrap=lv_list_create" APPEND) - idf_build_set_property(LINK_OPTIONS "-Wl,--wrap=lv_list_add_button" APPEND) - idf_build_set_property(LINK_OPTIONS "-Wl,--wrap=lv_obj_create" APPEND) - idf_build_set_property(LINK_OPTIONS "-Wl,--wrap=lv_obj_set_flex_flow" APPEND) - idf_build_set_property(LINK_OPTIONS "-Wl,--wrap=lv_switch_create" APPEND) idf_build_set_property(LINK_OPTIONS "-Wl,--wrap=lv_textarea_create" APPEND) else () diff --git a/Devices/btt-panda-touch/device.properties b/Devices/btt-panda-touch/device.properties index 4bab9a870..33b25f592 100644 --- a/Devices/btt-panda-touch/device.properties +++ b/Devices/btt-panda-touch/device.properties @@ -15,7 +15,6 @@ hardware.usbHostEnabled=true storage.userDataLocation=Internal display.size=5" -display.shape=rectangle display.dpi=187 lvgl.colorDepth=16 diff --git a/Devices/cl32/device.properties b/Devices/cl32/device.properties index 2553a4ffe..ddd42e13f 100644 --- a/Devices/cl32/device.properties +++ b/Devices/cl32/device.properties @@ -15,7 +15,6 @@ hardware.bluetooth=true storage.userDataLocation=SD display.size=2.9" -display.shape=rectangle display.dpi=139 lvgl.colorDepth=8 diff --git a/Devices/cyd-2432s022c/device.properties b/Devices/cyd-2432s022c/device.properties index 1053d5408..d8d557195 100644 --- a/Devices/cyd-2432s022c/device.properties +++ b/Devices/cyd-2432s022c/device.properties @@ -10,7 +10,6 @@ hardware.spiRam=false storage.userDataLocation=SD display.size=2.2" -display.shape=rectangle display.dpi=182 lvgl.colorDepth=16 diff --git a/Devices/cyd-2432s024c/device.properties b/Devices/cyd-2432s024c/device.properties index aac1bb88f..79fbab144 100644 --- a/Devices/cyd-2432s024c/device.properties +++ b/Devices/cyd-2432s024c/device.properties @@ -10,7 +10,6 @@ hardware.spiRam=false storage.userDataLocation=SD display.size=2.4" -display.shape=rectangle display.dpi=167 lvgl.colorDepth=16 diff --git a/Devices/cyd-2432s024r/device.properties b/Devices/cyd-2432s024r/device.properties index d9e22b351..0ecc3c95a 100644 --- a/Devices/cyd-2432s024r/device.properties +++ b/Devices/cyd-2432s024r/device.properties @@ -10,7 +10,6 @@ hardware.spiRam=false storage.userDataLocation=SD display.size=2.4" -display.shape=rectangle display.dpi=167 lvgl.colorDepth=16 diff --git a/Devices/cyd-2432s028r/device.properties b/Devices/cyd-2432s028r/device.properties index 4559a5c63..0f1cd7291 100644 --- a/Devices/cyd-2432s028r/device.properties +++ b/Devices/cyd-2432s028r/device.properties @@ -10,7 +10,6 @@ hardware.spiRam=false storage.userDataLocation=SD display.size=2.8" -display.shape=rectangle display.dpi=143 touch.calibrationSupported=true diff --git a/Devices/cyd-2432s028rv3/device.properties b/Devices/cyd-2432s028rv3/device.properties index 303a2ad6c..719d40944 100644 --- a/Devices/cyd-2432s028rv3/device.properties +++ b/Devices/cyd-2432s028rv3/device.properties @@ -10,7 +10,6 @@ hardware.spiRam=false storage.userDataLocation=SD display.size=2.8" -display.shape=rectangle display.dpi=143 touch.calibrationSupported=true diff --git a/Devices/cyd-2432s032c/device.properties b/Devices/cyd-2432s032c/device.properties index ffcd24643..9b36b1169 100644 --- a/Devices/cyd-2432s032c/device.properties +++ b/Devices/cyd-2432s032c/device.properties @@ -10,7 +10,6 @@ hardware.spiRam=false storage.userDataLocation=SD display.size=3.2" -display.shape=rectangle display.dpi=125 lvgl.colorDepth=16 diff --git a/Devices/cyd-3248s035c/device.properties b/Devices/cyd-3248s035c/device.properties index 7e468efc9..a51851cb6 100644 --- a/Devices/cyd-3248s035c/device.properties +++ b/Devices/cyd-3248s035c/device.properties @@ -10,7 +10,6 @@ hardware.spiRam=false storage.userDataLocation=SD display.size=3.5" -display.shape=rectangle display.dpi=165 lvgl.colorDepth=16 diff --git a/Devices/cyd-4848s040c/device.properties b/Devices/cyd-4848s040c/device.properties index ba432221a..4010ff28e 100644 --- a/Devices/cyd-4848s040c/device.properties +++ b/Devices/cyd-4848s040c/device.properties @@ -13,7 +13,6 @@ hardware.bluetooth=true storage.userDataLocation=SD display.size=4" -display.shape=rectangle display.dpi=170 lvgl.colorDepth=16 diff --git a/Devices/cyd-8048s043c/device.properties b/Devices/cyd-8048s043c/device.properties index d89583ac2..e8fdd45ae 100644 --- a/Devices/cyd-8048s043c/device.properties +++ b/Devices/cyd-8048s043c/device.properties @@ -15,7 +15,6 @@ hardware.bluetooth=true storage.userDataLocation=SD display.size=4.3" -display.shape=rectangle display.dpi=217 cdn.infoMessage= diff --git a/Devices/cyd-e32r28t/device.properties b/Devices/cyd-e32r28t/device.properties index e97a5ef5b..9cf2c9585 100644 --- a/Devices/cyd-e32r28t/device.properties +++ b/Devices/cyd-e32r28t/device.properties @@ -10,7 +10,6 @@ hardware.spiRam=false storage.userDataLocation=SD display.size=2.8" -display.shape=rectangle display.dpi=143 touch.calibrationSupported=true diff --git a/Devices/cyd-e32r32p/device.properties b/Devices/cyd-e32r32p/device.properties index e2627342d..fb8e19615 100644 --- a/Devices/cyd-e32r32p/device.properties +++ b/Devices/cyd-e32r32p/device.properties @@ -10,7 +10,6 @@ hardware.spiRam=false storage.userDataLocation=SD display.size=2.8" -display.shape=rectangle display.dpi=125 touch.calibrationSupported=true diff --git a/Devices/elecrow-crowpanel-advance-28/device.properties b/Devices/elecrow-crowpanel-advance-28/device.properties index a94a9e8d0..4e8cba9ac 100644 --- a/Devices/elecrow-crowpanel-advance-28/device.properties +++ b/Devices/elecrow-crowpanel-advance-28/device.properties @@ -15,7 +15,6 @@ hardware.bluetooth=true storage.userDataLocation=SD display.size=2.8" -display.shape=rectangle display.dpi=143 lvgl.colorDepth=16 diff --git a/Devices/elecrow-crowpanel-advance-35/device.properties b/Devices/elecrow-crowpanel-advance-35/device.properties index 3aca6302c..434b63456 100644 --- a/Devices/elecrow-crowpanel-advance-35/device.properties +++ b/Devices/elecrow-crowpanel-advance-35/device.properties @@ -15,7 +15,6 @@ hardware.bluetooth=true storage.userDataLocation=SD display.size=3.5" -display.shape=rectangle display.dpi=165 cdn.warningMessage=This device has display driver and memory issues. App Hub doesn't work. diff --git a/Devices/elecrow-crowpanel-advance-50/device.properties b/Devices/elecrow-crowpanel-advance-50/device.properties index 9840c228b..019d03ea2 100644 --- a/Devices/elecrow-crowpanel-advance-50/device.properties +++ b/Devices/elecrow-crowpanel-advance-50/device.properties @@ -15,7 +15,6 @@ hardware.bluetooth=true storage.userDataLocation=SD display.size=5" -display.shape=rectangle display.dpi=187 lvgl.colorDepth=16 diff --git a/Devices/elecrow-crowpanel-basic-28/device.properties b/Devices/elecrow-crowpanel-basic-28/device.properties index 58ee43716..2e331dbd0 100644 --- a/Devices/elecrow-crowpanel-basic-28/device.properties +++ b/Devices/elecrow-crowpanel-basic-28/device.properties @@ -10,7 +10,6 @@ hardware.spiRam=false storage.userDataLocation=SD display.size=2.8" -display.shape=rectangle display.dpi=143 touch.calibrationSupported=true diff --git a/Devices/elecrow-crowpanel-basic-35/device.properties b/Devices/elecrow-crowpanel-basic-35/device.properties index ba55b52d5..702b683f7 100644 --- a/Devices/elecrow-crowpanel-basic-35/device.properties +++ b/Devices/elecrow-crowpanel-basic-35/device.properties @@ -10,7 +10,6 @@ hardware.spiRam=false storage.userDataLocation=SD display.size=3.5" -display.shape=rectangle display.dpi=165 touch.calibrationSupported=true diff --git a/Devices/elecrow-crowpanel-basic-50/device.properties b/Devices/elecrow-crowpanel-basic-50/device.properties index 7e0b5e18d..0c69017c9 100644 --- a/Devices/elecrow-crowpanel-basic-50/device.properties +++ b/Devices/elecrow-crowpanel-basic-50/device.properties @@ -15,7 +15,6 @@ hardware.bluetooth=true storage.userDataLocation=SD display.size=5.0" -display.shape=rectangle display.dpi=187 lvgl.colorDepth=16 diff --git a/Devices/es3c28p/LICENSE-Apache-2.0.md b/Devices/es3c28p/LICENSE-Apache-2.0.md new file mode 100644 index 000000000..f5f4b8b5e --- /dev/null +++ b/Devices/es3c28p/LICENSE-Apache-2.0.md @@ -0,0 +1,195 @@ +Apache License +============== + +_Version 2.0, January 2004_ +_<>_ + +### Terms and Conditions for use, reproduction, and distribution + +#### 1. Definitions + +“License” shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +“Licensor” shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +“Legal Entity” shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, “control” means **(i)** the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the +outstanding shares, or **(iii)** beneficial ownership of such entity. + +“You” (or “Your”) shall mean an individual or Legal Entity exercising +permissions granted by this License. + +“Source” form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +“Object” form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +“Work” shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +“Derivative Works” shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +“Contribution” shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +“submitted” means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as “Not a Contribution.” + +“Contributor” shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +#### 2. Grant of Copyright License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +#### 3. Grant of Patent License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +#### 4. Redistribution + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +* **(a)** You must give any other recipients of the Work or Derivative Works a copy of +this License; and +* **(b)** You must cause any modified files to carry prominent notices stating that You +changed the files; and +* **(c)** You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. + +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +#### 5. Submission of Contributions + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +#### 6. Trademarks + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +#### 7. Disclaimer of Warranty + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +#### 8. Limitation of Liability + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +#### 9. Accepting Warranty or Additional Liability + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +_END OF TERMS AND CONDITIONS_ + +### APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets `[]` replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same “printed page” as the copyright notice for easier identification within +third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/Devices/es3c28p/device.properties b/Devices/es3c28p/device.properties index 7140e6178..3e3f48275 100644 --- a/Devices/es3c28p/device.properties +++ b/Devices/es3c28p/device.properties @@ -13,7 +13,6 @@ hardware.tinyUsbMsc=true hardware.bluetooth=true display.size=2.8" -display.shape=rectangle display.dpi=143 lvgl.colorDepth=16 diff --git a/Devices/guition-jc1060p470ciwy/device.properties b/Devices/guition-jc1060p470ciwy/device.properties index c126dc66b..82edaffd5 100644 --- a/Devices/guition-jc1060p470ciwy/device.properties +++ b/Devices/guition-jc1060p470ciwy/device.properties @@ -14,7 +14,6 @@ hardware.bluetooth=true storage.userDataLocation=SD display.size=7" -display.shape=rectangle display.dpi=187 lvgl.colorDepth=16 diff --git a/Devices/guition-jc2432w328c/device.properties b/Devices/guition-jc2432w328c/device.properties index f2c06450f..39fa27761 100644 --- a/Devices/guition-jc2432w328c/device.properties +++ b/Devices/guition-jc2432w328c/device.properties @@ -10,7 +10,6 @@ hardware.spiRam=false storage.userDataLocation=SD display.size=2.8" -display.shape=rectangle display.dpi=143 lvgl.colorDepth=16 diff --git a/Devices/guition-jc8048w550c/device.properties b/Devices/guition-jc8048w550c/device.properties index fb2540242..c7287f35e 100644 --- a/Devices/guition-jc8048w550c/device.properties +++ b/Devices/guition-jc8048w550c/device.properties @@ -14,7 +14,6 @@ hardware.bluetooth=true storage.userDataLocation=SD display.size=5" -display.shape=rectangle display.dpi=187 lvgl.colorDepth=16 diff --git a/Devices/heltec-wifi-lora-32-v3/device.properties b/Devices/heltec-wifi-lora-32-v3/device.properties index 54e9cb053..bd0d98d5c 100644 --- a/Devices/heltec-wifi-lora-32-v3/device.properties +++ b/Devices/heltec-wifi-lora-32-v3/device.properties @@ -15,7 +15,6 @@ hardware.bluetooth=true storage.userDataLocation=Internal display.size=0.96" -display.shape=rectangle display.dpi=149 cdn.infoMessage=Due to the small size of the screen, the icons don't render properly. diff --git a/Devices/lilygo-t5-epd47-s3/device.properties b/Devices/lilygo-t5-epd47-s3/device.properties index f518283d2..a520f030c 100644 --- a/Devices/lilygo-t5-epd47-s3/device.properties +++ b/Devices/lilygo-t5-epd47-s3/device.properties @@ -15,12 +15,9 @@ hardware.bluetooth=true storage.userDataLocation=Internal display.size=4.7" -display.shape=rectangle display.dpi=235 lvgl.colorDepth=8 lvgl.fontSize=24 lvgl.theme=Mono lvgl.statusbarColorsInverted=true - -sdkconfig.CONFIG_LV_THEME_DEFAULT_TRANSITION_TIME=0 diff --git a/Devices/lilygo-tdeck-max/device.properties b/Devices/lilygo-tdeck-max/device.properties index 20462171b..d0fb231f3 100644 --- a/Devices/lilygo-tdeck-max/device.properties +++ b/Devices/lilygo-tdeck-max/device.properties @@ -19,15 +19,10 @@ hardware.bluetooth=true storage.userDataLocation=Internal display.size=3.1" -display.shape=rectangle display.dpi=128 lvgl.colorDepth=8 -# Monochrome theme: the default colour theme renders accent-coloured UI that -# thresholds to invisible on a 1bpp panel (enables CONFIG_LV_USE_THEME_MONO). lvgl.theme=Mono lvgl.statusbarColorsInverted=true cdn.warningMessage=Display not reliably working. Use at your own risk. - -sdkconfig.CONFIG_LV_THEME_DEFAULT_TRANSITION_TIME=0 diff --git a/Devices/lilygo-tdeck-plus/device.properties b/Devices/lilygo-tdeck-plus/device.properties index b9a460775..09d50663a 100644 --- a/Devices/lilygo-tdeck-plus/device.properties +++ b/Devices/lilygo-tdeck-plus/device.properties @@ -16,7 +16,6 @@ hardware.bluetooth=true storage.userDataLocation=SD display.size=2.8" -display.shape=rectangle display.dpi=143 cdn.infoMessage=To put the device into bootloader mode:
1. Press the trackball and then the reset button at the same time,
2. Let go of the reset button, then the trackball.

When this website reports that flashing is finished, you likely have to press the reset button. diff --git a/Devices/lilygo-tdeck-pro/device.properties b/Devices/lilygo-tdeck-pro/device.properties index 7ac27e12a..8d43e9a87 100644 --- a/Devices/lilygo-tdeck-pro/device.properties +++ b/Devices/lilygo-tdeck-pro/device.properties @@ -18,15 +18,10 @@ hardware.bluetooth=true storage.userDataLocation=SD display.size=3.1" -display.shape=rectangle display.dpi=128 lvgl.colorDepth=8 -# Monochrome theme: the default colour theme renders accent-coloured UI that -# thresholds to invisible on a 1bpp panel (enables CONFIG_LV_USE_THEME_MONO). lvgl.theme=Mono lvgl.statusbarColorsInverted=true cdn.warningMessage=Only for hardware revision 1.0! Display not reliably working. Use at your own risk. - -sdkconfig.CONFIG_LV_THEME_DEFAULT_TRANSITION_TIME=0 diff --git a/Devices/lilygo-tdeck/device.properties b/Devices/lilygo-tdeck/device.properties index 512c11681..ae048e02f 100644 --- a/Devices/lilygo-tdeck/device.properties +++ b/Devices/lilygo-tdeck/device.properties @@ -16,7 +16,6 @@ hardware.bluetooth=true storage.userDataLocation=SD display.size=2.8" -display.shape=rectangle display.dpi=143 cdn.infoMessage=To put the device into bootloader mode:
1. Press the trackball and then the reset button at the same time,
2. Let go of the reset button, then the trackball.

When this website reports that flashing is finished, you likely have to press the reset button. diff --git a/Devices/lilygo-tdisplay-s3/device.properties b/Devices/lilygo-tdisplay-s3/device.properties index 8c1fd943d..a4acfacb0 100644 --- a/Devices/lilygo-tdisplay-s3/device.properties +++ b/Devices/lilygo-tdisplay-s3/device.properties @@ -16,7 +16,6 @@ hardware.bluetooth=true storage.userDataLocation=Internal display.size=1.9" -display.shape=rectangle display.dpi=191 lvgl.colorDepth=16 diff --git a/Devices/lilygo-tdisplay/device.properties b/Devices/lilygo-tdisplay/device.properties index c303178b4..65f6e5d5b 100644 --- a/Devices/lilygo-tdisplay/device.properties +++ b/Devices/lilygo-tdisplay/device.properties @@ -13,7 +13,6 @@ hardware.esptoolFlashFreq=80M storage.userDataLocation=Internal display.size=1.14" -display.shape=rectangle display.dpi=242 lvgl.colorDepth=16 diff --git a/Devices/lilygo-tdongle-s3/device.properties b/Devices/lilygo-tdongle-s3/device.properties index 6bdcf84d4..29026ceb7 100644 --- a/Devices/lilygo-tdongle-s3/device.properties +++ b/Devices/lilygo-tdongle-s3/device.properties @@ -15,7 +15,6 @@ hardware.bluetooth=true storage.userDataLocation=SD display.size=0.96" -display.shape=rectangle display.dpi=186 lvgl.colorDepth=16 diff --git a/Devices/lilygo-tembed-cc1101-plus/device.properties b/Devices/lilygo-tembed-cc1101-plus/device.properties index 0c46c00e6..3fc5b71e5 100644 --- a/Devices/lilygo-tembed-cc1101-plus/device.properties +++ b/Devices/lilygo-tembed-cc1101-plus/device.properties @@ -16,7 +16,6 @@ hardware.bluetooth=true storage.userDataLocation=SD display.size=1.9" -display.shape=rectangle display.dpi=191 lvgl.colorDepth=16 diff --git a/Devices/lilygo-thmi/device.properties b/Devices/lilygo-thmi/device.properties index b2676f753..f96e72b0d 100644 --- a/Devices/lilygo-thmi/device.properties +++ b/Devices/lilygo-thmi/device.properties @@ -15,7 +15,6 @@ hardware.bluetooth=true storage.userDataLocation=SD display.size=2.8" -display.shape=rectangle display.dpi=125 touch.calibrationSupported=true diff --git a/Devices/lilygo-tlora-pager/device.properties b/Devices/lilygo-tlora-pager/device.properties index db8935774..67036824f 100644 --- a/Devices/lilygo-tlora-pager/device.properties +++ b/Devices/lilygo-tlora-pager/device.properties @@ -16,7 +16,6 @@ hardware.bluetooth=true storage.userDataLocation=SD display.size=2.33" -display.shape=rectangle display.dpi=227 lvgl.colorDepth=16 diff --git a/Devices/m5stack-cardputer-adv/device.properties b/Devices/m5stack-cardputer-adv/device.properties index dcbe6a8bd..88f711d13 100644 --- a/Devices/m5stack-cardputer-adv/device.properties +++ b/Devices/m5stack-cardputer-adv/device.properties @@ -13,7 +13,6 @@ hardware.bluetooth=true storage.userDataLocation=SD display.size=1.14" -display.shape=rectangle # TODO: dps is actually 242, but this breaks UI (button selection becomes invisible and switch visibility is reduced) display.dpi=139 diff --git a/Devices/m5stack-cardputer/device.properties b/Devices/m5stack-cardputer/device.properties index 6f4f0529a..5acb634c3 100644 --- a/Devices/m5stack-cardputer/device.properties +++ b/Devices/m5stack-cardputer/device.properties @@ -13,7 +13,6 @@ hardware.bluetooth=true storage.userDataLocation=SD display.size=1.14" -display.shape=rectangle # TODO: dps is actually 242, but this breaks UI (button selection becomes invisible and switch visibility is reduced) display.dpi=139 diff --git a/Devices/m5stack-core2/device.properties b/Devices/m5stack-core2/device.properties index 0df414df0..75df93deb 100644 --- a/Devices/m5stack-core2/device.properties +++ b/Devices/m5stack-core2/device.properties @@ -13,7 +13,6 @@ hardware.esptoolFlashFreq=80M storage.userDataLocation=SD display.size=2" -display.shape=rectangle display.dpi=200 cdn.warningMessage=This board implementation concerns the original Core2 hardware and **not** the v1.1 variant diff --git a/Devices/m5stack-cores3/device.properties b/Devices/m5stack-cores3/device.properties index 65a648bcc..dae43e479 100644 --- a/Devices/m5stack-cores3/device.properties +++ b/Devices/m5stack-cores3/device.properties @@ -15,7 +15,6 @@ hardware.bluetooth=true storage.userDataLocation=Internal display.size=2" -display.shape=rectangle display.dpi=200 lvgl.colorDepth=16 diff --git a/Devices/m5stack-papers3/device.properties b/Devices/m5stack-papers3/device.properties index 69b644d3f..c9b589727 100644 --- a/Devices/m5stack-papers3/device.properties +++ b/Devices/m5stack-papers3/device.properties @@ -16,7 +16,6 @@ hardware.bluetooth=true storage.userDataLocation=SD display.size=4.7" -display.shape=rectangle display.dpi=235 lvgl.colorDepth=8 @@ -25,4 +24,3 @@ lvgl.theme=Mono lvgl.statusbarColorsInverted=true sdkconfig.CONFIG_EPD_DISPLAY_TYPE_ED047TC2=y -sdkconfig.CONFIG_LV_THEME_DEFAULT_TRANSITION_TIME=0 \ No newline at end of file diff --git a/Devices/m5stack-stackchan/device.properties b/Devices/m5stack-stackchan/device.properties index cbe96d54f..7fba1d863 100644 --- a/Devices/m5stack-stackchan/device.properties +++ b/Devices/m5stack-stackchan/device.properties @@ -15,7 +15,6 @@ hardware.bluetooth=true storage.userDataLocation=SD display.size=2" -display.shape=rectangle display.dpi=200 lvgl.colorDepth=16 diff --git a/Devices/m5stack-stickc-plus2/device.properties b/Devices/m5stack-stickc-plus2/device.properties index 15cf14a13..4e8c6ac96 100644 --- a/Devices/m5stack-stickc-plus2/device.properties +++ b/Devices/m5stack-stickc-plus2/device.properties @@ -13,7 +13,6 @@ hardware.esptoolFlashFreq=80M storage.userDataLocation=Internal display.size=1.14" -display.shape=rectangle display.dpi=242 lvgl.colorDepth=16 diff --git a/Devices/m5stack-sticks3/device.properties b/Devices/m5stack-sticks3/device.properties index afe3717c4..1aef1abc7 100644 --- a/Devices/m5stack-sticks3/device.properties +++ b/Devices/m5stack-sticks3/device.properties @@ -15,7 +15,6 @@ hardware.bluetooth=true storage.userDataLocation=Internal display.size=1.14" -display.shape=rectangle display.dpi=242 lvgl.colorDepth=16 diff --git a/Devices/m5stack-tab5/device.properties b/Devices/m5stack-tab5/device.properties index 97fd68691..d8c6658d1 100644 --- a/Devices/m5stack-tab5/device.properties +++ b/Devices/m5stack-tab5/device.properties @@ -21,7 +21,6 @@ storage.userDataLocation=SD dependencies.useDeprecatedHal=false display.size=5" -display.shape=rectangle display.dpi=294 lvgl.colorDepth=16 diff --git a/Devices/tulip-4r11/device.properties b/Devices/tulip-4r11/device.properties index 9afa9cf72..6e6565d9b 100644 --- a/Devices/tulip-4r11/device.properties +++ b/Devices/tulip-4r11/device.properties @@ -18,7 +18,6 @@ touch.calibrationSupported=true touch.calibrationRequired=false display.size=7" -display.shape=rectangle display.dpi=117 lvgl.colorDepth=16 diff --git a/Devices/unphone/device.properties b/Devices/unphone/device.properties index e49500535..2bcae7cd3 100644 --- a/Devices/unphone/device.properties +++ b/Devices/unphone/device.properties @@ -13,7 +13,6 @@ hardware.bluetooth=true storage.userDataLocation=SD display.size=3.5" -display.shape=rectangle display.dpi=165 touch.calibrationSupported=true diff --git a/Devices/waveshare-esp32-s3-geek/device.properties b/Devices/waveshare-esp32-s3-geek/device.properties index 685e389ca..ce932f6de 100644 --- a/Devices/waveshare-esp32-s3-geek/device.properties +++ b/Devices/waveshare-esp32-s3-geek/device.properties @@ -18,7 +18,6 @@ hardware.bluetooth=true storage.userDataLocation=SD display.size=1.14" -display.shape=rectangle display.dpi=143 lvgl.colorDepth=16 diff --git a/Devices/waveshare-s3-lcd-13/device.properties b/Devices/waveshare-s3-lcd-13/device.properties index e8752d276..232afd04d 100644 --- a/Devices/waveshare-s3-lcd-13/device.properties +++ b/Devices/waveshare-s3-lcd-13/device.properties @@ -17,7 +17,6 @@ hardware.bluetooth=true storage.userDataLocation=SD display.size=1.3" -display.shape=rectangle display.dpi=261 lvgl.colorDepth=16 diff --git a/Devices/waveshare-s3-touch-lcd-128/device.properties b/Devices/waveshare-s3-touch-lcd-128/device.properties index f73d29aa3..38312d5dc 100644 --- a/Devices/waveshare-s3-touch-lcd-128/device.properties +++ b/Devices/waveshare-s3-touch-lcd-128/device.properties @@ -18,7 +18,6 @@ hardware.bluetooth=true storage.userDataLocation=Internal display.size=1.28" -display.shape=circle display.dpi=265 lvgl.colorDepth=16 diff --git a/Devices/waveshare-s3-touch-lcd-128/waveshare,s3-touch-lcd-128.dts b/Devices/waveshare-s3-touch-lcd-128/waveshare,s3-touch-lcd-128.dts index 812e33086..1d9faa7b2 100644 --- a/Devices/waveshare-s3-touch-lcd-128/waveshare,s3-touch-lcd-128.dts +++ b/Devices/waveshare-s3-touch-lcd-128/waveshare,s3-touch-lcd-128.dts @@ -84,6 +84,7 @@ compatible = "galaxycore,gc9a01"; horizontal-resolution = <240>; vertical-resolution = <240>; + shape = ; mirror-y; invert-color; bgr-order; diff --git a/Devices/waveshare-s3-touch-lcd-147/device.properties b/Devices/waveshare-s3-touch-lcd-147/device.properties index d1f59300e..1e2c95cbd 100644 --- a/Devices/waveshare-s3-touch-lcd-147/device.properties +++ b/Devices/waveshare-s3-touch-lcd-147/device.properties @@ -16,7 +16,6 @@ hardware.bluetooth=true storage.userDataLocation=SD display.size=1.47" -display.shape=rectangle display.dpi=247 lvgl.colorDepth=16 diff --git a/Devices/waveshare-s3-touch-lcd-147/waveshare,s3-touch-lcd-147.dts b/Devices/waveshare-s3-touch-lcd-147/waveshare,s3-touch-lcd-147.dts index fcad9fd57..df7bfebf9 100644 --- a/Devices/waveshare-s3-touch-lcd-147/waveshare,s3-touch-lcd-147.dts +++ b/Devices/waveshare-s3-touch-lcd-147/waveshare,s3-touch-lcd-147.dts @@ -77,6 +77,9 @@ compatible = "jadard,jd9853"; horizontal-resolution = <172>; vertical-resolution = <320>; + // The panel has rounded corners. The radius is estimated, as it isn't specified. + shape = ; + corner-radius = <40>; gap-x = <34>; invert-color; pin-dc = <&gpio0 45 GPIO_FLAG_NONE>; diff --git a/Devices/waveshare-s3-touch-lcd-43/device.properties b/Devices/waveshare-s3-touch-lcd-43/device.properties index 7d326801a..bde03843a 100644 --- a/Devices/waveshare-s3-touch-lcd-43/device.properties +++ b/Devices/waveshare-s3-touch-lcd-43/device.properties @@ -16,7 +16,6 @@ hardware.bluetooth=true storage.userDataLocation=SD display.size=4.3" -display.shape=rectangle display.dpi=217 lvgl.colorDepth=16 diff --git a/Devices/wireless-tag-wt32-sc01-plus/device.properties b/Devices/wireless-tag-wt32-sc01-plus/device.properties index 1c70f0135..41b764c96 100644 --- a/Devices/wireless-tag-wt32-sc01-plus/device.properties +++ b/Devices/wireless-tag-wt32-sc01-plus/device.properties @@ -15,7 +15,6 @@ hardware.bluetooth=true storage.userDataLocation=SD display.size=3.5" -display.shape=rectangle display.dpi=165 lvgl.colorDepth=16 diff --git a/Documentation/ideas.md b/Documentation/ideas.md index 89017ee85..946320791 100644 --- a/Documentation/ideas.md +++ b/Documentation/ideas.md @@ -7,7 +7,9 @@ ## Higher Priority -- Terminal app should always run in non-LVGL mode if no PSRAM is present. +- Update to LVGL 9.6.x (ESP-IDF and POSIX) +- Terminal app: on devices with pointer device: LVGL rendering mode should hide the toolbar by default, but tapping the terminal should toggle the visibility of the toolbar. +- Terminal app: should always run in non-LVGL mode if no PSRAM is present. - Crash app should work without LVGL, like Boot app. It should wait for any input (keyboard or pointer) to continue. - NimBLE looses pairing key after reboot - CrashDiagnostics shouldn't show a QR when there's no callstack diff --git a/Documentation/pics/screenshot-AppList.png b/Documentation/pics/screenshot-AppList.png deleted file mode 100644 index 920ef917f..000000000 Binary files a/Documentation/pics/screenshot-AppList.png and /dev/null differ diff --git a/Documentation/pics/screenshot-Files.png b/Documentation/pics/screenshot-Files.png new file mode 100644 index 000000000..e6a579902 Binary files /dev/null and b/Documentation/pics/screenshot-Files.png differ diff --git a/Documentation/pics/screenshot-Launcher.png b/Documentation/pics/screenshot-Launcher.png index 0f4e21adc..dfdfc183a 100644 Binary files a/Documentation/pics/screenshot-Launcher.png and b/Documentation/pics/screenshot-Launcher.png differ diff --git a/Documentation/pics/screenshot-Settings.png b/Documentation/pics/screenshot-Settings.png index a59074a47..bc92adeaf 100644 Binary files a/Documentation/pics/screenshot-Settings.png and b/Documentation/pics/screenshot-Settings.png differ diff --git a/Documentation/pics/screenshot-Terminal.png b/Documentation/pics/screenshot-Terminal.png new file mode 100644 index 000000000..01a46e03c Binary files /dev/null and b/Documentation/pics/screenshot-Terminal.png differ diff --git a/Documentation/pics/tactility-devices.webp b/Documentation/pics/tactility-devices.webp deleted file mode 100644 index 17f7b5507..000000000 Binary files a/Documentation/pics/tactility-devices.webp and /dev/null differ diff --git a/Drivers/bq25896-module/LICENSE-Apache-2.0.md b/Drivers/bq25896-module/LICENSE-Apache-2.0.md new file mode 100644 index 000000000..f5f4b8b5e --- /dev/null +++ b/Drivers/bq25896-module/LICENSE-Apache-2.0.md @@ -0,0 +1,195 @@ +Apache License +============== + +_Version 2.0, January 2004_ +_<>_ + +### Terms and Conditions for use, reproduction, and distribution + +#### 1. Definitions + +“License” shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +“Licensor” shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +“Legal Entity” shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, “control” means **(i)** the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the +outstanding shares, or **(iii)** beneficial ownership of such entity. + +“You” (or “Your”) shall mean an individual or Legal Entity exercising +permissions granted by this License. + +“Source” form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +“Object” form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +“Work” shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +“Derivative Works” shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +“Contribution” shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +“submitted” means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as “Not a Contribution.” + +“Contributor” shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +#### 2. Grant of Copyright License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +#### 3. Grant of Patent License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +#### 4. Redistribution + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +* **(a)** You must give any other recipients of the Work or Derivative Works a copy of +this License; and +* **(b)** You must cause any modified files to carry prominent notices stating that You +changed the files; and +* **(c)** You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. + +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +#### 5. Submission of Contributions + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +#### 6. Trademarks + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +#### 7. Disclaimer of Warranty + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +#### 8. Limitation of Liability + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +#### 9. Accepting Warranty or Additional Liability + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +_END OF TERMS AND CONDITIONS_ + +### APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets `[]` replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same “printed page” as the copyright notice for easier identification within +third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/Drivers/bq27220-module/LICENSE-Apache-2.0.md b/Drivers/bq27220-module/LICENSE-Apache-2.0.md new file mode 100644 index 000000000..f5f4b8b5e --- /dev/null +++ b/Drivers/bq27220-module/LICENSE-Apache-2.0.md @@ -0,0 +1,195 @@ +Apache License +============== + +_Version 2.0, January 2004_ +_<>_ + +### Terms and Conditions for use, reproduction, and distribution + +#### 1. Definitions + +“License” shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +“Licensor” shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +“Legal Entity” shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, “control” means **(i)** the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the +outstanding shares, or **(iii)** beneficial ownership of such entity. + +“You” (or “Your”) shall mean an individual or Legal Entity exercising +permissions granted by this License. + +“Source” form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +“Object” form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +“Work” shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +“Derivative Works” shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +“Contribution” shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +“submitted” means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as “Not a Contribution.” + +“Contributor” shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +#### 2. Grant of Copyright License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +#### 3. Grant of Patent License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +#### 4. Redistribution + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +* **(a)** You must give any other recipients of the Work or Derivative Works a copy of +this License; and +* **(b)** You must cause any modified files to carry prominent notices stating that You +changed the files; and +* **(c)** You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. + +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +#### 5. Submission of Contributions + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +#### 6. Trademarks + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +#### 7. Disclaimer of Warranty + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +#### 8. Limitation of Liability + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +#### 9. Accepting Warranty or Additional Liability + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +_END OF TERMS AND CONDITIONS_ + +### APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets `[]` replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same “printed page” as the copyright notice for easier identification within +third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/Drivers/button-control-module/source/button_control.cpp b/Drivers/button-control-module/source/button_control.cpp index 3f6f91da4..868a164c6 100644 --- a/Drivers/button-control-module/source/button_control.cpp +++ b/Drivers/button-control-module/source/button_control.cpp @@ -103,7 +103,7 @@ static error_t start(Device* device) { error_t error = acquire_button( config->pin_primary, - two_button_mode ? (uint32_t)CODEPOINT_ENTER : (uint32_t)CODEPOINT_ARROW_DOWN, + two_button_mode ? (uint32_t)CODEPOINT_ENTER : (uint32_t)CODEPOINT_FOCUS_NEXT, two_button_mode ? (uint32_t)CODEPOINT_ESCAPE : (uint32_t)CODEPOINT_ENTER, &internal->primary ); @@ -112,7 +112,7 @@ static error_t start(Device* device) { return error; } - error = acquire_button(config->pin_secondary, CODEPOINT_ARROW_DOWN, CODEPOINT_ARROW_UP, &internal->secondary); + error = acquire_button(config->pin_secondary, CODEPOINT_FOCUS_NEXT, CODEPOINT_FOCUS_PREVIOUS, &internal->secondary); if (error != ERROR_NONE) { if (internal->primary.in_use) { gpio_descriptor_release(internal->primary.descriptor); diff --git a/Drivers/cst328-module/LICENSE-Apache-2.0.md b/Drivers/cst328-module/LICENSE-Apache-2.0.md new file mode 100644 index 000000000..f5f4b8b5e --- /dev/null +++ b/Drivers/cst328-module/LICENSE-Apache-2.0.md @@ -0,0 +1,195 @@ +Apache License +============== + +_Version 2.0, January 2004_ +_<>_ + +### Terms and Conditions for use, reproduction, and distribution + +#### 1. Definitions + +“License” shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +“Licensor” shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +“Legal Entity” shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, “control” means **(i)** the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the +outstanding shares, or **(iii)** beneficial ownership of such entity. + +“You” (or “Your”) shall mean an individual or Legal Entity exercising +permissions granted by this License. + +“Source” form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +“Object” form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +“Work” shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +“Derivative Works” shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +“Contribution” shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +“submitted” means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as “Not a Contribution.” + +“Contributor” shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +#### 2. Grant of Copyright License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +#### 3. Grant of Patent License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +#### 4. Redistribution + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +* **(a)** You must give any other recipients of the Work or Derivative Works a copy of +this License; and +* **(b)** You must cause any modified files to carry prominent notices stating that You +changed the files; and +* **(c)** You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. + +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +#### 5. Submission of Contributions + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +#### 6. Trademarks + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +#### 7. Disclaimer of Warranty + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +#### 8. Limitation of Liability + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +#### 9. Accepting Warranty or Additional Liability + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +_END OF TERMS AND CONDITIONS_ + +### APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets `[]` replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same “printed page” as the copyright notice for easier identification within +third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/Drivers/cst66xx-module/LICENSE-Apache-2.0.md b/Drivers/cst66xx-module/LICENSE-Apache-2.0.md new file mode 100644 index 000000000..f5f4b8b5e --- /dev/null +++ b/Drivers/cst66xx-module/LICENSE-Apache-2.0.md @@ -0,0 +1,195 @@ +Apache License +============== + +_Version 2.0, January 2004_ +_<>_ + +### Terms and Conditions for use, reproduction, and distribution + +#### 1. Definitions + +“License” shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +“Licensor” shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +“Legal Entity” shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, “control” means **(i)** the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the +outstanding shares, or **(iii)** beneficial ownership of such entity. + +“You” (or “Your”) shall mean an individual or Legal Entity exercising +permissions granted by this License. + +“Source” form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +“Object” form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +“Work” shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +“Derivative Works” shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +“Contribution” shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +“submitted” means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as “Not a Contribution.” + +“Contributor” shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +#### 2. Grant of Copyright License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +#### 3. Grant of Patent License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +#### 4. Redistribution + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +* **(a)** You must give any other recipients of the Work or Derivative Works a copy of +this License; and +* **(b)** You must cause any modified files to carry prominent notices stating that You +changed the files; and +* **(c)** You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. + +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +#### 5. Submission of Contributions + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +#### 6. Trademarks + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +#### 7. Disclaimer of Warranty + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +#### 8. Limitation of Liability + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +#### 9. Accepting Warranty or Additional Liability + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +_END OF TERMS AND CONDITIONS_ + +### APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets `[]` replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same “printed page” as the copyright notice for easier identification within +third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/Drivers/cst816s-module/LICENSE-Apache-2.0.md b/Drivers/cst816s-module/LICENSE-Apache-2.0.md new file mode 100644 index 000000000..f5f4b8b5e --- /dev/null +++ b/Drivers/cst816s-module/LICENSE-Apache-2.0.md @@ -0,0 +1,195 @@ +Apache License +============== + +_Version 2.0, January 2004_ +_<>_ + +### Terms and Conditions for use, reproduction, and distribution + +#### 1. Definitions + +“License” shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +“Licensor” shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +“Legal Entity” shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, “control” means **(i)** the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the +outstanding shares, or **(iii)** beneficial ownership of such entity. + +“You” (or “Your”) shall mean an individual or Legal Entity exercising +permissions granted by this License. + +“Source” form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +“Object” form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +“Work” shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +“Derivative Works” shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +“Contribution” shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +“submitted” means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as “Not a Contribution.” + +“Contributor” shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +#### 2. Grant of Copyright License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +#### 3. Grant of Patent License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +#### 4. Redistribution + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +* **(a)** You must give any other recipients of the Work or Derivative Works a copy of +this License; and +* **(b)** You must cause any modified files to carry prominent notices stating that You +changed the files; and +* **(c)** You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. + +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +#### 5. Submission of Contributions + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +#### 6. Trademarks + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +#### 7. Disclaimer of Warranty + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +#### 8. Limitation of Liability + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +#### 9. Accepting Warranty or Additional Liability + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +_END OF TERMS AND CONDITIONS_ + +### APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets `[]` replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same “printed page” as the copyright notice for easier identification within +third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/Drivers/drv2605-module/LICENSE-Apache-2.0.md b/Drivers/drv2605-module/LICENSE-Apache-2.0.md new file mode 100644 index 000000000..f5f4b8b5e --- /dev/null +++ b/Drivers/drv2605-module/LICENSE-Apache-2.0.md @@ -0,0 +1,195 @@ +Apache License +============== + +_Version 2.0, January 2004_ +_<>_ + +### Terms and Conditions for use, reproduction, and distribution + +#### 1. Definitions + +“License” shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +“Licensor” shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +“Legal Entity” shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, “control” means **(i)** the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the +outstanding shares, or **(iii)** beneficial ownership of such entity. + +“You” (or “Your”) shall mean an individual or Legal Entity exercising +permissions granted by this License. + +“Source” form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +“Object” form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +“Work” shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +“Derivative Works” shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +“Contribution” shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +“submitted” means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as “Not a Contribution.” + +“Contributor” shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +#### 2. Grant of Copyright License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +#### 3. Grant of Patent License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +#### 4. Redistribution + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +* **(a)** You must give any other recipients of the Work or Derivative Works a copy of +this License; and +* **(b)** You must cause any modified files to carry prominent notices stating that You +changed the files; and +* **(c)** You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. + +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +#### 5. Submission of Contributions + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +#### 6. Trademarks + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +#### 7. Disclaimer of Warranty + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +#### 8. Limitation of Liability + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +#### 9. Accepting Warranty or Additional Liability + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +_END OF TERMS AND CONDITIONS_ + +### APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets `[]` replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same “printed page” as the copyright notice for easier identification within +third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/Drivers/gc9a01-module/bindings/galaxycore,gc9a01.yaml b/Drivers/gc9a01-module/bindings/galaxycore,gc9a01.yaml index 9fa38debe..cc815a2a2 100644 --- a/Drivers/gc9a01-module/bindings/galaxycore,gc9a01.yaml +++ b/Drivers/gc9a01-module/bindings/galaxycore,gc9a01.yaml @@ -2,6 +2,8 @@ description: Galaxycore GC9A01 round display panel bus: spi +include: ["display.yaml"] + compatible: "galaxycore,gc9a01" properties: diff --git a/Drivers/gc9a01-module/include/drivers/gc9a01.h b/Drivers/gc9a01-module/include/drivers/gc9a01.h index 233811faf..03486f3b1 100644 --- a/Drivers/gc9a01-module/include/drivers/gc9a01.h +++ b/Drivers/gc9a01-module/include/drivers/gc9a01.h @@ -9,9 +9,13 @@ extern "C" { #include #include +#include #include struct Gc9a01Config { + // From display.yaml: a DisplayShapeType value and the corner radius for rounded displays + uint8_t shape; + uint16_t corner_radius; uint16_t horizontal_resolution; uint16_t vertical_resolution; int32_t gap_x; diff --git a/Drivers/gc9a01-module/source/gc9a01.cpp b/Drivers/gc9a01-module/source/gc9a01.cpp index ca89764dc..a2d956c2a 100644 --- a/Drivers/gc9a01-module/source/gc9a01.cpp +++ b/Drivers/gc9a01-module/source/gc9a01.cpp @@ -316,6 +316,11 @@ static error_t gc9a01_get_backlight(Device* device, Device** backlight) { // endregion +static void gc9a01_get_shape(Device* device, DisplayShape* shape) { + const auto* config = GET_CONFIG(device); + *shape = { .shape = config->shape, .corner_radius = config->corner_radius }; +} + static const DisplayApi gc9a01_display_api = { .capabilities = DISPLAY_CAPABILITY_CAP_MIRROR | DISPLAY_CAPABILITY_CAP_SWAP_XY | DISPLAY_CAPABILITY_CAP_SET_GAP | DISPLAY_CAPABILITY_INVERT_COLOR | DISPLAY_CAPABILITY_ON_OFF | @@ -343,6 +348,7 @@ static const DisplayApi gc9a01_display_api = { .get_frame_buffer_count = nullptr, .get_backlight = gc9a01_get_backlight, .has_capability = nullptr, + .get_shape = gc9a01_get_shape, }; Driver gc9a01_driver = { diff --git a/Drivers/gpio-encoder-module/bindings/tactility,gpio-encoder.yaml b/Drivers/gpio-encoder-module/bindings/tactility,gpio-encoder.yaml index aa22d3f89..a380748f0 100644 --- a/Drivers/gpio-encoder-module/bindings/tactility,gpio-encoder.yaml +++ b/Drivers/gpio-encoder-module/bindings/tactility,gpio-encoder.yaml @@ -1,7 +1,7 @@ description: > GPIO-attached rotary encoder wheel - a 2-phase quadrature encoder (decoded via the ESP32 hardware PCNT peripheral) plus an optional separate click/enter button. Exposes a - KEYBOARD_TYPE device: each wheel detent is translated to an arrow up/down key, and the + KEYBOARD_TYPE device: each wheel detent is translated to a focus next/previous key, and the button (if present) press/release is translated to the enter key. compatible: "tactility,gpio-encoder" diff --git a/Drivers/gpio-encoder-module/source/gpio_encoder.cpp b/Drivers/gpio-encoder-module/source/gpio_encoder.cpp index 613a20411..ef9594742 100644 --- a/Drivers/gpio-encoder-module/source/gpio_encoder.cpp +++ b/Drivers/gpio-encoder-module/source/gpio_encoder.cpp @@ -262,7 +262,7 @@ static void poll_wheel(GpioEncoderInternal* internal) { int32_t detents = total / internal->pulses_per_detent; internal->pulse_remainder = total % internal->pulses_per_detent; - uint32_t key = detents >= 0 ? CODEPOINT_ARROW_DOWN : CODEPOINT_ARROW_UP; + uint32_t key = detents >= 0 ? CODEPOINT_FOCUS_NEXT : CODEPOINT_FOCUS_PREVIOUS; int32_t count = detents >= 0 ? detents : -detents; for (int32_t i = 0; i < count; i++) { // A press without its matching release would leave the consumer thinking the key diff --git a/Drivers/jd9853-module/bindings/jadard,jd9853.yaml b/Drivers/jd9853-module/bindings/jadard,jd9853.yaml index c7ae39874..7bcc4d86f 100644 --- a/Drivers/jd9853-module/bindings/jadard,jd9853.yaml +++ b/Drivers/jd9853-module/bindings/jadard,jd9853.yaml @@ -2,6 +2,8 @@ description: Jadard JD9853 display panel bus: spi +include: ["display.yaml"] + compatible: "jadard,jd9853" properties: diff --git a/Drivers/jd9853-module/include/drivers/jd9853.h b/Drivers/jd9853-module/include/drivers/jd9853.h index 435b77d4a..daaed2eda 100644 --- a/Drivers/jd9853-module/include/drivers/jd9853.h +++ b/Drivers/jd9853-module/include/drivers/jd9853.h @@ -9,9 +9,13 @@ extern "C" { #include #include +#include #include struct Jd9853Config { + // From display.yaml: a DisplayShapeType value and the corner radius for rounded displays + uint8_t shape; + uint16_t corner_radius; uint16_t horizontal_resolution; uint16_t vertical_resolution; int32_t gap_x; diff --git a/Drivers/jd9853-module/source/jd9853.cpp b/Drivers/jd9853-module/source/jd9853.cpp index 09ac98f98..03cf9a92b 100644 --- a/Drivers/jd9853-module/source/jd9853.cpp +++ b/Drivers/jd9853-module/source/jd9853.cpp @@ -329,6 +329,11 @@ static error_t jd9853_get_backlight(Device* device, Device** backlight) { // endregion +static void jd9853_get_shape(Device* device, DisplayShape* shape) { + const auto* config = GET_CONFIG(device); + *shape = { .shape = config->shape, .corner_radius = config->corner_radius }; +} + static const DisplayApi jd9853_display_api = { .capabilities = DISPLAY_CAPABILITY_CAP_MIRROR | DISPLAY_CAPABILITY_CAP_SWAP_XY | DISPLAY_CAPABILITY_CAP_SET_GAP | DISPLAY_CAPABILITY_INVERT_COLOR | DISPLAY_CAPABILITY_ON_OFF | @@ -356,6 +361,7 @@ static const DisplayApi jd9853_display_api = { .get_frame_buffer_count = nullptr, .get_backlight = jd9853_get_backlight, .has_capability = nullptr, + .get_shape = jd9853_get_shape, }; Driver jd9853_driver = { diff --git a/Drivers/st7735-module/LICENSE-Apache-2.0.md b/Drivers/st7735-module/LICENSE-Apache-2.0.md new file mode 100644 index 000000000..f5f4b8b5e --- /dev/null +++ b/Drivers/st7735-module/LICENSE-Apache-2.0.md @@ -0,0 +1,195 @@ +Apache License +============== + +_Version 2.0, January 2004_ +_<>_ + +### Terms and Conditions for use, reproduction, and distribution + +#### 1. Definitions + +“License” shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +“Licensor” shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +“Legal Entity” shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, “control” means **(i)** the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the +outstanding shares, or **(iii)** beneficial ownership of such entity. + +“You” (or “Your”) shall mean an individual or Legal Entity exercising +permissions granted by this License. + +“Source” form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +“Object” form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +“Work” shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +“Derivative Works” shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +“Contribution” shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +“submitted” means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as “Not a Contribution.” + +“Contributor” shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +#### 2. Grant of Copyright License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +#### 3. Grant of Patent License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +#### 4. Redistribution + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +* **(a)** You must give any other recipients of the Work or Derivative Works a copy of +this License; and +* **(b)** You must cause any modified files to carry prominent notices stating that You +changed the files; and +* **(c)** You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. + +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +#### 5. Submission of Contributions + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +#### 6. Trademarks + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +#### 7. Disclaimer of Warranty + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +#### 8. Limitation of Liability + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +#### 9. Accepting Warranty or Additional Liability + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +_END OF TERMS AND CONDITIONS_ + +### APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets `[]` replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same “printed page” as the copyright notice for easier identification within +third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/Drivers/sy6970-module/LICENSE-Apache-2.0.md b/Drivers/sy6970-module/LICENSE-Apache-2.0.md new file mode 100644 index 000000000..f5f4b8b5e --- /dev/null +++ b/Drivers/sy6970-module/LICENSE-Apache-2.0.md @@ -0,0 +1,195 @@ +Apache License +============== + +_Version 2.0, January 2004_ +_<>_ + +### Terms and Conditions for use, reproduction, and distribution + +#### 1. Definitions + +“License” shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +“Licensor” shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +“Legal Entity” shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, “control” means **(i)** the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the +outstanding shares, or **(iii)** beneficial ownership of such entity. + +“You” (or “Your”) shall mean an individual or Legal Entity exercising +permissions granted by this License. + +“Source” form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +“Object” form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +“Work” shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +“Derivative Works” shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +“Contribution” shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +“submitted” means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as “Not a Contribution.” + +“Contributor” shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +#### 2. Grant of Copyright License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +#### 3. Grant of Patent License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +#### 4. Redistribution + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +* **(a)** You must give any other recipients of the Work or Derivative Works a copy of +this License; and +* **(b)** You must cause any modified files to carry prominent notices stating that You +changed the files; and +* **(c)** You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. + +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +#### 5. Submission of Contributions + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +#### 6. Trademarks + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +#### 7. Disclaimer of Warranty + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +#### 8. Limitation of Liability + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +#### 9. Accepting Warranty or Additional Liability + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +_END OF TERMS AND CONDITIONS_ + +### APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets `[]` replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same “printed page” as the copyright notice for easier identification within +third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/Drivers/tca8418-module/LICENSE-Apache-2.0.md b/Drivers/tca8418-module/LICENSE-Apache-2.0.md new file mode 100644 index 000000000..f5f4b8b5e --- /dev/null +++ b/Drivers/tca8418-module/LICENSE-Apache-2.0.md @@ -0,0 +1,195 @@ +Apache License +============== + +_Version 2.0, January 2004_ +_<>_ + +### Terms and Conditions for use, reproduction, and distribution + +#### 1. Definitions + +“License” shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +“Licensor” shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +“Legal Entity” shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, “control” means **(i)** the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the +outstanding shares, or **(iii)** beneficial ownership of such entity. + +“You” (or “Your”) shall mean an individual or Legal Entity exercising +permissions granted by this License. + +“Source” form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +“Object” form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +“Work” shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +“Derivative Works” shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +“Contribution” shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +“submitted” means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as “Not a Contribution.” + +“Contributor” shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +#### 2. Grant of Copyright License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +#### 3. Grant of Patent License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +#### 4. Redistribution + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +* **(a)** You must give any other recipients of the Work or Derivative Works a copy of +this License; and +* **(b)** You must cause any modified files to carry prominent notices stating that You +changed the files; and +* **(c)** You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. + +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +#### 5. Submission of Contributions + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +#### 6. Trademarks + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +#### 7. Disclaimer of Warranty + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +#### 8. Limitation of Liability + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +#### 9. Accepting Warranty or Additional Liability + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +_END OF TERMS AND CONDITIONS_ + +### APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets `[]` replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same “printed page” as the copyright notice for easier identification within +third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/LICENSE.md b/LICENSE.md index c59ce9e79..e8759e701 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -13,7 +13,7 @@ These applications are not part of the Tactility operating system's main firmwar The intentions behind picking the licenses for the subprojects: -1. Forks of the entire Tactility project required to be open source as: GPL v3.0 applies to the main firmware projects (see `Firmware/` and `Tactility/`). +1. Forks of the entire Tactility project required to be open source as: GPL v3.0 applies to the main firmware project (see `Tactility/`). 2. It should be possible to make closed source external applications using TactilitySDK and the libraries it includes (it must exclude software with a GPL license). 3. It should be possible to make closed source forks that only contain TactilityKernel, the platform implementations (`Platforms/*`) and most of the device and driver implementations. As few as possible driver and device combinations should prevent this. @@ -30,12 +30,12 @@ The license texts that are relevant to this document: **IMPORTANT:** This document may have inaccuracies. It mainly exists to create awareness about license differences. -The top-level projects `Firmware/` and `Tactility/` are licensed under `GPL v3.0`. +The top-level project `Tactility/` is licensed under `GPL v3.0`. Most devices are licensed with `Apache License v2.0` while some are implemented as `GPL v3.0` until author consent is given to change the license. New device implementations should be licensed under `Apache License v2.0`. -Most drivers have an `Apache License v2.0`, with exceptions such as `Drivers/gps-generic-module/`. +Most drivers have an `Apache License v2.0`, with exceptions such as `Drivers/gps-meshtastic-module/`. Licensing may also differ for subprojects intended for use in external applications. Driver subprojects aren't generally used directly in external app projects, but if they are, make sure to check their licenses. @@ -65,7 +65,6 @@ Below is an overview of the licenses of some of the subprojects. | Project | License | |--------------------|-------------------------| | Tactility | GNU Public License v3.0 | -| TactilityC | Apache License v2.0 | | TactilityFreeRTOS | Apache License v2.0 | | TactilityKernel | Apache License v2.0 | | Tests | (varies) | diff --git a/Modules/app-module/include/app/manifest.h b/Modules/app-module/include/app/manifest.h index 0b81bb733..efe4ae306 100644 --- a/Modules/app-module/include/app/manifest.h +++ b/Modules/app-module/include/app/manifest.h @@ -28,7 +28,7 @@ enum AppCategory { /** Bit flags for AppManifest::flags. */ enum AppManifestFlags { - /** Excluded from generic app-browsing UIs (e.g. AppList, Settings) */ + /** Excluded from generic app-browsing UIs (e.g. Launcher, Settings) */ APP_MANIFEST_FLAG_HIDDEN = 1 << 0, /** No window-manager dependency. Safe to start from a context with no GUI available. */ APP_MANIFEST_FLAG_HEADLESS = 1 << 1, diff --git a/Modules/audio-decoder-module/LICENSE-Apache-2.0.md b/Modules/audio-decoder-module/LICENSE-Apache-2.0.md new file mode 100644 index 000000000..f5f4b8b5e --- /dev/null +++ b/Modules/audio-decoder-module/LICENSE-Apache-2.0.md @@ -0,0 +1,195 @@ +Apache License +============== + +_Version 2.0, January 2004_ +_<>_ + +### Terms and Conditions for use, reproduction, and distribution + +#### 1. Definitions + +“License” shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +“Licensor” shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +“Legal Entity” shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, “control” means **(i)** the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the +outstanding shares, or **(iii)** beneficial ownership of such entity. + +“You” (or “Your”) shall mean an individual or Legal Entity exercising +permissions granted by this License. + +“Source” form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +“Object” form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +“Work” shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +“Derivative Works” shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +“Contribution” shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +“submitted” means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as “Not a Contribution.” + +“Contributor” shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +#### 2. Grant of Copyright License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +#### 3. Grant of Patent License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +#### 4. Redistribution + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +* **(a)** You must give any other recipients of the Work or Derivative Works a copy of +this License; and +* **(b)** You must cause any modified files to carry prominent notices stating that You +changed the files; and +* **(c)** You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. + +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +#### 5. Submission of Contributions + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +#### 6. Trademarks + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +#### 7. Disclaimer of Warranty + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +#### 8. Limitation of Liability + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +#### 9. Accepting Warranty or Additional Liability + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +_END OF TERMS AND CONDITIONS_ + +### APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets `[]` replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same “printed page” as the copyright notice for easier identification within +third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/Modules/coreutils-module/LICENSE-Apache-2.0.md b/Modules/coreutils-module/LICENSE-Apache-2.0.md new file mode 100644 index 000000000..f5f4b8b5e --- /dev/null +++ b/Modules/coreutils-module/LICENSE-Apache-2.0.md @@ -0,0 +1,195 @@ +Apache License +============== + +_Version 2.0, January 2004_ +_<>_ + +### Terms and Conditions for use, reproduction, and distribution + +#### 1. Definitions + +“License” shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +“Licensor” shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +“Legal Entity” shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, “control” means **(i)** the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the +outstanding shares, or **(iii)** beneficial ownership of such entity. + +“You” (or “Your”) shall mean an individual or Legal Entity exercising +permissions granted by this License. + +“Source” form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +“Object” form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +“Work” shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +“Derivative Works” shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +“Contribution” shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +“submitted” means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as “Not a Contribution.” + +“Contributor” shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +#### 2. Grant of Copyright License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +#### 3. Grant of Patent License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +#### 4. Redistribution + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +* **(a)** You must give any other recipients of the Work or Derivative Works a copy of +this License; and +* **(b)** You must cause any modified files to carry prominent notices stating that You +changed the files; and +* **(c)** You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. + +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +#### 5. Submission of Contributions + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +#### 6. Trademarks + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +#### 7. Disclaimer of Warranty + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +#### 8. Limitation of Liability + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +#### 9. Accepting Warranty or Additional Liability + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +_END OF TERMS AND CONDITIONS_ + +### APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets `[]` replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same “printed page” as the copyright notice for easier identification within +third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/Modules/lvgl-module/CMakeLists.txt b/Modules/lvgl-module/CMakeLists.txt index 6c1f79af0..de7d34e0e 100644 --- a/Modules/lvgl-module/CMakeLists.txt +++ b/Modules/lvgl-module/CMakeLists.txt @@ -29,6 +29,15 @@ else () set(ui_density_variable "LVGL_UI_DENSITY_DEFAULT") message("UI density set to default: ${ui_density}") endif () +# Read the theme: whether the device uses dark mode by default +GET_VALUE_FROM_MAP(device_properties "lvgl.theme" lvgl_theme) +if (NOT lvgl_theme OR lvgl_theme STREQUAL "DefaultDark") + set(theme_dark 1) +elseif (lvgl_theme STREQUAL "DefaultLight" OR lvgl_theme STREQUAL "Mono") + set(theme_dark 0) +else () + message(FATAL_ERROR "Invalid lvgl.theme: '${lvgl_theme}'. Must be 'DefaultDark', 'DefaultLight' or 'Mono'") +endif () # #################################### # Create module @@ -74,4 +83,6 @@ target_compile_definitions(${MODULE_NAME} PUBLIC "-DLV_LVGL_H_INCLUDE_SIMPLE" # UiDensity "-DTT_LVGL_UI_DENSITY=${ui_density_variable}" + # Default theme mode: 1 for dark, 0 for light + "-DTT_LVGL_THEME_DARK=${theme_dark}" ) diff --git a/Modules/lvgl-module/generate-icons.py b/Modules/lvgl-module/generate-icons.py index 2306ae811..6712aa99c 100755 --- a/Modules/lvgl-module/generate-icons.py +++ b/Modules/lvgl-module/generate-icons.py @@ -183,6 +183,7 @@ def generate_icon_names(codepoint_map: dict, codepoint_names: list, variable_nam statusbar_symbol_code_point_names = [ # Location tracking "location_on", + "location_off", # Development server "cloud", # Low on available memory diff --git a/Modules/lvgl-module/include/lvgl/devices/trackball.h b/Modules/lvgl-module/include/lvgl/devices/trackball.h index 15e37d1d8..e3405a8b8 100644 --- a/Modules/lvgl-module/include/lvgl/devices/trackball.h +++ b/Modules/lvgl-module/include/lvgl/devices/trackball.h @@ -17,8 +17,8 @@ extern "C" { * @brief Trackball operating mode. */ enum LvglTrackballMode { - /** Navigation via enc_diff (scroll wheel behavior). No cursor is shown. */ - LVGL_TRACKBALL_MODE_ENCODER, + /** Navigation with arrow keys, and the button as the enter key. No cursor is shown. */ + LVGL_TRACKBALL_MODE_KEYS, /** Mouse cursor via point.x/point.y. A cursor is shown if an image was set via * lvgl_trackball_set_cursor_image(). */ LVGL_TRACKBALL_MODE_POINTER @@ -33,8 +33,8 @@ struct LvglTrackballSettings { /** When false, movement and button presses are drained from the device and discarded: * the indev reports no movement and stays released. */ bool enabled; - /** Encoder mode: steps per raw trackball tick. Must be >= 1. */ - uint8_t encoder_sensitivity; + /** Keys mode: arrow key presses per raw trackball tick. Must be >= 1. */ + uint8_t key_sensitivity; /** Pointer mode: pixels per raw trackball tick. Must be >= 1. */ uint8_t pointer_sensitivity; }; @@ -43,7 +43,7 @@ struct LvglTrackballSettings { * @brief Creates an lv_indev_t bound to the given TRACKBALL_TYPE device and registers a read * callback that polls the device through its TrackballApi. * - * The indev starts in LVGL_TRACKBALL_MODE_ENCODER, enabled, with default sensitivities (see + * The indev starts in LVGL_TRACKBALL_MODE_KEYS, enabled, with default sensitivities (see * lvgl_trackball_settings_get_default()). Call lvgl_trackball_set_settings() to change this, * e.g. to switch to pointer mode - each device added this way can independently run with or * without pointer support. @@ -68,8 +68,8 @@ error_t lvgl_trackball_add(struct Device* device, lv_display_t* display, lv_inde void lvgl_trackball_remove(lv_indev_t* indev); /** - * @return the default (all fields explicitly set) LvglTrackballSettings: encoder mode, enabled, - * encoder_sensitivity 1, pointer_sensitivity 10. + * @return the default (all fields explicitly set) LvglTrackballSettings: keys mode, enabled, + * key_sensitivity 1, pointer_sensitivity 10. */ struct LvglTrackballSettings lvgl_trackball_settings_get_default(void); diff --git a/Modules/lvgl-module/include/lvgl/grid_navigation.h b/Modules/lvgl-module/include/lvgl/grid_navigation.h new file mode 100644 index 000000000..ee97b2f2e --- /dev/null +++ b/Modules/lvgl-module/include/lvgl/grid_navigation.h @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Lets the arrow keys move the focus between the container's children by their position, e.g. in a grid of buttons. + * The container takes the place of its children in the focus group. Moving past the first or last row leaves the container. + * @param[in] container the container with the focusable children + */ +void lvgl_grid_navigation_add(lv_obj_t* container); + +/** + * @brief Turns grid navigation off again: the focusable children rejoin the container's focus group. + * @param[in] container a container that was passed to lvgl_grid_navigation_add() + */ +void lvgl_grid_navigation_remove(lv_obj_t* container); + +/** + * @brief Moves the focus to the next or previous widget, for devices that can only step through widgets. + * The children of grid navigation containers are visited in their order: entering a container forwards focuses its first child, and backwards its last child. + * @param[in] group the focus group + * @param[in] next true for the next widget, false for the previous widget + */ +void lvgl_grid_navigation_step(lv_group_t* group, bool next); + +/** + * @brief Hides the selection that keys made, e.g. when the touchscreen is used. The focus stays where it is, + * and the next key that moves the focus shows the selection again. + * @param[in] group the focus group + */ +void lvgl_focus_hide_key_selection(lv_group_t* group); + +/** + * @param[in] container a container that was passed to lvgl_grid_navigation_add() + * @return the focused child, or NULL when no child is focused + */ +lv_obj_t* lvgl_grid_navigation_get_focused(lv_obj_t* container); + +/** + * @param[in] obj the object to check + * @return true when the object is a container that was passed to lvgl_grid_navigation_add() + */ +bool lvgl_grid_navigation_is_container(const lv_obj_t* obj); + +#ifdef __cplusplus +} +#endif diff --git a/Modules/lvgl-module/include/lvgl/icons/names.h b/Modules/lvgl-module/include/lvgl/icons/names.h index 688a98965..027f87720 100644 --- a/Modules/lvgl-module/include/lvgl/icons/names.h +++ b/Modules/lvgl-module/include/lvgl/icons/names.h @@ -9,7 +9,7 @@ extern "C" { #endif /** Changes whenever the icon TTF subset, its codepoints file or the icon name lists change */ -#define LVGL_ICON_FONT_VERSION 0xEE67835Du +#define LVGL_ICON_FONT_VERSION 0x1B608834u // Icon names per icon font, resolved through the .codepoints file next to the TTF extern const char* const lvgl_icon_shared_names[]; diff --git a/Modules/lvgl-module/include/lvgl/icons/statusbar.h b/Modules/lvgl-module/include/lvgl/icons/statusbar.h index fa1a2d3ec..48b73925d 100644 --- a/Modules/lvgl-module/include/lvgl/icons/statusbar.h +++ b/Modules/lvgl-module/include/lvgl/icons/statusbar.h @@ -3,6 +3,7 @@ #pragma once #define LVGL_ICON_STATUSBAR_LOCATION_ON "\xEF\x87\x9B" +#define LVGL_ICON_STATUSBAR_LOCATION_OFF "\xEE\x83\x87" #define LVGL_ICON_STATUSBAR_CLOUD "\xEF\x85\x9C" #define LVGL_ICON_STATUSBAR_MEMORY "\xEE\x8C\xA2" #define LVGL_ICON_STATUSBAR_SD_CARD "\xEE\x98\xA3" diff --git a/Modules/lvgl-module/include/lvgl/insets.h b/Modules/lvgl-module/include/lvgl/insets.h new file mode 100644 index 000000000..87ecfa85d --- /dev/null +++ b/Modules/lvgl-module/include/lvgl/insets.h @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include + +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** Margins in pixels on each side of a display */ +typedef struct { + int32_t top; + int32_t bottom; + int32_t left; + int32_t right; +} LvglInsets; + +/** + * @brief Gets the shape of the display's visible area. + * @param[in] display the display + * @param[out] out_shape the shape: a rectangle when the display doesn't report one + */ +void lvgl_display_get_shape(lv_display_t* display, struct DisplayShape* out_shape); + +/** + * @brief Gets the margins that keep a rectangle inside the display's visible area. + * They're all zero for rectangular displays. + * @param[in] display the display + * @param[out] out_insets the margins + */ +void lvgl_display_get_insets(lv_display_t* display, LvglInsets* out_insets); + +/** + * @brief Gets the horizontal margin on both sides of a row, for layouts that follow the display's edge. + * @param[in] display the display + * @param[in] y the row, in display coordinates + * @return the margin in pixels on the left and on the right of the row + */ +int32_t lvgl_display_get_row_inset(lv_display_t* display, int32_t y); + +/** + * @brief Moves the object's content inward where it overlaps the curved edge of a round or rounded display. + * The extra horizontal padding is added to the object's current padding, and follows its size changes. + * Does nothing on rectangular displays. + * @param[in] obj the object, with its own padding already set + */ +void lvgl_obj_add_edge_padding(lv_obj_t* obj); + +#ifdef __cplusplus +} +#endif diff --git a/Modules/lvgl-module/include/lvgl/theme.h b/Modules/lvgl-module/include/lvgl/theme.h new file mode 100644 index 000000000..d22620945 --- /dev/null +++ b/Modules/lvgl-module/include/lvgl/theme.h @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** User-configurable theme settings */ +typedef struct { + bool is_dark; + /** Use the monochrome theme, also on colour displays. Monochrome and greyscale displays always use it. */ + bool is_mono; + /** Slow-refresh displays (e.g. e-paper) never show animations */ + bool animations_enabled; + lv_color_t color_primary; + lv_color_t color_secondary; + lv_color_t color_error; +} LvglThemeSettings; + +/** + * @brief Gets the theme settings, or the defaults when no settings were set. + * @param[out] settings the current theme settings + */ +void lvgl_theme_get_settings(LvglThemeSettings* settings); + +/** + * @brief Gets the default theme settings for this device. + * @param[out] settings the default theme settings + */ +void lvgl_theme_get_default_settings(LvglThemeSettings* settings); + +/** + * @brief Checks whether the active theme is the monochrome theme: on monochrome and greyscale displays, or when selected in the settings. + * @return true when the monochrome theme is active + */ +bool lvgl_theme_is_mono(void); + +/** + * @brief Sets the theme settings. They are used for displays that are added afterwards, e.g. when LVGL (re)starts. + * @param[in] settings the theme settings + */ +void lvgl_theme_set_settings(const LvglThemeSettings* settings); + +#ifdef __cplusplus +} +#endif diff --git a/Modules/lvgl-module/include/lvgl/widgets/badge.h b/Modules/lvgl-module/include/lvgl/widgets/badge.h new file mode 100644 index 000000000..84d326f97 --- /dev/null +++ b/Modules/lvgl-module/include/lvgl/widgets/badge.h @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Creates a badge: a small dot that marks its parent (e.g. a favourite item). + * The theme determines its size and colour. It ignores the parent's layout, so the app aligns it (e.g. LV_ALIGN_TOP_RIGHT). + * @param[in] parent the object to mark + * @return the created badge + */ +lv_obj_t* lvgl_badge_create(lv_obj_t* parent); + +#ifdef __cplusplus +} +#endif diff --git a/Modules/lvgl-module/include/lvgl/widgets/card.h b/Modules/lvgl-module/include/lvgl/widgets/card.h new file mode 100644 index 000000000..8b15f1617 --- /dev/null +++ b/Modules/lvgl-module/include/lvgl/widgets/card.h @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Creates a card: a container that groups related content and stands out from its background. + * The theme styles it as a card. Plain containers (lv_obj_create()) don't stand out, as they share the background colour. + * @param[in] parent the parent object for the new card + * @return the created card + */ +lv_obj_t* lvgl_card_create(lv_obj_t* parent); + +#ifdef __cplusplus +} +#endif diff --git a/Modules/lvgl-module/include/lvgl/widgets/chip.h b/Modules/lvgl-module/include/lvgl/widgets/chip.h new file mode 100644 index 000000000..9b85ef149 --- /dev/null +++ b/Modules/lvgl-module/include/lvgl/widgets/chip.h @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Creates a chip: a compact button that shows a choice. The theme shows it as selected when it has LV_STATE_CHECKED. + * The chip doesn't toggle by itself, so the app decides which chips are selected (e.g. one of several options). + * @param[in] parent the parent object for the new chip + * @return the created chip + */ +lv_obj_t* lvgl_chip_create(lv_obj_t* parent); + +#ifdef __cplusplus +} +#endif diff --git a/Modules/lvgl-module/include/lvgl/widgets/icon_button.h b/Modules/lvgl-module/include/lvgl/widgets/icon_button.h new file mode 100644 index 000000000..43d1c13b1 --- /dev/null +++ b/Modules/lvgl-module/include/lvgl/widgets/icon_button.h @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** The look of an icon button, as styled by the theme */ +enum LvglIconButtonVariant { + /** No container, the icon uses the theme's icon colour */ + LVGL_ICON_BUTTON_STANDARD, + /** Filled with the primary colour, for the most important action */ + LVGL_ICON_BUTTON_FILLED, + /** Filled with a softer secondary colour, for other prominent actions */ + LVGL_ICON_BUTTON_TONAL +}; + +/** + * @brief Creates a button that shows an icon (or short text) without a filled background. + * It behaves like a regular button, but the theme styles it as an icon button. + * @param[in] parent the parent object for the new button + * @return the created button + */ +lv_obj_t* lvgl_icon_button_create(lv_obj_t* parent); + +/** + * @brief Creates an icon button with the specified look. + * @param[in] parent the parent object for the new button + * @param[in] variant the look of the button + * @return the created button + */ +lv_obj_t* lvgl_icon_button_create_variant(lv_obj_t* parent, enum LvglIconButtonVariant variant); + +#ifdef __cplusplus +} +#endif diff --git a/Modules/lvgl-module/include/lvgl/widgets/page_indicator.h b/Modules/lvgl-module/include/lvgl/widgets/page_indicator.h new file mode 100644 index 000000000..b97609f4f --- /dev/null +++ b/Modules/lvgl-module/include/lvgl/widgets/page_indicator.h @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Creates a page indicator: a row of dots with one dot per page, where the dot of the current page is filled. + * The theme determines the size and colour of the dots. + * When the dots are wider than the indicator's maximum width (lv_obj_set_style_max_width()), it shows the page number instead, e.g. "2/12". + * @param[in] parent the parent object + * @return the created page indicator + */ +lv_obj_t* lvgl_page_indicator_create(lv_obj_t* parent); + +/** + * @brief Sets the number of pages, which is the number of dots. + * @param[in] obj the page indicator + * @param[in] count the number of pages + */ +void lvgl_page_indicator_set_page_count(lv_obj_t* obj, uint32_t count); + +/** + * @brief Sets the current page, whose dot is filled. + * @param[in] obj the page indicator + * @param[in] page the zero-based index of the current page + */ +void lvgl_page_indicator_set_page(lv_obj_t* obj, uint32_t page); + +#ifdef __cplusplus +} +#endif diff --git a/Modules/lvgl-module/private/lvgl/devices/keyboard_private.h b/Modules/lvgl-module/private/lvgl/devices/keyboard_private.h index 21d1c3658..3db773c51 100644 --- a/Modules/lvgl-module/private/lvgl/devices/keyboard_private.h +++ b/Modules/lvgl-module/private/lvgl/devices/keyboard_private.h @@ -4,9 +4,22 @@ extern "C" { #endif +#include + +#include + void lvgl_keyboard_on_start_lvgl(); void lvgl_keyboard_on_stop_lvgl(); +/** + * @brief Translates a key codepoint into the LVGL key for the object that the indev's group focuses. + * The arrow up and down keys move the focus to the previous and next widget, or within a grid navigation container. + * @param[in] indev the indev that reports the key + * @param[in] codepoint the key codepoint (see tactility/drivers/keyboard.h) + * @return the LVGL key + */ +uint32_t lvgl_keyboard_translate_key(lv_indev_t* indev, uint32_t codepoint); + #ifdef __cplusplus } #endif diff --git a/Modules/lvgl-module/private/lvgl/insets_math.h b/Modules/lvgl-module/private/lvgl/insets_math.h new file mode 100644 index 000000000..ca2b68f88 --- /dev/null +++ b/Modules/lvgl-module/private/lvgl/insets_math.h @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include + +#include + +/** Calculates the insets of a display with the given shape and resolution */ +LvglInsets lvgl_insets_calculate(const DisplayShape& shape, int32_t width, int32_t height); + +/** Calculates the horizontal margin of a row of a display with the given shape and resolution */ +int32_t lvgl_insets_calculate_row(const DisplayShape& shape, int32_t width, int32_t height, int32_t y); diff --git a/Modules/lvgl-module/private/lvgl/theme_private.h b/Modules/lvgl-module/private/lvgl/theme_private.h new file mode 100644 index 000000000..b73fca735 --- /dev/null +++ b/Modules/lvgl-module/private/lvgl/theme_private.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Initializes the theme for a display. When the display needs the other theme than the active one, + * the active theme is deinitialized first, so only one theme is allocated. + * @param[in] display the display that the theme is for + * @param[in] isMonoDisplay the display can only show monochrome or greyscale + * @param[in] supportsAnimations the display can show animations (e.g. not e-paper) + * @return the theme + */ +lv_theme_t* lvgl_theme_init_for_display(lv_display_t* display, bool isMonoDisplay, bool supportsAnimations); + +/** Deinitializes the active theme. LVGL frees its memory when it stops, which includes the theme. */ +void lvgl_theme_deinit(void); + +#ifdef __cplusplus +} +#endif diff --git a/Modules/lvgl-module/source/devices/devices.cpp b/Modules/lvgl-module/source/devices/devices.cpp index 3ecf5bd5f..27238f7f2 100644 --- a/Modules/lvgl-module/source/devices/devices.cpp +++ b/Modules/lvgl-module/source/devices/devices.cpp @@ -13,6 +13,8 @@ #include +#include + #include constexpr auto* TAG = "lvgl"; @@ -268,6 +270,8 @@ void lvgl_devices_detach() { display = lv_disp_get_next(NULL); } + lvgl_theme_deinit(); + lvgl_unlock(); } diff --git a/Modules/lvgl-module/source/devices/display.cpp b/Modules/lvgl-module/source/devices/display.cpp index 1c88ef8d2..3e0ff0ee2 100644 --- a/Modules/lvgl-module/source/devices/display.cpp +++ b/Modules/lvgl-module/source/devices/display.cpp @@ -1,9 +1,9 @@ // SPDX-License-Identifier: Apache-2.0 -#define LV_USE_PRIVATE_API 1 // For the lv_theme_t declaration - #include +#include #include +#include #include #include @@ -12,6 +12,7 @@ #include + #include #ifdef ESP_PLATFORM @@ -20,38 +21,14 @@ constexpr auto* TAG = "lvgl_display"; -#if LV_USE_THEME_MONO - -static lv_style_t mono_button_style; -static bool mono_button_style_initialized = false; -static lv_theme_t mono_theme_extension; - -static void mono_theme_extension_apply(lv_theme_t*, lv_obj_t* obj) { - if (lv_obj_check_type(obj, &lv_button_class)) { - lv_obj_add_style(obj, &mono_button_style, LV_PART_MAIN | LV_STATE_DEFAULT); - } +// The themes are generated with LSS from Modules/lvgl-module/themes/*.lss +static void lvgl_display_apply_theme(lv_display_t* disp, struct Device* device, lv_color_format_t color_format) { + bool is_mono_display = color_format == LV_COLOR_FORMAT_I1 || color_format == LV_COLOR_FORMAT_L8; + // Slow-refresh panels (e-paper) can't show animations + bool supports_animations = !display_has_capability(device, DISPLAY_CAPABILITY_SLOW_REFRESH); + lv_display_set_theme(disp, lvgl_theme_init_for_display(disp, is_mono_display, supports_animations)); } -// The mono theme draws a border around every button. This removes it in the default state, keeping the pressed border and focus outline. -static void lvgl_display_extend_mono_theme(lv_display_t* disp) { - if (!mono_button_style_initialized) { - lv_style_init(&mono_button_style); - lv_style_set_border_width(&mono_button_style, 0); - mono_button_style_initialized = true; - } - // LVGL prefers the default and simple themes when they are also enabled, so only the mono theme itself is extended - lv_theme_t* mono_theme = lv_display_get_theme(disp); - if (mono_theme == nullptr || mono_theme != lv_theme_mono_get()) { - return; - } - mono_theme_extension = *mono_theme; - lv_theme_set_parent(&mono_theme_extension, mono_theme); - lv_theme_set_apply_cb(&mono_theme_extension, mono_theme_extension_apply); - lv_display_set_theme(disp, &mono_theme_extension); -} - -#endif - struct LvglDisplayCtx { void* buf1; void* buf2; @@ -504,9 +481,7 @@ error_t lvgl_display_add(struct Device* device, const struct LvglDisplayConfig* return ERROR_OUT_OF_MEMORY; } -#if LV_USE_THEME_MONO - lvgl_display_extend_mono_theme(disp); -#endif + lvgl_display_apply_theme(disp, device, lv_color_format); ctx->render_mode = render_mode; lv_display_set_color_format(disp, lv_color_format); diff --git a/Modules/lvgl-module/source/devices/keyboard.cpp b/Modules/lvgl-module/source/devices/keyboard.cpp index 96f16bd24..8c917ecb3 100644 --- a/Modules/lvgl-module/source/devices/keyboard.cpp +++ b/Modules/lvgl-module/source/devices/keyboard.cpp @@ -1,6 +1,8 @@ // SPDX-License-Identifier: Apache-2.0 #include #include +#include +#include #include #include @@ -61,6 +63,20 @@ static uint32_t codepoint_to_lv_key(uint32_t key) { } } +uint32_t lvgl_keyboard_translate_key(lv_indev_t* indev, uint32_t codepoint) { + lv_group_t* group = lv_indev_get_group(indev); + lv_obj_t* focused = group != nullptr ? lv_group_get_focused(group) : nullptr; + if (lvgl_grid_navigation_is_container(focused)) { + // Grid navigation moves the focus in two dimensions with the arrow keys + if (codepoint == CODEPOINT_ARROW_UP) { + return LV_KEY_UP; + } else if (codepoint == CODEPOINT_ARROW_DOWN) { + return LV_KEY_DOWN; + } + } + return codepoint_to_lv_key(codepoint); +} + static void lvgl_keyboard_read_cb(lv_indev_t* indev, lv_indev_data_t* data) { auto* wrapper = static_cast(lv_indev_get_driver_data(indev)); @@ -71,7 +87,19 @@ static void lvgl_keyboard_read_cb(lv_indev_t* indev, lv_indev_data_t* data) { return; } - data->key = codepoint_to_lv_key(key_data.key); + lv_group_t* group = lv_indev_get_group(indev); + if (key_data.key == CODEPOINT_FOCUS_NEXT || key_data.key == CODEPOINT_FOCUS_PREVIOUS) { + // Devices that can only step through widgets also visit the children of grid navigation containers. + // LVGL doesn't see these keys: the group would skip the children, as only the container is in the group. + if (key_data.pressed && group != nullptr) { + lvgl_grid_navigation_step(group, key_data.key == CODEPOINT_FOCUS_NEXT); + } + data->state = LV_INDEV_STATE_RELEASED; + data->continue_reading = key_data.continue_reading; + return; + } + + data->key = lvgl_keyboard_translate_key(indev, key_data.key); data->state = key_data.pressed ? LV_INDEV_STATE_PRESSED : LV_INDEV_STATE_RELEASED; data->continue_reading = key_data.continue_reading; } diff --git a/Modules/lvgl-module/source/devices/pointer.cpp b/Modules/lvgl-module/source/devices/pointer.cpp index 8963b621a..5b1ac8ad4 100644 --- a/Modules/lvgl-module/source/devices/pointer.cpp +++ b/Modules/lvgl-module/source/devices/pointer.cpp @@ -1,6 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 #include #include +#include #include #include @@ -140,8 +141,8 @@ static void lvgl_pointer_pool_refresh(struct LvglPointerPool* pool, int32_t nati // the touch controller reports points in a different order (no touch-ID/tracking field exists // anywhere in this stack - see esp_lcd_touch_get_coordinates()/PointerApi.get_touched_points()). // Unmatched raw points (new touches) claim the nearest inactive slot. Slots with no matching -// point this round go inactive (RELEASED). -static void lvgl_pointer_pool_assign(struct LvglPointerPool* pool, int32_t native_x_max) { +// point this round go inactive (RELEASED). Returns true when a new touch started. +static bool lvgl_pointer_pool_assign(struct LvglPointerPool* pool, int32_t native_x_max) { // Touch drivers clamp raw coordinates to the panel's configured native resolution regardless // of calibration, so native_x_max is a valid scale reference for the distance cap even when calibration is disabled. // Falls back to a conservative fixed pixel value if the display/resolution isn't available for some reason. @@ -182,6 +183,7 @@ static void lvgl_pointer_pool_assign(struct LvglPointerPool* pool, int32_t nativ // Second pass: any unclaimed raw point is a new touch - hand it to the first inactive slot // that wasn't just released this round. + bool touch_started = false; for (uint8_t r = 0; r < pool->raw_count; r++) { if (raw_claimed[r]) continue; for (uint8_t s = 0; s < pool->slot_count; s++) { @@ -190,9 +192,11 @@ static void lvgl_pointer_pool_assign(struct LvglPointerPool* pool, int32_t nativ pool->slot_point[s].x = (lv_coord_t)pool->raw_x[r]; pool->slot_point[s].y = (lv_coord_t)pool->raw_y[r]; raw_claimed[r] = true; + touch_started = true; break; } } + return touch_started; } // The actual LVGL indev read callback, shared by every slot in the pool. Only the first slot to @@ -219,7 +223,11 @@ static void lvgl_pointer_read_cb(lv_indev_t* indev, lv_indev_data_t* data) { int32_t native_x_max = display != NULL ? lv_display_get_original_horizontal_resolution(display) - 1 : 0; int32_t native_y_max = display != NULL ? lv_display_get_original_vertical_resolution(display) - 1 : 0; lvgl_pointer_pool_refresh(pool, native_x_max, native_y_max); - lvgl_pointer_pool_assign(pool, native_x_max); + // A touch hides the selection that keys made, so it doesn't look like two widgets are selected. + // This runs before LVGL handles the press, so a widget that the touch focuses isn't affected. + if (lvgl_pointer_pool_assign(pool, native_x_max) && lv_group_get_default() != NULL) { + lvgl_focus_hide_key_selection(lv_group_get_default()); + } } pool->round_pos = (uint8_t)((pool->round_pos + 1) % pool->slot_count); diff --git a/Modules/lvgl-module/source/devices/trackball.cpp b/Modules/lvgl-module/source/devices/trackball.cpp index 6a75aeeac..d46b5ab43 100644 --- a/Modules/lvgl-module/source/devices/trackball.cpp +++ b/Modules/lvgl-module/source/devices/trackball.cpp @@ -2,11 +2,16 @@ #include #include #include +#include +#include #include +#include #include +#include #include +#include constexpr auto* TAG = "lvgl_trackball"; @@ -16,6 +21,13 @@ struct LvglTrackballCtx { int32_t cursor_y; lv_obj_t* cursor; const void* cursor_image_src; + // Keys mode: arrow key presses that are still to be reported + uint32_t pending_codepoint; + int32_t pending_count; + // Keys mode: the LVGL key that is reported as pressed, or 0 + uint32_t pressed_key; + // Pointer mode: whether the button was pressed during the previous read + bool pointer_pressed; }; static inline int32_t clamp(int32_t value, int32_t min_value, int32_t max_value) { @@ -68,6 +80,49 @@ static void hide_cursor(LvglTrackballCtx* ctx) { lv_obj_add_flag(ctx->cursor, LV_OBJ_FLAG_HIDDEN); } +// Reports one key event per read: the button as the enter key, and movement as arrow key presses along its strongest axis. +static void read_keys(lv_indev_t* indev, LvglTrackballCtx* ctx, lv_indev_data_t* data, int32_t dx, int32_t dy, bool button_pressed) { + if (dx != 0 || dy != 0) { + uint32_t codepoint; + if (std::abs(dx) >= std::abs(dy)) { + codepoint = dx > 0 ? CODEPOINT_ARROW_RIGHT : CODEPOINT_ARROW_LEFT; + } else { + codepoint = dy > 0 ? CODEPOINT_ARROW_DOWN : CODEPOINT_ARROW_UP; + } + const int32_t steps = std::max(std::abs(dx), std::abs(dy)) * static_cast(ctx->settings.key_sensitivity); + // A change of direction drops the presses that weren't reported yet + if (codepoint != ctx->pending_codepoint) { + ctx->pending_codepoint = codepoint; + ctx->pending_count = 0; + } + ctx->pending_count = clamp(ctx->pending_count + steps, 0, INT16_MAX); + lv_display_trigger_activity(lv_indev_get_display(indev)); + } + + if (ctx->pressed_key == LV_KEY_ENTER && button_pressed) { + data->key = LV_KEY_ENTER; + data->state = LV_INDEV_STATE_PRESSED; + } else if (ctx->pressed_key != 0) { + // Every press is released before the next event, so LVGL sees each arrow key press + data->key = ctx->pressed_key; + data->state = LV_INDEV_STATE_RELEASED; + ctx->pressed_key = 0; + data->continue_reading = ctx->pending_count > 0; + } else if (button_pressed) { + ctx->pressed_key = LV_KEY_ENTER; + data->key = LV_KEY_ENTER; + data->state = LV_INDEV_STATE_PRESSED; + lv_display_trigger_activity(lv_indev_get_display(indev)); + } else if (ctx->pending_count > 0) { + ctx->pending_count--; + ctx->pressed_key = lvgl_keyboard_translate_key(indev, ctx->pending_codepoint); + data->key = ctx->pressed_key; + data->state = LV_INDEV_STATE_PRESSED; + } else { + data->state = LV_INDEV_STATE_RELEASED; + } +} + static void lvgl_trackball_read_cb(lv_indev_t* indev, lv_indev_data_t* data) { auto* wrapper = static_cast(lv_indev_get_driver_data(indev)); auto* ctx = static_cast(wrapper->context); @@ -83,27 +138,30 @@ static void lvgl_trackball_read_cb(lv_indev_t* indev, lv_indev_data_t* data) { lv_display_t* display = lv_indev_get_display(indev); - if (ctx->settings.mode == LVGL_TRACKBALL_MODE_ENCODER) { - int32_t ticks = (dx + dy) * static_cast(ctx->settings.encoder_sensitivity); - data->enc_diff = static_cast(clamp(ticks, INT16_MIN, INT16_MAX)); - if (ticks != 0) { - lv_display_trigger_activity(display); - } - } else { - int32_t max_x = display != nullptr ? lv_display_get_original_horizontal_resolution(display) - 1 : 0; - int32_t max_y = display != nullptr ? lv_display_get_original_vertical_resolution(display) - 1 : 0; - ctx->cursor_x = clamp(ctx->cursor_x + dx * static_cast(ctx->settings.pointer_sensitivity), 0, max_x); - ctx->cursor_y = clamp(ctx->cursor_y + dy * static_cast(ctx->settings.pointer_sensitivity), 0, max_y); - data->point.x = static_cast(ctx->cursor_x); - data->point.y = static_cast(ctx->cursor_y); - } - bool pressed = false; if (ctx->settings.enabled) { trackball_get_button_pressed(wrapper->device, &pressed); } + + if (ctx->settings.mode == LVGL_TRACKBALL_MODE_KEYS) { + read_keys(indev, ctx, data, dx, dy, pressed); + return; + } + + int32_t max_x = display != nullptr ? lv_display_get_original_horizontal_resolution(display) - 1 : 0; + int32_t max_y = display != nullptr ? lv_display_get_original_vertical_resolution(display) - 1 : 0; + ctx->cursor_x = clamp(ctx->cursor_x + dx * static_cast(ctx->settings.pointer_sensitivity), 0, max_x); + ctx->cursor_y = clamp(ctx->cursor_y + dy * static_cast(ctx->settings.pointer_sensitivity), 0, max_y); + data->point.x = static_cast(ctx->cursor_x); + data->point.y = static_cast(ctx->cursor_y); data->state = pressed ? LV_INDEV_STATE_PRESSED : LV_INDEV_STATE_RELEASED; + // Like a touch, a click hides the selection that keys made + if (pressed && !ctx->pointer_pressed && lv_group_get_default() != nullptr) { + lvgl_focus_hide_key_selection(lv_group_get_default()); + } + ctx->pointer_pressed = pressed; + if (pressed) { lv_display_trigger_activity(display); } @@ -113,9 +171,9 @@ extern "C" { LvglTrackballSettings lvgl_trackball_settings_get_default() { return LvglTrackballSettings { - .mode = LVGL_TRACKBALL_MODE_ENCODER, + .mode = LVGL_TRACKBALL_MODE_KEYS, .enabled = true, - .encoder_sensitivity = 1, + .key_sensitivity = 1, .pointer_sensitivity = 10, }; } @@ -147,7 +205,7 @@ error_t lvgl_trackball_add(struct Device* device, lv_display_t* display, lv_inde return ERROR_OUT_OF_MEMORY; } - lv_indev_set_type(indev, LV_INDEV_TYPE_ENCODER); + lv_indev_set_type(indev, LV_INDEV_TYPE_KEYPAD); lv_indev_set_read_cb(indev, lvgl_trackball_read_cb); lv_indev_set_driver_data(indev, wrapper); if (display != nullptr) { @@ -155,10 +213,10 @@ error_t lvgl_trackball_add(struct Device* device, lv_display_t* display, lv_inde } recenter_cursor(ctx, indev); - // Encoder indevs are useless without a group (LVGL dispatch bails out immediately if indev->group is NULL) + // Keypad indevs are useless without a group (LVGL dispatch bails out immediately if indev->group is NULL) // Use the keyboard group for now, until it's refactored into a proper global/shared group. See ideas.md // When refactoring this, you probably want to remove the calls to lvgl_keyboard_* below. - if (ctx->settings.mode == LVGL_TRACKBALL_MODE_ENCODER) { + if (ctx->settings.mode == LVGL_TRACKBALL_MODE_KEYS) { lvgl_keyboard_enable(indev); } @@ -188,8 +246,8 @@ error_t lvgl_trackball_set_settings(lv_indev_t* indev, const struct LvglTrackbal } if ( - (settings->mode != LVGL_TRACKBALL_MODE_ENCODER && settings->mode != LVGL_TRACKBALL_MODE_POINTER) || - settings->encoder_sensitivity == 0 || + (settings->mode != LVGL_TRACKBALL_MODE_KEYS && settings->mode != LVGL_TRACKBALL_MODE_POINTER) || + settings->key_sensitivity == 0 || settings->pointer_sensitivity == 0 ) { return ERROR_INVALID_ARGUMENT; @@ -203,6 +261,8 @@ error_t lvgl_trackball_set_settings(lv_indev_t* indev, const struct LvglTrackbal ctx->settings = *settings; if (mode_changed) { + ctx->pending_count = 0; + ctx->pressed_key = 0; if (settings->mode == LVGL_TRACKBALL_MODE_POINTER) { lvgl_keyboard_disable(indev); lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER); @@ -210,12 +270,12 @@ error_t lvgl_trackball_set_settings(lv_indev_t* indev, const struct LvglTrackbal show_cursor(ctx, indev); } else { hide_cursor(ctx); - lv_indev_set_type(indev, LV_INDEV_TYPE_ENCODER); + lv_indev_set_type(indev, LV_INDEV_TYPE_KEYPAD); lvgl_keyboard_enable(indev); } } - // Cursor visibility only tracks the enabled toggle in pointer mode - in encoder mode it must + // Cursor visibility only tracks the enabled toggle in pointer mode - in keys mode it must // stay hidden regardless of enabled, otherwise this unconditionally un-hides the cursor // hide_cursor() just hid above (enabled is independent of mode, and defaults to true). if (ctx->cursor != nullptr && ctx->settings.mode == LVGL_TRACKBALL_MODE_POINTER) { diff --git a/Modules/lvgl-module/source/grid_navigation.cpp b/Modules/lvgl-module/source/grid_navigation.cpp new file mode 100644 index 000000000..ec0136b6b --- /dev/null +++ b/Modules/lvgl-module/source/grid_navigation.cpp @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: Apache-2.0 +#include + +// Marks containers with grid navigation, so the keyboard sends them up and down keys +constexpr lv_obj_flag_t GRID_NAVIGATION_FLAG = LV_OBJ_FLAG_USER_1; + +static void remove_from_group(lv_obj_t* child) { + if (lv_obj_get_group(child) != nullptr) { + lv_group_remove_obj(child); + } +} + +// Children join the default group when they're created, but grid navigation focuses them through the container +static void on_child_created(lv_event_t* event) { + auto* child = static_cast(lv_event_get_param(event)); + auto* container = lv_event_get_current_target_obj(event); + if (child != nullptr && lv_obj_get_parent(child) == container) { + remove_from_group(child); + } +} + +// Grid navigation shows the focused child as selected whenever the container gets the focus. That's only +// right when a key moved the focus there, not when it moved because e.g. the previously focused widget was deleted. +static void on_container_focused(lv_event_t* event) { + lv_indev_t* indev = lv_indev_active(); + const lv_indev_type_t type = indev != nullptr ? lv_indev_get_type(indev) : LV_INDEV_TYPE_NONE; + if (type == LV_INDEV_TYPE_KEYPAD || type == LV_INDEV_TYPE_ENCODER) { + return; + } + lv_obj_t* child = lvgl_grid_navigation_get_focused(lv_event_get_current_target_obj(event)); + if (child != nullptr) { + lv_obj_remove_state(child, LV_STATE_FOCUS_KEY); + } +} + +void lvgl_grid_navigation_add(lv_obj_t* container) { + lv_gridnav_add(container, LV_GRIDNAV_CTRL_NONE); + // Added after grid navigation's own handler, so it runs after it + lv_obj_add_event_cb(container, on_container_focused, LV_EVENT_FOCUSED, nullptr); + lv_obj_add_flag(container, GRID_NAVIGATION_FLAG); + + const uint32_t child_count = lv_obj_get_child_count(container); + for (uint32_t i = 0; i < child_count; i++) { + remove_from_group(lv_obj_get_child(container, static_cast(i))); + } + lv_obj_add_event_cb(container, on_child_created, LV_EVENT_CHILD_CREATED, nullptr); + + auto* group = lv_group_get_default(); + if (group != nullptr && lv_obj_get_group(container) == nullptr) { + lv_group_add_obj(group, container); + } +} + +void lvgl_grid_navigation_remove(lv_obj_t* container) { + if (!lvgl_grid_navigation_is_container(container)) { + return; + } + lv_obj_t* focused_child = lvgl_grid_navigation_get_focused(container); + lv_group_t* group = lv_obj_get_group(container); + const bool container_focused = group != nullptr && lv_group_get_focused(group) == container; + + lv_gridnav_remove(container); + lv_obj_remove_event_cb(container, on_child_created); + lv_obj_remove_event_cb(container, on_container_focused); + lv_obj_remove_flag(container, GRID_NAVIGATION_FLAG); + + if (group == nullptr) { + return; + } + // The children are added at the end of the group + const uint32_t child_count = lv_obj_get_child_count(container); + for (uint32_t i = 0; i < child_count; i++) { + auto* child = lv_obj_get_child(container, static_cast(i)); + if (lv_obj_is_group_def(child) && lv_obj_get_group(child) == nullptr) { + lv_group_add_obj(group, child); + } + } + if (container_focused && focused_child != nullptr) { + lv_group_focus_obj(focused_child); + } +} + +// Matches which children grid navigation can focus +static bool is_focusable(lv_obj_t* obj) { + return !lv_obj_has_flag(obj, LV_OBJ_FLAG_HIDDEN) && lv_obj_has_flag(obj, static_cast(LV_OBJ_FLAG_CLICKABLE | LV_OBJ_FLAG_CLICK_FOCUSABLE)); +} + +// Finds the next focusable child after (or before) the given index +static lv_obj_t* find_focusable_child(lv_obj_t* container, int32_t index, bool next) { + const auto child_count = static_cast(lv_obj_get_child_count(container)); + for (index += next ? 1 : -1; index >= 0 && index < child_count; index += next ? 1 : -1) { + auto* child = lv_obj_get_child(container, index); + if (is_focusable(child)) { + return child; + } + } + return nullptr; +} + +void lvgl_grid_navigation_step(lv_group_t* group, bool next) { + lv_obj_t* focused = lv_group_get_focused(group); + if (lvgl_grid_navigation_is_container(focused)) { + lv_obj_t* focused_child = lvgl_grid_navigation_get_focused(focused); + const int32_t index = focused_child != nullptr ? lv_obj_get_index(focused_child) : (next ? -1 : static_cast(lv_obj_get_child_count(focused))); + lv_obj_t* child = find_focusable_child(focused, index, next); + if (child != nullptr) { + lv_gridnav_set_focused(focused, child, LV_ANIM_ON); + return; + } + } + + lv_group_set_editing(group, false); + if (next) { + lv_group_focus_next(group); + } else { + lv_group_focus_prev(group); + } + + // Grid navigation restores the child that was focused last, but stepping enters at the side it came from + lv_obj_t* entered = lv_group_get_focused(group); + if (lvgl_grid_navigation_is_container(entered)) { + const int32_t start = next ? -1 : static_cast(lv_obj_get_child_count(entered)); + lv_obj_t* child = find_focusable_child(entered, start, next); + if (child != nullptr) { + lv_gridnav_set_focused(entered, child, LV_ANIM_ON); + } + } +} + +void lvgl_focus_hide_key_selection(lv_group_t* group) { + lv_obj_t* focused = lv_group_get_focused(group); + if (focused == nullptr) { + return; + } + lv_group_set_editing(group, false); + constexpr auto key_states = static_cast(LV_STATE_FOCUS_KEY | LV_STATE_EDITED); + lv_obj_remove_state(focused, key_states); + if (lvgl_grid_navigation_is_container(focused)) { + lv_obj_t* child = lvgl_grid_navigation_get_focused(focused); + if (child != nullptr) { + lv_obj_remove_state(child, key_states); + } + } +} + +lv_obj_t* lvgl_grid_navigation_get_focused(lv_obj_t* container) { + // Grid navigation marks the focused child with the focused state + const uint32_t child_count = lv_obj_get_child_count(container); + for (uint32_t i = 0; i < child_count; i++) { + auto* child = lv_obj_get_child(container, static_cast(i)); + if (lv_obj_has_state(child, LV_STATE_FOCUSED)) { + return child; + } + } + return nullptr; +} + +bool lvgl_grid_navigation_is_container(const lv_obj_t* obj) { + return obj != nullptr && lv_obj_has_flag(obj, GRID_NAVIGATION_FLAG); +} diff --git a/Modules/lvgl-module/source/icon_names.c b/Modules/lvgl-module/source/icon_names.c index ddc878bb7..34b143de0 100644 --- a/Modules/lvgl-module/source/icon_names.c +++ b/Modules/lvgl-module/source/icon_names.c @@ -58,6 +58,7 @@ const size_t lvgl_icon_shared_name_count = sizeof(lvgl_icon_shared_names) / size const char* const lvgl_icon_statusbar_names[] = { "location_on", + "location_off", "cloud", "memory", "sd_card", diff --git a/Modules/lvgl-module/source/insets.cpp b/Modules/lvgl-module/source/insets.cpp new file mode 100644 index 000000000..f1fe6f5a6 --- /dev/null +++ b/Modules/lvgl-module/source/insets.cpp @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include +#include + +#include +#include + +static DisplayShape get_shape(lv_display_t* display) { + DisplayShape shape = { .shape = DISPLAY_SHAPE_RECTANGLE, .corner_radius = 0 }; + const auto* wrapper = static_cast(lv_display_get_driver_data(display)); + if (wrapper != nullptr && wrapper->device != nullptr) { + display_get_shape(wrapper->device, &shape); + } + return shape; +} + +struct EdgePadding { + lv_obj_t* obj; + // The padding before the display's edge was added + int32_t base_left; + int32_t base_right; +}; + +static void update_edge_padding(void* data) { + const auto* padding = static_cast(data); + lv_display_t* display = lv_obj_get_display(padding->obj); + lv_area_t coords; + lv_obj_get_coords(padding->obj, &coords); + // The curve is widest at the object's edge that is nearest to the display's top or bottom + const int32_t inset = std::max(lvgl_display_get_row_inset(display, coords.y1), lvgl_display_get_row_inset(display, coords.y2)); + const int32_t right_edge = lv_display_get_horizontal_resolution(display) - 1; + lv_obj_set_style_pad_left(padding->obj, padding->base_left + std::max(0, inset - coords.x1), LV_STATE_DEFAULT); + lv_obj_set_style_pad_right(padding->obj, padding->base_right + std::max(0, inset - (right_edge - coords.x2)), LV_STATE_DEFAULT); +} + +// The position is final once the layout pass that changed the size has finished +static void on_size_changed(lv_event_t* event) { + lv_async_call(update_edge_padding, lv_event_get_user_data(event)); +} + +static void on_deleted(lv_event_t* event) { + auto* padding = static_cast(lv_event_get_user_data(event)); + lv_async_call_cancel(update_edge_padding, padding); + delete padding; +} + +extern "C" { + +void lvgl_obj_add_edge_padding(lv_obj_t* obj) { + if (get_shape(lv_obj_get_display(obj)).shape == DISPLAY_SHAPE_RECTANGLE) { + return; + } + auto* padding = new(std::nothrow) EdgePadding { + .obj = obj, + .base_left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN), + .base_right = lv_obj_get_style_pad_right(obj, LV_PART_MAIN) + }; + if (padding == nullptr) { + return; + } + lv_obj_add_event_cb(obj, on_size_changed, LV_EVENT_SIZE_CHANGED, padding); + lv_obj_add_event_cb(obj, on_deleted, LV_EVENT_DELETE, padding); +} + +void lvgl_display_get_shape(lv_display_t* display, DisplayShape* out_shape) { + *out_shape = get_shape(display); +} + +void lvgl_display_get_insets(lv_display_t* display, LvglInsets* out_insets) { + *out_insets = lvgl_insets_calculate(get_shape(display), lv_display_get_horizontal_resolution(display), lv_display_get_vertical_resolution(display)); +} + +int32_t lvgl_display_get_row_inset(lv_display_t* display, int32_t y) { + return lvgl_insets_calculate_row(get_shape(display), lv_display_get_horizontal_resolution(display), lv_display_get_vertical_resolution(display), y); +} + +} diff --git a/Modules/lvgl-module/source/insets_math.cpp b/Modules/lvgl-module/source/insets_math.cpp new file mode 100644 index 000000000..14fcb2e7c --- /dev/null +++ b/Modules/lvgl-module/source/insets_math.cpp @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: Apache-2.0 +#include + +#include +#include +#include + +constexpr double INV_SQRT2 = 1.0 / std::numbers::sqrt2; + +// The part of a corner's square that a circle with the given radius cuts off along its diagonal +static int32_t corner_inset(int32_t radius) { + return static_cast(std::ceil(radius * (1.0 - INV_SQRT2))); +} + +// The horizontal distance between a circle's edge and its bounding box at the given distance from the circle's center row +static int32_t circle_row_inset(int32_t radius, int32_t distance_from_center) { + if (distance_from_center >= radius) { + return radius; + } + const double half_width = std::sqrt(static_cast(radius) * radius - static_cast(distance_from_center) * distance_from_center); + return static_cast(std::ceil(radius - half_width)); +} + +LvglInsets lvgl_insets_calculate(const DisplayShape& shape, int32_t width, int32_t height) { + switch (shape.shape) { + case DISPLAY_SHAPE_ROUNDED: { + const int32_t radius = std::min(shape.corner_radius, std::min(width, height) / 2); + const int32_t inset = corner_inset(radius); + return { .top = inset, .bottom = inset, .left = 0, .right = 0 }; + } + case DISPLAY_SHAPE_CIRCLE: { + const int32_t diameter = std::min(width, height); + const int32_t inset = corner_inset(diameter / 2); + const int32_t horizontal = inset + (width - diameter) / 2; + const int32_t vertical = inset + (height - diameter) / 2; + return { .top = vertical, .bottom = vertical, .left = horizontal, .right = horizontal }; + } + default: + return { .top = 0, .bottom = 0, .left = 0, .right = 0 }; + } +} + +int32_t lvgl_insets_calculate_row(const DisplayShape& shape, int32_t width, int32_t height, int32_t y) { + switch (shape.shape) { + case DISPLAY_SHAPE_ROUNDED: { + const int32_t radius = std::min(shape.corner_radius, std::min(width, height) / 2); + // Distance from the row to the center row of the nearest corner's circle + const int32_t distance = std::max(radius - y, y - (height - 1 - radius)); + return distance > 0 ? circle_row_inset(radius, distance) : 0; + } + case DISPLAY_SHAPE_CIRCLE: { + const int32_t diameter = std::min(width, height); + const int32_t radius = diameter / 2; + const int32_t distance = std::abs(y - height / 2); + return std::min(circle_row_inset(radius, distance) + (width - diameter) / 2, width / 2); + } + default: + return 0; + } +} diff --git a/Modules/lvgl-module/source/symbols.c b/Modules/lvgl-module/source/symbols.c index e9e5b8199..13352ec16 100644 --- a/Modules/lvgl-module/source/symbols.c +++ b/Modules/lvgl-module/source/symbols.c @@ -10,6 +10,14 @@ #include #include +#include +#include +#include +#include +#include +#include +#include +#include #include #include @@ -56,6 +64,35 @@ const struct ModuleSymbol lvgl_module_symbols[] = { DEFINE_MODULE_SYMBOL(lvgl_pointer_add), DEFINE_MODULE_SYMBOL(lvgl_pointer_get_slot_index), DEFINE_MODULE_SYMBOL(lvgl_pointer_remove), + // lvgl_grid_navigation + DEFINE_MODULE_SYMBOL(lvgl_grid_navigation_add), + DEFINE_MODULE_SYMBOL(lvgl_grid_navigation_remove), + DEFINE_MODULE_SYMBOL(lvgl_grid_navigation_step), + DEFINE_MODULE_SYMBOL(lvgl_grid_navigation_get_focused), + DEFINE_MODULE_SYMBOL(lvgl_grid_navigation_is_container), + DEFINE_MODULE_SYMBOL(lvgl_focus_hide_key_selection), + // lvgl_insets + DEFINE_MODULE_SYMBOL(lvgl_display_get_shape), + DEFINE_MODULE_SYMBOL(lvgl_display_get_insets), + DEFINE_MODULE_SYMBOL(lvgl_display_get_row_inset), + DEFINE_MODULE_SYMBOL(lvgl_obj_add_edge_padding), + // lvgl_theme + DEFINE_MODULE_SYMBOL(lvgl_theme_get_settings), + DEFINE_MODULE_SYMBOL(lvgl_theme_get_default_settings), + DEFINE_MODULE_SYMBOL(lvgl_theme_set_settings), + DEFINE_MODULE_SYMBOL(lvgl_theme_is_mono), + // lvgl_badge + DEFINE_MODULE_SYMBOL(lvgl_badge_create), + DEFINE_MODULE_SYMBOL(lvgl_page_indicator_create), + DEFINE_MODULE_SYMBOL(lvgl_page_indicator_set_page_count), + DEFINE_MODULE_SYMBOL(lvgl_page_indicator_set_page), + // lvgl_card + DEFINE_MODULE_SYMBOL(lvgl_card_create), + // lvgl_chip + DEFINE_MODULE_SYMBOL(lvgl_chip_create), + // lvgl_icon_button + DEFINE_MODULE_SYMBOL(lvgl_icon_button_create), + DEFINE_MODULE_SYMBOL(lvgl_icon_button_create_variant), // lvgl_spinner DEFINE_MODULE_SYMBOL(lvgl_spinner_create), // lvgl_toolbar diff --git a/Modules/lvgl-module/source/theme.cpp b/Modules/lvgl-module/source/theme.cpp new file mode 100644 index 000000000..80090ffad --- /dev/null +++ b/Modules/lvgl-module/source/theme.cpp @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include + +#include +#include + +#include "themes/lv_theme_material.h" +#include "themes/lv_theme_material_mono.h" + +constexpr auto* TAG = "lvgl_theme"; + +enum class ActiveTheme { + None, + Material, + MaterialMono +}; + +static LvglThemeSettings theme_settings; +static bool theme_settings_set = false; +static ActiveTheme active_theme = ActiveTheme::None; + +void lvgl_theme_get_default_settings(LvglThemeSettings* settings) { + lv_theme_material_config_t material_config; + lv_theme_material_config_init(&material_config); + settings->is_dark = TT_LVGL_THEME_DARK; + settings->is_mono = false; + settings->animations_enabled = true; + settings->color_primary = material_config.color_primary; + settings->color_secondary = material_config.color_secondary; + settings->color_error = material_config.color_error; +} + +void lvgl_theme_get_settings(LvglThemeSettings* settings) { + if (theme_settings_set) { + *settings = theme_settings; + } else { + lvgl_theme_get_default_settings(settings); + } +} + +void lvgl_theme_set_settings(const LvglThemeSettings* settings) { + theme_settings = *settings; + theme_settings_set = true; +} + +bool lvgl_theme_is_mono() { + return active_theme == ActiveTheme::MaterialMono; +} + +static lv_theme_t* get_active_theme() { + switch (active_theme) { + case ActiveTheme::Material: + return lv_theme_material_get(); + case ActiveTheme::MaterialMono: + return lv_theme_material_mono_get(); + default: + return nullptr; + } +} + +void lvgl_theme_deinit() { + switch (active_theme) { + case ActiveTheme::Material: + lv_theme_material_deinit(); + break; + case ActiveTheme::MaterialMono: + lv_theme_material_mono_deinit(); + break; + default: + break; + } + active_theme = ActiveTheme::None; +} + +lv_theme_t* lvgl_theme_init_for_display(lv_display_t* display, bool isMonoDisplay, bool supportsAnimations) { + LvglThemeSettings settings; + lvgl_theme_get_settings(&settings); + const bool is_compact = lvgl_get_ui_density() == LVGL_UI_DENSITY_COMPACT; + const bool animations_enabled = settings.animations_enabled && supportsAnimations; + const ActiveTheme required_theme = (isMonoDisplay || settings.is_mono) ? ActiveTheme::MaterialMono : ActiveTheme::Material; + + // Only one theme is allocated at a time + lv_theme_t* replaced_theme = nullptr; + if (active_theme != ActiveTheme::None && active_theme != required_theme) { + replaced_theme = get_active_theme(); + lvgl_theme_deinit(); + } + + lv_theme_t* theme; + if (required_theme == ActiveTheme::MaterialMono) { + lv_theme_material_mono_config_t config; + lv_theme_material_mono_config_init(&config); + config.is_dark = settings.is_dark; + config.is_compact = is_compact; + config.animations_enabled = animations_enabled; + theme = lv_theme_material_mono_init(display, &config); + } else { + lv_theme_material_config_t config; + lv_theme_material_config_init(&config); + config.is_dark = settings.is_dark; + config.is_compact = is_compact; + config.animations_enabled = animations_enabled; + config.color_primary = settings.color_primary; + config.color_secondary = settings.color_secondary; + config.color_error = settings.color_error; + theme = lv_theme_material_init(display, &config); + } + active_theme = required_theme; + + // Other displays mustn't keep the deinitialized theme. Their widgets keep the old styles until they're recreated. + if (replaced_theme != nullptr) { + LOG_W(TAG, "Displays need different themes: switching all displays to the new theme"); + lv_display_t* other_display = lv_display_get_next(nullptr); + while (other_display != nullptr) { + if (lv_display_get_theme(other_display) == replaced_theme) { + lv_display_set_theme(other_display, theme); + } + other_display = lv_display_get_next(other_display); + } + } + + return theme; +} diff --git a/Modules/lvgl-module/source/themes/lv_theme_material.c b/Modules/lvgl-module/source/themes/lv_theme_material.c new file mode 100644 index 000000000..7b4ec65bf --- /dev/null +++ b/Modules/lvgl-module/source/themes/lv_theme_material.c @@ -0,0 +1,2308 @@ +/* Generated by LSS. Do not edit. */ + +#define LV_USE_PRIVATE_API 1 /* For lv_theme_t */ + +#include "lv_theme_material.h" + +/********************* + * DEFINES + *********************/ + +extern const lv_obj_class_t lvgl_toolbar_class; +extern const lv_obj_class_t lvgl_icon_button_class; +extern const lv_obj_class_t lvgl_icon_button_filled_class; +extern const lv_obj_class_t lvgl_icon_button_tonal_class; +extern const lv_obj_class_t lvgl_sliderbox_class; +extern const lv_obj_class_t lvgl_card_class; +extern const lv_obj_class_t lvgl_chip_class; +extern const lv_obj_class_t lvgl_badge_class; +extern const lv_obj_class_t lvgl_page_indicator_class; +extern const lv_obj_class_t lvgl_page_indicator_dot_class; + +#define NEUTRAL_4 lv_color_hex(0x0E0E0E) +#define NEUTRAL_6 lv_color_hex(0x131313) +#define NEUTRAL_10 lv_color_hex(0x1C1C1C) +#define NEUTRAL_12 lv_color_hex(0x1F1F1F) +#define NEUTRAL_17 lv_color_hex(0x2B2B2B) +#define NEUTRAL_20 lv_color_hex(0x303030) +#define NEUTRAL_22 lv_color_hex(0x343434) +#define NEUTRAL_30 lv_color_hex(0x474747) +#define NEUTRAL_50 lv_color_hex(0x777777) +#define NEUTRAL_60 lv_color_hex(0x919191) +#define NEUTRAL_80 lv_color_hex(0xC6C6C6) +#define NEUTRAL_90 lv_color_hex(0xE2E2E2) +#define NEUTRAL_92 lv_color_hex(0xE8E8E8) +#define NEUTRAL_94 lv_color_hex(0xEEEEEE) +#define NEUTRAL_95 lv_color_hex(0xF0F0F0) +#define NEUTRAL_96 lv_color_hex(0xF3F3F3) +#define NEUTRAL_98 lv_color_hex(0xFAFAFA) +#define NEUTRAL_100 lv_color_hex(0xFFFFFF) +#define TINT_NEUTRAL (10) +#define TINT_NEUTRAL_VARIANT (20) +#define CORNER_NONE 0 +#define CORNER_EXTRA_SMALL (LV_DPX_CALC(theme->disp_dpi, 4)) +#define CORNER_SMALL (LV_DPX_CALC(theme->disp_dpi, 8)) +#define CORNER_MEDIUM (LV_DPX_CALC(theme->disp_dpi, 12)) +#define CORNER_LARGE (LV_DPX_CALC(theme->disp_dpi, 16)) +#define CORNER_EXTRA_LARGE (LV_DPX_CALC(theme->disp_dpi, 28)) +#define CORNER_FULL LV_RADIUS_CIRCLE +#define CORNER_COMPACT 3 +#define SPACE_1 (LV_DPX_CALC(theme->disp_dpi, 4)) +#define SPACE_2 (LV_DPX_CALC(theme->disp_dpi, 8)) +#define SPACE_3 (LV_DPX_CALC(theme->disp_dpi, 12)) +#define SPACE_4 (LV_DPX_CALC(theme->disp_dpi, 16)) +#define SPACE_6 (LV_DPX_CALC(theme->disp_dpi, 24)) +#define OPACITY_STATE_HOVER (20) +#define OPACITY_STATE_FOCUS (LV_OPA_10) +#define OPACITY_STATE_PRESSED (LV_OPA_10) +#define OPACITY_DISABLED_CONTENT (97) +#define OPACITY_DISABLED_CONTAINER (31) +#define OPACITY_SCRIM (82) +#define DURATION_SHORT 100 +#define DURATION_MEDIUM 250 +#define DURATION_LONG 500 +#define COLOR_PRIMARY (theme->config.is_dark ? lv_color_mix(theme->config.color_primary, lv_color_white(), 140) : theme->config.color_primary) +#define COLOR_ON_PRIMARY (theme->config.is_dark ? lv_color_mix(theme->config.color_primary, lv_color_black(), LV_OPA_50) : lv_color_white()) +#define COLOR_PRIMARY_CONTAINER (theme->config.is_dark ? lv_color_mix(theme->config.color_primary, lv_color_black(), LV_OPA_70) : lv_color_mix(theme->config.color_primary, lv_color_white(), 64)) +#define COLOR_ON_PRIMARY_CONTAINER (theme->config.is_dark ? lv_color_mix(theme->config.color_primary, lv_color_white(), LV_OPA_20) : lv_color_mix(theme->config.color_primary, lv_color_black(), LV_OPA_60)) +#define COLOR_SECONDARY (theme->config.is_dark ? lv_color_mix(theme->config.color_secondary, lv_color_white(), 140) : theme->config.color_secondary) +#define COLOR_ON_SECONDARY (theme->config.is_dark ? lv_color_mix(theme->config.color_secondary, lv_color_black(), LV_OPA_50) : lv_color_white()) +#define COLOR_SECONDARY_CONTAINER (theme->config.is_dark ? lv_color_mix(theme->config.color_secondary, lv_color_black(), LV_OPA_70) : lv_color_mix(theme->config.color_secondary, lv_color_white(), 64)) +#define COLOR_ON_SECONDARY_CONTAINER (theme->config.is_dark ? lv_color_mix(theme->config.color_secondary, lv_color_white(), LV_OPA_20) : lv_color_mix(theme->config.color_secondary, lv_color_black(), LV_OPA_60)) +#define COLOR_ERROR (theme->config.is_dark ? lv_color_mix(theme->config.color_error, lv_color_white(), 140) : theme->config.color_error) +#define COLOR_ON_ERROR (theme->config.is_dark ? lv_color_mix(theme->config.color_error, lv_color_black(), LV_OPA_50) : lv_color_white()) +#define COLOR_SURFACE (lv_color_mix(theme->config.color_primary, theme->config.is_dark ? NEUTRAL_6 : NEUTRAL_98, TINT_NEUTRAL)) +#define COLOR_ON_SURFACE (lv_color_mix(theme->config.color_primary, theme->config.is_dark ? NEUTRAL_90 : NEUTRAL_10, TINT_NEUTRAL)) +#define COLOR_ON_SURFACE_VARIANT (lv_color_mix(theme->config.color_primary, theme->config.is_dark ? NEUTRAL_80 : NEUTRAL_30, TINT_NEUTRAL_VARIANT)) +#define COLOR_SURFACE_CONTAINER_LOWEST (lv_color_mix(theme->config.color_primary, theme->config.is_dark ? NEUTRAL_4 : NEUTRAL_100, TINT_NEUTRAL)) +#define COLOR_SURFACE_CONTAINER_LOW (lv_color_mix(theme->config.color_primary, theme->config.is_dark ? NEUTRAL_10 : NEUTRAL_96, TINT_NEUTRAL)) +#define COLOR_SURFACE_CONTAINER (lv_color_mix(theme->config.color_primary, theme->config.is_dark ? NEUTRAL_12 : NEUTRAL_94, TINT_NEUTRAL)) +#define COLOR_SURFACE_CONTAINER_HIGH (lv_color_mix(theme->config.color_primary, theme->config.is_dark ? NEUTRAL_17 : NEUTRAL_92, TINT_NEUTRAL)) +#define COLOR_SURFACE_CONTAINER_HIGHEST (lv_color_mix(theme->config.color_primary, theme->config.is_dark ? NEUTRAL_22 : NEUTRAL_90, TINT_NEUTRAL)) +#define COLOR_OUTLINE (lv_color_mix(theme->config.color_primary, theme->config.is_dark ? NEUTRAL_60 : NEUTRAL_50, TINT_NEUTRAL_VARIANT)) +#define COLOR_OUTLINE_VARIANT (lv_color_mix(theme->config.color_primary, theme->config.is_dark ? NEUTRAL_30 : NEUTRAL_80, TINT_NEUTRAL_VARIANT)) +#define COLOR_INVERSE_SURFACE (lv_color_mix(theme->config.color_primary, theme->config.is_dark ? NEUTRAL_90 : NEUTRAL_20, TINT_NEUTRAL)) +#define COLOR_INVERSE_ON_SURFACE (lv_color_mix(theme->config.color_primary, theme->config.is_dark ? NEUTRAL_20 : NEUTRAL_95, TINT_NEUTRAL)) +#define COLOR_SCRIM lv_color_black() +#define SHAPE_CONTROL (theme->config.is_compact ? CORNER_COMPACT : CORNER_FULL) +#define SHAPE_CONTAINER (theme->config.is_compact ? CORNER_COMPACT : CORNER_MEDIUM) +#define SHAPE_FIELD (theme->config.is_compact ? CORNER_COMPACT : CORNER_EXTRA_SMALL) +#define SHAPE_KEY (theme->config.is_compact ? CORNER_COMPACT : CORNER_SMALL) +#define SHAPE_DIALOG (theme->config.is_compact ? CORNER_COMPACT : CORNER_EXTRA_LARGE) +#define SHAPE_MENU (theme->config.is_compact ? CORNER_COMPACT : CORNER_EXTRA_SMALL) +#define DENSITY_CONTAINER_PAD (theme->config.is_compact ? 2 : SPACE_4) +#define DENSITY_CONTAINER_GAP (theme->config.is_compact ? 2 : SPACE_3) +#define DENSITY_CONTAINER_BORDER (theme->config.is_compact ? 1 : 0) +#define DENSITY_CONTROL_PAD_HOR (theme->config.is_compact ? 3 : SPACE_6) +#define DENSITY_CONTROL_PAD_VER (theme->config.is_compact ? 3 : LV_DPX_CALC(theme->disp_dpi, 10)) +#define DENSITY_CONTROL_GAP (theme->config.is_compact ? 2 : SPACE_2) +#define DENSITY_FIELD_PAD_HOR (theme->config.is_compact ? 2 : SPACE_4) +#define DENSITY_FIELD_PAD_VER (theme->config.is_compact ? 2 : SPACE_2) +#define DENSITY_LIST_PAD (theme->config.is_compact ? 2 : SPACE_2) +#define DENSITY_LIST_ITEM_PAD_HOR (theme->config.is_compact ? 2 : SPACE_4) +#define DENSITY_LIST_ITEM_PAD_VER (theme->config.is_compact ? 2 : SPACE_3) +#define DENSITY_DIALOG_PAD (theme->config.is_compact ? 2 : SPACE_6) +#define DENSITY_KEY_GAP (theme->config.is_compact ? 2 : SPACE_1) +#define DENSITY_BORDER (LV_DPX_CALC(theme->disp_dpi, 1)) +#define FOCUS_COLOR COLOR_SECONDARY +#define FOCUS_WIDTH (theme->config.is_compact ? 1 : LV_DPX_CALC(theme->disp_dpi, 3)) +#define FOCUS_OFFSET (theme->config.is_compact ? 1 : LV_DPX_CALC(theme->disp_dpi, 2)) +#define FOCUS_INSET_WIDTH (theme->config.is_compact ? 1 : LV_DPX_CALC(theme->disp_dpi, 2)) +#define EDIT_COLOR COLOR_PRIMARY +#define STATE_PRESSED_ON_PRIMARY (lv_color_mix(COLOR_ON_PRIMARY, COLOR_PRIMARY, OPACITY_STATE_PRESSED)) +#define STATE_PRESSED_ON_SECONDARY (lv_color_mix(COLOR_ON_SECONDARY, COLOR_SECONDARY, OPACITY_STATE_PRESSED)) +#define STATE_LAYER_COLOR COLOR_ON_SURFACE +#define MOTION_DURATION_SHORT (theme->config.animations_enabled ? DURATION_SHORT : 0) +#define MOTION_DURATION_MEDIUM (theme->config.animations_enabled ? DURATION_MEDIUM : 0) +#define MOTION_DURATION_LONG (theme->config.animations_enabled ? DURATION_LONG : 0) +#define SCREEN_COLOR COLOR_SURFACE +#define SCREEN_TEXT_COLOR COLOR_ON_SURFACE +#define SCREEN_GAP DENSITY_CONTAINER_GAP +#define CONTAINER_COLOR COLOR_SURFACE_CONTAINER_LOW +#define CONTAINER_TEXT_COLOR COLOR_ON_SURFACE +#define CONTAINER_BORDER_COLOR COLOR_OUTLINE_VARIANT +#define CONTAINER_BORDER_WIDTH DENSITY_CONTAINER_BORDER +#define CONTAINER_SHAPE SHAPE_CONTAINER +#define CONTAINER_PAD DENSITY_CONTAINER_PAD +#define CONTAINER_GAP DENSITY_CONTAINER_GAP +#define CARD_COLOR COLOR_SURFACE_CONTAINER_HIGH +#define CARD_TEXT_COLOR COLOR_ON_SURFACE +#define CARD_BORDER_COLOR COLOR_OUTLINE_VARIANT +#define CARD_BORDER_WIDTH DENSITY_CONTAINER_BORDER +#define CARD_SHAPE SHAPE_CONTAINER +#define CARD_PAD DENSITY_CONTAINER_PAD +#define CARD_GAP DENSITY_CONTAINER_GAP +#define CHIP_LABEL_COLOR COLOR_ON_SURFACE_VARIANT +#define CHIP_BORDER_COLOR COLOR_OUTLINE +#define CHIP_SELECTED_COLOR COLOR_SECONDARY_CONTAINER +#define CHIP_SELECTED_LABEL_COLOR COLOR_ON_SECONDARY_CONTAINER +#define CHIP_SHAPE (theme->config.is_compact ? CORNER_COMPACT : CORNER_SMALL) +#define CHIP_PAD_HOR (theme->config.is_compact ? 3 : SPACE_4) +#define CHIP_PAD_VER (theme->config.is_compact ? 2 : LV_DPX_CALC(theme->disp_dpi, 6)) +#define PAGE_INDICATOR_COLOR COLOR_ON_SURFACE_VARIANT +#define PAGE_INDICATOR_DOT_SIZE (theme->config.is_compact ? 4 : LV_DPX_CALC(theme->disp_dpi, 6)) +#define PAGE_INDICATOR_GAP (theme->config.is_compact ? 3 : LV_DPX_CALC(theme->disp_dpi, 6)) +#define BADGE_COLOR COLOR_SECONDARY +#define BADGE_SIZE (theme->config.is_compact ? 4 : LV_DPX_CALC(theme->disp_dpi, 6)) +#define SCROLLBAR_COLOR COLOR_OUTLINE +#define SCROLLBAR_OPACITY (LV_OPA_40) +#define SCROLLBAR_SCROLLED_OPACITY (LV_OPA_70) +#define SCROLLBAR_WIDTH (theme->config.is_compact ? 2 : SPACE_1) +#define SCROLLBAR_PAD (theme->config.is_compact ? 1 : SPACE_1) +#define BUTTON_CONTAINER_COLOR COLOR_PRIMARY +#define BUTTON_LABEL_COLOR COLOR_ON_PRIMARY +#define BUTTON_PRESSED_CONTAINER_COLOR STATE_PRESSED_ON_PRIMARY +#define BUTTON_CHECKED_CONTAINER_COLOR COLOR_SECONDARY +#define BUTTON_CHECKED_LABEL_COLOR COLOR_ON_SECONDARY +#define BUTTON_CHECKED_PRESSED_CONTAINER_COLOR STATE_PRESSED_ON_SECONDARY +#define BUTTON_DISABLED_CONTAINER_COLOR COLOR_ON_SURFACE +#define BUTTON_DISABLED_CONTAINER_OPACITY OPACITY_DISABLED_CONTAINER +#define BUTTON_DISABLED_LABEL_COLOR COLOR_ON_SURFACE +#define BUTTON_DISABLED_LABEL_OPACITY OPACITY_DISABLED_CONTENT +#define BUTTON_SHAPE SHAPE_CONTROL +#define BUTTON_PAD_HOR DENSITY_CONTROL_PAD_HOR +#define BUTTON_PAD_VER DENSITY_CONTROL_PAD_VER +#define BUTTON_GAP DENSITY_CONTROL_GAP +#define TEXT_BUTTON_LABEL_COLOR COLOR_PRIMARY +#define TEXT_BUTTON_PRESSED_CONTAINER_COLOR COLOR_PRIMARY +#define TEXT_BUTTON_PAD_HOR (theme->config.is_compact ? 3 : SPACE_3) +#define ICON_BUTTON_COLOR COLOR_ON_SURFACE +#define ICON_BUTTON_CONTAINER_SHAPE (theme->config.is_compact ? CORNER_COMPACT : CORNER_LARGE) +#define ICON_BUTTON_FILLED_COLOR COLOR_PRIMARY +#define ICON_BUTTON_FILLED_ICON_COLOR COLOR_ON_PRIMARY +#define ICON_BUTTON_FILLED_PRESSED_COLOR STATE_PRESSED_ON_PRIMARY +#define ICON_BUTTON_TONAL_COLOR COLOR_SECONDARY_CONTAINER +#define ICON_BUTTON_TONAL_ICON_COLOR COLOR_ON_SECONDARY_CONTAINER +#define ICON_BUTTON_TONAL_PRESSED_COLOR (lv_color_mix(COLOR_ON_SECONDARY_CONTAINER, COLOR_SECONDARY_CONTAINER, OPACITY_STATE_PRESSED)) +#define APP_BAR_COLOR (lv_color_mix(theme->config.color_primary, NEUTRAL_22, TINT_NEUTRAL)) +#define APP_BAR_CONTENT_COLOR lv_color_white() +#define APP_BAR_PRESSED_OPACITY (LV_OPA_20) +#define APP_BAR_PAD_HOR (theme->config.is_compact ? 0 : SPACE_1) +#define APP_BAR_GAP (theme->config.is_compact ? 2 : SPACE_1) +#define APP_BAR_FOCUS_WIDTH FOCUS_INSET_WIDTH +#define FOCUS_RING_COLOR FOCUS_COLOR +#define FOCUS_RING_WIDTH FOCUS_WIDTH +#define FOCUS_RING_OFFSET FOCUS_OFFSET +#define EDIT_RING_COLOR EDIT_COLOR +#define SWITCH_WIDTH (theme->config.is_compact ? 25 : LV_DPX_CALC(theme->disp_dpi, 52)) +#define SWITCH_HEIGHT (theme->config.is_compact ? 15 : LV_DPX_CALC(theme->disp_dpi, 32)) +#define SWITCH_TRACK_COLOR COLOR_SURFACE_CONTAINER_HIGHEST +#define SWITCH_TRACK_BORDER_COLOR COLOR_OUTLINE +#define SWITCH_TRACK_BORDER_WIDTH (theme->config.is_compact ? 1 : LV_DPX_CALC(theme->disp_dpi, 2)) +#define SWITCH_TRACK_SELECTED_COLOR COLOR_PRIMARY +#define SWITCH_HANDLE_COLOR COLOR_OUTLINE +#define SWITCH_HANDLE_SELECTED_COLOR COLOR_ON_PRIMARY +#define SWITCH_HANDLE_PAD (theme->config.is_compact ? -3 : -LV_DPX_CALC(theme->disp_dpi, 8)) +#define SWITCH_HANDLE_SELECTED_PAD (theme->config.is_compact ? -2 : -LV_DPX_CALC(theme->disp_dpi, 4)) +#define SWITCH_HANDLE_PRESSED_PAD (theme->config.is_compact ? -1 : -LV_DPX_CALC(theme->disp_dpi, 2)) +#define CHECKBOX_GAP (theme->config.is_compact ? 3 : SPACE_3) +#define CHECKBOX_BOX_COLOR COLOR_SURFACE +#define CHECKBOX_BOX_OPACITY LV_OPA_TRANSP +#define CHECKBOX_BOX_BORDER_COLOR COLOR_ON_SURFACE_VARIANT +#define CHECKBOX_BOX_BORDER_WIDTH (theme->config.is_compact ? 1 : LV_DPX_CALC(theme->disp_dpi, 2)) +#define CHECKBOX_BOX_SHAPE (theme->config.is_compact ? 2 : LV_DPX_CALC(theme->disp_dpi, 2)) +#define CHECKBOX_BOX_PAD (theme->config.is_compact ? 1 : LV_DPX_CALC(theme->disp_dpi, 2)) +#define CHECKBOX_BOX_SELECTED_COLOR COLOR_PRIMARY +#define CHECKBOX_ICON_COLOR COLOR_ON_PRIMARY +#define SLIDER_TRACK_COLOR COLOR_SECONDARY_CONTAINER +#define SLIDER_ACTIVE_TRACK_COLOR COLOR_PRIMARY +#define SLIDER_HANDLE_COLOR COLOR_PRIMARY +#define SLIDER_TRACK_HEIGHT (LV_DPX_CALC(theme->disp_dpi, 8)) +#define SLIDER_HANDLE_PAD ((theme->config.is_compact ? 2 : LV_DPX_CALC(theme->disp_dpi, 6)) + LV_DPX_CALC(theme->disp_dpi, 4)) +#define SLIDERBOX_GAP (theme->config.is_compact ? 4 : ((SLIDER_TRACK_HEIGHT / 2) + SLIDER_HANDLE_PAD) + SPACE_2) +#define PROGRESS_TRACK_COLOR COLOR_SECONDARY_CONTAINER +#define PROGRESS_INDICATOR_COLOR COLOR_PRIMARY +#define ARC_TRACK_COLOR COLOR_SECONDARY_CONTAINER +#define ARC_INDICATOR_COLOR COLOR_PRIMARY +#define ARC_WIDTH (theme->config.is_compact ? 3 : SPACE_1) +#define ARC_HANDLE_COLOR COLOR_PRIMARY +#define ARC_HANDLE_PAD (theme->config.is_compact ? 2 : SPACE_1) +#define FIELD_COLOR COLOR_SURFACE +#define FIELD_OPACITY LV_OPA_TRANSP +#define FIELD_TEXT_COLOR COLOR_ON_SURFACE +#define FIELD_BORDER_COLOR COLOR_OUTLINE +#define FIELD_BORDER_WIDTH DENSITY_BORDER +#define FIELD_FOCUSED_BORDER_COLOR COLOR_PRIMARY +#define FIELD_FOCUSED_BORDER_WIDTH (theme->config.is_compact ? 1 : LV_DPX_CALC(theme->disp_dpi, 2)) +#define FIELD_SHAPE SHAPE_FIELD +#define FIELD_PAD_HOR DENSITY_FIELD_PAD_HOR +#define FIELD_PAD_VER DENSITY_FIELD_PAD_VER +#define FIELD_PLACEHOLDER_COLOR COLOR_ON_SURFACE_VARIANT +#define FIELD_CURSOR_COLOR COLOR_PRIMARY +#define FIELD_CURSOR_WIDTH (theme->config.is_compact ? 1 : LV_DPX_CALC(theme->disp_dpi, 2)) +#define FIELD_SELECTION_COLOR COLOR_PRIMARY_CONTAINER +#define FIELD_SELECTION_TEXT_COLOR COLOR_ON_PRIMARY_CONTAINER +#define SPINBOX_CURSOR_COLOR COLOR_PRIMARY +#define SPINBOX_CURSOR_TEXT_COLOR COLOR_ON_PRIMARY +#define MENU_COLOR COLOR_SURFACE_CONTAINER +#define MENU_TEXT_COLOR COLOR_ON_SURFACE +#define MENU_SHAPE SHAPE_MENU +#define MENU_PAD (theme->config.is_compact ? 1 : SPACE_2) +#define MENU_MAX_HEIGHT (LV_DPI_DEF * 2) +#define MENU_ITEM_SELECTED_COLOR COLOR_SECONDARY_CONTAINER +#define MENU_ITEM_SELECTED_TEXT_COLOR COLOR_ON_SECONDARY_CONTAINER +#define MENU_ITEM_LINE_SPACE (theme->config.is_compact ? 4 : SPACE_4) +#define ROLLER_COLOR COLOR_SURFACE_CONTAINER +#define ROLLER_TEXT_COLOR COLOR_ON_SURFACE_VARIANT +#define ROLLER_SELECTED_COLOR COLOR_SECONDARY_CONTAINER +#define ROLLER_SELECTED_TEXT_COLOR COLOR_ON_SECONDARY_CONTAINER +#define ROLLER_LINE_SPACE (theme->config.is_compact ? 4 : SPACE_4) +#define KEYS_COLOR COLOR_SURFACE_CONTAINER +#define KEYS_PAD DENSITY_KEY_GAP +#define KEYS_GAP DENSITY_KEY_GAP +#define KEY_COLOR COLOR_SURFACE_CONTAINER_HIGHEST +#define KEY_TEXT_COLOR COLOR_ON_SURFACE +#define KEY_SHAPE SHAPE_KEY +#define KEY_CHECKED_COLOR COLOR_SECONDARY_CONTAINER +#define KEY_CHECKED_TEXT_COLOR COLOR_ON_SECONDARY_CONTAINER +#define KEY_SELECTED_COLOR COLOR_PRIMARY +#define KEY_SELECTED_TEXT_COLOR COLOR_ON_PRIMARY +#define LIST_COLOR COLOR_SURFACE +#define LIST_PAD DENSITY_LIST_PAD +#define LIST_ITEM_COLOR COLOR_SURFACE +#define LIST_ITEM_OPACITY LV_OPA_TRANSP +#define LIST_ITEM_TEXT_COLOR COLOR_ON_SURFACE +#define LIST_ITEM_PAD_HOR DENSITY_LIST_ITEM_PAD_HOR +#define LIST_ITEM_PAD_VER DENSITY_LIST_ITEM_PAD_VER +#define LIST_ITEM_GAP (theme->config.is_compact ? 3 : SPACE_4) +#define LIST_ITEM_FOCUSED_COLOR COLOR_SECONDARY_CONTAINER +#define LIST_ITEM_FOCUSED_TEXT_COLOR COLOR_ON_SECONDARY_CONTAINER +#define LIST_DIVIDER_COLOR COLOR_OUTLINE_VARIANT +#define LIST_DIVIDER_WIDTH DENSITY_BORDER +#define LIST_SUBHEADER_TEXT_COLOR COLOR_ON_SURFACE_VARIANT +#define TABS_DIVIDER_COLOR COLOR_OUTLINE_VARIANT +#define TAB_LABEL_COLOR COLOR_ON_SURFACE_VARIANT +#define TAB_SELECTED_LABEL_COLOR COLOR_PRIMARY +#define TAB_INDICATOR_COLOR COLOR_PRIMARY +#define TAB_INDICATOR_WIDTH (theme->config.is_compact ? 2 : LV_DPX_CALC(theme->disp_dpi, 3)) +#define TAB_PAGE_PAD DENSITY_CONTAINER_PAD +#define DIALOG_COLOR COLOR_SURFACE_CONTAINER_HIGH +#define DIALOG_TEXT_COLOR COLOR_ON_SURFACE_VARIANT +#define DIALOG_TITLE_COLOR COLOR_ON_SURFACE +#define DIALOG_SHAPE SHAPE_DIALOG +#define DIALOG_PAD DENSITY_DIALOG_PAD +#define DIALOG_GAP (theme->config.is_compact ? 2 : SPACE_2) +#define DIALOG_SCRIM_COLOR COLOR_SCRIM +#define DIALOG_SCRIM_OPACITY OPACITY_SCRIM +#define SETTINGS_COLOR COLOR_SURFACE +#define SETTINGS_SECTION_COLOR COLOR_SURFACE_CONTAINER_LOW +#define SETTINGS_SECTION_SHAPE SHAPE_CONTAINER +#define SETTINGS_ITEM_PAD_HOR DENSITY_LIST_ITEM_PAD_HOR +#define SETTINGS_ITEM_PAD_VER DENSITY_LIST_ITEM_PAD_VER +#define SETTINGS_ITEM_GAP DENSITY_CONTAINER_GAP +#define SETTINGS_DIVIDER_COLOR COLOR_OUTLINE_VARIANT +#define SETTINGS_ITEM_SELECTED_COLOR COLOR_SECONDARY_CONTAINER +#define SETTINGS_ITEM_SELECTED_TEXT_COLOR COLOR_ON_SECONDARY_CONTAINER +#define WINDOW_HEADER_COLOR COLOR_SURFACE_CONTAINER +#define WINDOW_HEADER_PAD (theme->config.is_compact ? 2 : SPACE_2) +#define WINDOW_CONTENT_PAD DENSITY_CONTAINER_PAD +#define TABLE_COLOR COLOR_SURFACE +#define TABLE_CELL_TEXT_COLOR COLOR_ON_SURFACE +#define TABLE_CELL_PAD (theme->config.is_compact ? 2 : SPACE_3) +#define TABLE_DIVIDER_COLOR COLOR_OUTLINE_VARIANT +#define CHART_COLOR COLOR_SURFACE_CONTAINER_LOW +#define CHART_GRID_COLOR COLOR_OUTLINE_VARIANT +#define CHART_SERIES_WIDTH (theme->config.is_compact ? 1 : LV_DPX_CALC(theme->disp_dpi, 2)) +#define CHART_POINT_SIZE (theme->config.is_compact ? 3 : LV_DPX_CALC(theme->disp_dpi, 6)) +#define CHART_INDICATOR_COLOR COLOR_PRIMARY +#define CALENDAR_COLOR COLOR_SURFACE_CONTAINER_HIGH +#define CALENDAR_SHAPE (theme->config.is_compact ? CORNER_COMPACT : CORNER_LARGE) +#define CALENDAR_PAD (theme->config.is_compact ? 2 : SPACE_2) +#define CALENDAR_DAY_TEXT_COLOR COLOR_ON_SURFACE +#define CALENDAR_HEADER_PAD (theme->config.is_compact ? 2 : SPACE_3) +#define SCALE_COLOR COLOR_ON_SURFACE_VARIANT +#define SCALE_WIDTH DENSITY_BORDER +#define SCALE_TICK_LENGTH (theme->config.is_compact ? 3 : LV_DPX_CALC(theme->disp_dpi, 6)) +#define DIVIDER_COLOR COLOR_OUTLINE_VARIANT +#define DIVIDER_WIDTH DENSITY_BORDER +#define LED_GLOW_WIDTH (theme->config.is_compact ? 4 : LV_DPX_CALC(theme->disp_dpi, 15)) +#define LED_GLOW_SPREAD (theme->config.is_compact ? 1 : LV_DPX_CALC(theme->disp_dpi, 5)) + +/********************** + * TYPEDEFS + **********************/ + +typedef struct { + lv_style_t transition_enter; + lv_style_t transition_exit; + lv_style_t focus_ring; + lv_style_t edit_ring; + lv_style_t focus_ring_inset; + lv_style_t edit_ring_inset; + lv_style_t state_pressed; + lv_style_t disabled; + lv_style_t scrollbar; + lv_style_t scrollbar_scrolled; +#if LV_USE_TABVIEW || LV_USE_TILEVIEW || LV_USE_WIN + lv_style_t page; +#endif + lv_style_t screen; + lv_style_t container; + lv_style_t card; + lv_style_t page_indicator; +#if LV_USE_LABEL + lv_style_t page_indicator_label; +#endif + lv_style_t page_indicator_dot; + lv_style_t page_indicator_dot_current; + lv_style_t badge; + lv_style_t chip; + lv_style_t chip_selected; +#if LV_USE_BUTTON + lv_style_t button; +#endif +#if LV_USE_BUTTON + lv_style_t button_pressed; +#endif +#if LV_USE_BUTTON + lv_style_t button_checked; +#endif +#if LV_USE_BUTTON + lv_style_t button_checked_pressed; +#endif + lv_style_t button_disabled; +#if LV_USE_MSGBOX + lv_style_t text_button; +#endif +#if LV_USE_MSGBOX + lv_style_t text_button_pressed; +#endif + lv_style_t icon_button; + lv_style_t icon_button_filled; + lv_style_t icon_button_filled_pressed; + lv_style_t icon_button_tonal; + lv_style_t icon_button_tonal_pressed; + lv_style_t app_bar; + lv_style_t app_bar_button; + lv_style_t app_bar_button_pressed; + lv_style_t app_bar_focus_ring; +#if LV_USE_DROPDOWN + lv_style_t app_bar_field; +#endif +#if LV_USE_SWITCH + lv_style_t switch_track; +#endif +#if LV_USE_SWITCH + lv_style_t switch_track_selected; +#endif +#if LV_USE_SWITCH + lv_style_t switch_indicator_selected; +#endif +#if LV_USE_SWITCH + lv_style_t switch_handle; +#endif +#if LV_USE_SWITCH + lv_style_t switch_handle_selected; +#endif +#if LV_USE_SWITCH + lv_style_t switch_handle_pressed; +#endif +#if LV_USE_CHECKBOX + lv_style_t checkbox; +#endif +#if LV_USE_CHECKBOX + lv_style_t checkbox_box; +#endif +#if LV_USE_CHECKBOX + lv_style_t checkbox_box_selected; +#endif + lv_style_t sliderbox; +#if LV_USE_BAR || LV_USE_SLIDER + lv_style_t slider_track_size; +#endif +#if LV_USE_SLIDER + lv_style_t slider_knob_room; +#endif +#if LV_USE_SLIDER + lv_style_t slider_track; +#endif +#if LV_USE_SLIDER + lv_style_t slider_active_track; +#endif +#if LV_USE_SLIDER + lv_style_t slider_handle; +#endif +#if LV_USE_BAR + lv_style_t progress_track; +#endif +#if LV_USE_BAR + lv_style_t progress_indicator; +#endif +#if LV_USE_ARC || LV_USE_SPINNER + lv_style_t arc_track; +#endif +#if LV_USE_ARC || LV_USE_SPINNER + lv_style_t arc_indicator; +#endif +#if LV_USE_ARC + lv_style_t arc_handle; +#endif +#if LV_USE_DROPDOWN || LV_USE_SPINBOX || LV_USE_TEXTAREA + lv_style_t field; +#endif +#if LV_USE_SPINBOX || LV_USE_TEXTAREA + lv_style_t field_focused; +#endif +#if LV_USE_TEXTAREA + lv_style_t field_placeholder; +#endif +#if LV_USE_TEXTAREA + lv_style_t field_cursor; +#endif +#if LV_USE_LABEL && LV_USE_TEXTAREA + lv_style_t field_selection; +#endif +#if LV_USE_SPINBOX + lv_style_t spinbox_cursor; +#endif +#if LV_USE_DROPDOWN + lv_style_t menu; +#endif +#if LV_USE_DROPDOWN + lv_style_t menu_item; +#endif +#if LV_USE_DROPDOWN + lv_style_t menu_item_selected; +#endif +#if LV_USE_ROLLER + lv_style_t roller; +#endif +#if LV_USE_ROLLER + lv_style_t roller_selected; +#endif +#if LV_USE_BUTTONMATRIX || LV_USE_KEYBOARD + lv_style_t keys; +#endif +#if LV_USE_BUTTONMATRIX || LV_USE_KEYBOARD + lv_style_t key; +#endif +#if LV_USE_BUTTONMATRIX || LV_USE_KEYBOARD + lv_style_t key_pressed; +#endif +#if LV_USE_KEYBOARD + lv_style_t key_checked; +#endif +#if LV_USE_BUTTONMATRIX || LV_USE_KEYBOARD + lv_style_t key_selected; +#endif +#if LV_USE_LIST + lv_style_t list; +#endif +#if LV_USE_LIST + lv_style_t list_item; +#endif +#if LV_USE_LIST || LV_USE_TABLE + lv_style_t list_item_focused; +#endif +#if LV_USE_LIST + lv_style_t list_subheader; +#endif +#if LV_USE_TABVIEW + lv_style_t tabs; +#endif +#if LV_USE_BUTTON && LV_USE_TABVIEW + lv_style_t tab; +#endif +#if LV_USE_BUTTON && LV_USE_TABVIEW + lv_style_t tab_selected; +#endif +#if LV_USE_TABVIEW + lv_style_t tab_page; +#endif +#if LV_USE_MSGBOX + lv_style_t dialog; +#endif +#if LV_USE_MSGBOX + lv_style_t dialog_header; +#endif +#if LV_USE_LABEL && LV_USE_MSGBOX + lv_style_t dialog_title; +#endif +#if LV_USE_MSGBOX + lv_style_t dialog_content; +#endif +#if LV_USE_MSGBOX + lv_style_t dialog_actions; +#endif +#if LV_USE_MSGBOX + lv_style_t dialog_scrim; +#endif +#if LV_USE_MENU + lv_style_t settings; +#endif +#if LV_USE_MENU + lv_style_t settings_sidebar; +#endif +#if LV_USE_MENU + lv_style_t settings_content; +#endif +#if LV_USE_MENU + lv_style_t settings_page; +#endif +#if LV_USE_MENU + lv_style_t settings_header; +#endif +#if LV_USE_MENU + lv_style_t settings_section; +#endif +#if LV_USE_MENU + lv_style_t settings_item; +#endif +#if LV_USE_MENU + lv_style_t settings_item_selected; +#endif +#if LV_USE_MENU + lv_style_t settings_separator; +#endif +#if LV_USE_WIN + lv_style_t window_header; +#endif +#if LV_USE_LABEL && LV_USE_WIN + lv_style_t window_title; +#endif +#if LV_USE_WIN + lv_style_t window_content; +#endif +#if LV_USE_TABLE + lv_style_t table; +#endif +#if LV_USE_TABLE + lv_style_t table_cell; +#endif +#if LV_USE_CHART + lv_style_t chart; +#endif +#if LV_USE_CHART + lv_style_t chart_series; +#endif +#if LV_USE_CHART + lv_style_t chart_indicator; +#endif +#if LV_USE_CALENDAR + lv_style_t calendar; +#endif +#if LV_USE_BUTTONMATRIX && LV_USE_CALENDAR + lv_style_t calendar_days; +#endif +#if LV_USE_BUTTONMATRIX && LV_USE_CALENDAR + lv_style_t calendar_day; +#endif +#if (LV_USE_CALENDAR && LV_USE_CALENDAR_HEADER_ARROW) || (LV_USE_CALENDAR && LV_USE_CALENDAR_HEADER_DROPDOWN) + lv_style_t calendar_header; +#endif +#if LV_USE_SCALE + lv_style_t scale; +#endif +#if LV_USE_LINE + lv_style_t divider; +#endif +#if LV_USE_LED + lv_style_t led; +#endif +} my_theme_styles_t; + +typedef enum { + DISP_SMALL = 3, + DISP_MEDIUM = 2, + DISP_LARGE = 1, +} disp_size_t; + +typedef struct { + lv_theme_t base; + disp_size_t disp_size; + int32_t disp_dpi; + lv_theme_material_config_t config; + bool inited; + my_theme_styles_t styles; + lv_style_transition_dsc_t trans_state_enter; + lv_style_transition_dsc_t trans_state_exit; +} my_theme_t; + +/********************** + * STATIC PROTOTYPES + **********************/ + +static void style_init_reset(lv_style_t * style); +static void theme_apply(lv_theme_t * th, lv_obj_t * obj); +static void resolution_change_event_cb(lv_event_t * e); + +/********************** + * STATIC VARIABLES + **********************/ + +static my_theme_t * theme_def = NULL; + +/********************** + * STATIC FUNCTIONS + **********************/ + +static inline lv_obj_t * lss_ancestor(lv_obj_t * obj, uint32_t depth) +{ + while(obj != NULL && depth > 0) { + obj = lv_obj_get_parent(obj); + depth--; + } + return obj; +} + +static inline bool lss_is_child(lv_obj_t * parent, int32_t index, lv_obj_t * child) +{ + return parent != NULL && lv_obj_get_child(parent, index) == child; +} + +static bool config_equals(const lv_theme_material_config_t * a, const lv_theme_material_config_t * b) +{ + return a->is_dark == b->is_dark && + a->is_compact == b->is_compact && + a->animations_enabled == b->animations_enabled && + lv_color_eq(a->color_primary, b->color_primary) && + lv_color_eq(a->color_secondary, b->color_secondary) && + lv_color_eq(a->color_error, b->color_error) && + a->font_small == b->font_small && + a->font_normal == b->font_normal && + a->font_large == b->font_large; +} + +static void style_init(my_theme_t * theme) +{ + static const lv_style_prop_t trans_state_enter_props[] = { + LV_STYLE_BG_COLOR, LV_STYLE_BG_OPA, LV_STYLE_BORDER_COLOR, LV_STYLE_TEXT_COLOR, LV_STYLE_OUTLINE_WIDTH, LV_STYLE_OUTLINE_OPA, + 0 + }; + lv_style_transition_dsc_init(&theme->trans_state_enter, trans_state_enter_props, lv_anim_path_ease_out, MOTION_DURATION_SHORT, 0, NULL); + + static const lv_style_prop_t trans_state_exit_props[] = { + LV_STYLE_BG_COLOR, LV_STYLE_BG_OPA, LV_STYLE_BORDER_COLOR, LV_STYLE_TEXT_COLOR, LV_STYLE_OUTLINE_WIDTH, LV_STYLE_OUTLINE_OPA, + 0 + }; + lv_style_transition_dsc_init(&theme->trans_state_exit, trans_state_exit_props, lv_anim_path_ease_out, MOTION_DURATION_SHORT, 70, NULL); + + style_init_reset(&theme->styles.transition_enter); + if(theme->config.animations_enabled) { + lv_style_set_transition(&theme->styles.transition_enter, &theme->trans_state_enter); + } + + style_init_reset(&theme->styles.transition_exit); + if(theme->config.animations_enabled) { + lv_style_set_transition(&theme->styles.transition_exit, &theme->trans_state_exit); + } + + style_init_reset(&theme->styles.focus_ring); + lv_style_set_outline_color(&theme->styles.focus_ring, FOCUS_RING_COLOR); + lv_style_set_outline_width(&theme->styles.focus_ring, FOCUS_RING_WIDTH); + lv_style_set_outline_pad(&theme->styles.focus_ring, FOCUS_RING_OFFSET); + lv_style_set_outline_opa(&theme->styles.focus_ring, LV_OPA_COVER); + + style_init_reset(&theme->styles.edit_ring); + lv_style_set_outline_color(&theme->styles.edit_ring, EDIT_RING_COLOR); + lv_style_set_outline_width(&theme->styles.edit_ring, FOCUS_RING_WIDTH); + lv_style_set_outline_pad(&theme->styles.edit_ring, FOCUS_RING_OFFSET); + lv_style_set_outline_opa(&theme->styles.edit_ring, LV_OPA_COVER); + + style_init_reset(&theme->styles.focus_ring_inset); + lv_style_set_outline_color(&theme->styles.focus_ring_inset, FOCUS_RING_COLOR); + lv_style_set_outline_width(&theme->styles.focus_ring_inset, FOCUS_INSET_WIDTH); + lv_style_set_outline_pad(&theme->styles.focus_ring_inset, -FOCUS_INSET_WIDTH); + lv_style_set_outline_opa(&theme->styles.focus_ring_inset, LV_OPA_COVER); + + style_init_reset(&theme->styles.edit_ring_inset); + lv_style_set_outline_color(&theme->styles.edit_ring_inset, EDIT_RING_COLOR); + lv_style_set_outline_width(&theme->styles.edit_ring_inset, FOCUS_INSET_WIDTH); + lv_style_set_outline_pad(&theme->styles.edit_ring_inset, -FOCUS_INSET_WIDTH); + lv_style_set_outline_opa(&theme->styles.edit_ring_inset, LV_OPA_COVER); + + style_init_reset(&theme->styles.state_pressed); + lv_style_set_bg_color(&theme->styles.state_pressed, STATE_LAYER_COLOR); + lv_style_set_bg_opa(&theme->styles.state_pressed, OPACITY_STATE_PRESSED); + + style_init_reset(&theme->styles.disabled); + lv_style_set_opa(&theme->styles.disabled, OPACITY_DISABLED_CONTENT); + + style_init_reset(&theme->styles.scrollbar); + lv_style_set_bg_color(&theme->styles.scrollbar, SCROLLBAR_COLOR); + lv_style_set_bg_opa(&theme->styles.scrollbar, SCROLLBAR_OPACITY); + lv_style_set_radius(&theme->styles.scrollbar, LV_RADIUS_CIRCLE); + lv_style_set_width(&theme->styles.scrollbar, SCROLLBAR_WIDTH); + lv_style_set_pad_all(&theme->styles.scrollbar, SCROLLBAR_PAD); + + style_init_reset(&theme->styles.scrollbar_scrolled); + lv_style_set_bg_opa(&theme->styles.scrollbar_scrolled, SCROLLBAR_SCROLLED_OPACITY); + +#if LV_USE_TABVIEW || LV_USE_TILEVIEW || LV_USE_WIN + style_init_reset(&theme->styles.page); + lv_style_set_bg_opa(&theme->styles.page, LV_OPA_TRANSP); + lv_style_set_border_width(&theme->styles.page, 0); + lv_style_set_radius(&theme->styles.page, 0); + lv_style_set_pad_all(&theme->styles.page, 0); + lv_style_set_pad_gap(&theme->styles.page, 0); +#endif + + style_init_reset(&theme->styles.screen); + lv_style_set_bg_color(&theme->styles.screen, SCREEN_COLOR); + lv_style_set_bg_opa(&theme->styles.screen, LV_OPA_COVER); + lv_style_set_text_color(&theme->styles.screen, SCREEN_TEXT_COLOR); + lv_style_set_text_font(&theme->styles.screen, theme->config.font_normal); + lv_style_set_pad_row(&theme->styles.screen, SCREEN_GAP); + lv_style_set_pad_column(&theme->styles.screen, SCREEN_GAP); + lv_style_set_rotary_sensitivity(&theme->styles.screen, (theme->disp_dpi / 4) * 256); + + style_init_reset(&theme->styles.container); + lv_style_set_bg_color(&theme->styles.container, CONTAINER_COLOR); + lv_style_set_bg_opa(&theme->styles.container, LV_OPA_COVER); + lv_style_set_text_color(&theme->styles.container, CONTAINER_TEXT_COLOR); + lv_style_set_border_color(&theme->styles.container, CONTAINER_BORDER_COLOR); + lv_style_set_border_width(&theme->styles.container, CONTAINER_BORDER_WIDTH); + lv_style_set_border_post(&theme->styles.container, true); + lv_style_set_radius(&theme->styles.container, CONTAINER_SHAPE); + lv_style_set_pad_all(&theme->styles.container, CONTAINER_PAD); + lv_style_set_pad_gap(&theme->styles.container, CONTAINER_GAP); + lv_style_set_line_color(&theme->styles.container, DIVIDER_COLOR); + lv_style_set_line_width(&theme->styles.container, DIVIDER_WIDTH); + + style_init_reset(&theme->styles.card); + lv_style_set_bg_color(&theme->styles.card, CARD_COLOR); + lv_style_set_bg_opa(&theme->styles.card, LV_OPA_COVER); + lv_style_set_text_color(&theme->styles.card, CARD_TEXT_COLOR); + lv_style_set_border_color(&theme->styles.card, CARD_BORDER_COLOR); + lv_style_set_border_width(&theme->styles.card, CARD_BORDER_WIDTH); + lv_style_set_radius(&theme->styles.card, CARD_SHAPE); + lv_style_set_pad_all(&theme->styles.card, CARD_PAD); + lv_style_set_pad_gap(&theme->styles.card, CARD_GAP); + + style_init_reset(&theme->styles.page_indicator); + lv_style_set_bg_opa(&theme->styles.page_indicator, LV_OPA_TRANSP); + lv_style_set_border_width(&theme->styles.page_indicator, 0); + lv_style_set_pad_all(&theme->styles.page_indicator, 0); + lv_style_set_pad_gap(&theme->styles.page_indicator, PAGE_INDICATOR_GAP); + +#if LV_USE_LABEL + style_init_reset(&theme->styles.page_indicator_label); + lv_style_set_text_color(&theme->styles.page_indicator_label, PAGE_INDICATOR_COLOR); +#endif + + style_init_reset(&theme->styles.page_indicator_dot); + lv_style_set_width(&theme->styles.page_indicator_dot, PAGE_INDICATOR_DOT_SIZE); + lv_style_set_height(&theme->styles.page_indicator_dot, PAGE_INDICATOR_DOT_SIZE); + lv_style_set_radius(&theme->styles.page_indicator_dot, LV_RADIUS_CIRCLE); + lv_style_set_bg_color(&theme->styles.page_indicator_dot, PAGE_INDICATOR_COLOR); + lv_style_set_bg_opa(&theme->styles.page_indicator_dot, LV_OPA_TRANSP); + lv_style_set_border_color(&theme->styles.page_indicator_dot, PAGE_INDICATOR_COLOR); + lv_style_set_border_width(&theme->styles.page_indicator_dot, 1); + + style_init_reset(&theme->styles.page_indicator_dot_current); + lv_style_set_bg_opa(&theme->styles.page_indicator_dot_current, LV_OPA_COVER); + + style_init_reset(&theme->styles.badge); + lv_style_set_width(&theme->styles.badge, BADGE_SIZE); + lv_style_set_height(&theme->styles.badge, BADGE_SIZE); + lv_style_set_radius(&theme->styles.badge, LV_RADIUS_CIRCLE); + lv_style_set_bg_color(&theme->styles.badge, BADGE_COLOR); + lv_style_set_bg_opa(&theme->styles.badge, LV_OPA_COVER); + + style_init_reset(&theme->styles.chip); + lv_style_set_bg_opa(&theme->styles.chip, LV_OPA_TRANSP); + lv_style_set_text_color(&theme->styles.chip, CHIP_LABEL_COLOR); + lv_style_set_border_color(&theme->styles.chip, CHIP_BORDER_COLOR); + lv_style_set_border_width(&theme->styles.chip, DENSITY_BORDER); + lv_style_set_radius(&theme->styles.chip, CHIP_SHAPE); + lv_style_set_pad_hor(&theme->styles.chip, CHIP_PAD_HOR); + lv_style_set_pad_ver(&theme->styles.chip, CHIP_PAD_VER); + + style_init_reset(&theme->styles.chip_selected); + lv_style_set_bg_color(&theme->styles.chip_selected, CHIP_SELECTED_COLOR); + lv_style_set_bg_opa(&theme->styles.chip_selected, LV_OPA_COVER); + lv_style_set_text_color(&theme->styles.chip_selected, CHIP_SELECTED_LABEL_COLOR); + lv_style_set_border_color(&theme->styles.chip_selected, CHIP_SELECTED_COLOR); + +#if LV_USE_BUTTON + style_init_reset(&theme->styles.button); + lv_style_set_bg_color(&theme->styles.button, BUTTON_CONTAINER_COLOR); + lv_style_set_bg_opa(&theme->styles.button, LV_OPA_COVER); + lv_style_set_text_color(&theme->styles.button, BUTTON_LABEL_COLOR); + lv_style_set_radius(&theme->styles.button, BUTTON_SHAPE); + lv_style_set_pad_hor(&theme->styles.button, BUTTON_PAD_HOR); + lv_style_set_pad_ver(&theme->styles.button, BUTTON_PAD_VER); + lv_style_set_pad_gap(&theme->styles.button, BUTTON_GAP); +#endif + +#if LV_USE_BUTTON + style_init_reset(&theme->styles.button_pressed); + lv_style_set_bg_color(&theme->styles.button_pressed, BUTTON_PRESSED_CONTAINER_COLOR); +#endif + +#if LV_USE_BUTTON + style_init_reset(&theme->styles.button_checked); + lv_style_set_bg_color(&theme->styles.button_checked, BUTTON_CHECKED_CONTAINER_COLOR); + lv_style_set_text_color(&theme->styles.button_checked, BUTTON_CHECKED_LABEL_COLOR); +#endif + +#if LV_USE_BUTTON + style_init_reset(&theme->styles.button_checked_pressed); + lv_style_set_bg_color(&theme->styles.button_checked_pressed, BUTTON_CHECKED_PRESSED_CONTAINER_COLOR); +#endif + + style_init_reset(&theme->styles.button_disabled); + lv_style_set_bg_color(&theme->styles.button_disabled, BUTTON_DISABLED_CONTAINER_COLOR); + lv_style_set_bg_opa(&theme->styles.button_disabled, BUTTON_DISABLED_CONTAINER_OPACITY); + lv_style_set_text_color(&theme->styles.button_disabled, BUTTON_DISABLED_LABEL_COLOR); + lv_style_set_text_opa(&theme->styles.button_disabled, BUTTON_DISABLED_LABEL_OPACITY); + +#if LV_USE_MSGBOX + style_init_reset(&theme->styles.text_button); + lv_style_set_bg_opa(&theme->styles.text_button, LV_OPA_TRANSP); + lv_style_set_text_color(&theme->styles.text_button, TEXT_BUTTON_LABEL_COLOR); + lv_style_set_radius(&theme->styles.text_button, BUTTON_SHAPE); + lv_style_set_pad_hor(&theme->styles.text_button, TEXT_BUTTON_PAD_HOR); + lv_style_set_pad_ver(&theme->styles.text_button, BUTTON_PAD_VER); + lv_style_set_pad_gap(&theme->styles.text_button, BUTTON_GAP); +#endif + +#if LV_USE_MSGBOX + style_init_reset(&theme->styles.text_button_pressed); + lv_style_set_bg_color(&theme->styles.text_button_pressed, TEXT_BUTTON_PRESSED_CONTAINER_COLOR); + lv_style_set_bg_opa(&theme->styles.text_button_pressed, OPACITY_STATE_PRESSED); +#endif + + style_init_reset(&theme->styles.icon_button); + lv_style_set_bg_opa(&theme->styles.icon_button, LV_OPA_TRANSP); + lv_style_set_text_color(&theme->styles.icon_button, ICON_BUTTON_COLOR); + lv_style_set_radius(&theme->styles.icon_button, BUTTON_SHAPE); + lv_style_set_pad_all(&theme->styles.icon_button, BUTTON_PAD_VER); + + style_init_reset(&theme->styles.icon_button_filled); + lv_style_set_bg_color(&theme->styles.icon_button_filled, ICON_BUTTON_FILLED_COLOR); + lv_style_set_bg_opa(&theme->styles.icon_button_filled, LV_OPA_COVER); + lv_style_set_text_color(&theme->styles.icon_button_filled, ICON_BUTTON_FILLED_ICON_COLOR); + lv_style_set_radius(&theme->styles.icon_button_filled, ICON_BUTTON_CONTAINER_SHAPE); + lv_style_set_pad_all(&theme->styles.icon_button_filled, BUTTON_PAD_VER); + + style_init_reset(&theme->styles.icon_button_filled_pressed); + lv_style_set_bg_color(&theme->styles.icon_button_filled_pressed, ICON_BUTTON_FILLED_PRESSED_COLOR); + + style_init_reset(&theme->styles.icon_button_tonal); + lv_style_set_bg_color(&theme->styles.icon_button_tonal, ICON_BUTTON_TONAL_COLOR); + lv_style_set_bg_opa(&theme->styles.icon_button_tonal, LV_OPA_COVER); + lv_style_set_text_color(&theme->styles.icon_button_tonal, ICON_BUTTON_TONAL_ICON_COLOR); + lv_style_set_radius(&theme->styles.icon_button_tonal, ICON_BUTTON_CONTAINER_SHAPE); + lv_style_set_pad_all(&theme->styles.icon_button_tonal, BUTTON_PAD_VER); + + style_init_reset(&theme->styles.icon_button_tonal_pressed); + lv_style_set_bg_color(&theme->styles.icon_button_tonal_pressed, ICON_BUTTON_TONAL_PRESSED_COLOR); + + style_init_reset(&theme->styles.app_bar); + lv_style_set_bg_color(&theme->styles.app_bar, APP_BAR_COLOR); + lv_style_set_bg_opa(&theme->styles.app_bar, LV_OPA_COVER); + lv_style_set_text_color(&theme->styles.app_bar, APP_BAR_CONTENT_COLOR); + lv_style_set_radius(&theme->styles.app_bar, 0); + lv_style_set_border_width(&theme->styles.app_bar, 0); + lv_style_set_pad_hor(&theme->styles.app_bar, APP_BAR_PAD_HOR); + lv_style_set_pad_ver(&theme->styles.app_bar, 0); + lv_style_set_pad_gap(&theme->styles.app_bar, APP_BAR_GAP); + + style_init_reset(&theme->styles.app_bar_button); + lv_style_set_pad_all(&theme->styles.app_bar_button, 0); + lv_style_set_text_color(&theme->styles.app_bar_button, APP_BAR_CONTENT_COLOR); + + style_init_reset(&theme->styles.app_bar_button_pressed); + lv_style_set_bg_color(&theme->styles.app_bar_button_pressed, APP_BAR_CONTENT_COLOR); + lv_style_set_bg_opa(&theme->styles.app_bar_button_pressed, APP_BAR_PRESSED_OPACITY); + + style_init_reset(&theme->styles.app_bar_focus_ring); + lv_style_set_outline_color(&theme->styles.app_bar_focus_ring, FOCUS_RING_COLOR); + lv_style_set_outline_width(&theme->styles.app_bar_focus_ring, APP_BAR_FOCUS_WIDTH); + lv_style_set_outline_pad(&theme->styles.app_bar_focus_ring, -APP_BAR_FOCUS_WIDTH); + lv_style_set_outline_opa(&theme->styles.app_bar_focus_ring, LV_OPA_COVER); + +#if LV_USE_DROPDOWN + style_init_reset(&theme->styles.app_bar_field); + lv_style_set_text_color(&theme->styles.app_bar_field, APP_BAR_CONTENT_COLOR); + lv_style_set_border_color(&theme->styles.app_bar_field, APP_BAR_CONTENT_COLOR); +#endif + +#if LV_USE_SWITCH + style_init_reset(&theme->styles.switch_track); + lv_style_set_width(&theme->styles.switch_track, SWITCH_WIDTH); + lv_style_set_height(&theme->styles.switch_track, SWITCH_HEIGHT); + lv_style_set_bg_color(&theme->styles.switch_track, SWITCH_TRACK_COLOR); + lv_style_set_bg_opa(&theme->styles.switch_track, LV_OPA_COVER); + lv_style_set_border_color(&theme->styles.switch_track, SWITCH_TRACK_BORDER_COLOR); + lv_style_set_border_width(&theme->styles.switch_track, SWITCH_TRACK_BORDER_WIDTH); + lv_style_set_radius(&theme->styles.switch_track, LV_RADIUS_CIRCLE); + lv_style_set_anim_duration(&theme->styles.switch_track, MOTION_DURATION_SHORT); +#endif + +#if LV_USE_SWITCH + style_init_reset(&theme->styles.switch_track_selected); + lv_style_set_border_color(&theme->styles.switch_track_selected, SWITCH_TRACK_SELECTED_COLOR); +#endif + +#if LV_USE_SWITCH + style_init_reset(&theme->styles.switch_indicator_selected); + lv_style_set_bg_color(&theme->styles.switch_indicator_selected, SWITCH_TRACK_SELECTED_COLOR); + lv_style_set_bg_opa(&theme->styles.switch_indicator_selected, LV_OPA_COVER); + lv_style_set_radius(&theme->styles.switch_indicator_selected, LV_RADIUS_CIRCLE); +#endif + +#if LV_USE_SWITCH + style_init_reset(&theme->styles.switch_handle); + lv_style_set_bg_color(&theme->styles.switch_handle, SWITCH_HANDLE_COLOR); + lv_style_set_bg_opa(&theme->styles.switch_handle, LV_OPA_COVER); + lv_style_set_radius(&theme->styles.switch_handle, LV_RADIUS_CIRCLE); + lv_style_set_pad_all(&theme->styles.switch_handle, SWITCH_HANDLE_PAD); +#endif + +#if LV_USE_SWITCH + style_init_reset(&theme->styles.switch_handle_selected); + lv_style_set_bg_color(&theme->styles.switch_handle_selected, SWITCH_HANDLE_SELECTED_COLOR); + lv_style_set_pad_all(&theme->styles.switch_handle_selected, SWITCH_HANDLE_SELECTED_PAD); +#endif + +#if LV_USE_SWITCH + style_init_reset(&theme->styles.switch_handle_pressed); + lv_style_set_pad_all(&theme->styles.switch_handle_pressed, SWITCH_HANDLE_PRESSED_PAD); +#endif + +#if LV_USE_CHECKBOX + style_init_reset(&theme->styles.checkbox); + lv_style_set_pad_gap(&theme->styles.checkbox, CHECKBOX_GAP); + lv_style_set_text_color(&theme->styles.checkbox, COLOR_ON_SURFACE); +#endif + +#if LV_USE_CHECKBOX + style_init_reset(&theme->styles.checkbox_box); + lv_style_set_bg_color(&theme->styles.checkbox_box, CHECKBOX_BOX_COLOR); + lv_style_set_bg_opa(&theme->styles.checkbox_box, CHECKBOX_BOX_OPACITY); + lv_style_set_border_color(&theme->styles.checkbox_box, CHECKBOX_BOX_BORDER_COLOR); + lv_style_set_border_width(&theme->styles.checkbox_box, CHECKBOX_BOX_BORDER_WIDTH); + lv_style_set_radius(&theme->styles.checkbox_box, CHECKBOX_BOX_SHAPE); + lv_style_set_pad_all(&theme->styles.checkbox_box, CHECKBOX_BOX_PAD); + lv_style_set_text_font(&theme->styles.checkbox_box, theme->config.font_small); + lv_style_set_text_color(&theme->styles.checkbox_box, CHECKBOX_ICON_COLOR); +#endif + +#if LV_USE_CHECKBOX + style_init_reset(&theme->styles.checkbox_box_selected); + lv_style_set_bg_color(&theme->styles.checkbox_box_selected, CHECKBOX_BOX_SELECTED_COLOR); + lv_style_set_bg_opa(&theme->styles.checkbox_box_selected, LV_OPA_COVER); + lv_style_set_border_color(&theme->styles.checkbox_box_selected, CHECKBOX_BOX_SELECTED_COLOR); + lv_style_set_bg_image_src(&theme->styles.checkbox_box_selected, LV_SYMBOL_OK); +#endif + + style_init_reset(&theme->styles.sliderbox); + lv_style_set_pad_column(&theme->styles.sliderbox, SLIDERBOX_GAP); + +#if LV_USE_BAR || LV_USE_SLIDER + style_init_reset(&theme->styles.slider_track_size); + lv_style_set_height(&theme->styles.slider_track_size, SLIDER_TRACK_HEIGHT); +#endif + +#if LV_USE_SLIDER + style_init_reset(&theme->styles.slider_knob_room); + lv_style_set_margin_ver(&theme->styles.slider_knob_room, SLIDER_HANDLE_PAD); +#endif + +#if LV_USE_SLIDER + style_init_reset(&theme->styles.slider_track); + lv_style_set_bg_color(&theme->styles.slider_track, SLIDER_TRACK_COLOR); + lv_style_set_bg_opa(&theme->styles.slider_track, LV_OPA_COVER); + lv_style_set_radius(&theme->styles.slider_track, LV_RADIUS_CIRCLE); +#endif + +#if LV_USE_SLIDER + style_init_reset(&theme->styles.slider_active_track); + lv_style_set_bg_color(&theme->styles.slider_active_track, SLIDER_ACTIVE_TRACK_COLOR); + lv_style_set_bg_opa(&theme->styles.slider_active_track, LV_OPA_COVER); + lv_style_set_radius(&theme->styles.slider_active_track, LV_RADIUS_CIRCLE); +#endif + +#if LV_USE_SLIDER + style_init_reset(&theme->styles.slider_handle); + lv_style_set_bg_color(&theme->styles.slider_handle, SLIDER_HANDLE_COLOR); + lv_style_set_bg_opa(&theme->styles.slider_handle, LV_OPA_COVER); + lv_style_set_radius(&theme->styles.slider_handle, LV_RADIUS_CIRCLE); + lv_style_set_pad_all(&theme->styles.slider_handle, SLIDER_HANDLE_PAD); +#endif + +#if LV_USE_BAR + style_init_reset(&theme->styles.progress_track); + lv_style_set_bg_color(&theme->styles.progress_track, PROGRESS_TRACK_COLOR); + lv_style_set_bg_opa(&theme->styles.progress_track, LV_OPA_COVER); + lv_style_set_radius(&theme->styles.progress_track, LV_RADIUS_CIRCLE); +#endif + +#if LV_USE_BAR + style_init_reset(&theme->styles.progress_indicator); + lv_style_set_bg_color(&theme->styles.progress_indicator, PROGRESS_INDICATOR_COLOR); + lv_style_set_bg_opa(&theme->styles.progress_indicator, LV_OPA_COVER); + lv_style_set_radius(&theme->styles.progress_indicator, LV_RADIUS_CIRCLE); +#endif + +#if LV_USE_ARC || LV_USE_SPINNER + style_init_reset(&theme->styles.arc_track); + lv_style_set_arc_color(&theme->styles.arc_track, ARC_TRACK_COLOR); + lv_style_set_arc_width(&theme->styles.arc_track, ARC_WIDTH); + lv_style_set_arc_rounded(&theme->styles.arc_track, true); +#endif + +#if LV_USE_ARC || LV_USE_SPINNER + style_init_reset(&theme->styles.arc_indicator); + lv_style_set_arc_color(&theme->styles.arc_indicator, ARC_INDICATOR_COLOR); + lv_style_set_arc_width(&theme->styles.arc_indicator, ARC_WIDTH); + lv_style_set_arc_rounded(&theme->styles.arc_indicator, true); +#endif + +#if LV_USE_ARC + style_init_reset(&theme->styles.arc_handle); + lv_style_set_bg_color(&theme->styles.arc_handle, ARC_HANDLE_COLOR); + lv_style_set_bg_opa(&theme->styles.arc_handle, LV_OPA_COVER); + lv_style_set_radius(&theme->styles.arc_handle, LV_RADIUS_CIRCLE); + lv_style_set_pad_all(&theme->styles.arc_handle, ARC_HANDLE_PAD); +#endif + +#if LV_USE_DROPDOWN || LV_USE_SPINBOX || LV_USE_TEXTAREA + style_init_reset(&theme->styles.field); + lv_style_set_bg_color(&theme->styles.field, FIELD_COLOR); + lv_style_set_bg_opa(&theme->styles.field, FIELD_OPACITY); + lv_style_set_text_color(&theme->styles.field, FIELD_TEXT_COLOR); + lv_style_set_border_color(&theme->styles.field, FIELD_BORDER_COLOR); + lv_style_set_border_width(&theme->styles.field, FIELD_BORDER_WIDTH); + lv_style_set_radius(&theme->styles.field, FIELD_SHAPE); + lv_style_set_pad_hor(&theme->styles.field, FIELD_PAD_HOR); + lv_style_set_pad_ver(&theme->styles.field, FIELD_PAD_VER); + lv_style_set_pad_gap(&theme->styles.field, FIELD_PAD_VER); +#endif + +#if LV_USE_SPINBOX || LV_USE_TEXTAREA + style_init_reset(&theme->styles.field_focused); + lv_style_set_border_color(&theme->styles.field_focused, FIELD_FOCUSED_BORDER_COLOR); + lv_style_set_border_width(&theme->styles.field_focused, FIELD_FOCUSED_BORDER_WIDTH); +#endif + +#if LV_USE_TEXTAREA + style_init_reset(&theme->styles.field_placeholder); + lv_style_set_text_color(&theme->styles.field_placeholder, FIELD_PLACEHOLDER_COLOR); +#endif + +#if LV_USE_TEXTAREA + style_init_reset(&theme->styles.field_cursor); + lv_style_set_border_color(&theme->styles.field_cursor, FIELD_CURSOR_COLOR); + lv_style_set_border_width(&theme->styles.field_cursor, FIELD_CURSOR_WIDTH); + lv_style_set_border_side(&theme->styles.field_cursor, LV_BORDER_SIDE_LEFT); + lv_style_set_pad_left(&theme->styles.field_cursor, -1); + lv_style_set_anim_duration(&theme->styles.field_cursor, MOTION_DURATION_LONG); +#endif + +#if LV_USE_LABEL && LV_USE_TEXTAREA + style_init_reset(&theme->styles.field_selection); + lv_style_set_bg_color(&theme->styles.field_selection, FIELD_SELECTION_COLOR); + lv_style_set_text_color(&theme->styles.field_selection, FIELD_SELECTION_TEXT_COLOR); +#endif + +#if LV_USE_SPINBOX + style_init_reset(&theme->styles.spinbox_cursor); + lv_style_set_bg_color(&theme->styles.spinbox_cursor, SPINBOX_CURSOR_COLOR); + lv_style_set_bg_opa(&theme->styles.spinbox_cursor, LV_OPA_COVER); + lv_style_set_text_color(&theme->styles.spinbox_cursor, SPINBOX_CURSOR_TEXT_COLOR); +#endif + +#if LV_USE_DROPDOWN + style_init_reset(&theme->styles.menu); + lv_style_set_bg_color(&theme->styles.menu, MENU_COLOR); + lv_style_set_bg_opa(&theme->styles.menu, LV_OPA_COVER); + lv_style_set_text_color(&theme->styles.menu, MENU_TEXT_COLOR); + lv_style_set_radius(&theme->styles.menu, MENU_SHAPE); + lv_style_set_pad_all(&theme->styles.menu, MENU_PAD); + lv_style_set_text_line_space(&theme->styles.menu, MENU_ITEM_LINE_SPACE); + lv_style_set_max_height(&theme->styles.menu, MENU_MAX_HEIGHT); + lv_style_set_clip_corner(&theme->styles.menu, true); +#endif + +#if LV_USE_DROPDOWN + style_init_reset(&theme->styles.menu_item); + lv_style_set_bg_opa(&theme->styles.menu_item, LV_OPA_TRANSP); +#endif + +#if LV_USE_DROPDOWN + style_init_reset(&theme->styles.menu_item_selected); + lv_style_set_bg_color(&theme->styles.menu_item_selected, MENU_ITEM_SELECTED_COLOR); + lv_style_set_bg_opa(&theme->styles.menu_item_selected, LV_OPA_COVER); + lv_style_set_text_color(&theme->styles.menu_item_selected, MENU_ITEM_SELECTED_TEXT_COLOR); +#endif + +#if LV_USE_ROLLER + style_init_reset(&theme->styles.roller); + lv_style_set_bg_color(&theme->styles.roller, ROLLER_COLOR); + lv_style_set_bg_opa(&theme->styles.roller, LV_OPA_COVER); + lv_style_set_text_color(&theme->styles.roller, ROLLER_TEXT_COLOR); + lv_style_set_radius(&theme->styles.roller, SHAPE_CONTAINER); + lv_style_set_text_line_space(&theme->styles.roller, ROLLER_LINE_SPACE); + lv_style_set_text_align(&theme->styles.roller, LV_TEXT_ALIGN_CENTER); + lv_style_set_anim_duration(&theme->styles.roller, MOTION_DURATION_MEDIUM); +#endif + +#if LV_USE_ROLLER + style_init_reset(&theme->styles.roller_selected); + lv_style_set_bg_color(&theme->styles.roller_selected, ROLLER_SELECTED_COLOR); + lv_style_set_bg_opa(&theme->styles.roller_selected, LV_OPA_COVER); + lv_style_set_text_color(&theme->styles.roller_selected, ROLLER_SELECTED_TEXT_COLOR); +#endif + +#if LV_USE_BUTTONMATRIX || LV_USE_KEYBOARD + style_init_reset(&theme->styles.keys); + lv_style_set_bg_color(&theme->styles.keys, KEYS_COLOR); + lv_style_set_bg_opa(&theme->styles.keys, LV_OPA_COVER); + lv_style_set_radius(&theme->styles.keys, SHAPE_CONTAINER); + lv_style_set_pad_all(&theme->styles.keys, KEYS_PAD); + lv_style_set_pad_gap(&theme->styles.keys, KEYS_GAP); +#endif + +#if LV_USE_BUTTONMATRIX || LV_USE_KEYBOARD + style_init_reset(&theme->styles.key); + lv_style_set_bg_color(&theme->styles.key, KEY_COLOR); + lv_style_set_bg_opa(&theme->styles.key, LV_OPA_COVER); + lv_style_set_text_color(&theme->styles.key, KEY_TEXT_COLOR); + lv_style_set_radius(&theme->styles.key, KEY_SHAPE); +#endif + +#if LV_USE_BUTTONMATRIX || LV_USE_KEYBOARD + style_init_reset(&theme->styles.key_pressed); + lv_style_set_bg_color(&theme->styles.key_pressed, lv_color_mix(STATE_LAYER_COLOR, KEY_COLOR, OPACITY_STATE_PRESSED)); +#endif + +#if LV_USE_KEYBOARD + style_init_reset(&theme->styles.key_checked); + lv_style_set_bg_color(&theme->styles.key_checked, KEY_CHECKED_COLOR); + lv_style_set_text_color(&theme->styles.key_checked, KEY_CHECKED_TEXT_COLOR); +#endif + +#if LV_USE_BUTTONMATRIX || LV_USE_KEYBOARD + style_init_reset(&theme->styles.key_selected); + lv_style_set_bg_color(&theme->styles.key_selected, KEY_SELECTED_COLOR); + lv_style_set_text_color(&theme->styles.key_selected, KEY_SELECTED_TEXT_COLOR); +#endif + +#if LV_USE_LIST + style_init_reset(&theme->styles.list); + lv_style_set_bg_color(&theme->styles.list, LIST_COLOR); + lv_style_set_bg_opa(&theme->styles.list, LV_OPA_COVER); + lv_style_set_pad_hor(&theme->styles.list, 0); + lv_style_set_pad_ver(&theme->styles.list, LIST_PAD); + lv_style_set_pad_gap(&theme->styles.list, 0); + lv_style_set_clip_corner(&theme->styles.list, true); +#endif + +#if LV_USE_LIST + style_init_reset(&theme->styles.list_item); + lv_style_set_bg_color(&theme->styles.list_item, LIST_ITEM_COLOR); + lv_style_set_bg_opa(&theme->styles.list_item, LIST_ITEM_OPACITY); + lv_style_set_text_color(&theme->styles.list_item, LIST_ITEM_TEXT_COLOR); + lv_style_set_pad_hor(&theme->styles.list_item, LIST_ITEM_PAD_HOR); + lv_style_set_pad_ver(&theme->styles.list_item, LIST_ITEM_PAD_VER); + lv_style_set_pad_column(&theme->styles.list_item, LIST_ITEM_GAP); + lv_style_set_border_color(&theme->styles.list_item, LIST_DIVIDER_COLOR); + lv_style_set_border_width(&theme->styles.list_item, LIST_DIVIDER_WIDTH); + lv_style_set_border_side(&theme->styles.list_item, LV_BORDER_SIDE_BOTTOM); +#endif + +#if LV_USE_LIST || LV_USE_TABLE + style_init_reset(&theme->styles.list_item_focused); + lv_style_set_bg_color(&theme->styles.list_item_focused, LIST_ITEM_FOCUSED_COLOR); + lv_style_set_bg_opa(&theme->styles.list_item_focused, LV_OPA_COVER); + lv_style_set_text_color(&theme->styles.list_item_focused, LIST_ITEM_FOCUSED_TEXT_COLOR); +#endif + +#if LV_USE_LIST + style_init_reset(&theme->styles.list_subheader); + lv_style_set_text_color(&theme->styles.list_subheader, LIST_SUBHEADER_TEXT_COLOR); + lv_style_set_text_font(&theme->styles.list_subheader, theme->config.font_small); + lv_style_set_pad_hor(&theme->styles.list_subheader, LIST_ITEM_PAD_HOR); + lv_style_set_pad_top(&theme->styles.list_subheader, LIST_ITEM_PAD_VER); + lv_style_set_pad_bottom(&theme->styles.list_subheader, LIST_PAD); +#endif + +#if LV_USE_TABVIEW + style_init_reset(&theme->styles.tabs); + lv_style_set_bg_opa(&theme->styles.tabs, LV_OPA_TRANSP); + lv_style_set_border_color(&theme->styles.tabs, TABS_DIVIDER_COLOR); + lv_style_set_border_width(&theme->styles.tabs, DENSITY_BORDER); + lv_style_set_border_side(&theme->styles.tabs, LV_BORDER_SIDE_BOTTOM); +#endif + +#if LV_USE_BUTTON && LV_USE_TABVIEW + style_init_reset(&theme->styles.tab); + lv_style_set_bg_opa(&theme->styles.tab, LV_OPA_TRANSP); + lv_style_set_text_color(&theme->styles.tab, TAB_LABEL_COLOR); + lv_style_set_radius(&theme->styles.tab, 0); +#endif + +#if LV_USE_BUTTON && LV_USE_TABVIEW + style_init_reset(&theme->styles.tab_selected); + lv_style_set_text_color(&theme->styles.tab_selected, TAB_SELECTED_LABEL_COLOR); + lv_style_set_border_color(&theme->styles.tab_selected, TAB_INDICATOR_COLOR); + lv_style_set_border_width(&theme->styles.tab_selected, TAB_INDICATOR_WIDTH); + lv_style_set_border_side(&theme->styles.tab_selected, LV_BORDER_SIDE_BOTTOM); +#endif + +#if LV_USE_TABVIEW + style_init_reset(&theme->styles.tab_page); + lv_style_set_pad_all(&theme->styles.tab_page, TAB_PAGE_PAD); +#endif + +#if LV_USE_MSGBOX + style_init_reset(&theme->styles.dialog); + lv_style_set_bg_color(&theme->styles.dialog, DIALOG_COLOR); + lv_style_set_bg_opa(&theme->styles.dialog, LV_OPA_COVER); + lv_style_set_text_color(&theme->styles.dialog, DIALOG_TEXT_COLOR); + lv_style_set_radius(&theme->styles.dialog, DIALOG_SHAPE); + lv_style_set_pad_all(&theme->styles.dialog, 0); + lv_style_set_pad_gap(&theme->styles.dialog, 0); + lv_style_set_clip_corner(&theme->styles.dialog, true); +#endif + +#if LV_USE_MSGBOX + style_init_reset(&theme->styles.dialog_header); + lv_style_set_pad_hor(&theme->styles.dialog_header, DIALOG_PAD); + lv_style_set_pad_top(&theme->styles.dialog_header, DIALOG_PAD); + lv_style_set_pad_bottom(&theme->styles.dialog_header, DIALOG_GAP); + lv_style_set_pad_gap(&theme->styles.dialog_header, DIALOG_GAP); +#endif + +#if LV_USE_LABEL && LV_USE_MSGBOX + style_init_reset(&theme->styles.dialog_title); + lv_style_set_text_color(&theme->styles.dialog_title, DIALOG_TITLE_COLOR); + lv_style_set_text_font(&theme->styles.dialog_title, theme->config.font_large); +#endif + +#if LV_USE_MSGBOX + style_init_reset(&theme->styles.dialog_content); + lv_style_set_pad_hor(&theme->styles.dialog_content, DIALOG_PAD); + lv_style_set_pad_ver(&theme->styles.dialog_content, DIALOG_GAP); +#endif + +#if LV_USE_MSGBOX + style_init_reset(&theme->styles.dialog_actions); + lv_style_set_pad_all(&theme->styles.dialog_actions, DIALOG_PAD); + lv_style_set_pad_top(&theme->styles.dialog_actions, DIALOG_GAP); + lv_style_set_pad_gap(&theme->styles.dialog_actions, DIALOG_GAP); +#endif + +#if LV_USE_MSGBOX + style_init_reset(&theme->styles.dialog_scrim); + lv_style_set_bg_color(&theme->styles.dialog_scrim, DIALOG_SCRIM_COLOR); + lv_style_set_bg_opa(&theme->styles.dialog_scrim, DIALOG_SCRIM_OPACITY); +#endif + +#if LV_USE_MENU + style_init_reset(&theme->styles.settings); + lv_style_set_bg_color(&theme->styles.settings, SETTINGS_COLOR); + lv_style_set_bg_opa(&theme->styles.settings, LV_OPA_COVER); + lv_style_set_pad_all(&theme->styles.settings, 0); + lv_style_set_pad_gap(&theme->styles.settings, 0); + lv_style_set_radius(&theme->styles.settings, 0); + lv_style_set_clip_corner(&theme->styles.settings, true); +#endif + +#if LV_USE_MENU + style_init_reset(&theme->styles.settings_sidebar); + lv_style_set_pad_all(&theme->styles.settings_sidebar, 0); + lv_style_set_pad_gap(&theme->styles.settings_sidebar, 0); + lv_style_set_border_color(&theme->styles.settings_sidebar, SETTINGS_DIVIDER_COLOR); + lv_style_set_border_width(&theme->styles.settings_sidebar, DENSITY_BORDER); + lv_style_set_border_side(&theme->styles.settings_sidebar, LV_BORDER_SIDE_RIGHT); +#endif + +#if LV_USE_MENU + style_init_reset(&theme->styles.settings_content); + lv_style_set_pad_all(&theme->styles.settings_content, 0); + lv_style_set_pad_gap(&theme->styles.settings_content, 0); +#endif + +#if LV_USE_MENU + style_init_reset(&theme->styles.settings_page); + lv_style_set_pad_hor(&theme->styles.settings_page, 0); + lv_style_set_pad_gap(&theme->styles.settings_page, 0); +#endif + +#if LV_USE_MENU + style_init_reset(&theme->styles.settings_header); + lv_style_set_pad_hor(&theme->styles.settings_header, SETTINGS_ITEM_PAD_HOR); + lv_style_set_pad_ver(&theme->styles.settings_header, DENSITY_LIST_PAD); + lv_style_set_pad_gap(&theme->styles.settings_header, SETTINGS_ITEM_GAP); +#endif + +#if LV_USE_MENU + style_init_reset(&theme->styles.settings_section); + lv_style_set_bg_color(&theme->styles.settings_section, SETTINGS_SECTION_COLOR); + lv_style_set_bg_opa(&theme->styles.settings_section, LV_OPA_COVER); + lv_style_set_radius(&theme->styles.settings_section, SETTINGS_SECTION_SHAPE); + lv_style_set_clip_corner(&theme->styles.settings_section, true); +#endif + +#if LV_USE_MENU + style_init_reset(&theme->styles.settings_item); + lv_style_set_pad_hor(&theme->styles.settings_item, SETTINGS_ITEM_PAD_HOR); + lv_style_set_pad_ver(&theme->styles.settings_item, SETTINGS_ITEM_PAD_VER); + lv_style_set_pad_gap(&theme->styles.settings_item, SETTINGS_ITEM_GAP); +#endif + +#if LV_USE_MENU + style_init_reset(&theme->styles.settings_item_selected); + lv_style_set_bg_color(&theme->styles.settings_item_selected, SETTINGS_ITEM_SELECTED_COLOR); + lv_style_set_bg_opa(&theme->styles.settings_item_selected, LV_OPA_COVER); + lv_style_set_text_color(&theme->styles.settings_item_selected, SETTINGS_ITEM_SELECTED_TEXT_COLOR); +#endif + +#if LV_USE_MENU + style_init_reset(&theme->styles.settings_separator); + lv_style_set_bg_opa(&theme->styles.settings_separator, LV_OPA_TRANSP); + lv_style_set_pad_ver(&theme->styles.settings_separator, DENSITY_LIST_PAD); +#endif + +#if LV_USE_WIN + style_init_reset(&theme->styles.window_header); + lv_style_set_bg_color(&theme->styles.window_header, WINDOW_HEADER_COLOR); + lv_style_set_bg_opa(&theme->styles.window_header, LV_OPA_COVER); + lv_style_set_pad_all(&theme->styles.window_header, WINDOW_HEADER_PAD); + lv_style_set_pad_gap(&theme->styles.window_header, WINDOW_HEADER_PAD); +#endif + +#if LV_USE_LABEL && LV_USE_WIN + style_init_reset(&theme->styles.window_title); + lv_style_set_text_font(&theme->styles.window_title, theme->config.font_large); +#endif + +#if LV_USE_WIN + style_init_reset(&theme->styles.window_content); + lv_style_set_bg_opa(&theme->styles.window_content, LV_OPA_TRANSP); + lv_style_set_pad_all(&theme->styles.window_content, WINDOW_CONTENT_PAD); + lv_style_set_pad_gap(&theme->styles.window_content, DENSITY_CONTAINER_GAP); +#endif + +#if LV_USE_TABLE + style_init_reset(&theme->styles.table); + lv_style_set_bg_color(&theme->styles.table, TABLE_COLOR); + lv_style_set_bg_opa(&theme->styles.table, LV_OPA_COVER); + lv_style_set_pad_all(&theme->styles.table, 0); + lv_style_set_radius(&theme->styles.table, 0); + lv_style_set_border_color(&theme->styles.table, TABLE_DIVIDER_COLOR); + lv_style_set_border_width(&theme->styles.table, DENSITY_BORDER); +#endif + +#if LV_USE_TABLE + style_init_reset(&theme->styles.table_cell); + lv_style_set_bg_opa(&theme->styles.table_cell, LV_OPA_TRANSP); + lv_style_set_text_color(&theme->styles.table_cell, TABLE_CELL_TEXT_COLOR); + lv_style_set_pad_all(&theme->styles.table_cell, TABLE_CELL_PAD); + lv_style_set_border_color(&theme->styles.table_cell, TABLE_DIVIDER_COLOR); + lv_style_set_border_width(&theme->styles.table_cell, DENSITY_BORDER); + lv_style_set_border_side(&theme->styles.table_cell, LV_BORDER_SIDE_BOTTOM); +#endif + +#if LV_USE_CHART + style_init_reset(&theme->styles.chart); + lv_style_set_bg_color(&theme->styles.chart, CHART_COLOR); + lv_style_set_bg_opa(&theme->styles.chart, LV_OPA_COVER); + lv_style_set_radius(&theme->styles.chart, SHAPE_CONTAINER); + lv_style_set_pad_all(&theme->styles.chart, DENSITY_CONTAINER_PAD); + lv_style_set_line_color(&theme->styles.chart, CHART_GRID_COLOR); + lv_style_set_border_post(&theme->styles.chart, false); +#endif + +#if LV_USE_CHART + style_init_reset(&theme->styles.chart_series); + lv_style_set_line_width(&theme->styles.chart_series, CHART_SERIES_WIDTH); + lv_style_set_size(&theme->styles.chart_series, CHART_POINT_SIZE, CHART_POINT_SIZE); + lv_style_set_radius(&theme->styles.chart_series, CHART_POINT_SIZE); +#endif + +#if LV_USE_CHART + style_init_reset(&theme->styles.chart_indicator); + lv_style_set_bg_color(&theme->styles.chart_indicator, CHART_INDICATOR_COLOR); + lv_style_set_bg_opa(&theme->styles.chart_indicator, LV_OPA_COVER); + lv_style_set_radius(&theme->styles.chart_indicator, LV_RADIUS_CIRCLE); + lv_style_set_size(&theme->styles.chart_indicator, CHART_POINT_SIZE, CHART_POINT_SIZE); +#endif + +#if LV_USE_CALENDAR + style_init_reset(&theme->styles.calendar); + lv_style_set_bg_color(&theme->styles.calendar, CALENDAR_COLOR); + lv_style_set_bg_opa(&theme->styles.calendar, LV_OPA_COVER); + lv_style_set_radius(&theme->styles.calendar, CALENDAR_SHAPE); + lv_style_set_pad_all(&theme->styles.calendar, CALENDAR_PAD); + lv_style_set_pad_gap(&theme->styles.calendar, CALENDAR_PAD); +#endif + +#if LV_USE_BUTTONMATRIX && LV_USE_CALENDAR + style_init_reset(&theme->styles.calendar_days); + lv_style_set_bg_opa(&theme->styles.calendar_days, LV_OPA_TRANSP); + lv_style_set_pad_all(&theme->styles.calendar_days, 0); + lv_style_set_pad_gap(&theme->styles.calendar_days, DENSITY_KEY_GAP); +#endif + +#if LV_USE_BUTTONMATRIX && LV_USE_CALENDAR + style_init_reset(&theme->styles.calendar_day); + lv_style_set_bg_opa(&theme->styles.calendar_day, LV_OPA_TRANSP); + lv_style_set_text_color(&theme->styles.calendar_day, CALENDAR_DAY_TEXT_COLOR); + lv_style_set_radius(&theme->styles.calendar_day, LV_RADIUS_CIRCLE); +#endif + +#if (LV_USE_CALENDAR && LV_USE_CALENDAR_HEADER_ARROW) || (LV_USE_CALENDAR && LV_USE_CALENDAR_HEADER_DROPDOWN) + style_init_reset(&theme->styles.calendar_header); + lv_style_set_pad_all(&theme->styles.calendar_header, CALENDAR_HEADER_PAD); + lv_style_set_pad_gap(&theme->styles.calendar_header, CALENDAR_HEADER_PAD); +#endif + +#if LV_USE_SCALE + style_init_reset(&theme->styles.scale); + lv_style_set_line_color(&theme->styles.scale, SCALE_COLOR); + lv_style_set_line_width(&theme->styles.scale, SCALE_WIDTH); + lv_style_set_arc_color(&theme->styles.scale, SCALE_COLOR); + lv_style_set_arc_width(&theme->styles.scale, SCALE_WIDTH); + lv_style_set_text_color(&theme->styles.scale, SCALE_COLOR); + lv_style_set_length(&theme->styles.scale, SCALE_TICK_LENGTH); +#endif + +#if LV_USE_LINE + style_init_reset(&theme->styles.divider); + lv_style_set_line_color(&theme->styles.divider, DIVIDER_COLOR); + lv_style_set_line_width(&theme->styles.divider, DIVIDER_WIDTH); +#endif + +#if LV_USE_LED + style_init_reset(&theme->styles.led); + lv_style_set_bg_color(&theme->styles.led, lv_color_white()); + lv_style_set_bg_opa(&theme->styles.led, LV_OPA_COVER); + lv_style_set_bg_grad_color(&theme->styles.led, lv_palette_main(LV_PALETTE_GREY)); + lv_style_set_radius(&theme->styles.led, LV_RADIUS_CIRCLE); + lv_style_set_shadow_width(&theme->styles.led, LED_GLOW_WIDTH); + lv_style_set_shadow_color(&theme->styles.led, lv_color_white()); + lv_style_set_shadow_spread(&theme->styles.led, LED_GLOW_SPREAD); +#endif +} + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +void lv_theme_material_config_init(lv_theme_material_config_t * config) +{ + lv_memzero(config, sizeof(*config)); + config->is_dark = false; + config->is_compact = false; + config->animations_enabled = true; + config->color_primary = lv_color_hex(0x00BBFF); + config->color_secondary = lv_color_hex(0xEE81C3); + config->color_error = lv_color_hex(0xB3261E); + config->font_small = LV_FONT_DEFAULT; + config->font_normal = LV_FONT_DEFAULT; + config->font_large = LV_FONT_DEFAULT; +} + +lv_theme_t * lv_theme_material_init(lv_display_t * disp, const lv_theme_material_config_t * config) +{ + if(!lv_theme_material_is_inited()) { + theme_def = lv_malloc_zeroed(sizeof(my_theme_t)); + LV_ASSERT_MALLOC(theme_def); + } + + my_theme_t * theme = theme_def; + + lv_display_t * new_disp = disp == NULL ? lv_display_get_default() : disp; + int32_t new_dpi = lv_display_get_dpi(new_disp); + int32_t hor_res = lv_display_get_horizontal_resolution(new_disp); + int32_t ver_res = lv_display_get_vertical_resolution(new_disp); + int32_t greater_res = LV_MAX(hor_res, ver_res); + disp_size_t new_size; + + if(greater_res <= 320) new_size = DISP_SMALL; + else if(greater_res < 720) new_size = DISP_MEDIUM; + else new_size = DISP_LARGE; + + if(theme->inited && theme->disp_dpi == new_dpi && theme->disp_size == new_size && + config_equals(&theme->config, config)) { + return (lv_theme_t *) theme; + } + + theme->disp_size = new_size; + theme->disp_dpi = new_dpi; + theme->config = *config; + theme->base.disp = new_disp; + theme->base.apply_cb = theme_apply; + theme->base.color_primary = config->color_primary; + theme->base.color_secondary = config->color_secondary; + theme->base.font_small = config->font_small; + theme->base.font_normal = config->font_normal; + theme->base.font_large = config->font_large; + + style_init(theme); + + if(disp == NULL || lv_display_get_theme(disp) == (lv_theme_t *)theme) { + lv_obj_report_style_change(NULL); + } + + theme->inited = true; + + /* Re-initialize the styles when the resolution changes, as the display size affects the styles */ + lv_display_remove_event_cb_with_user_data(new_disp, resolution_change_event_cb, theme); + lv_display_add_event_cb(new_disp, resolution_change_event_cb, LV_EVENT_RESOLUTION_CHANGED, theme); + + return (lv_theme_t *) theme; +} + +bool lv_theme_material_is_inited(void) +{ + return theme_def != NULL && theme_def->inited; +} + +lv_theme_t * lv_theme_material_get(void) +{ + if(!lv_theme_material_is_inited()) { + return NULL; + } + return (lv_theme_t *) theme_def; +} + +void lv_theme_material_deinit(void) +{ + my_theme_t * theme = theme_def; + if(theme) { + if(theme->inited) { + lv_style_t * theme_styles = (lv_style_t *)(&(theme->styles)); + uint32_t i; + for(i = 0; i < sizeof(my_theme_styles_t) / sizeof(lv_style_t); i++) { + lv_style_reset(theme_styles + i); + } + } + lv_free(theme_def); + theme_def = NULL; + } +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void theme_apply(lv_theme_t * th, lv_obj_t * obj) +{ + LV_UNUSED(th); + + my_theme_t * theme = theme_def; + lv_obj_t * parent = lv_obj_get_parent(obj); + LV_UNUSED(theme); + LV_UNUSED(parent); + + /* null > * */ + if(parent == NULL) { + lv_obj_add_style(obj, &theme->styles.screen, 0); + lv_obj_add_style(obj, &theme->styles.scrollbar, LV_PART_SCROLLBAR); + lv_obj_add_style(obj, &theme->styles.scrollbar_scrolled, LV_PART_SCROLLBAR | LV_STATE_SCROLLED); + return; + } + if(lv_obj_check_type(obj, &lv_obj_class)) { +#if LV_USE_TABVIEW + /* tabview > obj[1] */ + if(lv_obj_check_type(parent, &lv_tabview_class) && lss_is_child(parent, 1, obj)) { + return; + } +#endif +#if LV_USE_TABVIEW + /* tabview > obj[0] */ + if(lv_obj_check_type(parent, &lv_tabview_class) && lss_is_child(parent, 0, obj)) { + lv_obj_add_style(obj, &theme->styles.tabs, 0); + return; + } +#endif +#if LV_USE_TABVIEW + /* tabview > * > obj */ + if(lv_obj_check_type(lss_ancestor(obj, 2), &lv_tabview_class)) { + lv_obj_add_style(obj, &theme->styles.tab_page, 0); + lv_obj_add_style(obj, &theme->styles.scrollbar, LV_PART_SCROLLBAR); + lv_obj_add_style(obj, &theme->styles.scrollbar_scrolled, LV_PART_SCROLLBAR | LV_STATE_SCROLLED); + return; + } +#endif +#if LV_USE_WIN + /* win > obj[0] */ + if(lv_obj_check_type(parent, &lv_win_class) && lss_is_child(parent, 0, obj)) { + lv_obj_add_style(obj, &theme->styles.window_header, 0); + return; + } +#endif +#if LV_USE_WIN + /* win > obj[1] */ + if(lv_obj_check_type(parent, &lv_win_class) && lss_is_child(parent, 1, obj)) { + lv_obj_add_style(obj, &theme->styles.window_content, 0); + lv_obj_add_style(obj, &theme->styles.scrollbar, LV_PART_SCROLLBAR); + lv_obj_add_style(obj, &theme->styles.scrollbar_scrolled, LV_PART_SCROLLBAR | LV_STATE_SCROLLED); + return; + } +#endif +#if LV_USE_CALENDAR + /* calendar > obj */ + if(lv_obj_check_type(parent, &lv_calendar_class)) { + return; + } +#endif + /* obj */ + lv_obj_add_style(obj, &theme->styles.container, 0); + lv_obj_add_style(obj, &theme->styles.scrollbar, LV_PART_SCROLLBAR); + lv_obj_add_style(obj, &theme->styles.scrollbar_scrolled, LV_PART_SCROLLBAR | LV_STATE_SCROLLED); + return; + } +#if LV_USE_BUTTON + if(lv_obj_check_type(obj, &lv_button_class)) { +#if LV_USE_TABVIEW + /* tabview > *[0] > button */ + if(lv_obj_check_type(lss_ancestor(obj, 2), &lv_tabview_class) && lss_is_child(lss_ancestor(obj, 2), 0, parent)) { + lv_obj_add_style(obj, &theme->styles.tab, 0); + lv_obj_add_style(obj, &theme->styles.transition_exit, 0); + lv_obj_add_style(obj, &theme->styles.state_pressed, LV_STATE_PRESSED); + lv_obj_add_style(obj, &theme->styles.transition_enter, LV_STATE_PRESSED); + lv_obj_add_style(obj, &theme->styles.tab_selected, LV_STATE_CHECKED); + lv_obj_add_style(obj, &theme->styles.focus_ring, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &theme->styles.edit_ring, LV_STATE_EDITED); + return; + } +#endif +#if LV_USE_MENU + /* menu_sidebar_header_cont > button */ + if(lv_obj_check_type(parent, &lv_menu_sidebar_header_cont_class)) { + lv_obj_add_style(obj, &theme->styles.icon_button, 0); + lv_obj_add_style(obj, &theme->styles.transition_exit, 0); + lv_obj_add_style(obj, &theme->styles.state_pressed, LV_STATE_PRESSED); + lv_obj_add_style(obj, &theme->styles.transition_enter, LV_STATE_PRESSED); + lv_obj_add_style(obj, &theme->styles.focus_ring, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &theme->styles.disabled, LV_STATE_DISABLED); + return; + } +#endif +#if LV_USE_MENU + /* menu_main_header_cont > button */ + if(lv_obj_check_type(parent, &lv_menu_main_header_cont_class)) { + lv_obj_add_style(obj, &theme->styles.icon_button, 0); + lv_obj_add_style(obj, &theme->styles.transition_exit, 0); + lv_obj_add_style(obj, &theme->styles.state_pressed, LV_STATE_PRESSED); + lv_obj_add_style(obj, &theme->styles.transition_enter, LV_STATE_PRESSED); + lv_obj_add_style(obj, &theme->styles.focus_ring, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &theme->styles.disabled, LV_STATE_DISABLED); + return; + } +#endif +#if LV_USE_WIN + /* win > obj[0] > button */ + if(lv_obj_check_type(lss_ancestor(obj, 2), &lv_win_class) && lv_obj_check_type(parent, &lv_obj_class) && lss_is_child(lss_ancestor(obj, 2), 0, parent)) { + lv_obj_add_style(obj, &theme->styles.icon_button, 0); + lv_obj_add_style(obj, &theme->styles.transition_exit, 0); + lv_obj_add_style(obj, &theme->styles.state_pressed, LV_STATE_PRESSED); + lv_obj_add_style(obj, &theme->styles.transition_enter, LV_STATE_PRESSED); + lv_obj_add_style(obj, &theme->styles.focus_ring, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &theme->styles.disabled, LV_STATE_DISABLED); + return; + } +#endif + /* button */ + lv_obj_add_style(obj, &theme->styles.button, 0); + lv_obj_add_style(obj, &theme->styles.transition_exit, 0); + lv_obj_add_style(obj, &theme->styles.button_pressed, LV_STATE_PRESSED); + lv_obj_add_style(obj, &theme->styles.transition_enter, LV_STATE_PRESSED); + lv_obj_add_style(obj, &theme->styles.button_checked, LV_STATE_CHECKED); + lv_obj_add_style(obj, &theme->styles.button_checked_pressed, LV_STATE_CHECKED | LV_STATE_PRESSED); + lv_obj_add_style(obj, &theme->styles.focus_ring, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &theme->styles.edit_ring, LV_STATE_EDITED); + lv_obj_add_style(obj, &theme->styles.button_disabled, LV_STATE_DISABLED); + return; + } +#endif + if(lv_obj_check_type(obj, &lvgl_icon_button_class)) { + /* toolbar > icon_button */ + if(lv_obj_check_type(parent, &lvgl_toolbar_class)) { + lv_obj_add_style(obj, &theme->styles.icon_button, 0); + lv_obj_add_style(obj, &theme->styles.app_bar_button, 0); + lv_obj_add_style(obj, &theme->styles.transition_exit, 0); + lv_obj_add_style(obj, &theme->styles.app_bar_button_pressed, LV_STATE_PRESSED); + lv_obj_add_style(obj, &theme->styles.transition_enter, LV_STATE_PRESSED); + lv_obj_add_style(obj, &theme->styles.app_bar_focus_ring, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &theme->styles.app_bar_focus_ring, LV_STATE_EDITED); + lv_obj_add_style(obj, &theme->styles.disabled, LV_STATE_DISABLED); + return; + } + /* icon_button */ + lv_obj_add_style(obj, &theme->styles.icon_button, 0); + lv_obj_add_style(obj, &theme->styles.transition_exit, 0); + lv_obj_add_style(obj, &theme->styles.state_pressed, LV_STATE_PRESSED); + lv_obj_add_style(obj, &theme->styles.transition_enter, LV_STATE_PRESSED); + lv_obj_add_style(obj, &theme->styles.focus_ring_inset, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &theme->styles.edit_ring_inset, LV_STATE_EDITED); + lv_obj_add_style(obj, &theme->styles.disabled, LV_STATE_DISABLED); + return; + } +#if LV_USE_DROPDOWN + if(lv_obj_check_type(obj, &lv_dropdown_class)) { + /* toolbar > dropdown */ + if(lv_obj_check_type(parent, &lvgl_toolbar_class)) { + lv_obj_add_style(obj, &theme->styles.field, 0); + lv_obj_add_style(obj, &theme->styles.transition_exit, 0); + lv_obj_add_style(obj, &theme->styles.state_pressed, LV_STATE_PRESSED); + lv_obj_add_style(obj, &theme->styles.transition_enter, LV_STATE_PRESSED); + lv_obj_add_style(obj, &theme->styles.focus_ring, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &theme->styles.edit_ring, LV_STATE_EDITED); + lv_obj_add_style(obj, &theme->styles.disabled, LV_STATE_DISABLED); + lv_obj_add_style(obj, &theme->styles.app_bar_field, 0); + lv_obj_add_style(obj, &theme->styles.app_bar_focus_ring, LV_STATE_FOCUS_KEY); + return; + } + /* dropdown */ + lv_obj_add_style(obj, &theme->styles.field, 0); + lv_obj_add_style(obj, &theme->styles.transition_exit, 0); + lv_obj_add_style(obj, &theme->styles.state_pressed, LV_STATE_PRESSED); + lv_obj_add_style(obj, &theme->styles.transition_enter, LV_STATE_PRESSED); + lv_obj_add_style(obj, &theme->styles.focus_ring, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &theme->styles.edit_ring, LV_STATE_EDITED); + lv_obj_add_style(obj, &theme->styles.disabled, LV_STATE_DISABLED); + return; + } +#endif + if(lv_obj_check_type(obj, &lvgl_toolbar_class)) { + /* toolbar */ + lv_obj_add_style(obj, &theme->styles.app_bar, 0); + return; + } + if(lv_obj_check_type(obj, &lvgl_icon_button_filled_class)) { + /* icon_button_filled */ + lv_obj_add_style(obj, &theme->styles.icon_button_filled, 0); + lv_obj_add_style(obj, &theme->styles.transition_exit, 0); + lv_obj_add_style(obj, &theme->styles.icon_button_filled_pressed, LV_STATE_PRESSED); + lv_obj_add_style(obj, &theme->styles.transition_enter, LV_STATE_PRESSED); + lv_obj_add_style(obj, &theme->styles.focus_ring, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &theme->styles.edit_ring, LV_STATE_EDITED); + lv_obj_add_style(obj, &theme->styles.button_disabled, LV_STATE_DISABLED); + return; + } + if(lv_obj_check_type(obj, &lvgl_icon_button_tonal_class)) { + /* icon_button_tonal */ + lv_obj_add_style(obj, &theme->styles.icon_button_tonal, 0); + lv_obj_add_style(obj, &theme->styles.transition_exit, 0); + lv_obj_add_style(obj, &theme->styles.icon_button_tonal_pressed, LV_STATE_PRESSED); + lv_obj_add_style(obj, &theme->styles.transition_enter, LV_STATE_PRESSED); + lv_obj_add_style(obj, &theme->styles.focus_ring, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &theme->styles.edit_ring, LV_STATE_EDITED); + lv_obj_add_style(obj, &theme->styles.button_disabled, LV_STATE_DISABLED); + return; + } +#if LV_USE_MSGBOX + if(lv_obj_check_type(obj, &lv_msgbox_header_button_class)) { + /* msgbox_header_button */ + lv_obj_add_style(obj, &theme->styles.icon_button, 0); + lv_obj_add_style(obj, &theme->styles.transition_exit, 0); + lv_obj_add_style(obj, &theme->styles.state_pressed, LV_STATE_PRESSED); + lv_obj_add_style(obj, &theme->styles.transition_enter, LV_STATE_PRESSED); + lv_obj_add_style(obj, &theme->styles.focus_ring, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &theme->styles.disabled, LV_STATE_DISABLED); + return; + } +#endif +#if LV_USE_MSGBOX + if(lv_obj_check_type(obj, &lv_msgbox_footer_button_class)) { + /* msgbox_footer_button */ + lv_obj_add_style(obj, &theme->styles.text_button, 0); + lv_obj_add_style(obj, &theme->styles.transition_exit, 0); + lv_obj_add_style(obj, &theme->styles.text_button_pressed, LV_STATE_PRESSED); + lv_obj_add_style(obj, &theme->styles.transition_enter, LV_STATE_PRESSED); + lv_obj_add_style(obj, &theme->styles.focus_ring, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &theme->styles.disabled, LV_STATE_DISABLED); + return; + } +#endif +#if LV_USE_LABEL + if(lv_obj_check_type(obj, &lv_label_class)) { +#if LV_USE_MSGBOX + /* msgbox_header > label */ + if(lv_obj_check_type(parent, &lv_msgbox_header_class)) { + lv_obj_add_style(obj, &theme->styles.dialog_title, 0); + return; + } +#endif +#if LV_USE_WIN + /* win > obj[0] > label */ + if(lv_obj_check_type(lss_ancestor(obj, 2), &lv_win_class) && lv_obj_check_type(parent, &lv_obj_class) && lss_is_child(lss_ancestor(obj, 2), 0, parent)) { + lv_obj_add_style(obj, &theme->styles.window_title, 0); + return; + } +#endif +#if LV_USE_TEXTAREA + /* textarea > label */ + if(lv_obj_check_type(parent, &lv_textarea_class)) { + lv_obj_add_style(obj, &theme->styles.field_selection, LV_PART_SELECTED); + return; + } +#endif + /* page_indicator > label */ + if(lv_obj_check_type(parent, &lvgl_page_indicator_class)) { + lv_obj_add_style(obj, &theme->styles.page_indicator_label, 0); + return; + } + return; + } +#endif +#if LV_USE_SWITCH + if(lv_obj_check_type(obj, &lv_switch_class)) { + /* switch */ + lv_obj_add_style(obj, &theme->styles.switch_track, 0); + lv_obj_add_style(obj, &theme->styles.switch_track_selected, LV_STATE_CHECKED); + lv_obj_add_style(obj, &theme->styles.switch_indicator_selected, LV_PART_INDICATOR | LV_STATE_CHECKED); + lv_obj_add_style(obj, &theme->styles.switch_handle, LV_PART_KNOB); + lv_obj_add_style(obj, &theme->styles.switch_handle_selected, LV_PART_KNOB | LV_STATE_CHECKED); + lv_obj_add_style(obj, &theme->styles.switch_handle_pressed, LV_PART_KNOB | LV_STATE_PRESSED); + lv_obj_add_style(obj, &theme->styles.focus_ring, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &theme->styles.edit_ring, LV_STATE_EDITED); + lv_obj_add_style(obj, &theme->styles.disabled, LV_STATE_DISABLED); + return; + } +#endif +#if LV_USE_CHECKBOX + if(lv_obj_check_type(obj, &lv_checkbox_class)) { + /* checkbox */ + lv_obj_add_style(obj, &theme->styles.checkbox, 0); + lv_obj_add_style(obj, &theme->styles.focus_ring, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &theme->styles.checkbox_box, LV_PART_INDICATOR); + lv_obj_add_style(obj, &theme->styles.checkbox_box_selected, LV_PART_INDICATOR | LV_STATE_CHECKED); + lv_obj_add_style(obj, &theme->styles.transition_exit, LV_PART_INDICATOR); + lv_obj_add_style(obj, &theme->styles.transition_enter, LV_PART_INDICATOR | LV_STATE_PRESSED); + lv_obj_add_style(obj, &theme->styles.disabled, LV_STATE_DISABLED); + return; + } +#endif + if(lv_obj_check_type(obj, &lvgl_page_indicator_class)) { + /* page_indicator */ + lv_obj_add_style(obj, &theme->styles.page_indicator, 0); + return; + } + if(lv_obj_check_type(obj, &lvgl_page_indicator_dot_class)) { + /* page_indicator_dot */ + lv_obj_add_style(obj, &theme->styles.page_indicator_dot, 0); + lv_obj_add_style(obj, &theme->styles.page_indicator_dot_current, LV_STATE_CHECKED); + return; + } + if(lv_obj_check_type(obj, &lvgl_badge_class)) { + /* badge */ + lv_obj_add_style(obj, &theme->styles.badge, 0); + return; + } +#if LV_USE_IMAGE + if(lv_obj_check_type(obj, &lv_image_class)) { + /* image */ + lv_obj_add_style(obj, &theme->styles.focus_ring_inset, LV_STATE_FOCUS_KEY); + return; + } +#endif + if(lv_obj_check_type(obj, &lvgl_chip_class)) { + /* chip */ + lv_obj_add_style(obj, &theme->styles.chip, 0); + lv_obj_add_style(obj, &theme->styles.transition_exit, 0); + lv_obj_add_style(obj, &theme->styles.state_pressed, LV_STATE_PRESSED); + lv_obj_add_style(obj, &theme->styles.transition_enter, LV_STATE_PRESSED); + lv_obj_add_style(obj, &theme->styles.chip_selected, LV_STATE_CHECKED); + lv_obj_add_style(obj, &theme->styles.focus_ring, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &theme->styles.edit_ring, LV_STATE_EDITED); + lv_obj_add_style(obj, &theme->styles.disabled, LV_STATE_DISABLED); + return; + } + if(lv_obj_check_type(obj, &lvgl_card_class)) { + /* card */ + lv_obj_add_style(obj, &theme->styles.card, 0); + lv_obj_add_style(obj, &theme->styles.scrollbar, LV_PART_SCROLLBAR); + return; + } + if(lv_obj_check_type(obj, &lvgl_sliderbox_class)) { + /* sliderbox */ + lv_obj_add_style(obj, &theme->styles.sliderbox, 0); + return; + } +#if LV_USE_SLIDER + if(lv_obj_check_type(obj, &lv_slider_class)) { + /* slider */ + lv_obj_add_style(obj, &theme->styles.slider_track, 0); + lv_obj_add_style(obj, &theme->styles.slider_track_size, 0); + lv_obj_add_style(obj, &theme->styles.slider_knob_room, 0); + lv_obj_add_style(obj, &theme->styles.focus_ring, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &theme->styles.edit_ring, LV_STATE_EDITED); + lv_obj_add_style(obj, &theme->styles.slider_active_track, LV_PART_INDICATOR); + lv_obj_add_style(obj, &theme->styles.slider_handle, LV_PART_KNOB); + lv_obj_add_style(obj, &theme->styles.disabled, LV_STATE_DISABLED); + return; + } +#endif +#if LV_USE_BAR + if(lv_obj_check_type(obj, &lv_bar_class)) { + /* bar */ + lv_obj_add_style(obj, &theme->styles.slider_track_size, 0); + lv_obj_add_style(obj, &theme->styles.progress_track, 0); + lv_obj_add_style(obj, &theme->styles.progress_indicator, LV_PART_INDICATOR); + lv_obj_add_style(obj, &theme->styles.focus_ring, LV_STATE_FOCUS_KEY); + return; + } +#endif +#if LV_USE_ARC + if(lv_obj_check_type(obj, &lv_arc_class)) { + /* arc */ + lv_obj_add_style(obj, &theme->styles.arc_track, 0); + lv_obj_add_style(obj, &theme->styles.arc_indicator, LV_PART_INDICATOR); + lv_obj_add_style(obj, &theme->styles.arc_handle, LV_PART_KNOB); + lv_obj_add_style(obj, &theme->styles.focus_ring, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &theme->styles.edit_ring, LV_STATE_EDITED); + return; + } +#endif +#if LV_USE_SPINNER + if(lv_obj_check_type(obj, &lv_spinner_class)) { + /* spinner */ + lv_obj_add_style(obj, &theme->styles.arc_track, 0); + lv_obj_add_style(obj, &theme->styles.arc_indicator, LV_PART_INDICATOR); + return; + } +#endif +#if LV_USE_TEXTAREA + if(lv_obj_check_type(obj, &lv_textarea_class)) { + /* textarea */ + lv_obj_add_style(obj, &theme->styles.field, 0); + lv_obj_add_style(obj, &theme->styles.field_focused, LV_STATE_FOCUSED); + lv_obj_add_style(obj, &theme->styles.focus_ring, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &theme->styles.edit_ring, LV_STATE_EDITED); + lv_obj_add_style(obj, &theme->styles.disabled, LV_STATE_DISABLED); + lv_obj_add_style(obj, &theme->styles.scrollbar, LV_PART_SCROLLBAR); + lv_obj_add_style(obj, &theme->styles.scrollbar_scrolled, LV_PART_SCROLLBAR | LV_STATE_SCROLLED); + lv_obj_add_style(obj, &theme->styles.field_cursor, LV_PART_CURSOR | LV_STATE_FOCUSED); + lv_obj_add_style(obj, &theme->styles.field_placeholder, LV_PART_TEXTAREA_PLACEHOLDER); + return; + } +#endif +#if LV_USE_SPINBOX + if(lv_obj_check_type(obj, &lv_spinbox_class)) { + /* spinbox */ + lv_obj_add_style(obj, &theme->styles.field, 0); + lv_obj_add_style(obj, &theme->styles.field_focused, LV_STATE_FOCUSED); + lv_obj_add_style(obj, &theme->styles.focus_ring, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &theme->styles.edit_ring, LV_STATE_EDITED); + lv_obj_add_style(obj, &theme->styles.spinbox_cursor, LV_PART_CURSOR); + return; + } +#endif +#if LV_USE_DROPDOWN + if(lv_obj_check_type(obj, &lv_dropdownlist_class)) { + /* dropdownlist */ + lv_obj_add_style(obj, &theme->styles.menu, 0); + lv_obj_add_style(obj, &theme->styles.scrollbar, LV_PART_SCROLLBAR); + lv_obj_add_style(obj, &theme->styles.scrollbar_scrolled, LV_PART_SCROLLBAR | LV_STATE_SCROLLED); + lv_obj_add_style(obj, &theme->styles.menu_item, LV_PART_SELECTED); + lv_obj_add_style(obj, &theme->styles.menu_item_selected, LV_PART_SELECTED | LV_STATE_CHECKED); + lv_obj_add_style(obj, &theme->styles.state_pressed, LV_PART_SELECTED | LV_STATE_PRESSED); + return; + } +#endif +#if LV_USE_ROLLER + if(lv_obj_check_type(obj, &lv_roller_class)) { + /* roller */ + lv_obj_add_style(obj, &theme->styles.roller, 0); + lv_obj_add_style(obj, &theme->styles.roller_selected, LV_PART_SELECTED); + lv_obj_add_style(obj, &theme->styles.focus_ring, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &theme->styles.edit_ring, LV_STATE_EDITED); + return; + } +#endif +#if LV_USE_BUTTONMATRIX + if(lv_obj_check_type(obj, &lv_buttonmatrix_class)) { +#if LV_USE_CALENDAR + /* calendar > buttonmatrix */ + if(lv_obj_check_type(parent, &lv_calendar_class)) { + lv_obj_add_style(obj, &theme->styles.calendar_days, 0); + lv_obj_add_style(obj, &theme->styles.calendar_day, LV_PART_ITEMS); + lv_obj_add_style(obj, &theme->styles.state_pressed, LV_PART_ITEMS | LV_STATE_PRESSED); + lv_obj_add_style(obj, &theme->styles.key_selected, LV_PART_ITEMS | LV_STATE_CHECKED); + lv_obj_add_style(obj, &theme->styles.focus_ring, LV_PART_ITEMS | LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &theme->styles.edit_ring, LV_PART_ITEMS | LV_STATE_EDITED); + lv_obj_add_style(obj, &theme->styles.disabled, LV_PART_ITEMS | LV_STATE_DISABLED); + return; + } +#endif + /* buttonmatrix */ + lv_obj_add_style(obj, &theme->styles.keys, 0); + lv_obj_add_style(obj, &theme->styles.focus_ring, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &theme->styles.edit_ring, LV_STATE_EDITED); + lv_obj_add_style(obj, &theme->styles.key, LV_PART_ITEMS); + lv_obj_add_style(obj, &theme->styles.key_pressed, LV_PART_ITEMS | LV_STATE_PRESSED); + lv_obj_add_style(obj, &theme->styles.key_selected, LV_PART_ITEMS | LV_STATE_CHECKED); + lv_obj_add_style(obj, &theme->styles.focus_ring, LV_PART_ITEMS | LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &theme->styles.disabled, LV_PART_ITEMS | LV_STATE_DISABLED); + return; + } +#endif +#if LV_USE_KEYBOARD + if(lv_obj_check_type(obj, &lv_keyboard_class)) { + /* keyboard */ + lv_obj_add_style(obj, &theme->styles.keys, 0); + lv_obj_add_style(obj, &theme->styles.focus_ring, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &theme->styles.edit_ring, LV_STATE_EDITED); + lv_obj_add_style(obj, &theme->styles.key, LV_PART_ITEMS); + lv_obj_add_style(obj, &theme->styles.key_pressed, LV_PART_ITEMS | LV_STATE_PRESSED); + lv_obj_add_style(obj, &theme->styles.key_checked, LV_PART_ITEMS | LV_STATE_CHECKED); + lv_obj_add_style(obj, &theme->styles.key_selected, LV_PART_ITEMS | LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &theme->styles.disabled, LV_PART_ITEMS | LV_STATE_DISABLED); + return; + } +#endif +#if LV_USE_LIST + if(lv_obj_check_type(obj, &lv_list_class)) { + /* list */ + lv_obj_add_style(obj, &theme->styles.list, 0); + lv_obj_add_style(obj, &theme->styles.scrollbar, LV_PART_SCROLLBAR); + lv_obj_add_style(obj, &theme->styles.scrollbar_scrolled, LV_PART_SCROLLBAR | LV_STATE_SCROLLED); + return; + } +#endif +#if LV_USE_LIST + if(lv_obj_check_type(obj, &lv_list_text_class)) { + /* list_text */ + lv_obj_add_style(obj, &theme->styles.list_subheader, 0); + return; + } +#endif +#if LV_USE_LIST + if(lv_obj_check_type(obj, &lv_list_button_class)) { + /* list_button */ + lv_obj_add_style(obj, &theme->styles.list_item, 0); + lv_obj_add_style(obj, &theme->styles.transition_exit, 0); + lv_obj_add_style(obj, &theme->styles.state_pressed, LV_STATE_PRESSED); + lv_obj_add_style(obj, &theme->styles.transition_enter, LV_STATE_PRESSED); + lv_obj_add_style(obj, &theme->styles.list_item_focused, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &theme->styles.disabled, LV_STATE_DISABLED); + return; + } +#endif +#if LV_USE_TABLE + if(lv_obj_check_type(obj, &lv_table_class)) { + /* table */ + lv_obj_add_style(obj, &theme->styles.table, 0); + lv_obj_add_style(obj, &theme->styles.focus_ring, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &theme->styles.edit_ring, LV_STATE_EDITED); + lv_obj_add_style(obj, &theme->styles.scrollbar, LV_PART_SCROLLBAR); + lv_obj_add_style(obj, &theme->styles.scrollbar_scrolled, LV_PART_SCROLLBAR | LV_STATE_SCROLLED); + lv_obj_add_style(obj, &theme->styles.table_cell, LV_PART_ITEMS); + lv_obj_add_style(obj, &theme->styles.state_pressed, LV_PART_ITEMS | LV_STATE_PRESSED); + lv_obj_add_style(obj, &theme->styles.list_item_focused, LV_PART_ITEMS | LV_STATE_FOCUS_KEY); + return; + } +#endif +#if LV_USE_MENU + if(lv_obj_check_type(obj, &lv_menu_class)) { + /* menu */ + lv_obj_add_style(obj, &theme->styles.settings, 0); + return; + } +#endif +#if LV_USE_MENU + if(lv_obj_check_type(obj, &lv_menu_sidebar_cont_class)) { + /* menu_sidebar_cont */ + lv_obj_add_style(obj, &theme->styles.settings_sidebar, 0); + lv_obj_add_style(obj, &theme->styles.scrollbar, LV_PART_SCROLLBAR); + lv_obj_add_style(obj, &theme->styles.scrollbar_scrolled, LV_PART_SCROLLBAR | LV_STATE_SCROLLED); + return; + } +#endif +#if LV_USE_MENU + if(lv_obj_check_type(obj, &lv_menu_main_cont_class)) { + /* menu_main_cont */ + lv_obj_add_style(obj, &theme->styles.settings_content, 0); + lv_obj_add_style(obj, &theme->styles.scrollbar, LV_PART_SCROLLBAR); + lv_obj_add_style(obj, &theme->styles.scrollbar_scrolled, LV_PART_SCROLLBAR | LV_STATE_SCROLLED); + return; + } +#endif +#if LV_USE_MENU + if(lv_obj_check_type(obj, &lv_menu_page_class)) { + /* menu_page */ + lv_obj_add_style(obj, &theme->styles.settings_page, 0); + lv_obj_add_style(obj, &theme->styles.scrollbar, LV_PART_SCROLLBAR); + lv_obj_add_style(obj, &theme->styles.scrollbar_scrolled, LV_PART_SCROLLBAR | LV_STATE_SCROLLED); + return; + } +#endif +#if LV_USE_MENU + if(lv_obj_check_type(obj, &lv_menu_sidebar_header_cont_class)) { + /* menu_sidebar_header_cont */ + lv_obj_add_style(obj, &theme->styles.settings_header, 0); + return; + } +#endif +#if LV_USE_MENU + if(lv_obj_check_type(obj, &lv_menu_main_header_cont_class)) { + /* menu_main_header_cont */ + lv_obj_add_style(obj, &theme->styles.settings_header, 0); + return; + } +#endif +#if LV_USE_MENU + if(lv_obj_check_type(obj, &lv_menu_section_class)) { + /* menu_section */ + lv_obj_add_style(obj, &theme->styles.settings_section, 0); + return; + } +#endif +#if LV_USE_MENU + if(lv_obj_check_type(obj, &lv_menu_cont_class)) { + /* menu_cont */ + lv_obj_add_style(obj, &theme->styles.settings_item, 0); + lv_obj_add_style(obj, &theme->styles.state_pressed, LV_STATE_PRESSED); + lv_obj_add_style(obj, &theme->styles.settings_item_selected, LV_STATE_CHECKED); + lv_obj_add_style(obj, &theme->styles.settings_item_selected, LV_STATE_FOCUS_KEY); + return; + } +#endif +#if LV_USE_MENU + if(lv_obj_check_type(obj, &lv_menu_separator_class)) { + /* menu_separator */ + lv_obj_add_style(obj, &theme->styles.settings_separator, 0); + return; + } +#endif +#if LV_USE_MSGBOX + if(lv_obj_check_type(obj, &lv_msgbox_class)) { + /* msgbox */ + lv_obj_add_style(obj, &theme->styles.dialog, 0); + return; + } +#endif +#if LV_USE_MSGBOX + if(lv_obj_check_type(obj, &lv_msgbox_backdrop_class)) { + /* msgbox_backdrop */ + lv_obj_add_style(obj, &theme->styles.dialog_scrim, 0); + return; + } +#endif +#if LV_USE_MSGBOX + if(lv_obj_check_type(obj, &lv_msgbox_header_class)) { + /* msgbox_header */ + lv_obj_add_style(obj, &theme->styles.dialog_header, 0); + return; + } +#endif +#if LV_USE_MSGBOX + if(lv_obj_check_type(obj, &lv_msgbox_content_class)) { + /* msgbox_content */ + lv_obj_add_style(obj, &theme->styles.dialog_content, 0); + lv_obj_add_style(obj, &theme->styles.scrollbar, LV_PART_SCROLLBAR); + lv_obj_add_style(obj, &theme->styles.scrollbar_scrolled, LV_PART_SCROLLBAR | LV_STATE_SCROLLED); + return; + } +#endif +#if LV_USE_MSGBOX + if(lv_obj_check_type(obj, &lv_msgbox_footer_class)) { + /* msgbox_footer */ + lv_obj_add_style(obj, &theme->styles.dialog_actions, 0); + return; + } +#endif +#if LV_USE_TABVIEW + if(lv_obj_check_type(obj, &lv_tabview_class)) { + /* tabview */ + lv_obj_add_style(obj, &theme->styles.page, 0); + return; + } +#endif +#if LV_USE_WIN + if(lv_obj_check_type(obj, &lv_win_class)) { + /* win */ + lv_obj_add_style(obj, &theme->styles.page, 0); + return; + } +#endif +#if LV_USE_TILEVIEW + if(lv_obj_check_type(obj, &lv_tileview_class)) { + /* tileview */ + lv_obj_add_style(obj, &theme->styles.page, 0); + lv_obj_add_style(obj, &theme->styles.scrollbar, LV_PART_SCROLLBAR); + lv_obj_add_style(obj, &theme->styles.scrollbar_scrolled, LV_PART_SCROLLBAR | LV_STATE_SCROLLED); + return; + } +#endif +#if LV_USE_TILEVIEW + if(lv_obj_check_type(obj, &lv_tileview_tile_class)) { + /* tileview_tile */ + lv_obj_add_style(obj, &theme->styles.scrollbar, LV_PART_SCROLLBAR); + lv_obj_add_style(obj, &theme->styles.scrollbar_scrolled, LV_PART_SCROLLBAR | LV_STATE_SCROLLED); + return; + } +#endif +#if LV_USE_CHART + if(lv_obj_check_type(obj, &lv_chart_class)) { + /* chart */ + lv_obj_add_style(obj, &theme->styles.chart, 0); + lv_obj_add_style(obj, &theme->styles.scrollbar, LV_PART_SCROLLBAR); + lv_obj_add_style(obj, &theme->styles.scrollbar_scrolled, LV_PART_SCROLLBAR | LV_STATE_SCROLLED); + lv_obj_add_style(obj, &theme->styles.chart_series, LV_PART_ITEMS); + lv_obj_add_style(obj, &theme->styles.chart_indicator, LV_PART_INDICATOR); + lv_obj_add_style(obj, &theme->styles.chart_series, LV_PART_CURSOR); + return; + } +#endif +#if LV_USE_CALENDAR + if(lv_obj_check_type(obj, &lv_calendar_class)) { + /* calendar */ + lv_obj_add_style(obj, &theme->styles.calendar, 0); + return; + } +#endif +#if LV_USE_CALENDAR && LV_USE_CALENDAR_HEADER_ARROW + if(lv_obj_check_type(obj, &lv_calendar_header_arrow_class)) { + /* calendar_header_arrow */ + lv_obj_add_style(obj, &theme->styles.calendar_header, 0); + return; + } +#endif +#if LV_USE_CALENDAR && LV_USE_CALENDAR_HEADER_DROPDOWN + if(lv_obj_check_type(obj, &lv_calendar_header_dropdown_class)) { + /* calendar_header_dropdown */ + lv_obj_add_style(obj, &theme->styles.calendar_header, 0); + return; + } +#endif +#if LV_USE_SCALE + if(lv_obj_check_type(obj, &lv_scale_class)) { + /* scale */ + lv_obj_add_style(obj, &theme->styles.scale, LV_PART_MAIN); + lv_obj_add_style(obj, &theme->styles.scale, LV_PART_INDICATOR); + lv_obj_add_style(obj, &theme->styles.scale, LV_PART_ITEMS); + return; + } +#endif +#if LV_USE_LINE + if(lv_obj_check_type(obj, &lv_line_class)) { + /* line */ + lv_obj_add_style(obj, &theme->styles.divider, 0); + return; + } +#endif +#if LV_USE_LED + if(lv_obj_check_type(obj, &lv_led_class)) { + /* led */ + lv_obj_add_style(obj, &theme->styles.led, 0); + return; + } +#endif +} + +static void style_init_reset(lv_style_t * style) +{ + if(lv_theme_material_is_inited()) { + lv_style_reset(style); + } + else { + lv_style_init(style); + } +} + +static void resolution_change_event_cb(lv_event_t * e) +{ + lv_display_t * disp = lv_event_get_target(e); + my_theme_t * theme = lv_event_get_user_data(e); + lv_theme_material_init(disp, &theme->config); +} diff --git a/Modules/lvgl-module/source/themes/lv_theme_material.h b/Modules/lvgl-module/source/themes/lv_theme_material.h new file mode 100644 index 000000000..42aa69f78 --- /dev/null +++ b/Modules/lvgl-module/source/themes/lv_theme_material.h @@ -0,0 +1,59 @@ +/* Generated by LSS. Do not edit. */ + +#ifndef LV_THEME_MATERIAL_H +#define LV_THEME_MATERIAL_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + bool is_dark; + bool is_compact; + bool animations_enabled; + lv_color_t color_primary; + lv_color_t color_secondary; + lv_color_t color_error; + const lv_font_t * font_small; + const lv_font_t * font_normal; + const lv_font_t * font_large; +} lv_theme_material_config_t; + +/** + * Reset a config to its default values + * @param config the config to initialize + */ +void lv_theme_material_config_init(lv_theme_material_config_t * config); + +/** + * Initialize the theme + * @param disp pointer to display (NULL for the default display) + * @param config the theme config, which is copied + * @return a pointer to reference this theme later + */ +lv_theme_t * lv_theme_material_init(lv_display_t * disp, const lv_theme_material_config_t * config); + +/** + * Check if the theme is initialized + * @return true if initialized, false otherwise + */ +bool lv_theme_material_is_inited(void); + +/** + * Get the theme + * @return a pointer to the theme, or NULL when not initialized + */ +lv_theme_t * lv_theme_material_get(void); + +/** + * Deinitialize the theme + */ +void lv_theme_material_deinit(void); + +#ifdef __cplusplus +} +#endif + +#endif /* LV_THEME_MATERIAL_H */ diff --git a/Modules/lvgl-module/source/themes/lv_theme_material_mono.c b/Modules/lvgl-module/source/themes/lv_theme_material_mono.c new file mode 100644 index 000000000..fcd53c2ce --- /dev/null +++ b/Modules/lvgl-module/source/themes/lv_theme_material_mono.c @@ -0,0 +1,1874 @@ +/* Generated by LSS. Do not edit. */ + +#define LV_USE_PRIVATE_API 1 /* For lv_theme_t */ + +#include "lv_theme_material_mono.h" + +/********************* + * DEFINES + *********************/ + +extern const lv_obj_class_t lvgl_toolbar_class; +extern const lv_obj_class_t lvgl_icon_button_class; +extern const lv_obj_class_t lvgl_icon_button_filled_class; +extern const lv_obj_class_t lvgl_icon_button_tonal_class; +extern const lv_obj_class_t lvgl_sliderbox_class; +extern const lv_obj_class_t lvgl_card_class; +extern const lv_obj_class_t lvgl_chip_class; +extern const lv_obj_class_t lvgl_badge_class; +extern const lv_obj_class_t lvgl_page_indicator_class; +extern const lv_obj_class_t lvgl_page_indicator_dot_class; + +#define BLACK lv_color_black() +#define WHITE lv_color_white() +#define CORNER_NONE 0 +#define CORNER_SMALL (LV_DPX_CALC(theme->disp_dpi, 4)) +#define CORNER_MEDIUM (LV_DPX_CALC(theme->disp_dpi, 8)) +#define CORNER_LARGE (LV_DPX_CALC(theme->disp_dpi, 12)) +#define CORNER_FULL LV_RADIUS_CIRCLE +#define CORNER_COMPACT 2 +#define SPACE_1 (LV_DPX_CALC(theme->disp_dpi, 2)) +#define SPACE_2 (LV_DPX_CALC(theme->disp_dpi, 4)) +#define SPACE_3 (LV_DPX_CALC(theme->disp_dpi, 6)) +#define SPACE_4 (LV_DPX_CALC(theme->disp_dpi, 8)) +#define SPACE_6 (LV_DPX_CALC(theme->disp_dpi, 12)) +#define LINE_THIN 1 +#define LINE_MEDIUM 2 +#define LINE_THICK 3 +#define DURATION_SHORT 100 +#define DURATION_MEDIUM 250 +#define DURATION_LONG 500 +#define INK (theme->config.is_dark ? WHITE : BLACK) +#define PAPER (theme->config.is_dark ? BLACK : WHITE) +#define COLOR_PRIMARY INK +#define COLOR_ON_PRIMARY PAPER +#define COLOR_SURFACE PAPER +#define COLOR_ON_SURFACE INK +#define COLOR_OUTLINE INK +#define SHAPE_CONTROL (theme->config.is_compact ? CORNER_COMPACT : CORNER_FULL) +#define SHAPE_CONTAINER (theme->config.is_compact ? CORNER_COMPACT : CORNER_MEDIUM) +#define SHAPE_FIELD (theme->config.is_compact ? CORNER_COMPACT : CORNER_SMALL) +#define SHAPE_KEY (theme->config.is_compact ? CORNER_COMPACT : CORNER_SMALL) +#define SHAPE_DIALOG (theme->config.is_compact ? CORNER_COMPACT : CORNER_LARGE) +#define DENSITY_CONTAINER_PAD (theme->config.is_compact ? 2 : SPACE_4) +#define DENSITY_CONTAINER_GAP (theme->config.is_compact ? 2 : SPACE_3) +#define DENSITY_CONTROL_PAD_HOR (theme->config.is_compact ? 3 : SPACE_6) +#define DENSITY_CONTROL_PAD_VER (theme->config.is_compact ? 2 : SPACE_3) +#define DENSITY_CONTROL_GAP (theme->config.is_compact ? 2 : SPACE_2) +#define DENSITY_FIELD_PAD (theme->config.is_compact ? 2 : SPACE_3) +#define DENSITY_LIST_PAD (theme->config.is_compact ? 1 : SPACE_2) +#define DENSITY_LIST_ITEM_PAD_HOR (theme->config.is_compact ? 2 : SPACE_4) +#define DENSITY_LIST_ITEM_PAD_VER (theme->config.is_compact ? 2 : SPACE_3) +#define DENSITY_DIALOG_PAD (theme->config.is_compact ? 2 : SPACE_6) +#define DENSITY_KEY_GAP (theme->config.is_compact ? 1 : SPACE_1) +#define BORDER LINE_THIN +#define FOCUS_WIDTH (theme->config.is_compact ? LINE_THIN : LINE_MEDIUM) +#define FOCUS_OFFSET (theme->config.is_compact ? LINE_THIN : LINE_MEDIUM) +#define EDIT_WIDTH (theme->config.is_compact ? LINE_MEDIUM : LINE_THICK) +#define FOCUS_INSET_WIDTH (theme->config.is_compact ? LINE_THIN : LINE_MEDIUM) +#define MOTION_DURATION_SHORT (theme->config.animations_enabled ? DURATION_SHORT : 0) +#define MOTION_DURATION_MEDIUM (theme->config.animations_enabled ? DURATION_MEDIUM : 0) +#define MOTION_DURATION_LONG (theme->config.animations_enabled ? DURATION_LONG : 0) +#define SCREEN_COLOR COLOR_SURFACE +#define SCREEN_TEXT_COLOR COLOR_ON_SURFACE +#define SCREEN_GAP DENSITY_CONTAINER_GAP +#define CONTAINER_COLOR COLOR_SURFACE +#define CONTAINER_TEXT_COLOR COLOR_ON_SURFACE +#define CONTAINER_BORDER_COLOR COLOR_OUTLINE +#define CONTAINER_BORDER_WIDTH BORDER +#define CONTAINER_SHAPE SHAPE_CONTAINER +#define CONTAINER_PAD DENSITY_CONTAINER_PAD +#define CONTAINER_GAP DENSITY_CONTAINER_GAP +#define CARD_COLOR COLOR_SURFACE +#define CARD_TEXT_COLOR COLOR_ON_SURFACE +#define CARD_BORDER_COLOR COLOR_OUTLINE +#define CARD_BORDER_WIDTH LINE_MEDIUM +#define CARD_SHAPE SHAPE_CONTAINER +#define CARD_PAD DENSITY_CONTAINER_PAD +#define CARD_GAP DENSITY_CONTAINER_GAP +#define CHIP_SHAPE (theme->config.is_compact ? CORNER_COMPACT : CORNER_SMALL) +#define CHIP_PAD_HOR (theme->config.is_compact ? 3 : SPACE_4) +#define CHIP_PAD_VER (theme->config.is_compact ? 2 : SPACE_2) +#define PAGE_INDICATOR_COLOR COLOR_ON_SURFACE +#define PAGE_INDICATOR_DOT_SIZE (theme->config.is_compact ? 4 : LV_DPX_CALC(theme->disp_dpi, 6)) +#define PAGE_INDICATOR_GAP (theme->config.is_compact ? 3 : LV_DPX_CALC(theme->disp_dpi, 6)) +#define BADGE_COLOR COLOR_ON_SURFACE +#define BADGE_SIZE (theme->config.is_compact ? 4 : LV_DPX_CALC(theme->disp_dpi, 6)) +#define SCROLLBAR_COLOR COLOR_ON_SURFACE +#define SCROLLBAR_WIDTH (theme->config.is_compact ? 2 : LV_DPX_CALC(theme->disp_dpi, 3)) +#define SCROLLBAR_PAD 1 +#define BUTTON_CONTAINER_COLOR COLOR_SURFACE +#define BUTTON_LABEL_COLOR COLOR_ON_SURFACE +#define BUTTON_BORDER_COLOR COLOR_OUTLINE +#define BUTTON_BORDER_WIDTH BORDER +#define BUTTON_INVERTED_CONTAINER_COLOR COLOR_PRIMARY +#define BUTTON_INVERTED_LABEL_COLOR COLOR_ON_PRIMARY +#define BUTTON_SHAPE SHAPE_CONTROL +#define BUTTON_PAD_HOR DENSITY_CONTROL_PAD_HOR +#define BUTTON_PAD_VER DENSITY_CONTROL_PAD_VER +#define BUTTON_GAP DENSITY_CONTROL_GAP +#define ICON_BUTTON_CONTAINER_SHAPE (theme->config.is_compact ? CORNER_COMPACT : CORNER_LARGE) +#define ICON_BUTTON_FILLED_COLOR COLOR_PRIMARY +#define ICON_BUTTON_FILLED_ICON_COLOR COLOR_ON_PRIMARY +#define ICON_BUTTON_TONAL_COLOR COLOR_SURFACE +#define ICON_BUTTON_TONAL_ICON_COLOR COLOR_ON_SURFACE +#define ICON_BUTTON_TONAL_BORDER_COLOR COLOR_OUTLINE +#define APP_BAR_TITLE_COLOR COLOR_ON_SURFACE +#define APP_BAR_DIVIDER_COLOR COLOR_OUTLINE +#define APP_BAR_PAD_HOR (theme->config.is_compact ? 0 : SPACE_1) +#define APP_BAR_GAP (theme->config.is_compact ? 2 : SPACE_1) +#define APP_BAR_FOCUS_WIDTH FOCUS_INSET_WIDTH +#define FOCUS_RING_COLOR COLOR_ON_SURFACE +#define FOCUS_RING_WIDTH FOCUS_WIDTH +#define FOCUS_RING_OFFSET FOCUS_OFFSET +#define EDIT_RING_WIDTH EDIT_WIDTH +#define SWITCH_WIDTH (theme->config.is_compact ? 25 : LV_DPX_CALC(theme->disp_dpi, 40)) +#define SWITCH_HEIGHT (theme->config.is_compact ? 15 : LV_DPX_CALC(theme->disp_dpi, 24)) +#define SWITCH_TRACK_COLOR COLOR_SURFACE +#define SWITCH_TRACK_BORDER_COLOR COLOR_OUTLINE +#define SWITCH_TRACK_BORDER_WIDTH BORDER +#define SWITCH_TRACK_SELECTED_COLOR COLOR_PRIMARY +#define SWITCH_HANDLE_COLOR COLOR_ON_SURFACE +#define SWITCH_HANDLE_SELECTED_COLOR COLOR_ON_PRIMARY +#define SWITCH_HANDLE_PAD (theme->config.is_compact ? -3 : -LV_DPX_CALC(theme->disp_dpi, 5)) +#define SWITCH_HANDLE_SELECTED_PAD (theme->config.is_compact ? -2 : -LV_DPX_CALC(theme->disp_dpi, 3)) +#define CHECKBOX_GAP (theme->config.is_compact ? 3 : SPACE_4) +#define CHECKBOX_BOX_COLOR COLOR_SURFACE +#define CHECKBOX_BOX_BORDER_COLOR COLOR_OUTLINE +#define CHECKBOX_BOX_BORDER_WIDTH BORDER +#define CHECKBOX_BOX_SHAPE 2 +#define CHECKBOX_BOX_PAD 1 +#define CHECKBOX_BOX_SELECTED_COLOR COLOR_PRIMARY +#define CHECKBOX_ICON_COLOR COLOR_ON_PRIMARY +#define SLIDER_TRACK_COLOR COLOR_SURFACE +#define SLIDER_TRACK_BORDER_COLOR COLOR_OUTLINE +#define SLIDER_ACTIVE_TRACK_COLOR COLOR_PRIMARY +#define SLIDER_HANDLE_COLOR COLOR_PRIMARY +#define SLIDER_TRACK_HEIGHT (LV_DPX_CALC(theme->disp_dpi, 8)) +#define SLIDER_HANDLE_PAD ((theme->config.is_compact ? 1 : LV_DPX_CALC(theme->disp_dpi, 3)) + LV_DPX_CALC(theme->disp_dpi, 4)) +#define SLIDERBOX_GAP (theme->config.is_compact ? 4 : ((SLIDER_TRACK_HEIGHT / 2) + SLIDER_HANDLE_PAD) + SPACE_2) +#define ARC_TRACK_COLOR COLOR_ON_SURFACE +#define ARC_TRACK_WIDTH LINE_THIN +#define ARC_INDICATOR_COLOR COLOR_PRIMARY +#define ARC_INDICATOR_WIDTH (theme->config.is_compact ? LINE_MEDIUM : LV_DPX_CALC(theme->disp_dpi, 4)) +#define ARC_HANDLE_COLOR COLOR_PRIMARY +#define ARC_HANDLE_PAD (theme->config.is_compact ? 1 : LV_DPX_CALC(theme->disp_dpi, 3)) +#define FIELD_COLOR COLOR_SURFACE +#define FIELD_TEXT_COLOR COLOR_ON_SURFACE +#define FIELD_BORDER_COLOR COLOR_OUTLINE +#define FIELD_BORDER_WIDTH BORDER +#define FIELD_FOCUSED_BORDER_WIDTH LINE_MEDIUM +#define FIELD_SHAPE SHAPE_FIELD +#define FIELD_PAD DENSITY_FIELD_PAD +#define FIELD_CURSOR_COLOR COLOR_ON_SURFACE +#define FIELD_CURSOR_WIDTH LINE_MEDIUM +#define FIELD_SELECTION_COLOR COLOR_PRIMARY +#define FIELD_SELECTION_TEXT_COLOR COLOR_ON_PRIMARY +#define MENU_COLOR COLOR_SURFACE +#define MENU_BORDER_COLOR COLOR_OUTLINE +#define MENU_SHAPE SHAPE_FIELD +#define MENU_MAX_HEIGHT (LV_DPI_DEF * 2) +#define MENU_ITEM_LINE_SPACE (theme->config.is_compact ? 2 : SPACE_3) +#define ROLLER_LINE_SPACE (theme->config.is_compact ? 2 : SPACE_3) +#define KEYS_PAD DENSITY_KEY_GAP +#define KEYS_GAP DENSITY_KEY_GAP +#define KEY_COLOR COLOR_SURFACE +#define KEY_TEXT_COLOR COLOR_ON_SURFACE +#define KEY_BORDER_COLOR COLOR_OUTLINE +#define KEY_SHAPE SHAPE_KEY +#define LIST_PAD DENSITY_LIST_PAD +#define LIST_ITEM_PAD_HOR DENSITY_LIST_ITEM_PAD_HOR +#define LIST_ITEM_PAD_VER DENSITY_LIST_ITEM_PAD_VER +#define LIST_ITEM_GAP (theme->config.is_compact ? 3 : SPACE_4) +#define LIST_DIVIDER_COLOR COLOR_OUTLINE +#define TABS_DIVIDER_COLOR COLOR_OUTLINE +#define TAB_INDICATOR_COLOR COLOR_PRIMARY +#define TAB_INDICATOR_WIDTH (theme->config.is_compact ? LINE_MEDIUM : LINE_THICK) +#define TAB_PAGE_PAD DENSITY_CONTAINER_PAD +#define DIALOG_COLOR COLOR_SURFACE +#define DIALOG_BORDER_COLOR COLOR_OUTLINE +#define DIALOG_BORDER_WIDTH LINE_MEDIUM +#define DIALOG_SHAPE SHAPE_DIALOG +#define DIALOG_PAD DENSITY_DIALOG_PAD +#define DIALOG_GAP (theme->config.is_compact ? 2 : SPACE_2) +#define WINDOW_HEADER_BORDER_COLOR COLOR_OUTLINE +#define WINDOW_HEADER_PAD (theme->config.is_compact ? 2 : SPACE_2) +#define WINDOW_CONTENT_PAD DENSITY_CONTAINER_PAD +#define TABLE_CELL_PAD (theme->config.is_compact ? 2 : SPACE_3) +#define TABLE_DIVIDER_COLOR COLOR_OUTLINE +#define CHART_GRID_COLOR COLOR_ON_SURFACE +#define CHART_POINT_SIZE (theme->config.is_compact ? 3 : LV_DPX_CALC(theme->disp_dpi, 6)) +#define CALENDAR_PAD (theme->config.is_compact ? 2 : SPACE_2) +#define SCALE_COLOR COLOR_ON_SURFACE +#define SCALE_TICK_LENGTH (theme->config.is_compact ? 3 : LV_DPX_CALC(theme->disp_dpi, 6)) +#define DIVIDER_COLOR COLOR_OUTLINE + +/********************** + * TYPEDEFS + **********************/ + +typedef struct { + lv_style_t focus_ring; + lv_style_t edit_ring; + lv_style_t focus_ring_inset; + lv_style_t edit_ring_inset; + lv_style_t inverted; + lv_style_t disabled; + lv_style_t scrollbar; +#if LV_USE_TABVIEW || LV_USE_TILEVIEW || LV_USE_WIN + lv_style_t page; +#endif + lv_style_t screen; + lv_style_t container; + lv_style_t card; + lv_style_t page_indicator; +#if LV_USE_LABEL + lv_style_t page_indicator_label; +#endif + lv_style_t page_indicator_dot; + lv_style_t page_indicator_dot_current; + lv_style_t badge; + lv_style_t chip; +#if LV_USE_BUTTON || LV_USE_MSGBOX + lv_style_t button; +#endif + lv_style_t icon_button; + lv_style_t icon_button_filled; + lv_style_t icon_button_filled_pressed; + lv_style_t icon_button_tonal; + lv_style_t app_bar; + lv_style_t app_bar_button; + lv_style_t app_bar_focus_ring; +#if LV_USE_SWITCH + lv_style_t switch_track; +#endif +#if LV_USE_SWITCH + lv_style_t switch_indicator_selected; +#endif +#if LV_USE_SWITCH + lv_style_t switch_handle; +#endif +#if LV_USE_SWITCH + lv_style_t switch_handle_selected; +#endif +#if LV_USE_CHECKBOX + lv_style_t checkbox; +#endif +#if LV_USE_CHECKBOX + lv_style_t checkbox_box; +#endif +#if LV_USE_CHECKBOX + lv_style_t checkbox_box_selected; +#endif + lv_style_t sliderbox; +#if LV_USE_BAR || LV_USE_SLIDER + lv_style_t slider_track_size; +#endif +#if LV_USE_SLIDER + lv_style_t slider_knob_room; +#endif +#if LV_USE_BAR || LV_USE_SLIDER + lv_style_t slider_track; +#endif +#if LV_USE_BAR || LV_USE_SLIDER + lv_style_t slider_active_track; +#endif +#if LV_USE_SLIDER + lv_style_t slider_handle; +#endif +#if LV_USE_ARC || LV_USE_SPINNER + lv_style_t arc_track; +#endif +#if LV_USE_ARC || LV_USE_SPINNER + lv_style_t arc_indicator; +#endif +#if LV_USE_ARC + lv_style_t arc_handle; +#endif +#if LV_USE_DROPDOWN || LV_USE_SPINBOX || LV_USE_TEXTAREA + lv_style_t field; +#endif +#if LV_USE_SPINBOX || LV_USE_TEXTAREA + lv_style_t field_focused; +#endif +#if LV_USE_TEXTAREA + lv_style_t field_cursor; +#endif +#if LV_USE_LABEL && LV_USE_TEXTAREA + lv_style_t field_selection; +#endif +#if LV_USE_DROPDOWN + lv_style_t menu; +#endif +#if LV_USE_DROPDOWN + lv_style_t menu_item; +#endif +#if LV_USE_ROLLER + lv_style_t roller; +#endif +#if LV_USE_BUTTONMATRIX || LV_USE_KEYBOARD + lv_style_t keys; +#endif +#if LV_USE_BUTTONMATRIX || LV_USE_KEYBOARD + lv_style_t key; +#endif +#if LV_USE_LIST + lv_style_t list; +#endif +#if LV_USE_LIST + lv_style_t list_item; +#endif +#if LV_USE_LIST + lv_style_t list_subheader; +#endif +#if LV_USE_TABVIEW + lv_style_t tabs; +#endif +#if LV_USE_BUTTON && LV_USE_TABVIEW + lv_style_t tab; +#endif +#if LV_USE_BUTTON && LV_USE_TABVIEW + lv_style_t tab_selected; +#endif +#if LV_USE_TABVIEW + lv_style_t tab_page; +#endif +#if LV_USE_MSGBOX + lv_style_t dialog; +#endif +#if LV_USE_MSGBOX + lv_style_t dialog_header; +#endif +#if LV_USE_LABEL && LV_USE_MSGBOX + lv_style_t dialog_title; +#endif +#if LV_USE_MSGBOX + lv_style_t dialog_content; +#endif +#if LV_USE_MSGBOX + lv_style_t dialog_actions; +#endif +#if LV_USE_MENU + lv_style_t settings; +#endif +#if LV_USE_MENU + lv_style_t settings_sidebar; +#endif +#if LV_USE_MENU + lv_style_t settings_content; +#endif +#if LV_USE_MENU + lv_style_t settings_page; +#endif +#if LV_USE_MENU + lv_style_t settings_header; +#endif +#if LV_USE_MENU + lv_style_t settings_section; +#endif +#if LV_USE_MENU + lv_style_t settings_item; +#endif +#if LV_USE_MENU + lv_style_t settings_separator; +#endif +#if LV_USE_WIN + lv_style_t window_header; +#endif +#if LV_USE_LABEL && LV_USE_WIN + lv_style_t window_title; +#endif +#if LV_USE_WIN + lv_style_t window_content; +#endif +#if LV_USE_TABLE + lv_style_t table; +#endif +#if LV_USE_TABLE + lv_style_t table_cell; +#endif +#if LV_USE_CHART + lv_style_t chart; +#endif +#if LV_USE_CHART + lv_style_t chart_series; +#endif +#if LV_USE_CHART + lv_style_t chart_indicator; +#endif +#if LV_USE_CALENDAR + lv_style_t calendar; +#endif +#if LV_USE_BUTTONMATRIX && LV_USE_CALENDAR + lv_style_t calendar_day; +#endif +#if LV_USE_SCALE + lv_style_t scale; +#endif +#if LV_USE_LINE + lv_style_t divider; +#endif +#if LV_USE_LED + lv_style_t led; +#endif +} my_theme_styles_t; + +typedef enum { + DISP_SMALL = 3, + DISP_MEDIUM = 2, + DISP_LARGE = 1, +} disp_size_t; + +typedef struct { + lv_theme_t base; + disp_size_t disp_size; + int32_t disp_dpi; + lv_theme_material_mono_config_t config; + bool inited; + my_theme_styles_t styles; + +} my_theme_t; + +/********************** + * STATIC PROTOTYPES + **********************/ + +static void style_init_reset(lv_style_t * style); +static void theme_apply(lv_theme_t * th, lv_obj_t * obj); +static void resolution_change_event_cb(lv_event_t * e); + +/********************** + * STATIC VARIABLES + **********************/ + +static my_theme_t * theme_def = NULL; + +/********************** + * STATIC FUNCTIONS + **********************/ + +static inline lv_obj_t * lss_ancestor(lv_obj_t * obj, uint32_t depth) +{ + while(obj != NULL && depth > 0) { + obj = lv_obj_get_parent(obj); + depth--; + } + return obj; +} + +static inline bool lss_is_child(lv_obj_t * parent, int32_t index, lv_obj_t * child) +{ + return parent != NULL && lv_obj_get_child(parent, index) == child; +} + +static bool config_equals(const lv_theme_material_mono_config_t * a, const lv_theme_material_mono_config_t * b) +{ + return a->is_dark == b->is_dark && + a->is_compact == b->is_compact && + a->animations_enabled == b->animations_enabled && + lv_color_eq(a->color_primary, b->color_primary) && + lv_color_eq(a->color_secondary, b->color_secondary) && + a->font_small == b->font_small && + a->font_normal == b->font_normal && + a->font_large == b->font_large; +} + +static void style_init(my_theme_t * theme) +{ + style_init_reset(&theme->styles.focus_ring); + lv_style_set_outline_color(&theme->styles.focus_ring, FOCUS_RING_COLOR); + lv_style_set_outline_width(&theme->styles.focus_ring, FOCUS_RING_WIDTH); + lv_style_set_outline_pad(&theme->styles.focus_ring, FOCUS_RING_OFFSET); + lv_style_set_outline_opa(&theme->styles.focus_ring, LV_OPA_COVER); + + style_init_reset(&theme->styles.edit_ring); + lv_style_set_outline_color(&theme->styles.edit_ring, FOCUS_RING_COLOR); + lv_style_set_outline_width(&theme->styles.edit_ring, EDIT_RING_WIDTH); + lv_style_set_outline_pad(&theme->styles.edit_ring, FOCUS_RING_OFFSET); + lv_style_set_outline_opa(&theme->styles.edit_ring, LV_OPA_COVER); + + style_init_reset(&theme->styles.focus_ring_inset); + lv_style_set_outline_color(&theme->styles.focus_ring_inset, FOCUS_RING_COLOR); + lv_style_set_outline_width(&theme->styles.focus_ring_inset, FOCUS_INSET_WIDTH); + lv_style_set_outline_pad(&theme->styles.focus_ring_inset, -FOCUS_INSET_WIDTH); + lv_style_set_outline_opa(&theme->styles.focus_ring_inset, LV_OPA_COVER); + + style_init_reset(&theme->styles.edit_ring_inset); + lv_style_set_outline_color(&theme->styles.edit_ring_inset, FOCUS_RING_COLOR); + lv_style_set_outline_width(&theme->styles.edit_ring_inset, EDIT_RING_WIDTH); + lv_style_set_outline_pad(&theme->styles.edit_ring_inset, -EDIT_RING_WIDTH); + lv_style_set_outline_opa(&theme->styles.edit_ring_inset, LV_OPA_COVER); + + style_init_reset(&theme->styles.inverted); + lv_style_set_bg_color(&theme->styles.inverted, BUTTON_INVERTED_CONTAINER_COLOR); + lv_style_set_bg_opa(&theme->styles.inverted, LV_OPA_COVER); + lv_style_set_text_color(&theme->styles.inverted, BUTTON_INVERTED_LABEL_COLOR); + lv_style_set_border_color(&theme->styles.inverted, BUTTON_INVERTED_LABEL_COLOR); + lv_style_set_line_color(&theme->styles.inverted, BUTTON_INVERTED_LABEL_COLOR); + lv_style_set_arc_color(&theme->styles.inverted, BUTTON_INVERTED_LABEL_COLOR); + lv_style_set_outline_color(&theme->styles.inverted, BUTTON_INVERTED_LABEL_COLOR); + + style_init_reset(&theme->styles.disabled); + lv_style_set_border_width(&theme->styles.disabled, 0); + + style_init_reset(&theme->styles.scrollbar); + lv_style_set_bg_color(&theme->styles.scrollbar, SCROLLBAR_COLOR); + lv_style_set_bg_opa(&theme->styles.scrollbar, LV_OPA_COVER); + lv_style_set_radius(&theme->styles.scrollbar, LV_RADIUS_CIRCLE); + lv_style_set_width(&theme->styles.scrollbar, SCROLLBAR_WIDTH); + lv_style_set_pad_all(&theme->styles.scrollbar, SCROLLBAR_PAD); + +#if LV_USE_TABVIEW || LV_USE_TILEVIEW || LV_USE_WIN + style_init_reset(&theme->styles.page); + lv_style_set_bg_opa(&theme->styles.page, LV_OPA_TRANSP); + lv_style_set_border_width(&theme->styles.page, 0); + lv_style_set_radius(&theme->styles.page, 0); + lv_style_set_pad_all(&theme->styles.page, 0); + lv_style_set_pad_gap(&theme->styles.page, 0); +#endif + + style_init_reset(&theme->styles.screen); + lv_style_set_bg_color(&theme->styles.screen, SCREEN_COLOR); + lv_style_set_bg_opa(&theme->styles.screen, LV_OPA_COVER); + lv_style_set_text_color(&theme->styles.screen, SCREEN_TEXT_COLOR); + lv_style_set_text_font(&theme->styles.screen, theme->config.font_normal); + lv_style_set_pad_row(&theme->styles.screen, SCREEN_GAP); + lv_style_set_pad_column(&theme->styles.screen, SCREEN_GAP); + + style_init_reset(&theme->styles.container); + lv_style_set_bg_color(&theme->styles.container, CONTAINER_COLOR); + lv_style_set_bg_opa(&theme->styles.container, LV_OPA_COVER); + lv_style_set_text_color(&theme->styles.container, CONTAINER_TEXT_COLOR); + lv_style_set_border_color(&theme->styles.container, CONTAINER_BORDER_COLOR); + lv_style_set_border_width(&theme->styles.container, CONTAINER_BORDER_WIDTH); + lv_style_set_radius(&theme->styles.container, CONTAINER_SHAPE); + lv_style_set_pad_all(&theme->styles.container, CONTAINER_PAD); + lv_style_set_pad_gap(&theme->styles.container, CONTAINER_GAP); + lv_style_set_line_color(&theme->styles.container, DIVIDER_COLOR); + lv_style_set_line_width(&theme->styles.container, BORDER); + lv_style_set_arc_color(&theme->styles.container, COLOR_ON_SURFACE); + lv_style_set_arc_width(&theme->styles.container, LINE_MEDIUM); + + style_init_reset(&theme->styles.card); + lv_style_set_bg_color(&theme->styles.card, CARD_COLOR); + lv_style_set_bg_opa(&theme->styles.card, LV_OPA_COVER); + lv_style_set_text_color(&theme->styles.card, CARD_TEXT_COLOR); + lv_style_set_border_color(&theme->styles.card, CARD_BORDER_COLOR); + lv_style_set_border_width(&theme->styles.card, CARD_BORDER_WIDTH); + lv_style_set_radius(&theme->styles.card, CARD_SHAPE); + lv_style_set_pad_all(&theme->styles.card, CARD_PAD); + lv_style_set_pad_gap(&theme->styles.card, CARD_GAP); + + style_init_reset(&theme->styles.page_indicator); + lv_style_set_bg_opa(&theme->styles.page_indicator, LV_OPA_TRANSP); + lv_style_set_border_width(&theme->styles.page_indicator, 0); + lv_style_set_pad_all(&theme->styles.page_indicator, 0); + lv_style_set_pad_gap(&theme->styles.page_indicator, PAGE_INDICATOR_GAP); + +#if LV_USE_LABEL + style_init_reset(&theme->styles.page_indicator_label); + lv_style_set_text_color(&theme->styles.page_indicator_label, PAGE_INDICATOR_COLOR); +#endif + + style_init_reset(&theme->styles.page_indicator_dot); + lv_style_set_width(&theme->styles.page_indicator_dot, PAGE_INDICATOR_DOT_SIZE); + lv_style_set_height(&theme->styles.page_indicator_dot, PAGE_INDICATOR_DOT_SIZE); + lv_style_set_radius(&theme->styles.page_indicator_dot, LV_RADIUS_CIRCLE); + lv_style_set_bg_color(&theme->styles.page_indicator_dot, PAGE_INDICATOR_COLOR); + lv_style_set_bg_opa(&theme->styles.page_indicator_dot, LV_OPA_TRANSP); + lv_style_set_border_color(&theme->styles.page_indicator_dot, PAGE_INDICATOR_COLOR); + lv_style_set_border_width(&theme->styles.page_indicator_dot, 1); + + style_init_reset(&theme->styles.page_indicator_dot_current); + lv_style_set_bg_opa(&theme->styles.page_indicator_dot_current, LV_OPA_COVER); + + style_init_reset(&theme->styles.badge); + lv_style_set_width(&theme->styles.badge, BADGE_SIZE); + lv_style_set_height(&theme->styles.badge, BADGE_SIZE); + lv_style_set_radius(&theme->styles.badge, LV_RADIUS_CIRCLE); + lv_style_set_bg_color(&theme->styles.badge, BADGE_COLOR); + lv_style_set_bg_opa(&theme->styles.badge, LV_OPA_COVER); + + style_init_reset(&theme->styles.chip); + lv_style_set_bg_color(&theme->styles.chip, COLOR_SURFACE); + lv_style_set_bg_opa(&theme->styles.chip, LV_OPA_COVER); + lv_style_set_text_color(&theme->styles.chip, COLOR_ON_SURFACE); + lv_style_set_border_color(&theme->styles.chip, COLOR_OUTLINE); + lv_style_set_border_width(&theme->styles.chip, BORDER); + lv_style_set_radius(&theme->styles.chip, CHIP_SHAPE); + lv_style_set_pad_hor(&theme->styles.chip, CHIP_PAD_HOR); + lv_style_set_pad_ver(&theme->styles.chip, CHIP_PAD_VER); + +#if LV_USE_BUTTON || LV_USE_MSGBOX + style_init_reset(&theme->styles.button); + lv_style_set_bg_color(&theme->styles.button, BUTTON_CONTAINER_COLOR); + lv_style_set_bg_opa(&theme->styles.button, LV_OPA_COVER); + lv_style_set_text_color(&theme->styles.button, BUTTON_LABEL_COLOR); + lv_style_set_border_color(&theme->styles.button, BUTTON_BORDER_COLOR); + lv_style_set_border_width(&theme->styles.button, BUTTON_BORDER_WIDTH); + lv_style_set_radius(&theme->styles.button, BUTTON_SHAPE); + lv_style_set_pad_hor(&theme->styles.button, BUTTON_PAD_HOR); + lv_style_set_pad_ver(&theme->styles.button, BUTTON_PAD_VER); + lv_style_set_pad_gap(&theme->styles.button, BUTTON_GAP); +#endif + + style_init_reset(&theme->styles.icon_button); + lv_style_set_bg_opa(&theme->styles.icon_button, LV_OPA_TRANSP); + lv_style_set_text_color(&theme->styles.icon_button, COLOR_ON_SURFACE); + lv_style_set_border_width(&theme->styles.icon_button, 0); + lv_style_set_radius(&theme->styles.icon_button, BUTTON_SHAPE); + lv_style_set_pad_all(&theme->styles.icon_button, BUTTON_PAD_VER); + + style_init_reset(&theme->styles.icon_button_filled); + lv_style_set_bg_color(&theme->styles.icon_button_filled, ICON_BUTTON_FILLED_COLOR); + lv_style_set_bg_opa(&theme->styles.icon_button_filled, LV_OPA_COVER); + lv_style_set_text_color(&theme->styles.icon_button_filled, ICON_BUTTON_FILLED_ICON_COLOR); + lv_style_set_border_width(&theme->styles.icon_button_filled, 0); + lv_style_set_radius(&theme->styles.icon_button_filled, ICON_BUTTON_CONTAINER_SHAPE); + lv_style_set_pad_all(&theme->styles.icon_button_filled, BUTTON_PAD_VER); + + style_init_reset(&theme->styles.icon_button_filled_pressed); + lv_style_set_bg_color(&theme->styles.icon_button_filled_pressed, ICON_BUTTON_TONAL_COLOR); + lv_style_set_text_color(&theme->styles.icon_button_filled_pressed, ICON_BUTTON_TONAL_ICON_COLOR); + lv_style_set_border_color(&theme->styles.icon_button_filled_pressed, ICON_BUTTON_TONAL_BORDER_COLOR); + lv_style_set_border_width(&theme->styles.icon_button_filled_pressed, BORDER); + + style_init_reset(&theme->styles.icon_button_tonal); + lv_style_set_bg_color(&theme->styles.icon_button_tonal, ICON_BUTTON_TONAL_COLOR); + lv_style_set_bg_opa(&theme->styles.icon_button_tonal, LV_OPA_COVER); + lv_style_set_text_color(&theme->styles.icon_button_tonal, ICON_BUTTON_TONAL_ICON_COLOR); + lv_style_set_border_color(&theme->styles.icon_button_tonal, ICON_BUTTON_TONAL_BORDER_COLOR); + lv_style_set_border_width(&theme->styles.icon_button_tonal, BORDER); + lv_style_set_radius(&theme->styles.icon_button_tonal, ICON_BUTTON_CONTAINER_SHAPE); + lv_style_set_pad_all(&theme->styles.icon_button_tonal, BUTTON_PAD_VER); + + style_init_reset(&theme->styles.app_bar); + lv_style_set_bg_opa(&theme->styles.app_bar, LV_OPA_TRANSP); + lv_style_set_text_color(&theme->styles.app_bar, APP_BAR_TITLE_COLOR); + lv_style_set_radius(&theme->styles.app_bar, 0); + lv_style_set_border_color(&theme->styles.app_bar, APP_BAR_DIVIDER_COLOR); + lv_style_set_border_width(&theme->styles.app_bar, BORDER); + lv_style_set_border_side(&theme->styles.app_bar, LV_BORDER_SIDE_BOTTOM); + lv_style_set_pad_hor(&theme->styles.app_bar, APP_BAR_PAD_HOR); + lv_style_set_pad_ver(&theme->styles.app_bar, 0); + lv_style_set_pad_gap(&theme->styles.app_bar, APP_BAR_GAP); + + style_init_reset(&theme->styles.app_bar_button); + lv_style_set_pad_all(&theme->styles.app_bar_button, 0); + + style_init_reset(&theme->styles.app_bar_focus_ring); + lv_style_set_outline_color(&theme->styles.app_bar_focus_ring, FOCUS_RING_COLOR); + lv_style_set_outline_width(&theme->styles.app_bar_focus_ring, APP_BAR_FOCUS_WIDTH); + lv_style_set_outline_pad(&theme->styles.app_bar_focus_ring, -APP_BAR_FOCUS_WIDTH); + lv_style_set_outline_opa(&theme->styles.app_bar_focus_ring, LV_OPA_COVER); + +#if LV_USE_SWITCH + style_init_reset(&theme->styles.switch_track); + lv_style_set_width(&theme->styles.switch_track, SWITCH_WIDTH); + lv_style_set_height(&theme->styles.switch_track, SWITCH_HEIGHT); + lv_style_set_bg_color(&theme->styles.switch_track, SWITCH_TRACK_COLOR); + lv_style_set_bg_opa(&theme->styles.switch_track, LV_OPA_COVER); + lv_style_set_border_color(&theme->styles.switch_track, SWITCH_TRACK_BORDER_COLOR); + lv_style_set_border_width(&theme->styles.switch_track, SWITCH_TRACK_BORDER_WIDTH); + lv_style_set_radius(&theme->styles.switch_track, LV_RADIUS_CIRCLE); + lv_style_set_pad_all(&theme->styles.switch_track, 0); + lv_style_set_anim_duration(&theme->styles.switch_track, MOTION_DURATION_SHORT); +#endif + +#if LV_USE_SWITCH + style_init_reset(&theme->styles.switch_indicator_selected); + lv_style_set_bg_color(&theme->styles.switch_indicator_selected, SWITCH_TRACK_SELECTED_COLOR); + lv_style_set_bg_opa(&theme->styles.switch_indicator_selected, LV_OPA_COVER); + lv_style_set_radius(&theme->styles.switch_indicator_selected, LV_RADIUS_CIRCLE); +#endif + +#if LV_USE_SWITCH + style_init_reset(&theme->styles.switch_handle); + lv_style_set_bg_color(&theme->styles.switch_handle, SWITCH_HANDLE_COLOR); + lv_style_set_bg_opa(&theme->styles.switch_handle, LV_OPA_COVER); + lv_style_set_radius(&theme->styles.switch_handle, LV_RADIUS_CIRCLE); + lv_style_set_pad_all(&theme->styles.switch_handle, SWITCH_HANDLE_PAD); +#endif + +#if LV_USE_SWITCH + style_init_reset(&theme->styles.switch_handle_selected); + lv_style_set_bg_color(&theme->styles.switch_handle_selected, SWITCH_HANDLE_SELECTED_COLOR); + lv_style_set_pad_all(&theme->styles.switch_handle_selected, SWITCH_HANDLE_SELECTED_PAD); +#endif + +#if LV_USE_CHECKBOX + style_init_reset(&theme->styles.checkbox); + lv_style_set_pad_gap(&theme->styles.checkbox, CHECKBOX_GAP); +#endif + +#if LV_USE_CHECKBOX + style_init_reset(&theme->styles.checkbox_box); + lv_style_set_bg_color(&theme->styles.checkbox_box, CHECKBOX_BOX_COLOR); + lv_style_set_bg_opa(&theme->styles.checkbox_box, LV_OPA_COVER); + lv_style_set_border_color(&theme->styles.checkbox_box, CHECKBOX_BOX_BORDER_COLOR); + lv_style_set_border_width(&theme->styles.checkbox_box, CHECKBOX_BOX_BORDER_WIDTH); + lv_style_set_radius(&theme->styles.checkbox_box, CHECKBOX_BOX_SHAPE); + lv_style_set_pad_all(&theme->styles.checkbox_box, CHECKBOX_BOX_PAD); + lv_style_set_text_font(&theme->styles.checkbox_box, theme->config.font_small); + lv_style_set_text_color(&theme->styles.checkbox_box, CHECKBOX_ICON_COLOR); +#endif + +#if LV_USE_CHECKBOX + style_init_reset(&theme->styles.checkbox_box_selected); + lv_style_set_bg_color(&theme->styles.checkbox_box_selected, CHECKBOX_BOX_SELECTED_COLOR); + lv_style_set_bg_image_src(&theme->styles.checkbox_box_selected, LV_SYMBOL_OK); +#endif + + style_init_reset(&theme->styles.sliderbox); + lv_style_set_pad_column(&theme->styles.sliderbox, SLIDERBOX_GAP); + +#if LV_USE_BAR || LV_USE_SLIDER + style_init_reset(&theme->styles.slider_track_size); + lv_style_set_height(&theme->styles.slider_track_size, SLIDER_TRACK_HEIGHT); +#endif + +#if LV_USE_SLIDER + style_init_reset(&theme->styles.slider_knob_room); + lv_style_set_margin_ver(&theme->styles.slider_knob_room, SLIDER_HANDLE_PAD); +#endif + +#if LV_USE_BAR || LV_USE_SLIDER + style_init_reset(&theme->styles.slider_track); + lv_style_set_bg_color(&theme->styles.slider_track, SLIDER_TRACK_COLOR); + lv_style_set_bg_opa(&theme->styles.slider_track, LV_OPA_COVER); + lv_style_set_border_color(&theme->styles.slider_track, SLIDER_TRACK_BORDER_COLOR); + lv_style_set_border_width(&theme->styles.slider_track, BORDER); + lv_style_set_radius(&theme->styles.slider_track, LV_RADIUS_CIRCLE); + lv_style_set_pad_all(&theme->styles.slider_track, 0); +#endif + +#if LV_USE_BAR || LV_USE_SLIDER + style_init_reset(&theme->styles.slider_active_track); + lv_style_set_bg_color(&theme->styles.slider_active_track, SLIDER_ACTIVE_TRACK_COLOR); + lv_style_set_bg_opa(&theme->styles.slider_active_track, LV_OPA_COVER); + lv_style_set_radius(&theme->styles.slider_active_track, LV_RADIUS_CIRCLE); +#endif + +#if LV_USE_SLIDER + style_init_reset(&theme->styles.slider_handle); + lv_style_set_bg_color(&theme->styles.slider_handle, SLIDER_HANDLE_COLOR); + lv_style_set_bg_opa(&theme->styles.slider_handle, LV_OPA_COVER); + lv_style_set_radius(&theme->styles.slider_handle, LV_RADIUS_CIRCLE); + lv_style_set_pad_all(&theme->styles.slider_handle, SLIDER_HANDLE_PAD); +#endif + +#if LV_USE_ARC || LV_USE_SPINNER + style_init_reset(&theme->styles.arc_track); + lv_style_set_arc_color(&theme->styles.arc_track, ARC_TRACK_COLOR); + lv_style_set_arc_width(&theme->styles.arc_track, ARC_TRACK_WIDTH); +#endif + +#if LV_USE_ARC || LV_USE_SPINNER + style_init_reset(&theme->styles.arc_indicator); + lv_style_set_arc_color(&theme->styles.arc_indicator, ARC_INDICATOR_COLOR); + lv_style_set_arc_width(&theme->styles.arc_indicator, ARC_INDICATOR_WIDTH); + lv_style_set_arc_rounded(&theme->styles.arc_indicator, true); +#endif + +#if LV_USE_ARC + style_init_reset(&theme->styles.arc_handle); + lv_style_set_bg_color(&theme->styles.arc_handle, ARC_HANDLE_COLOR); + lv_style_set_bg_opa(&theme->styles.arc_handle, LV_OPA_COVER); + lv_style_set_radius(&theme->styles.arc_handle, LV_RADIUS_CIRCLE); + lv_style_set_pad_all(&theme->styles.arc_handle, ARC_HANDLE_PAD); +#endif + +#if LV_USE_DROPDOWN || LV_USE_SPINBOX || LV_USE_TEXTAREA + style_init_reset(&theme->styles.field); + lv_style_set_bg_color(&theme->styles.field, FIELD_COLOR); + lv_style_set_bg_opa(&theme->styles.field, LV_OPA_COVER); + lv_style_set_text_color(&theme->styles.field, FIELD_TEXT_COLOR); + lv_style_set_border_color(&theme->styles.field, FIELD_BORDER_COLOR); + lv_style_set_border_width(&theme->styles.field, FIELD_BORDER_WIDTH); + lv_style_set_radius(&theme->styles.field, FIELD_SHAPE); + lv_style_set_pad_all(&theme->styles.field, FIELD_PAD); + lv_style_set_pad_gap(&theme->styles.field, FIELD_PAD); +#endif + +#if LV_USE_SPINBOX || LV_USE_TEXTAREA + style_init_reset(&theme->styles.field_focused); + lv_style_set_border_width(&theme->styles.field_focused, FIELD_FOCUSED_BORDER_WIDTH); +#endif + +#if LV_USE_TEXTAREA + style_init_reset(&theme->styles.field_cursor); + lv_style_set_border_color(&theme->styles.field_cursor, FIELD_CURSOR_COLOR); + lv_style_set_border_width(&theme->styles.field_cursor, FIELD_CURSOR_WIDTH); + lv_style_set_border_side(&theme->styles.field_cursor, LV_BORDER_SIDE_LEFT); + lv_style_set_bg_opa(&theme->styles.field_cursor, LV_OPA_TRANSP); + lv_style_set_anim_duration(&theme->styles.field_cursor, MOTION_DURATION_LONG); +#endif + +#if LV_USE_LABEL && LV_USE_TEXTAREA + style_init_reset(&theme->styles.field_selection); + lv_style_set_bg_color(&theme->styles.field_selection, FIELD_SELECTION_COLOR); + lv_style_set_bg_opa(&theme->styles.field_selection, LV_OPA_COVER); + lv_style_set_text_color(&theme->styles.field_selection, FIELD_SELECTION_TEXT_COLOR); +#endif + +#if LV_USE_DROPDOWN + style_init_reset(&theme->styles.menu); + lv_style_set_bg_color(&theme->styles.menu, MENU_COLOR); + lv_style_set_bg_opa(&theme->styles.menu, LV_OPA_COVER); + lv_style_set_border_color(&theme->styles.menu, MENU_BORDER_COLOR); + lv_style_set_border_width(&theme->styles.menu, BORDER); + lv_style_set_radius(&theme->styles.menu, MENU_SHAPE); + lv_style_set_text_line_space(&theme->styles.menu, MENU_ITEM_LINE_SPACE); + lv_style_set_max_height(&theme->styles.menu, MENU_MAX_HEIGHT); + lv_style_set_clip_corner(&theme->styles.menu, true); +#endif + +#if LV_USE_DROPDOWN + style_init_reset(&theme->styles.menu_item); + lv_style_set_bg_opa(&theme->styles.menu_item, LV_OPA_TRANSP); +#endif + +#if LV_USE_ROLLER + style_init_reset(&theme->styles.roller); + lv_style_set_bg_color(&theme->styles.roller, COLOR_SURFACE); + lv_style_set_bg_opa(&theme->styles.roller, LV_OPA_COVER); + lv_style_set_text_color(&theme->styles.roller, COLOR_ON_SURFACE); + lv_style_set_border_color(&theme->styles.roller, COLOR_OUTLINE); + lv_style_set_border_width(&theme->styles.roller, BORDER); + lv_style_set_radius(&theme->styles.roller, SHAPE_CONTAINER); + lv_style_set_text_line_space(&theme->styles.roller, ROLLER_LINE_SPACE); + lv_style_set_text_align(&theme->styles.roller, LV_TEXT_ALIGN_CENTER); + lv_style_set_anim_duration(&theme->styles.roller, MOTION_DURATION_MEDIUM); +#endif + +#if LV_USE_BUTTONMATRIX || LV_USE_KEYBOARD + style_init_reset(&theme->styles.keys); + lv_style_set_bg_opa(&theme->styles.keys, LV_OPA_TRANSP); + lv_style_set_border_width(&theme->styles.keys, 0); + lv_style_set_pad_all(&theme->styles.keys, KEYS_PAD); + lv_style_set_pad_gap(&theme->styles.keys, KEYS_GAP); +#endif + +#if LV_USE_BUTTONMATRIX || LV_USE_KEYBOARD + style_init_reset(&theme->styles.key); + lv_style_set_bg_color(&theme->styles.key, KEY_COLOR); + lv_style_set_bg_opa(&theme->styles.key, LV_OPA_COVER); + lv_style_set_text_color(&theme->styles.key, KEY_TEXT_COLOR); + lv_style_set_border_color(&theme->styles.key, KEY_BORDER_COLOR); + lv_style_set_border_width(&theme->styles.key, BORDER); + lv_style_set_radius(&theme->styles.key, KEY_SHAPE); +#endif + +#if LV_USE_LIST + style_init_reset(&theme->styles.list); + lv_style_set_bg_color(&theme->styles.list, COLOR_SURFACE); + lv_style_set_bg_opa(&theme->styles.list, LV_OPA_COVER); + lv_style_set_border_color(&theme->styles.list, COLOR_OUTLINE); + lv_style_set_border_width(&theme->styles.list, BORDER); + lv_style_set_radius(&theme->styles.list, SHAPE_CONTAINER); + lv_style_set_pad_hor(&theme->styles.list, 0); + lv_style_set_pad_ver(&theme->styles.list, LIST_PAD); + lv_style_set_pad_gap(&theme->styles.list, 0); + lv_style_set_clip_corner(&theme->styles.list, true); +#endif + +#if LV_USE_LIST + style_init_reset(&theme->styles.list_item); + lv_style_set_bg_opa(&theme->styles.list_item, LV_OPA_TRANSP); + lv_style_set_text_color(&theme->styles.list_item, COLOR_ON_SURFACE); + lv_style_set_border_color(&theme->styles.list_item, LIST_DIVIDER_COLOR); + lv_style_set_border_width(&theme->styles.list_item, BORDER); + lv_style_set_border_side(&theme->styles.list_item, LV_BORDER_SIDE_BOTTOM); + lv_style_set_radius(&theme->styles.list_item, 0); + lv_style_set_pad_hor(&theme->styles.list_item, LIST_ITEM_PAD_HOR); + lv_style_set_pad_ver(&theme->styles.list_item, LIST_ITEM_PAD_VER); + lv_style_set_pad_column(&theme->styles.list_item, LIST_ITEM_GAP); +#endif + +#if LV_USE_LIST + style_init_reset(&theme->styles.list_subheader); + lv_style_set_text_font(&theme->styles.list_subheader, theme->config.font_small); + lv_style_set_text_decor(&theme->styles.list_subheader, LV_TEXT_DECOR_UNDERLINE); + lv_style_set_pad_hor(&theme->styles.list_subheader, LIST_ITEM_PAD_HOR); + lv_style_set_pad_top(&theme->styles.list_subheader, LIST_ITEM_PAD_VER); + lv_style_set_pad_bottom(&theme->styles.list_subheader, LIST_PAD); +#endif + +#if LV_USE_TABVIEW + style_init_reset(&theme->styles.tabs); + lv_style_set_bg_opa(&theme->styles.tabs, LV_OPA_TRANSP); + lv_style_set_border_color(&theme->styles.tabs, TABS_DIVIDER_COLOR); + lv_style_set_border_width(&theme->styles.tabs, BORDER); + lv_style_set_border_side(&theme->styles.tabs, LV_BORDER_SIDE_BOTTOM); +#endif + +#if LV_USE_BUTTON && LV_USE_TABVIEW + style_init_reset(&theme->styles.tab); + lv_style_set_bg_opa(&theme->styles.tab, LV_OPA_TRANSP); + lv_style_set_text_color(&theme->styles.tab, COLOR_ON_SURFACE); + lv_style_set_border_width(&theme->styles.tab, 0); + lv_style_set_radius(&theme->styles.tab, 0); +#endif + +#if LV_USE_BUTTON && LV_USE_TABVIEW + style_init_reset(&theme->styles.tab_selected); + lv_style_set_border_color(&theme->styles.tab_selected, TAB_INDICATOR_COLOR); + lv_style_set_border_width(&theme->styles.tab_selected, TAB_INDICATOR_WIDTH); + lv_style_set_border_side(&theme->styles.tab_selected, LV_BORDER_SIDE_BOTTOM); +#endif + +#if LV_USE_TABVIEW + style_init_reset(&theme->styles.tab_page); + lv_style_set_pad_all(&theme->styles.tab_page, TAB_PAGE_PAD); +#endif + +#if LV_USE_MSGBOX + style_init_reset(&theme->styles.dialog); + lv_style_set_bg_color(&theme->styles.dialog, DIALOG_COLOR); + lv_style_set_bg_opa(&theme->styles.dialog, LV_OPA_COVER); + lv_style_set_text_color(&theme->styles.dialog, COLOR_ON_SURFACE); + lv_style_set_border_color(&theme->styles.dialog, DIALOG_BORDER_COLOR); + lv_style_set_border_width(&theme->styles.dialog, DIALOG_BORDER_WIDTH); + lv_style_set_radius(&theme->styles.dialog, DIALOG_SHAPE); + lv_style_set_pad_all(&theme->styles.dialog, 0); + lv_style_set_pad_gap(&theme->styles.dialog, 0); + lv_style_set_clip_corner(&theme->styles.dialog, true); +#endif + +#if LV_USE_MSGBOX + style_init_reset(&theme->styles.dialog_header); + lv_style_set_pad_hor(&theme->styles.dialog_header, DIALOG_PAD); + lv_style_set_pad_top(&theme->styles.dialog_header, DIALOG_PAD); + lv_style_set_pad_bottom(&theme->styles.dialog_header, DIALOG_GAP); + lv_style_set_pad_gap(&theme->styles.dialog_header, DIALOG_GAP); +#endif + +#if LV_USE_LABEL && LV_USE_MSGBOX + style_init_reset(&theme->styles.dialog_title); + lv_style_set_text_font(&theme->styles.dialog_title, theme->config.font_large); +#endif + +#if LV_USE_MSGBOX + style_init_reset(&theme->styles.dialog_content); + lv_style_set_pad_hor(&theme->styles.dialog_content, DIALOG_PAD); + lv_style_set_pad_ver(&theme->styles.dialog_content, DIALOG_GAP); +#endif + +#if LV_USE_MSGBOX + style_init_reset(&theme->styles.dialog_actions); + lv_style_set_pad_all(&theme->styles.dialog_actions, DIALOG_PAD); + lv_style_set_pad_top(&theme->styles.dialog_actions, DIALOG_GAP); + lv_style_set_pad_gap(&theme->styles.dialog_actions, DIALOG_GAP); +#endif + +#if LV_USE_MENU + style_init_reset(&theme->styles.settings); + lv_style_set_bg_color(&theme->styles.settings, COLOR_SURFACE); + lv_style_set_bg_opa(&theme->styles.settings, LV_OPA_COVER); + lv_style_set_pad_all(&theme->styles.settings, 0); + lv_style_set_pad_gap(&theme->styles.settings, 0); + lv_style_set_radius(&theme->styles.settings, 0); + lv_style_set_clip_corner(&theme->styles.settings, true); +#endif + +#if LV_USE_MENU + style_init_reset(&theme->styles.settings_sidebar); + lv_style_set_pad_all(&theme->styles.settings_sidebar, 0); + lv_style_set_pad_gap(&theme->styles.settings_sidebar, 0); + lv_style_set_border_color(&theme->styles.settings_sidebar, COLOR_OUTLINE); + lv_style_set_border_width(&theme->styles.settings_sidebar, BORDER); + lv_style_set_border_side(&theme->styles.settings_sidebar, LV_BORDER_SIDE_RIGHT); +#endif + +#if LV_USE_MENU + style_init_reset(&theme->styles.settings_content); + lv_style_set_pad_all(&theme->styles.settings_content, 0); + lv_style_set_pad_gap(&theme->styles.settings_content, 0); +#endif + +#if LV_USE_MENU + style_init_reset(&theme->styles.settings_page); + lv_style_set_pad_hor(&theme->styles.settings_page, 0); + lv_style_set_pad_gap(&theme->styles.settings_page, 0); +#endif + +#if LV_USE_MENU + style_init_reset(&theme->styles.settings_header); + lv_style_set_pad_hor(&theme->styles.settings_header, DENSITY_LIST_ITEM_PAD_HOR); + lv_style_set_pad_ver(&theme->styles.settings_header, DENSITY_LIST_PAD); + lv_style_set_pad_gap(&theme->styles.settings_header, DENSITY_CONTAINER_GAP); +#endif + +#if LV_USE_MENU + style_init_reset(&theme->styles.settings_section); + lv_style_set_bg_color(&theme->styles.settings_section, COLOR_SURFACE); + lv_style_set_bg_opa(&theme->styles.settings_section, LV_OPA_COVER); + lv_style_set_border_color(&theme->styles.settings_section, COLOR_OUTLINE); + lv_style_set_border_width(&theme->styles.settings_section, BORDER); + lv_style_set_radius(&theme->styles.settings_section, SHAPE_CONTAINER); + lv_style_set_clip_corner(&theme->styles.settings_section, true); +#endif + +#if LV_USE_MENU + style_init_reset(&theme->styles.settings_item); + lv_style_set_pad_hor(&theme->styles.settings_item, DENSITY_LIST_ITEM_PAD_HOR); + lv_style_set_pad_ver(&theme->styles.settings_item, DENSITY_LIST_ITEM_PAD_VER); + lv_style_set_pad_gap(&theme->styles.settings_item, DENSITY_CONTAINER_GAP); +#endif + +#if LV_USE_MENU + style_init_reset(&theme->styles.settings_separator); + lv_style_set_bg_opa(&theme->styles.settings_separator, LV_OPA_TRANSP); + lv_style_set_pad_ver(&theme->styles.settings_separator, DENSITY_LIST_PAD); +#endif + +#if LV_USE_WIN + style_init_reset(&theme->styles.window_header); + lv_style_set_bg_color(&theme->styles.window_header, COLOR_SURFACE); + lv_style_set_bg_opa(&theme->styles.window_header, LV_OPA_COVER); + lv_style_set_border_color(&theme->styles.window_header, WINDOW_HEADER_BORDER_COLOR); + lv_style_set_border_width(&theme->styles.window_header, BORDER); + lv_style_set_border_side(&theme->styles.window_header, LV_BORDER_SIDE_BOTTOM); + lv_style_set_pad_all(&theme->styles.window_header, WINDOW_HEADER_PAD); + lv_style_set_pad_gap(&theme->styles.window_header, WINDOW_HEADER_PAD); +#endif + +#if LV_USE_LABEL && LV_USE_WIN + style_init_reset(&theme->styles.window_title); + lv_style_set_text_font(&theme->styles.window_title, theme->config.font_large); +#endif + +#if LV_USE_WIN + style_init_reset(&theme->styles.window_content); + lv_style_set_bg_opa(&theme->styles.window_content, LV_OPA_TRANSP); + lv_style_set_pad_all(&theme->styles.window_content, WINDOW_CONTENT_PAD); + lv_style_set_pad_gap(&theme->styles.window_content, DENSITY_CONTAINER_GAP); +#endif + +#if LV_USE_TABLE + style_init_reset(&theme->styles.table); + lv_style_set_bg_color(&theme->styles.table, COLOR_SURFACE); + lv_style_set_bg_opa(&theme->styles.table, LV_OPA_COVER); + lv_style_set_border_color(&theme->styles.table, COLOR_OUTLINE); + lv_style_set_border_width(&theme->styles.table, BORDER); + lv_style_set_radius(&theme->styles.table, 0); + lv_style_set_pad_all(&theme->styles.table, 0); +#endif + +#if LV_USE_TABLE + style_init_reset(&theme->styles.table_cell); + lv_style_set_bg_opa(&theme->styles.table_cell, LV_OPA_TRANSP); + lv_style_set_text_color(&theme->styles.table_cell, COLOR_ON_SURFACE); + lv_style_set_pad_all(&theme->styles.table_cell, TABLE_CELL_PAD); + lv_style_set_border_color(&theme->styles.table_cell, TABLE_DIVIDER_COLOR); + lv_style_set_border_width(&theme->styles.table_cell, BORDER); + lv_style_set_border_side(&theme->styles.table_cell, LV_BORDER_SIDE_BOTTOM); +#endif + +#if LV_USE_CHART + style_init_reset(&theme->styles.chart); + lv_style_set_bg_color(&theme->styles.chart, COLOR_SURFACE); + lv_style_set_bg_opa(&theme->styles.chart, LV_OPA_COVER); + lv_style_set_border_color(&theme->styles.chart, COLOR_OUTLINE); + lv_style_set_border_width(&theme->styles.chart, BORDER); + lv_style_set_radius(&theme->styles.chart, SHAPE_CONTAINER); + lv_style_set_pad_all(&theme->styles.chart, DENSITY_CONTAINER_PAD); + lv_style_set_line_color(&theme->styles.chart, CHART_GRID_COLOR); +#endif + +#if LV_USE_CHART + style_init_reset(&theme->styles.chart_series); + lv_style_set_line_color(&theme->styles.chart_series, COLOR_ON_SURFACE); + lv_style_set_line_width(&theme->styles.chart_series, LINE_MEDIUM); + lv_style_set_size(&theme->styles.chart_series, CHART_POINT_SIZE, CHART_POINT_SIZE); +#endif + +#if LV_USE_CHART + style_init_reset(&theme->styles.chart_indicator); + lv_style_set_bg_color(&theme->styles.chart_indicator, COLOR_PRIMARY); + lv_style_set_bg_opa(&theme->styles.chart_indicator, LV_OPA_COVER); + lv_style_set_radius(&theme->styles.chart_indicator, LV_RADIUS_CIRCLE); + lv_style_set_size(&theme->styles.chart_indicator, CHART_POINT_SIZE, CHART_POINT_SIZE); +#endif + +#if LV_USE_CALENDAR + style_init_reset(&theme->styles.calendar); + lv_style_set_bg_color(&theme->styles.calendar, COLOR_SURFACE); + lv_style_set_bg_opa(&theme->styles.calendar, LV_OPA_COVER); + lv_style_set_border_color(&theme->styles.calendar, COLOR_OUTLINE); + lv_style_set_border_width(&theme->styles.calendar, BORDER); + lv_style_set_radius(&theme->styles.calendar, SHAPE_CONTAINER); + lv_style_set_pad_all(&theme->styles.calendar, CALENDAR_PAD); + lv_style_set_pad_gap(&theme->styles.calendar, CALENDAR_PAD); +#endif + +#if LV_USE_BUTTONMATRIX && LV_USE_CALENDAR + style_init_reset(&theme->styles.calendar_day); + lv_style_set_bg_opa(&theme->styles.calendar_day, LV_OPA_TRANSP); + lv_style_set_border_width(&theme->styles.calendar_day, 0); + lv_style_set_radius(&theme->styles.calendar_day, LV_RADIUS_CIRCLE); +#endif + +#if LV_USE_SCALE + style_init_reset(&theme->styles.scale); + lv_style_set_line_color(&theme->styles.scale, SCALE_COLOR); + lv_style_set_line_width(&theme->styles.scale, BORDER); + lv_style_set_arc_color(&theme->styles.scale, SCALE_COLOR); + lv_style_set_arc_width(&theme->styles.scale, BORDER); + lv_style_set_text_color(&theme->styles.scale, SCALE_COLOR); + lv_style_set_length(&theme->styles.scale, SCALE_TICK_LENGTH); +#endif + +#if LV_USE_LINE + style_init_reset(&theme->styles.divider); + lv_style_set_line_color(&theme->styles.divider, DIVIDER_COLOR); + lv_style_set_line_width(&theme->styles.divider, BORDER); +#endif + +#if LV_USE_LED + style_init_reset(&theme->styles.led); + lv_style_set_bg_color(&theme->styles.led, COLOR_ON_SURFACE); + lv_style_set_bg_opa(&theme->styles.led, LV_OPA_COVER); + lv_style_set_radius(&theme->styles.led, LV_RADIUS_CIRCLE); + lv_style_set_border_color(&theme->styles.led, COLOR_OUTLINE); + lv_style_set_border_width(&theme->styles.led, BORDER); +#endif +} + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +void lv_theme_material_mono_config_init(lv_theme_material_mono_config_t * config) +{ + lv_memzero(config, sizeof(*config)); + config->is_dark = false; + config->is_compact = false; + config->animations_enabled = true; + config->color_primary = lv_color_black(); + config->color_secondary = lv_color_black(); + config->font_small = LV_FONT_DEFAULT; + config->font_normal = LV_FONT_DEFAULT; + config->font_large = LV_FONT_DEFAULT; +} + +lv_theme_t * lv_theme_material_mono_init(lv_display_t * disp, const lv_theme_material_mono_config_t * config) +{ + if(!lv_theme_material_mono_is_inited()) { + theme_def = lv_malloc_zeroed(sizeof(my_theme_t)); + LV_ASSERT_MALLOC(theme_def); + } + + my_theme_t * theme = theme_def; + + lv_display_t * new_disp = disp == NULL ? lv_display_get_default() : disp; + int32_t new_dpi = lv_display_get_dpi(new_disp); + int32_t hor_res = lv_display_get_horizontal_resolution(new_disp); + int32_t ver_res = lv_display_get_vertical_resolution(new_disp); + int32_t greater_res = LV_MAX(hor_res, ver_res); + disp_size_t new_size; + + if(greater_res <= 320) new_size = DISP_SMALL; + else if(greater_res < 720) new_size = DISP_MEDIUM; + else new_size = DISP_LARGE; + + if(theme->inited && theme->disp_dpi == new_dpi && theme->disp_size == new_size && + config_equals(&theme->config, config)) { + return (lv_theme_t *) theme; + } + + theme->disp_size = new_size; + theme->disp_dpi = new_dpi; + theme->config = *config; + theme->base.disp = new_disp; + theme->base.apply_cb = theme_apply; + theme->base.color_primary = config->color_primary; + theme->base.color_secondary = config->color_secondary; + theme->base.font_small = config->font_small; + theme->base.font_normal = config->font_normal; + theme->base.font_large = config->font_large; + + style_init(theme); + + if(disp == NULL || lv_display_get_theme(disp) == (lv_theme_t *)theme) { + lv_obj_report_style_change(NULL); + } + + theme->inited = true; + + /* Re-initialize the styles when the resolution changes, as the display size affects the styles */ + lv_display_remove_event_cb_with_user_data(new_disp, resolution_change_event_cb, theme); + lv_display_add_event_cb(new_disp, resolution_change_event_cb, LV_EVENT_RESOLUTION_CHANGED, theme); + + return (lv_theme_t *) theme; +} + +bool lv_theme_material_mono_is_inited(void) +{ + return theme_def != NULL && theme_def->inited; +} + +lv_theme_t * lv_theme_material_mono_get(void) +{ + if(!lv_theme_material_mono_is_inited()) { + return NULL; + } + return (lv_theme_t *) theme_def; +} + +void lv_theme_material_mono_deinit(void) +{ + my_theme_t * theme = theme_def; + if(theme) { + if(theme->inited) { + lv_style_t * theme_styles = (lv_style_t *)(&(theme->styles)); + uint32_t i; + for(i = 0; i < sizeof(my_theme_styles_t) / sizeof(lv_style_t); i++) { + lv_style_reset(theme_styles + i); + } + } + lv_free(theme_def); + theme_def = NULL; + } +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void theme_apply(lv_theme_t * th, lv_obj_t * obj) +{ + LV_UNUSED(th); + + my_theme_t * theme = theme_def; + lv_obj_t * parent = lv_obj_get_parent(obj); + LV_UNUSED(theme); + LV_UNUSED(parent); + + /* null > * */ + if(parent == NULL) { + lv_obj_add_style(obj, &theme->styles.screen, 0); + lv_obj_add_style(obj, &theme->styles.scrollbar, LV_PART_SCROLLBAR); + return; + } + if(lv_obj_check_type(obj, &lv_obj_class)) { +#if LV_USE_TABVIEW + /* tabview > obj[1] */ + if(lv_obj_check_type(parent, &lv_tabview_class) && lss_is_child(parent, 1, obj)) { + return; + } +#endif +#if LV_USE_TABVIEW + /* tabview > obj[0] */ + if(lv_obj_check_type(parent, &lv_tabview_class) && lss_is_child(parent, 0, obj)) { + lv_obj_add_style(obj, &theme->styles.tabs, 0); + return; + } +#endif +#if LV_USE_TABVIEW + /* tabview > * > obj */ + if(lv_obj_check_type(lss_ancestor(obj, 2), &lv_tabview_class)) { + lv_obj_add_style(obj, &theme->styles.tab_page, 0); + lv_obj_add_style(obj, &theme->styles.scrollbar, LV_PART_SCROLLBAR); + return; + } +#endif +#if LV_USE_WIN + /* win > obj[0] */ + if(lv_obj_check_type(parent, &lv_win_class) && lss_is_child(parent, 0, obj)) { + lv_obj_add_style(obj, &theme->styles.window_header, 0); + return; + } +#endif +#if LV_USE_WIN + /* win > obj[1] */ + if(lv_obj_check_type(parent, &lv_win_class) && lss_is_child(parent, 1, obj)) { + lv_obj_add_style(obj, &theme->styles.window_content, 0); + lv_obj_add_style(obj, &theme->styles.scrollbar, LV_PART_SCROLLBAR); + return; + } +#endif +#if LV_USE_CALENDAR + /* calendar > obj */ + if(lv_obj_check_type(parent, &lv_calendar_class)) { + return; + } +#endif + /* obj */ + lv_obj_add_style(obj, &theme->styles.container, 0); + lv_obj_add_style(obj, &theme->styles.scrollbar, LV_PART_SCROLLBAR); + return; + } +#if LV_USE_BUTTON + if(lv_obj_check_type(obj, &lv_button_class)) { +#if LV_USE_TABVIEW + /* tabview > *[0] > button */ + if(lv_obj_check_type(lss_ancestor(obj, 2), &lv_tabview_class) && lss_is_child(lss_ancestor(obj, 2), 0, parent)) { + lv_obj_add_style(obj, &theme->styles.tab, 0); + lv_obj_add_style(obj, &theme->styles.inverted, LV_STATE_PRESSED); + lv_obj_add_style(obj, &theme->styles.tab_selected, LV_STATE_CHECKED); + lv_obj_add_style(obj, &theme->styles.focus_ring, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &theme->styles.edit_ring, LV_STATE_EDITED); + return; + } +#endif +#if LV_USE_MENU + /* menu_sidebar_header_cont > button */ + if(lv_obj_check_type(parent, &lv_menu_sidebar_header_cont_class)) { + lv_obj_add_style(obj, &theme->styles.icon_button, 0); + lv_obj_add_style(obj, &theme->styles.inverted, LV_STATE_PRESSED); + lv_obj_add_style(obj, &theme->styles.focus_ring, LV_STATE_FOCUS_KEY); + return; + } +#endif +#if LV_USE_MENU + /* menu_main_header_cont > button */ + if(lv_obj_check_type(parent, &lv_menu_main_header_cont_class)) { + lv_obj_add_style(obj, &theme->styles.icon_button, 0); + lv_obj_add_style(obj, &theme->styles.inverted, LV_STATE_PRESSED); + lv_obj_add_style(obj, &theme->styles.focus_ring, LV_STATE_FOCUS_KEY); + return; + } +#endif +#if LV_USE_WIN + /* win > obj[0] > button */ + if(lv_obj_check_type(lss_ancestor(obj, 2), &lv_win_class) && lv_obj_check_type(parent, &lv_obj_class) && lss_is_child(lss_ancestor(obj, 2), 0, parent)) { + lv_obj_add_style(obj, &theme->styles.icon_button, 0); + lv_obj_add_style(obj, &theme->styles.inverted, LV_STATE_PRESSED); + lv_obj_add_style(obj, &theme->styles.focus_ring, LV_STATE_FOCUS_KEY); + return; + } +#endif + /* button */ + lv_obj_add_style(obj, &theme->styles.button, 0); + lv_obj_add_style(obj, &theme->styles.inverted, LV_STATE_PRESSED); + lv_obj_add_style(obj, &theme->styles.inverted, LV_STATE_CHECKED); + lv_obj_add_style(obj, &theme->styles.focus_ring, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &theme->styles.edit_ring, LV_STATE_EDITED); + lv_obj_add_style(obj, &theme->styles.disabled, LV_STATE_DISABLED); + return; + } +#endif + if(lv_obj_check_type(obj, &lvgl_icon_button_class)) { + /* toolbar > icon_button */ + if(lv_obj_check_type(parent, &lvgl_toolbar_class)) { + lv_obj_add_style(obj, &theme->styles.icon_button, 0); + lv_obj_add_style(obj, &theme->styles.app_bar_button, 0); + lv_obj_add_style(obj, &theme->styles.inverted, LV_STATE_PRESSED); + lv_obj_add_style(obj, &theme->styles.app_bar_focus_ring, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &theme->styles.app_bar_focus_ring, LV_STATE_EDITED); + lv_obj_add_style(obj, &theme->styles.disabled, LV_STATE_DISABLED); + return; + } + /* icon_button */ + lv_obj_add_style(obj, &theme->styles.icon_button, 0); + lv_obj_add_style(obj, &theme->styles.inverted, LV_STATE_PRESSED); + lv_obj_add_style(obj, &theme->styles.focus_ring_inset, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &theme->styles.edit_ring_inset, LV_STATE_EDITED); + lv_obj_add_style(obj, &theme->styles.disabled, LV_STATE_DISABLED); + return; + } + if(lv_obj_check_type(obj, &lvgl_toolbar_class)) { + /* toolbar */ + lv_obj_add_style(obj, &theme->styles.app_bar, 0); + return; + } + if(lv_obj_check_type(obj, &lvgl_icon_button_filled_class)) { + /* icon_button_filled */ + lv_obj_add_style(obj, &theme->styles.icon_button_filled, 0); + lv_obj_add_style(obj, &theme->styles.icon_button_filled_pressed, LV_STATE_PRESSED); + lv_obj_add_style(obj, &theme->styles.focus_ring, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &theme->styles.edit_ring, LV_STATE_EDITED); + lv_obj_add_style(obj, &theme->styles.disabled, LV_STATE_DISABLED); + return; + } + if(lv_obj_check_type(obj, &lvgl_icon_button_tonal_class)) { + /* icon_button_tonal */ + lv_obj_add_style(obj, &theme->styles.icon_button_tonal, 0); + lv_obj_add_style(obj, &theme->styles.inverted, LV_STATE_PRESSED); + lv_obj_add_style(obj, &theme->styles.focus_ring, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &theme->styles.edit_ring, LV_STATE_EDITED); + lv_obj_add_style(obj, &theme->styles.disabled, LV_STATE_DISABLED); + return; + } +#if LV_USE_MSGBOX + if(lv_obj_check_type(obj, &lv_msgbox_header_button_class)) { + /* msgbox_header_button */ + lv_obj_add_style(obj, &theme->styles.icon_button, 0); + lv_obj_add_style(obj, &theme->styles.inverted, LV_STATE_PRESSED); + lv_obj_add_style(obj, &theme->styles.focus_ring, LV_STATE_FOCUS_KEY); + return; + } +#endif +#if LV_USE_MSGBOX + if(lv_obj_check_type(obj, &lv_msgbox_footer_button_class)) { + /* msgbox_footer_button */ + lv_obj_add_style(obj, &theme->styles.button, 0); + lv_obj_add_style(obj, &theme->styles.inverted, LV_STATE_PRESSED); + lv_obj_add_style(obj, &theme->styles.inverted, LV_STATE_CHECKED); + lv_obj_add_style(obj, &theme->styles.focus_ring, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &theme->styles.edit_ring, LV_STATE_EDITED); + lv_obj_add_style(obj, &theme->styles.disabled, LV_STATE_DISABLED); + return; + } +#endif +#if LV_USE_LABEL + if(lv_obj_check_type(obj, &lv_label_class)) { +#if LV_USE_MSGBOX + /* msgbox_header > label */ + if(lv_obj_check_type(parent, &lv_msgbox_header_class)) { + lv_obj_add_style(obj, &theme->styles.dialog_title, 0); + return; + } +#endif +#if LV_USE_WIN + /* win > obj[0] > label */ + if(lv_obj_check_type(lss_ancestor(obj, 2), &lv_win_class) && lv_obj_check_type(parent, &lv_obj_class) && lss_is_child(lss_ancestor(obj, 2), 0, parent)) { + lv_obj_add_style(obj, &theme->styles.window_title, 0); + return; + } +#endif +#if LV_USE_TEXTAREA + /* textarea > label */ + if(lv_obj_check_type(parent, &lv_textarea_class)) { + lv_obj_add_style(obj, &theme->styles.field_selection, LV_PART_SELECTED); + return; + } +#endif + /* page_indicator > label */ + if(lv_obj_check_type(parent, &lvgl_page_indicator_class)) { + lv_obj_add_style(obj, &theme->styles.page_indicator_label, 0); + return; + } + return; + } +#endif +#if LV_USE_SWITCH + if(lv_obj_check_type(obj, &lv_switch_class)) { + /* switch */ + lv_obj_add_style(obj, &theme->styles.switch_track, 0); + lv_obj_add_style(obj, &theme->styles.switch_indicator_selected, LV_PART_INDICATOR | LV_STATE_CHECKED); + lv_obj_add_style(obj, &theme->styles.switch_handle, LV_PART_KNOB); + lv_obj_add_style(obj, &theme->styles.switch_handle_selected, LV_PART_KNOB | LV_STATE_CHECKED); + lv_obj_add_style(obj, &theme->styles.focus_ring, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &theme->styles.edit_ring, LV_STATE_EDITED); + lv_obj_add_style(obj, &theme->styles.disabled, LV_STATE_DISABLED); + return; + } +#endif +#if LV_USE_CHECKBOX + if(lv_obj_check_type(obj, &lv_checkbox_class)) { + /* checkbox */ + lv_obj_add_style(obj, &theme->styles.checkbox, 0); + lv_obj_add_style(obj, &theme->styles.focus_ring, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &theme->styles.checkbox_box, LV_PART_INDICATOR); + lv_obj_add_style(obj, &theme->styles.checkbox_box_selected, LV_PART_INDICATOR | LV_STATE_CHECKED); + lv_obj_add_style(obj, &theme->styles.disabled, LV_PART_INDICATOR | LV_STATE_DISABLED); + return; + } +#endif + if(lv_obj_check_type(obj, &lvgl_page_indicator_class)) { + /* page_indicator */ + lv_obj_add_style(obj, &theme->styles.page_indicator, 0); + return; + } + if(lv_obj_check_type(obj, &lvgl_page_indicator_dot_class)) { + /* page_indicator_dot */ + lv_obj_add_style(obj, &theme->styles.page_indicator_dot, 0); + lv_obj_add_style(obj, &theme->styles.page_indicator_dot_current, LV_STATE_CHECKED); + return; + } + if(lv_obj_check_type(obj, &lvgl_badge_class)) { + /* badge */ + lv_obj_add_style(obj, &theme->styles.badge, 0); + return; + } + if(lv_obj_check_type(obj, &lvgl_chip_class)) { + /* chip */ + lv_obj_add_style(obj, &theme->styles.chip, 0); + lv_obj_add_style(obj, &theme->styles.inverted, LV_STATE_CHECKED); + lv_obj_add_style(obj, &theme->styles.inverted, LV_STATE_PRESSED); + lv_obj_add_style(obj, &theme->styles.focus_ring, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &theme->styles.edit_ring, LV_STATE_EDITED); + lv_obj_add_style(obj, &theme->styles.disabled, LV_STATE_DISABLED); + return; + } + if(lv_obj_check_type(obj, &lvgl_card_class)) { + /* card */ + lv_obj_add_style(obj, &theme->styles.card, 0); + lv_obj_add_style(obj, &theme->styles.scrollbar, LV_PART_SCROLLBAR); + return; + } + if(lv_obj_check_type(obj, &lvgl_sliderbox_class)) { + /* sliderbox */ + lv_obj_add_style(obj, &theme->styles.sliderbox, 0); + return; + } +#if LV_USE_SLIDER + if(lv_obj_check_type(obj, &lv_slider_class)) { + /* slider */ + lv_obj_add_style(obj, &theme->styles.slider_track, 0); + lv_obj_add_style(obj, &theme->styles.slider_track_size, 0); + lv_obj_add_style(obj, &theme->styles.slider_knob_room, 0); + lv_obj_add_style(obj, &theme->styles.focus_ring, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &theme->styles.edit_ring, LV_STATE_EDITED); + lv_obj_add_style(obj, &theme->styles.slider_active_track, LV_PART_INDICATOR); + lv_obj_add_style(obj, &theme->styles.slider_handle, LV_PART_KNOB); + return; + } +#endif +#if LV_USE_BAR + if(lv_obj_check_type(obj, &lv_bar_class)) { + /* bar */ + lv_obj_add_style(obj, &theme->styles.slider_track_size, 0); + lv_obj_add_style(obj, &theme->styles.slider_track, 0); + lv_obj_add_style(obj, &theme->styles.slider_active_track, LV_PART_INDICATOR); + lv_obj_add_style(obj, &theme->styles.focus_ring, LV_STATE_FOCUS_KEY); + return; + } +#endif +#if LV_USE_ARC + if(lv_obj_check_type(obj, &lv_arc_class)) { + /* arc */ + lv_obj_add_style(obj, &theme->styles.arc_track, 0); + lv_obj_add_style(obj, &theme->styles.arc_indicator, LV_PART_INDICATOR); + lv_obj_add_style(obj, &theme->styles.arc_handle, LV_PART_KNOB); + lv_obj_add_style(obj, &theme->styles.focus_ring, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &theme->styles.edit_ring, LV_STATE_EDITED); + return; + } +#endif +#if LV_USE_SPINNER + if(lv_obj_check_type(obj, &lv_spinner_class)) { + /* spinner */ + lv_obj_add_style(obj, &theme->styles.arc_track, 0); + lv_obj_add_style(obj, &theme->styles.arc_indicator, LV_PART_INDICATOR); + return; + } +#endif +#if LV_USE_TEXTAREA + if(lv_obj_check_type(obj, &lv_textarea_class)) { + /* textarea */ + lv_obj_add_style(obj, &theme->styles.field, 0); + lv_obj_add_style(obj, &theme->styles.field_focused, LV_STATE_FOCUSED); + lv_obj_add_style(obj, &theme->styles.focus_ring, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &theme->styles.edit_ring, LV_STATE_EDITED); + lv_obj_add_style(obj, &theme->styles.disabled, LV_STATE_DISABLED); + lv_obj_add_style(obj, &theme->styles.scrollbar, LV_PART_SCROLLBAR); + lv_obj_add_style(obj, &theme->styles.field_cursor, LV_PART_CURSOR | LV_STATE_FOCUSED); + return; + } +#endif +#if LV_USE_SPINBOX + if(lv_obj_check_type(obj, &lv_spinbox_class)) { + /* spinbox */ + lv_obj_add_style(obj, &theme->styles.field, 0); + lv_obj_add_style(obj, &theme->styles.field_focused, LV_STATE_FOCUSED); + lv_obj_add_style(obj, &theme->styles.focus_ring, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &theme->styles.edit_ring, LV_STATE_EDITED); + lv_obj_add_style(obj, &theme->styles.inverted, LV_PART_CURSOR); + return; + } +#endif +#if LV_USE_DROPDOWN + if(lv_obj_check_type(obj, &lv_dropdown_class)) { + /* dropdown */ + lv_obj_add_style(obj, &theme->styles.field, 0); + lv_obj_add_style(obj, &theme->styles.inverted, LV_STATE_PRESSED); + lv_obj_add_style(obj, &theme->styles.focus_ring, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &theme->styles.edit_ring, LV_STATE_EDITED); + lv_obj_add_style(obj, &theme->styles.disabled, LV_STATE_DISABLED); + return; + } +#endif +#if LV_USE_DROPDOWN + if(lv_obj_check_type(obj, &lv_dropdownlist_class)) { + /* dropdownlist */ + lv_obj_add_style(obj, &theme->styles.menu, 0); + lv_obj_add_style(obj, &theme->styles.scrollbar, LV_PART_SCROLLBAR); + lv_obj_add_style(obj, &theme->styles.menu_item, LV_PART_SELECTED); + lv_obj_add_style(obj, &theme->styles.inverted, LV_PART_SELECTED | LV_STATE_CHECKED); + lv_obj_add_style(obj, &theme->styles.inverted, LV_PART_SELECTED | LV_STATE_PRESSED); + return; + } +#endif +#if LV_USE_ROLLER + if(lv_obj_check_type(obj, &lv_roller_class)) { + /* roller */ + lv_obj_add_style(obj, &theme->styles.roller, 0); + lv_obj_add_style(obj, &theme->styles.inverted, LV_PART_SELECTED); + lv_obj_add_style(obj, &theme->styles.focus_ring, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &theme->styles.edit_ring, LV_STATE_EDITED); + return; + } +#endif +#if LV_USE_BUTTONMATRIX + if(lv_obj_check_type(obj, &lv_buttonmatrix_class)) { +#if LV_USE_CALENDAR + /* calendar > buttonmatrix */ + if(lv_obj_check_type(parent, &lv_calendar_class)) { + lv_obj_add_style(obj, &theme->styles.keys, 0); + lv_obj_add_style(obj, &theme->styles.calendar_day, LV_PART_ITEMS); + lv_obj_add_style(obj, &theme->styles.inverted, LV_PART_ITEMS | LV_STATE_PRESSED); + lv_obj_add_style(obj, &theme->styles.inverted, LV_PART_ITEMS | LV_STATE_CHECKED); + lv_obj_add_style(obj, &theme->styles.focus_ring, LV_PART_ITEMS | LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &theme->styles.disabled, LV_PART_ITEMS | LV_STATE_DISABLED); + return; + } +#endif + /* buttonmatrix */ + lv_obj_add_style(obj, &theme->styles.keys, 0); + lv_obj_add_style(obj, &theme->styles.focus_ring, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &theme->styles.edit_ring, LV_STATE_EDITED); + lv_obj_add_style(obj, &theme->styles.key, LV_PART_ITEMS); + lv_obj_add_style(obj, &theme->styles.inverted, LV_PART_ITEMS | LV_STATE_PRESSED); + lv_obj_add_style(obj, &theme->styles.inverted, LV_PART_ITEMS | LV_STATE_CHECKED); + lv_obj_add_style(obj, &theme->styles.focus_ring, LV_PART_ITEMS | LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &theme->styles.disabled, LV_PART_ITEMS | LV_STATE_DISABLED); + return; + } +#endif +#if LV_USE_KEYBOARD + if(lv_obj_check_type(obj, &lv_keyboard_class)) { + /* keyboard */ + lv_obj_add_style(obj, &theme->styles.keys, 0); + lv_obj_add_style(obj, &theme->styles.focus_ring, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &theme->styles.edit_ring, LV_STATE_EDITED); + lv_obj_add_style(obj, &theme->styles.key, LV_PART_ITEMS); + lv_obj_add_style(obj, &theme->styles.inverted, LV_PART_ITEMS | LV_STATE_PRESSED); + lv_obj_add_style(obj, &theme->styles.inverted, LV_PART_ITEMS | LV_STATE_CHECKED); + lv_obj_add_style(obj, &theme->styles.focus_ring, LV_PART_ITEMS | LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &theme->styles.disabled, LV_PART_ITEMS | LV_STATE_DISABLED); + return; + } +#endif +#if LV_USE_LIST + if(lv_obj_check_type(obj, &lv_list_class)) { + /* list */ + lv_obj_add_style(obj, &theme->styles.list, 0); + lv_obj_add_style(obj, &theme->styles.scrollbar, LV_PART_SCROLLBAR); + return; + } +#endif +#if LV_USE_LIST + if(lv_obj_check_type(obj, &lv_list_text_class)) { + /* list_text */ + lv_obj_add_style(obj, &theme->styles.list_subheader, 0); + return; + } +#endif +#if LV_USE_LIST + if(lv_obj_check_type(obj, &lv_list_button_class)) { + /* list_button */ + lv_obj_add_style(obj, &theme->styles.list_item, 0); + lv_obj_add_style(obj, &theme->styles.inverted, LV_STATE_PRESSED); + lv_obj_add_style(obj, &theme->styles.inverted, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &theme->styles.disabled, LV_STATE_DISABLED); + return; + } +#endif +#if LV_USE_TABLE + if(lv_obj_check_type(obj, &lv_table_class)) { + /* table */ + lv_obj_add_style(obj, &theme->styles.table, 0); + lv_obj_add_style(obj, &theme->styles.focus_ring, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &theme->styles.edit_ring, LV_STATE_EDITED); + lv_obj_add_style(obj, &theme->styles.scrollbar, LV_PART_SCROLLBAR); + lv_obj_add_style(obj, &theme->styles.table_cell, LV_PART_ITEMS); + lv_obj_add_style(obj, &theme->styles.inverted, LV_PART_ITEMS | LV_STATE_PRESSED); + lv_obj_add_style(obj, &theme->styles.inverted, LV_PART_ITEMS | LV_STATE_FOCUS_KEY); + return; + } +#endif +#if LV_USE_MENU + if(lv_obj_check_type(obj, &lv_menu_class)) { + /* menu */ + lv_obj_add_style(obj, &theme->styles.settings, 0); + return; + } +#endif +#if LV_USE_MENU + if(lv_obj_check_type(obj, &lv_menu_sidebar_cont_class)) { + /* menu_sidebar_cont */ + lv_obj_add_style(obj, &theme->styles.settings_sidebar, 0); + lv_obj_add_style(obj, &theme->styles.scrollbar, LV_PART_SCROLLBAR); + return; + } +#endif +#if LV_USE_MENU + if(lv_obj_check_type(obj, &lv_menu_main_cont_class)) { + /* menu_main_cont */ + lv_obj_add_style(obj, &theme->styles.settings_content, 0); + lv_obj_add_style(obj, &theme->styles.scrollbar, LV_PART_SCROLLBAR); + return; + } +#endif +#if LV_USE_MENU + if(lv_obj_check_type(obj, &lv_menu_page_class)) { + /* menu_page */ + lv_obj_add_style(obj, &theme->styles.settings_page, 0); + lv_obj_add_style(obj, &theme->styles.scrollbar, LV_PART_SCROLLBAR); + return; + } +#endif +#if LV_USE_MENU + if(lv_obj_check_type(obj, &lv_menu_sidebar_header_cont_class)) { + /* menu_sidebar_header_cont */ + lv_obj_add_style(obj, &theme->styles.settings_header, 0); + return; + } +#endif +#if LV_USE_MENU + if(lv_obj_check_type(obj, &lv_menu_main_header_cont_class)) { + /* menu_main_header_cont */ + lv_obj_add_style(obj, &theme->styles.settings_header, 0); + return; + } +#endif +#if LV_USE_MENU + if(lv_obj_check_type(obj, &lv_menu_section_class)) { + /* menu_section */ + lv_obj_add_style(obj, &theme->styles.settings_section, 0); + return; + } +#endif +#if LV_USE_MENU + if(lv_obj_check_type(obj, &lv_menu_cont_class)) { + /* menu_cont */ + lv_obj_add_style(obj, &theme->styles.settings_item, 0); + lv_obj_add_style(obj, &theme->styles.inverted, LV_STATE_PRESSED); + lv_obj_add_style(obj, &theme->styles.inverted, LV_STATE_CHECKED); + lv_obj_add_style(obj, &theme->styles.inverted, LV_STATE_FOCUS_KEY); + return; + } +#endif +#if LV_USE_MENU + if(lv_obj_check_type(obj, &lv_menu_separator_class)) { + /* menu_separator */ + lv_obj_add_style(obj, &theme->styles.settings_separator, 0); + return; + } +#endif +#if LV_USE_MSGBOX + if(lv_obj_check_type(obj, &lv_msgbox_class)) { + /* msgbox */ + lv_obj_add_style(obj, &theme->styles.dialog, 0); + return; + } +#endif +#if LV_USE_MSGBOX + if(lv_obj_check_type(obj, &lv_msgbox_backdrop_class)) { + /* msgbox_backdrop */ + return; + } +#endif +#if LV_USE_MSGBOX + if(lv_obj_check_type(obj, &lv_msgbox_header_class)) { + /* msgbox_header */ + lv_obj_add_style(obj, &theme->styles.dialog_header, 0); + return; + } +#endif +#if LV_USE_MSGBOX + if(lv_obj_check_type(obj, &lv_msgbox_content_class)) { + /* msgbox_content */ + lv_obj_add_style(obj, &theme->styles.dialog_content, 0); + lv_obj_add_style(obj, &theme->styles.scrollbar, LV_PART_SCROLLBAR); + return; + } +#endif +#if LV_USE_MSGBOX + if(lv_obj_check_type(obj, &lv_msgbox_footer_class)) { + /* msgbox_footer */ + lv_obj_add_style(obj, &theme->styles.dialog_actions, 0); + return; + } +#endif +#if LV_USE_TABVIEW + if(lv_obj_check_type(obj, &lv_tabview_class)) { + /* tabview */ + lv_obj_add_style(obj, &theme->styles.page, 0); + return; + } +#endif +#if LV_USE_WIN + if(lv_obj_check_type(obj, &lv_win_class)) { + /* win */ + lv_obj_add_style(obj, &theme->styles.page, 0); + return; + } +#endif +#if LV_USE_TILEVIEW + if(lv_obj_check_type(obj, &lv_tileview_class)) { + /* tileview */ + lv_obj_add_style(obj, &theme->styles.page, 0); + lv_obj_add_style(obj, &theme->styles.scrollbar, LV_PART_SCROLLBAR); + return; + } +#endif +#if LV_USE_TILEVIEW + if(lv_obj_check_type(obj, &lv_tileview_tile_class)) { + /* tileview_tile */ + lv_obj_add_style(obj, &theme->styles.scrollbar, LV_PART_SCROLLBAR); + return; + } +#endif +#if LV_USE_CHART + if(lv_obj_check_type(obj, &lv_chart_class)) { + /* chart */ + lv_obj_add_style(obj, &theme->styles.chart, 0); + lv_obj_add_style(obj, &theme->styles.scrollbar, LV_PART_SCROLLBAR); + lv_obj_add_style(obj, &theme->styles.chart_series, LV_PART_ITEMS); + lv_obj_add_style(obj, &theme->styles.chart_indicator, LV_PART_INDICATOR); + lv_obj_add_style(obj, &theme->styles.chart_series, LV_PART_CURSOR); + return; + } +#endif +#if LV_USE_CALENDAR + if(lv_obj_check_type(obj, &lv_calendar_class)) { + /* calendar */ + lv_obj_add_style(obj, &theme->styles.calendar, 0); + return; + } +#endif +#if LV_USE_SCALE + if(lv_obj_check_type(obj, &lv_scale_class)) { + /* scale */ + lv_obj_add_style(obj, &theme->styles.scale, LV_PART_MAIN); + lv_obj_add_style(obj, &theme->styles.scale, LV_PART_INDICATOR); + lv_obj_add_style(obj, &theme->styles.scale, LV_PART_ITEMS); + return; + } +#endif +#if LV_USE_LINE + if(lv_obj_check_type(obj, &lv_line_class)) { + /* line */ + lv_obj_add_style(obj, &theme->styles.divider, 0); + return; + } +#endif +#if LV_USE_LED + if(lv_obj_check_type(obj, &lv_led_class)) { + /* led */ + lv_obj_add_style(obj, &theme->styles.led, 0); + return; + } +#endif +} + +static void style_init_reset(lv_style_t * style) +{ + if(lv_theme_material_mono_is_inited()) { + lv_style_reset(style); + } + else { + lv_style_init(style); + } +} + +static void resolution_change_event_cb(lv_event_t * e) +{ + lv_display_t * disp = lv_event_get_target(e); + my_theme_t * theme = lv_event_get_user_data(e); + lv_theme_material_mono_init(disp, &theme->config); +} diff --git a/Modules/lvgl-module/source/themes/lv_theme_material_mono.h b/Modules/lvgl-module/source/themes/lv_theme_material_mono.h new file mode 100644 index 000000000..3da37e557 --- /dev/null +++ b/Modules/lvgl-module/source/themes/lv_theme_material_mono.h @@ -0,0 +1,58 @@ +/* Generated by LSS. Do not edit. */ + +#ifndef LV_THEME_MATERIAL_MONO_H +#define LV_THEME_MATERIAL_MONO_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + bool is_dark; + bool is_compact; + bool animations_enabled; + lv_color_t color_primary; + lv_color_t color_secondary; + const lv_font_t * font_small; + const lv_font_t * font_normal; + const lv_font_t * font_large; +} lv_theme_material_mono_config_t; + +/** + * Reset a config to its default values + * @param config the config to initialize + */ +void lv_theme_material_mono_config_init(lv_theme_material_mono_config_t * config); + +/** + * Initialize the theme + * @param disp pointer to display (NULL for the default display) + * @param config the theme config, which is copied + * @return a pointer to reference this theme later + */ +lv_theme_t * lv_theme_material_mono_init(lv_display_t * disp, const lv_theme_material_mono_config_t * config); + +/** + * Check if the theme is initialized + * @return true if initialized, false otherwise + */ +bool lv_theme_material_mono_is_inited(void); + +/** + * Get the theme + * @return a pointer to the theme, or NULL when not initialized + */ +lv_theme_t * lv_theme_material_mono_get(void); + +/** + * Deinitialize the theme + */ +void lv_theme_material_mono_deinit(void); + +#ifdef __cplusplus +} +#endif + +#endif /* LV_THEME_MATERIAL_MONO_H */ diff --git a/Modules/lvgl-module/source/widgets/badge.cpp b/Modules/lvgl-module/source/widgets/badge.cpp new file mode 100644 index 000000000..d91defb03 --- /dev/null +++ b/Modules/lvgl-module/source/widgets/badge.cpp @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 +#define LV_USE_PRIVATE_API 1 // For the lv_obj_class_t declaration + +#include + +static void badge_constructor(const lv_obj_class_t* class_p, lv_obj_t* obj) { + LV_UNUSED(class_p); + lv_obj_remove_flag(obj, LV_OBJ_FLAG_CLICKABLE); + lv_obj_remove_flag(obj, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_add_flag(obj, LV_OBJ_FLAG_IGNORE_LAYOUT); +} + +// Not static: the theme styles badges by this class +extern "C" const lv_obj_class_t lvgl_badge_class = { + .base_class = &lv_obj_class, + .constructor_cb = badge_constructor, + .destructor_cb = nullptr, + .event_cb = nullptr, + .user_data = nullptr, + .name = "badge", + .width_def = 0, + .height_def = 0, + .editable = LV_OBJ_CLASS_EDITABLE_INHERIT, + .group_def = LV_OBJ_CLASS_GROUP_DEF_FALSE, + .instance_size = 0, + .theme_inheritable = LV_OBJ_CLASS_THEME_INHERITABLE_FALSE +}; + +lv_obj_t* lvgl_badge_create(lv_obj_t* parent) { + lv_obj_t* obj = lv_obj_class_create_obj(&lvgl_badge_class, parent); + lv_obj_class_init_obj(obj); + return obj; +} diff --git a/Modules/lvgl-module/source/widgets/card.cpp b/Modules/lvgl-module/source/widgets/card.cpp new file mode 100644 index 000000000..a1eaa7cb1 --- /dev/null +++ b/Modules/lvgl-module/source/widgets/card.cpp @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +#define LV_USE_PRIVATE_API 1 // For the lv_obj_class_t declaration + +#include + +// Not static: the theme styles cards by this class +extern "C" const lv_obj_class_t lvgl_card_class = { + .base_class = &lv_obj_class, + .constructor_cb = nullptr, + .destructor_cb = nullptr, + .event_cb = nullptr, + .user_data = nullptr, + .name = "card", + .width_def = 0, + .height_def = 0, + .editable = LV_OBJ_CLASS_EDITABLE_INHERIT, + .group_def = LV_OBJ_CLASS_GROUP_DEF_INHERIT, + .instance_size = 0, + .theme_inheritable = LV_OBJ_CLASS_THEME_INHERITABLE_FALSE +}; + +lv_obj_t* lvgl_card_create(lv_obj_t* parent) { + lv_obj_t* obj = lv_obj_class_create_obj(&lvgl_card_class, parent); + lv_obj_class_init_obj(obj); + return obj; +} diff --git a/Modules/lvgl-module/source/widgets/chip.cpp b/Modules/lvgl-module/source/widgets/chip.cpp new file mode 100644 index 000000000..c544954a4 --- /dev/null +++ b/Modules/lvgl-module/source/widgets/chip.cpp @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +#define LV_USE_PRIVATE_API 1 // For the lv_obj_class_t declaration + +#include + +// Not static: the theme styles chips by this class +extern "C" const lv_obj_class_t lvgl_chip_class = { + .base_class = &lv_button_class, + .constructor_cb = nullptr, + .destructor_cb = nullptr, + .event_cb = nullptr, + .user_data = nullptr, + .name = "chip", + .width_def = 0, + .height_def = 0, + .editable = LV_OBJ_CLASS_EDITABLE_INHERIT, + .group_def = LV_OBJ_CLASS_GROUP_DEF_INHERIT, + .instance_size = 0, + .theme_inheritable = LV_OBJ_CLASS_THEME_INHERITABLE_FALSE +}; + +lv_obj_t* lvgl_chip_create(lv_obj_t* parent) { + lv_obj_t* obj = lv_obj_class_create_obj(&lvgl_chip_class, parent); + lv_obj_class_init_obj(obj); + return obj; +} diff --git a/Modules/lvgl-module/source/widgets/icon_button.cpp b/Modules/lvgl-module/source/widgets/icon_button.cpp new file mode 100644 index 000000000..b94f25a12 --- /dev/null +++ b/Modules/lvgl-module/source/widgets/icon_button.cpp @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: Apache-2.0 +#define LV_USE_PRIVATE_API 1 // For the lv_obj_class_t declaration + +#include + +// Not static: the theme styles icon buttons by these classes. Each variant is a separate class, +// because the theme applies its styles while the object is created. + +extern "C" const lv_obj_class_t lvgl_icon_button_class = { + .base_class = &lv_button_class, + .constructor_cb = nullptr, + .destructor_cb = nullptr, + .event_cb = nullptr, + .user_data = nullptr, + .name = "icon_button", + .width_def = 0, + .height_def = 0, + .editable = LV_OBJ_CLASS_EDITABLE_INHERIT, + .group_def = LV_OBJ_CLASS_GROUP_DEF_INHERIT, + .instance_size = 0, + .theme_inheritable = LV_OBJ_CLASS_THEME_INHERITABLE_FALSE +}; + +extern "C" const lv_obj_class_t lvgl_icon_button_filled_class = { + .base_class = &lv_button_class, + .constructor_cb = nullptr, + .destructor_cb = nullptr, + .event_cb = nullptr, + .user_data = nullptr, + .name = "icon_button_filled", + .width_def = 0, + .height_def = 0, + .editable = LV_OBJ_CLASS_EDITABLE_INHERIT, + .group_def = LV_OBJ_CLASS_GROUP_DEF_INHERIT, + .instance_size = 0, + .theme_inheritable = LV_OBJ_CLASS_THEME_INHERITABLE_FALSE +}; + +extern "C" const lv_obj_class_t lvgl_icon_button_tonal_class = { + .base_class = &lv_button_class, + .constructor_cb = nullptr, + .destructor_cb = nullptr, + .event_cb = nullptr, + .user_data = nullptr, + .name = "icon_button_tonal", + .width_def = 0, + .height_def = 0, + .editable = LV_OBJ_CLASS_EDITABLE_INHERIT, + .group_def = LV_OBJ_CLASS_GROUP_DEF_INHERIT, + .instance_size = 0, + .theme_inheritable = LV_OBJ_CLASS_THEME_INHERITABLE_FALSE +}; + +static const lv_obj_class_t* get_variant_class(enum LvglIconButtonVariant variant) { + switch (variant) { + case LVGL_ICON_BUTTON_FILLED: + return &lvgl_icon_button_filled_class; + case LVGL_ICON_BUTTON_TONAL: + return &lvgl_icon_button_tonal_class; + default: + return &lvgl_icon_button_class; + } +} + +lv_obj_t* lvgl_icon_button_create_variant(lv_obj_t* parent, enum LvglIconButtonVariant variant) { + lv_obj_t* obj = lv_obj_class_create_obj(get_variant_class(variant), parent); + lv_obj_class_init_obj(obj); + return obj; +} + +lv_obj_t* lvgl_icon_button_create(lv_obj_t* parent) { + return lvgl_icon_button_create_variant(parent, LVGL_ICON_BUTTON_STANDARD); +} diff --git a/Modules/lvgl-module/source/widgets/page_indicator.cpp b/Modules/lvgl-module/source/widgets/page_indicator.cpp new file mode 100644 index 000000000..b40afe447 --- /dev/null +++ b/Modules/lvgl-module/source/widgets/page_indicator.cpp @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: Apache-2.0 +#define LV_USE_PRIVATE_API 1 // For the lv_obj_class_t declaration + +#include + +#include + +static void refresh(lv_obj_t* obj); + +static void on_style_changed(lv_event_t* event) { + refresh(lv_event_get_current_target_obj(event)); +} + +static void page_indicator_constructor(const lv_obj_class_t* class_p, lv_obj_t* obj) { + LV_UNUSED(class_p); + lv_obj_remove_flag(obj, LV_OBJ_FLAG_CLICKABLE); + lv_obj_remove_flag(obj, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_ROW); + lv_obj_set_flex_align(obj, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + // Shows the page numbers instead of the dots when they don't fit in the indicator's maximum width + lv_obj_t* label = lv_label_create(obj); + lv_obj_add_flag(label, LV_OBJ_FLAG_HIDDEN); + // A change of the maximum width can switch between dots and numbers + lv_obj_add_event_cb(obj, on_style_changed, LV_EVENT_STYLE_CHANGED, nullptr); +} + +static void page_indicator_dot_constructor(const lv_obj_class_t* class_p, lv_obj_t* obj) { + LV_UNUSED(class_p); + lv_obj_remove_flag(obj, LV_OBJ_FLAG_CLICKABLE); + lv_obj_remove_flag(obj, LV_OBJ_FLAG_SCROLLABLE); +} + +// Not static: the theme styles page indicators and their dots by these classes +extern "C" const lv_obj_class_t lvgl_page_indicator_class = { + .base_class = &lv_obj_class, + .constructor_cb = page_indicator_constructor, + .destructor_cb = nullptr, + .event_cb = nullptr, + .user_data = nullptr, + .name = "page_indicator", + .width_def = LV_SIZE_CONTENT, + .height_def = LV_SIZE_CONTENT, + .editable = LV_OBJ_CLASS_EDITABLE_INHERIT, + .group_def = LV_OBJ_CLASS_GROUP_DEF_FALSE, + .instance_size = 0, + .theme_inheritable = LV_OBJ_CLASS_THEME_INHERITABLE_FALSE +}; + +extern "C" const lv_obj_class_t lvgl_page_indicator_dot_class = { + .base_class = &lv_obj_class, + .constructor_cb = page_indicator_dot_constructor, + .destructor_cb = nullptr, + .event_cb = nullptr, + .user_data = nullptr, + .name = "page_indicator_dot", + .width_def = 0, + .height_def = 0, + .editable = LV_OBJ_CLASS_EDITABLE_INHERIT, + .group_def = LV_OBJ_CLASS_GROUP_DEF_FALSE, + .instance_size = 0, + .theme_inheritable = LV_OBJ_CLASS_THEME_INHERITABLE_FALSE +}; + +// The first child is the label, the dots follow +static lv_obj_t* get_label(lv_obj_t* obj) { + return lv_obj_get_child(obj, 0); +} + +static uint32_t get_dot_count(lv_obj_t* obj) { + return lv_obj_get_child_count(obj) - 1; +} + +static lv_obj_t* get_dot(lv_obj_t* obj, uint32_t index) { + return lv_obj_get_child(obj, static_cast(index + 1)); +} + +static void refresh(lv_obj_t* obj) { + const uint32_t count = get_dot_count(obj); + if (count == 0) { + return; + } + const int32_t dot_width = lv_obj_get_style_width(get_dot(obj, 0), LV_PART_MAIN); + const int32_t gap = lv_obj_get_style_pad_column(obj, LV_PART_MAIN); + const int32_t dots_width = static_cast(count) * dot_width + static_cast(count - 1) * gap; + const bool show_numbers = dots_width > lv_obj_get_style_max_width(obj, LV_PART_MAIN); + + lv_obj_t* label = get_label(obj); + lv_obj_set_flag(label, LV_OBJ_FLAG_HIDDEN, !show_numbers); + uint32_t page = 0; + for (uint32_t i = 0; i < count; i++) { + lv_obj_t* dot = get_dot(obj, i); + lv_obj_set_flag(dot, LV_OBJ_FLAG_HIDDEN, show_numbers); + if (lv_obj_has_state(dot, LV_STATE_CHECKED)) { + page = i; + } + } + if (show_numbers) { + char text[24]; + std::snprintf(text, sizeof(text), "%lu/%lu", static_cast(page + 1), static_cast(count)); + lv_label_set_text(label, text); + } +} + +extern "C" { + +lv_obj_t* lvgl_page_indicator_create(lv_obj_t* parent) { + lv_obj_t* obj = lv_obj_class_create_obj(&lvgl_page_indicator_class, parent); + lv_obj_class_init_obj(obj); + return obj; +} + +void lvgl_page_indicator_set_page_count(lv_obj_t* obj, uint32_t count) { + uint32_t current = get_dot_count(obj); + while (current > count) { + lv_obj_delete(lv_obj_get_child(obj, -1)); + current--; + } + while (current < count) { + lv_obj_t* dot = lv_obj_class_create_obj(&lvgl_page_indicator_dot_class, obj); + lv_obj_class_init_obj(dot); + current++; + } + refresh(obj); +} + +void lvgl_page_indicator_set_page(lv_obj_t* obj, uint32_t page) { + const uint32_t count = get_dot_count(obj); + for (uint32_t i = 0; i < count; i++) { + lv_obj_set_state(get_dot(obj, i), LV_STATE_CHECKED, i == page); + } + refresh(obj); +} + +} diff --git a/Modules/lvgl-module/source/widgets/sliderbox.cpp b/Modules/lvgl-module/source/widgets/sliderbox.cpp index 59de76e09..ec4301431 100644 --- a/Modules/lvgl-module/source/widgets/sliderbox.cpp +++ b/Modules/lvgl-module/source/widgets/sliderbox.cpp @@ -17,7 +17,8 @@ typedef struct { static void sliderbox_constructor(const lv_obj_class_t* classPointer, lv_obj_t* obj); -static lv_obj_class_t sliderbox_class = { +// Not static: the theme styles sliderboxes by this class +extern "C" const lv_obj_class_t lvgl_sliderbox_class = { .base_class = &lv_obj_class, .constructor_cb = &sliderbox_constructor, .destructor_cb = nullptr, @@ -38,7 +39,6 @@ static void sliderbox_constructor(const lv_obj_class_t* classPointer, lv_obj_t* lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_ROW); lv_obj_set_flex_align(obj, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); lv_obj_set_style_pad_all(obj, 0, LV_STATE_DEFAULT); - lv_obj_set_style_pad_column(obj, 4, LV_STATE_DEFAULT); lv_obj_set_style_border_width(obj, 0, LV_STATE_DEFAULT); lv_obj_set_style_bg_opa(obj, 0, LV_STATE_DEFAULT); } @@ -139,7 +139,7 @@ lv_obj_t* lvgl_sliderbox_create(lv_obj_t* parent, int32_t min, int32_t max, int3 step = 1; } - lv_obj_t* obj = lv_obj_class_create_obj(&sliderbox_class, parent); + lv_obj_t* obj = lv_obj_class_create_obj(&lvgl_sliderbox_class, parent); lv_obj_class_init_obj(obj); auto* sliderBox = reinterpret_cast(obj); @@ -165,10 +165,12 @@ lv_obj_t* lvgl_sliderbox_create(lv_obj_t* parent, int32_t min, int32_t max, int3 // +4 padding wasn't enough margin for 3-digit values to never wrap; widened to // a flat per-character estimate instead of trusting raw glyph width too tightly. char buffer[16]; + lv_text_attributes_t text_attributes; + lv_text_attributes_init(&text_attributes); lv_snprintf(buffer, sizeof(buffer), "%" LV_PRId32, min); - auto width_min = lv_text_get_width(buffer, lv_strlen(buffer), lv_obj_get_style_text_font(sliderBox->valueLabel, LV_PART_MAIN), 0); + auto width_min = lv_text_get_width(buffer, lv_strlen(buffer), lv_obj_get_style_text_font(sliderBox->valueLabel, LV_PART_MAIN), &text_attributes); lv_snprintf(buffer, sizeof(buffer), "%" LV_PRId32, max); - auto width_max = lv_text_get_width(buffer, lv_strlen(buffer), lv_obj_get_style_text_font(sliderBox->valueLabel, LV_PART_MAIN), 0); + auto width_max = lv_text_get_width(buffer, lv_strlen(buffer), lv_obj_get_style_text_font(sliderBox->valueLabel, LV_PART_MAIN), &text_attributes); auto max_width = (width_min > width_max) ? width_min : width_max; lv_obj_set_width(sliderBox->valueLabel, max_width + 8); diff --git a/Modules/lvgl-module/source/widgets/toolbar.cpp b/Modules/lvgl-module/source/widgets/toolbar.cpp index 99cef708d..4c69bd7b1 100644 --- a/Modules/lvgl-module/source/widgets/toolbar.cpp +++ b/Modules/lvgl-module/source/widgets/toolbar.cpp @@ -4,11 +4,17 @@ #include #include +#include +#include #include +#include #include #include #include +#include + +constexpr auto* TAG = "lvgl_toolbar"; static uint32_t getToolbarHeight(UiDensity uiDensity) { if (uiDensity == LVGL_UI_DENSITY_COMPACT) { @@ -26,54 +32,11 @@ static const _lv_font_t* getToolbarFont(UiDensity uiDensity) { } } -static uint32_t getActionIconPadding(UiDensity uiDensity) { +/** The size of icon buttons, which leaves room for the theme's focus indicator */ +static uint32_t getActionButtonSize(UiDensity uiDensity) { auto toolbar_height = getToolbarHeight(uiDensity); - // Minimal 8 pixels total padding for selection/animation (4+4 pixels) - return (uiDensity != LVGL_UI_DENSITY_COMPACT) ? (uint32_t)(toolbar_height * 0.2f) : 8; -} - -static bool is_monochrome(lv_obj_t* obj) { - return lv_display_get_color_format(lv_obj_get_display(obj)) == LV_COLOR_FORMAT_I1; -} - -/** - * Makes a toolbar button transparent. Its icon or text is drawn in the accent colour when it's - * selected or pressed, instead of showing an outline. - */ -static void apply_button_style(lv_obj_t* button) { - lv_obj_set_style_bg_opa(button, LV_OPA_TRANSP, LV_STATE_DEFAULT); - lv_obj_set_style_shadow_width(button, 0, LV_STATE_DEFAULT); - // Colours can't show the selection on monochrome displays, so those keep the theme's outline - if (is_monochrome(button)) { - return; - } - const lv_color_t accent = lv_theme_get_color_primary(button); - const lv_state_t states[] = { LV_STATE_FOCUSED, LV_STATE_FOCUS_KEY, LV_STATE_PRESSED }; - for (const lv_state_t state : states) { - lv_obj_set_style_outline_width(button, 0, state); - // Inherited by the button's label or image, including symbol images - lv_obj_set_style_text_color(button, accent, state); - } -} - -/** - * Helps with button expansion and also with vertical alignment of content, - * as the parent flex doesn't allow for vertical alignment - */ -static lv_obj_t* create_action_wrapper(lv_obj_t* parent, UiDensity ui_density) { - auto* wrapper = lv_obj_create(parent); - auto toolbar_height = getToolbarHeight(ui_density); - lv_obj_set_size(wrapper, LV_SIZE_CONTENT, toolbar_height); - - auto icon_padding = getActionIconPadding(ui_density); - auto icon_padding_half = icon_padding / 2; - - lv_obj_set_style_pad_all(wrapper, icon_padding_half, LV_STATE_DEFAULT); // For selection and touch animation - lv_obj_set_style_bg_opa(wrapper, 0, LV_STATE_DEFAULT); - lv_obj_set_style_border_width(wrapper, 0, LV_STATE_DEFAULT); - lv_obj_set_style_border_opa(wrapper, 0, LV_STATE_DEFAULT); - - return wrapper; + auto padding = (uiDensity != LVGL_UI_DENSITY_COMPACT) ? (uint32_t)(toolbar_height * 0.2f) : 8; + return toolbar_height - padding; } typedef struct { @@ -81,14 +44,14 @@ typedef struct { lv_obj_t* title_label; lv_obj_t* close_button; lv_obj_t* close_button_image; - lv_obj_t* action_container; uint8_t action_count; lv_event_cb_t nav_action_callback; } Toolbar; static void toolbar_constructor(const lv_obj_class_t* class_p, lv_obj_t* obj); -static lv_obj_class_t toolbar_class = { +// Not static: the theme styles toolbars by this class +extern "C" const lv_obj_class_t lvgl_toolbar_class = { .base_class = &lv_obj_class, .constructor_cb = &toolbar_constructor, .destructor_cb = nullptr, @@ -96,7 +59,7 @@ static lv_obj_class_t toolbar_class = { .user_data = nullptr, .name = nullptr, .width_def = LV_PCT(100), - .height_def = 0, + .height_def = LV_SIZE_CONTENT, .editable = false, .group_def = LV_OBJ_CLASS_GROUP_DEF_TRUE, .instance_size = sizeof(Toolbar), @@ -121,63 +84,40 @@ static void toolbar_constructor(const lv_obj_class_t* class_p, lv_obj_t* obj) { lv_obj_add_flag(obj, LV_OBJ_FLAG_SCROLL_ON_FOCUS); } +static lv_obj_t* create_action_button(lv_obj_t* parent, UiDensity ui_density) { + auto button_size = getActionButtonSize(ui_density); + lv_obj_t* button = lvgl_icon_button_create(parent); + lv_obj_set_size(button, button_size, button_size); + return button; +} + lv_obj_t* lvgl_toolbar_create(lv_obj_t* parent, const char* title) { auto ui_density = lvgl_get_ui_density(); - auto toolbar_height = getToolbarHeight(ui_density); - toolbar_class.height_def = toolbar_height; - lv_obj_t* obj = lv_obj_class_create_obj(&toolbar_class, parent); + lv_obj_t* obj = lv_obj_class_create_obj(&lvgl_toolbar_class, parent); lv_obj_class_init_obj(obj); - lv_obj_set_height(obj, toolbar_height); + lv_obj_set_height(obj, getToolbarHeight(ui_density)); auto* toolbar = reinterpret_cast(obj); toolbar->nav_action_callback = nullptr; - lv_obj_set_width(obj, LV_PCT(100)); - lv_obj_set_style_pad_all(obj, 0, LV_STATE_DEFAULT); - lv_obj_set_style_pad_column(obj, 0, LV_STATE_DEFAULT); lv_obj_center(obj); - lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_ROW); - - auto icon_padding = getActionIconPadding(ui_density); - auto* close_button_wrapper = create_action_wrapper(obj, ui_density); + lvgl_obj_add_edge_padding(obj); - toolbar->close_button = lv_button_create(close_button_wrapper); - apply_button_style(toolbar->close_button); - - lv_obj_set_size(toolbar->close_button, toolbar_height - icon_padding, toolbar_height - icon_padding); + lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_ROW); + lv_obj_set_flex_align(obj, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + // The arrow keys move between the close button and the actions, and up and down leave the toolbar + lvgl_grid_navigation_add(obj); - lv_obj_set_style_pad_all(toolbar->close_button, 0, LV_STATE_DEFAULT); - lv_obj_align(toolbar->close_button, LV_ALIGN_CENTER, 0, 0); + toolbar->close_button = create_action_button(obj, ui_density); toolbar->close_button_image = lv_image_create(toolbar->close_button); lv_obj_align(toolbar->close_button_image, LV_ALIGN_CENTER, 0, 0); - auto* title_wrapper = lv_obj_create(obj); - uint32_t title_left_padding = (ui_density != LVGL_UI_DENSITY_COMPACT) ? icon_padding : 2; - uint32_t title_right_padding = (ui_density != LVGL_UI_DENSITY_COMPACT) ? (icon_padding / 2) : 2; - lv_obj_set_size(title_wrapper, LV_SIZE_CONTENT, LV_PCT(100)); - lv_obj_set_style_bg_opa(title_wrapper, 0, LV_STATE_DEFAULT); - lv_obj_set_style_pad_left(title_wrapper, title_left_padding, LV_STATE_DEFAULT); - lv_obj_set_style_pad_right(title_wrapper, title_right_padding, LV_STATE_DEFAULT); - lv_obj_set_style_pad_ver(title_wrapper, 0, LV_STATE_DEFAULT); - lv_obj_set_style_border_width(title_wrapper, 0, LV_STATE_DEFAULT); - lv_obj_set_flex_grow(title_wrapper, 1); - - toolbar->title_label = lv_label_create(title_wrapper); + toolbar->title_label = lv_label_create(obj); lv_obj_set_style_text_font(toolbar->title_label, getToolbarFont(ui_density), LV_STATE_DEFAULT); lv_label_set_text(toolbar->title_label, title); lv_label_set_long_mode(toolbar->title_label, LV_LABEL_LONG_MODE_SCROLL); - lv_obj_set_style_text_align(toolbar->title_label, LV_TEXT_ALIGN_LEFT, LV_STATE_DEFAULT); - lv_obj_align(toolbar->title_label, LV_ALIGN_LEFT_MID, 0, 0); - lv_obj_set_width(toolbar->title_label, LV_PCT(100)); - - toolbar->action_container = lv_obj_create(obj); - lv_obj_set_width(toolbar->action_container, LV_SIZE_CONTENT); - lv_obj_set_flex_flow(toolbar->action_container, LV_FLEX_FLOW_ROW); - lv_obj_set_style_pad_all(toolbar->action_container, 0, LV_STATE_DEFAULT); - lv_obj_set_style_pad_column(toolbar->action_container, 0, LV_STATE_DEFAULT); - lv_obj_set_style_border_width(toolbar->action_container, 0, LV_STATE_DEFAULT); - lv_obj_set_style_bg_opa(toolbar->action_container, 0, LV_STATE_DEFAULT); + lv_obj_set_flex_grow(toolbar->title_label, 1); lvgl_toolbar_set_nav_action(obj, LV_SYMBOL_CLOSE, &default_nav_action, nullptr); @@ -186,8 +126,8 @@ lv_obj_t* lvgl_toolbar_create(lv_obj_t* parent, const char* title) { // an extra time for every screen. if (!device_has_active_by_type(&POINTER_TYPE)) { lv_obj_update_layout(obj); // Resolve flex layout first, so focus/state invalidate against final coords - lv_group_focus_obj(toolbar->close_button); - lv_obj_add_state(toolbar->close_button, LV_STATE_FOCUS_KEY); + lv_group_focus_obj(obj); + lv_gridnav_set_focused(obj, toolbar->close_button, LV_ANIM_OFF); } return obj; @@ -215,19 +155,7 @@ static lv_obj_t* toolbar_add_button_action(lv_obj_t* obj, const char* imageOrBut check(toolbar->action_count < TOOLBAR_ACTION_LIMIT, "max actions reached"); toolbar->action_count++; - auto ui_density = lvgl_get_ui_density(); - auto toolbar_height = getToolbarHeight(ui_density); - - auto* wrapper = create_action_wrapper(toolbar->action_container, ui_density); - - auto padding = getActionIconPadding(ui_density); - - lv_obj_t* action_button = lv_button_create(wrapper); - lv_obj_set_size(action_button, toolbar_height - padding, toolbar_height - padding); - lv_obj_set_style_pad_all(action_button, 0, LV_STATE_DEFAULT); - lv_obj_align(action_button, LV_ALIGN_CENTER, 0, 0); - apply_button_style(action_button); - + lv_obj_t* action_button = create_action_button(obj, lvgl_get_ui_density()); lv_obj_add_event_cb(action_button, callback, LV_EVENT_SHORT_CLICKED, user_data); lv_obj_t* button_content; if (isImage) { @@ -255,14 +183,7 @@ lv_obj_t* lvgl_toolbar_add_switch_action(lv_obj_t* obj) { check(toolbar->action_count < TOOLBAR_ACTION_LIMIT, "max actions reached"); toolbar->action_count++; - auto ui_density = lvgl_get_ui_density(); - auto* wrapper = create_action_wrapper(toolbar->action_container, ui_density); - lv_obj_set_style_pad_hor(wrapper, 4, LV_STATE_DEFAULT); - - lv_obj_t* widget = lv_switch_create(wrapper); - lv_obj_set_align(widget, LV_ALIGN_CENTER); - - return widget; + return lv_switch_create(obj); } lv_obj_t* lvgl_toolbar_add_dropdown_action(lv_obj_t* obj, const char* options, lv_coord_t width, const char* text) { @@ -270,11 +191,11 @@ lv_obj_t* lvgl_toolbar_add_dropdown_action(lv_obj_t* obj, const char* options, l check(toolbar->action_count < TOOLBAR_ACTION_LIMIT, "max actions reached"); toolbar->action_count++; - auto ui_density = lvgl_get_ui_density(); - auto* wrapper = create_action_wrapper(toolbar->action_container, ui_density); - lv_obj_set_style_pad_hor(wrapper, 4, LV_STATE_DEFAULT); + // The arrow keys can't both navigate the toolbar and the opened dropdown + LOG_W(TAG, "Dropdown actions are deprecated: the toolbar can't be navigated with the arrow keys when it has one"); + lvgl_grid_navigation_remove(obj); - lv_obj_t* widget = lv_dropdown_create(wrapper); + lv_obj_t* widget = lv_dropdown_create(obj); lv_dropdown_set_options(widget, options); lv_dropdown_set_selected_highlight(widget, false); if (width > 0) { @@ -283,7 +204,6 @@ lv_obj_t* lvgl_toolbar_add_dropdown_action(lv_obj_t* obj, const char* options, l if (text != nullptr) { lv_dropdown_set_text(widget, text); } - lv_obj_set_align(widget, LV_ALIGN_CENTER); return widget; } @@ -293,11 +213,7 @@ lv_obj_t* lvgl_toolbar_add_spinner_action(lv_obj_t* obj) { check(toolbar->action_count < TOOLBAR_ACTION_LIMIT, "max actions reached"); toolbar->action_count++; - auto ui_density = lvgl_get_ui_density(); - auto* wrapper = create_action_wrapper(toolbar->action_container, ui_density); - - auto* spinner = lvgl_spinner_create(wrapper); - lv_obj_set_align(spinner, LV_ALIGN_CENTER); + auto* spinner = lvgl_spinner_create(obj); if (lv_display_get_color_format(lv_obj_get_display(obj)) == LV_COLOR_FORMAT_L8) { lv_obj_set_style_image_recolor(spinner, lv_theme_get_color_secondary(obj), LV_STATE_DEFAULT); @@ -309,6 +225,9 @@ lv_obj_t* lvgl_toolbar_add_spinner_action(lv_obj_t* obj) { void lvgl_toolbar_clear_actions(lv_obj_t* obj) { auto* toolbar = reinterpret_cast(obj); - lv_obj_clean(toolbar->action_container); + // The actions follow the close button and the title + while (lv_obj_get_child_count(obj) > 2) { + lv_obj_delete(lv_obj_get_child(obj, 2)); + } toolbar->action_count = 0; } diff --git a/Modules/lvgl-module/tests/CMakeLists.txt b/Modules/lvgl-module/tests/CMakeLists.txt new file mode 100644 index 000000000..961d96265 --- /dev/null +++ b/Modules/lvgl-module/tests/CMakeLists.txt @@ -0,0 +1,22 @@ +project(LvglModuleTests) + +enable_language(C CXX ASM) + +file(GLOB_RECURSE TEST_SOURCES CONFIGURE_DEPENDS ${PROJECT_SOURCE_DIR}/source/*.cpp) +add_executable(LvglModuleTests EXCLUDE_FROM_ALL + ${TEST_SOURCES} + ${PROJECT_SOURCE_DIR}/../source/insets_math.cpp +) + +target_include_directories(LvglModuleTests PRIVATE + ${DOCTESTINC} + ${PROJECT_SOURCE_DIR}/../include + ${PROJECT_SOURCE_DIR}/../private +) + +add_test(NAME LvglModuleTests COMMAND LvglModuleTests) + +target_link_libraries(LvglModuleTests PUBLIC + TactilityKernel + lvgl +) diff --git a/Modules/lvgl-module/tests/source/insets_math_test.cpp b/Modules/lvgl-module/tests/source/insets_math_test.cpp new file mode 100644 index 000000000..ff11b01bf --- /dev/null +++ b/Modules/lvgl-module/tests/source/insets_math_test.cpp @@ -0,0 +1,62 @@ +#include "doctest.h" + +#include + +static constexpr DisplayShape RECTANGLE = { .shape = DISPLAY_SHAPE_RECTANGLE, .corner_radius = 0 }; +static constexpr DisplayShape CIRCLE = { .shape = DISPLAY_SHAPE_CIRCLE, .corner_radius = 0 }; +static constexpr DisplayShape ROUNDED_40 = { .shape = DISPLAY_SHAPE_ROUNDED, .corner_radius = 40 }; + +TEST_CASE("a rectangle has no insets") { + const auto insets = lvgl_insets_calculate(RECTANGLE, 320, 240); + CHECK_EQ(insets.top, 0); + CHECK_EQ(insets.bottom, 0); + CHECK_EQ(insets.left, 0); + CHECK_EQ(insets.right, 0); + CHECK_EQ(lvgl_insets_calculate_row(RECTANGLE, 320, 240, 0), 0); +} + +TEST_CASE("a circle's insets fit the inscribed square") { + const auto insets = lvgl_insets_calculate(CIRCLE, 240, 240); + // 120 * (1 - 1/sqrt(2)) = 35.1 + CHECK_EQ(insets.top, 36); + CHECK_EQ(insets.bottom, 36); + CHECK_EQ(insets.left, 36); + CHECK_EQ(insets.right, 36); +} + +TEST_CASE("a circle on a wider panel is centered horizontally") { + const auto insets = lvgl_insets_calculate(CIRCLE, 280, 240); + CHECK_EQ(insets.top, 36); + CHECK_EQ(insets.left, 56); + CHECK_EQ(insets.right, 56); +} + +TEST_CASE("a circle's row inset follows its edge") { + CHECK_EQ(lvgl_insets_calculate_row(CIRCLE, 240, 240, 120), 0); + CHECK_EQ(lvgl_insets_calculate_row(CIRCLE, 240, 240, 0), 120); + // Halfway between the center and the top: 120 - sqrt(120^2 - 60^2) = 16.1 + CHECK_EQ(lvgl_insets_calculate_row(CIRCLE, 240, 240, 60), 17); +} + +TEST_CASE("a rounded rectangle has insets at the top and bottom only") { + const auto insets = lvgl_insets_calculate(ROUNDED_40, 172, 320); + // 40 * (1 - 1/sqrt(2)) = 11.7 + CHECK_EQ(insets.top, 12); + CHECK_EQ(insets.bottom, 12); + CHECK_EQ(insets.left, 0); + CHECK_EQ(insets.right, 0); +} + +TEST_CASE("a rounded rectangle's row inset is only in the corners") { + CHECK_EQ(lvgl_insets_calculate_row(ROUNDED_40, 172, 320, 0), 40); + CHECK_EQ(lvgl_insets_calculate_row(ROUNDED_40, 172, 320, 319), 40); + CHECK_EQ(lvgl_insets_calculate_row(ROUNDED_40, 172, 320, 40), 0); + CHECK_EQ(lvgl_insets_calculate_row(ROUNDED_40, 172, 320, 160), 0); +} + +TEST_CASE("a corner radius larger than half the panel is limited") { + const DisplayShape huge = { .shape = DISPLAY_SHAPE_ROUNDED, .corner_radius = 500 }; + const auto insets = lvgl_insets_calculate(huge, 172, 320); + // Limited to 86: 86 * (1 - 1/sqrt(2)) = 25.2 + CHECK_EQ(insets.top, 26); +} diff --git a/Modules/lvgl-module/tests/source/main.cpp b/Modules/lvgl-module/tests/source/main.cpp new file mode 100644 index 000000000..a3f832e49 --- /dev/null +++ b/Modules/lvgl-module/tests/source/main.cpp @@ -0,0 +1,2 @@ +#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN +#include "doctest.h" diff --git a/Modules/lvgl-module/themes/material.lss b/Modules/lvgl-module/themes/material.lss new file mode 100644 index 000000000..6da95531b --- /dev/null +++ b/Modules/lvgl-module/themes/material.lss @@ -0,0 +1,1711 @@ +// Material 3 theme for colour displays +// +// Tokens are layered: +// 1. Base tokens: raw values without meaning (tones, scales) +// 2. Semantic tokens: Material 3 roles (colors, shapes, density, states) +// 3. Widget tokens: per widget, built from semantic tokens +// Styles only use widget tokens, or semantic tokens for shared state styles. + +@theme material; + +@config { + isDark: bool = false; + isCompact: bool = false; + animationsEnabled: bool = true; + colorPrimary: color = #00bbff; + colorSecondary: color = #ee81c3; + colorError: color = #B3261E; + fontSmall: font = raw("LV_FONT_DEFAULT"); + fontNormal: font = raw("LV_FONT_DEFAULT"); + fontLarge: font = raw("LV_FONT_DEFAULT"); +} + +// Tactility widgets +@widget toolbar: lvgl_toolbar_class; +@widget icon_button: lvgl_icon_button_class; +@widget icon_button_filled: lvgl_icon_button_filled_class; +@widget icon_button_tonal: lvgl_icon_button_tonal_class; +@widget sliderbox: lvgl_sliderbox_class; +@widget card: lvgl_card_class; +@widget chip: lvgl_chip_class; +@widget badge: lvgl_badge_class; +@widget page_indicator: lvgl_page_indicator_class; +@widget page_indicator_dot: lvgl_page_indicator_dot_class; + +// ============================================================ +// 1. Base tokens +// ============================================================ + +// Neutral tones (greys by Material 3 tone) +@neutral-4: #0E0E0E; +@neutral-6: #131313; +@neutral-10: #1C1C1C; +@neutral-12: #1F1F1F; +@neutral-17: #2B2B2B; +@neutral-20: #303030; +@neutral-22: #343434; +@neutral-30: #474747; +@neutral-50: #777777; +@neutral-60: #919191; +@neutral-80: #C6C6C6; +@neutral-90: #E2E2E2; +@neutral-92: #E8E8E8; +@neutral-94: #EEEEEE; +@neutral-95: #F0F0F0; +@neutral-96: #F3F3F3; +@neutral-98: #FAFAFA; +@neutral-100: #FFFFFF; + +// How much of the primary color tints the neutral tones +@tint-neutral: 4%; +@tint-neutral-variant: 8%; + +// Shape scale +@corner-none: 0; +@corner-extra-small: 4dp; +@corner-small: 8dp; +@corner-medium: 12dp; +@corner-large: 16dp; +@corner-extra-large: 28dp; +@corner-full: circle; +@corner-compact: 3; + +// Spacing scale (4dp grid) +@space-1: 4dp; +@space-2: 8dp; +@space-3: 12dp; +@space-4: 16dp; +@space-6: 24dp; + +// Opacity scale +@opacity-state-hover: 8%; +@opacity-state-focus: 10%; +@opacity-state-pressed: 10%; +@opacity-disabled-content: 38%; +@opacity-disabled-container: 12%; +@opacity-scrim: 32%; + +// Motion +@duration-short: 100; +@duration-medium: 250; +@duration-long: 500; + +// ============================================================ +// 2. Semantic tokens +// ============================================================ + +// Tonal roles derived from a seed color +@color-primary: if($isDark, mix($colorPrimary, white, 55%), $colorPrimary); +@color-on-primary: if($isDark, mix($colorPrimary, black, 50%), white); +@color-primary-container: if($isDark, mix($colorPrimary, black, 70%), mix($colorPrimary, white, 25%)); +@color-on-primary-container: if($isDark, mix($colorPrimary, white, 20%), mix($colorPrimary, black, 60%)); + +@color-secondary: if($isDark, mix($colorSecondary, white, 55%), $colorSecondary); +@color-on-secondary: if($isDark, mix($colorSecondary, black, 50%), white); +@color-secondary-container: if($isDark, mix($colorSecondary, black, 70%), mix($colorSecondary, white, 25%)); +@color-on-secondary-container: if($isDark, mix($colorSecondary, white, 20%), mix($colorSecondary, black, 60%)); + +@color-error: if($isDark, mix($colorError, white, 55%), $colorError); +@color-on-error: if($isDark, mix($colorError, black, 50%), white); + +// Surfaces: neutral tones tinted with the primary color +@color-surface: mix($colorPrimary, if($isDark, @neutral-6, @neutral-98), @tint-neutral); +@color-on-surface: mix($colorPrimary, if($isDark, @neutral-90, @neutral-10), @tint-neutral); +@color-on-surface-variant: mix($colorPrimary, if($isDark, @neutral-80, @neutral-30), @tint-neutral-variant); +@color-surface-container-lowest: mix($colorPrimary, if($isDark, @neutral-4, @neutral-100), @tint-neutral); +@color-surface-container-low: mix($colorPrimary, if($isDark, @neutral-10, @neutral-96), @tint-neutral); +@color-surface-container: mix($colorPrimary, if($isDark, @neutral-12, @neutral-94), @tint-neutral); +@color-surface-container-high: mix($colorPrimary, if($isDark, @neutral-17, @neutral-92), @tint-neutral); +@color-surface-container-highest: mix($colorPrimary, if($isDark, @neutral-22, @neutral-90), @tint-neutral); +@color-outline: mix($colorPrimary, if($isDark, @neutral-60, @neutral-50), @tint-neutral-variant); +@color-outline-variant: mix($colorPrimary, if($isDark, @neutral-30, @neutral-80), @tint-neutral-variant); +@color-inverse-surface: mix($colorPrimary, if($isDark, @neutral-90, @neutral-20), @tint-neutral); +@color-inverse-on-surface: mix($colorPrimary, if($isDark, @neutral-20, @neutral-95), @tint-neutral); +@color-scrim: black; + +// Shapes +@shape-control: if($isCompact, @corner-compact, @corner-full); +@shape-container: if($isCompact, @corner-compact, @corner-medium); +@shape-field: if($isCompact, @corner-compact, @corner-extra-small); +@shape-key: if($isCompact, @corner-compact, @corner-small); +@shape-dialog: if($isCompact, @corner-compact, @corner-extra-large); +@shape-menu: if($isCompact, @corner-compact, @corner-extra-small); + +// Density +@density-container-pad: if($isCompact, 2, @space-4); +@density-container-gap: if($isCompact, 2, @space-3); +@density-container-border: if($isCompact, 1, 0); +@density-control-pad-hor: if($isCompact, 3, @space-6); +@density-control-pad-ver: if($isCompact, 3, 10dp); +@density-control-gap: if($isCompact, 2, @space-2); +@density-field-pad-hor: if($isCompact, 2, @space-4); +@density-field-pad-ver: if($isCompact, 2, @space-2); +@density-list-pad: if($isCompact, 2, @space-2); +@density-list-item-pad-hor: if($isCompact, 2, @space-4); +@density-list-item-pad-ver: if($isCompact, 2, @space-3); +@density-dialog-pad: if($isCompact, 2, @space-6); +@density-key-gap: if($isCompact, 2, @space-1); +@density-border: 1dp; + +// Focus indicator +@focus-color: @color-secondary; +@focus-width: if($isCompact, 1, 3dp); +@focus-offset: if($isCompact, 1, 2dp); +// Drawn inside the widget, for widgets with little room around them (icon buttons) +@focus-inset-width: if($isCompact, 1, 2dp); +@edit-color: @color-primary; + +// State layers: the state color blended over the container color +@state-pressed-on-primary: mix(@color-on-primary, @color-primary, @opacity-state-pressed); +@state-pressed-on-secondary: mix(@color-on-secondary, @color-secondary, @opacity-state-pressed); +@state-layer-color: @color-on-surface; + +// Motion +@motion-duration-short: if($animationsEnabled, @duration-short, 0); +@motion-duration-medium: if($animationsEnabled, @duration-medium, 0); +@motion-duration-long: if($animationsEnabled, @duration-long, 0); + +// ============================================================ +// 3. Widget tokens +// ============================================================ + +// Screen +@screen-color: @color-surface; +@screen-text-color: @color-on-surface; +@screen-gap: @density-container-gap; + +// Container (lv_obj): Material 3 filled card +@container-color: @color-surface-container-low; +@container-text-color: @color-on-surface; +@container-border-color: @color-outline-variant; +@container-border-width: @density-container-border; +@container-shape: @shape-container; +@container-pad: @density-container-pad; +@container-gap: @density-container-gap; + +// Card (lvgl_card_create): a container one tone above the background, so it stands out +@card-color: @color-surface-container-high; +@card-text-color: @color-on-surface; +@card-border-color: @color-outline-variant; +@card-border-width: @density-container-border; +@card-shape: @shape-container; +@card-pad: @density-container-pad; +@card-gap: @density-container-gap; + +// Chip (lvgl_chip_create): Material 3 filter chip, selected when checked +@chip-label-color: @color-on-surface-variant; +@chip-border-color: @color-outline; +@chip-selected-color: @color-secondary-container; +@chip-selected-label-color: @color-on-secondary-container; +@chip-shape: if($isCompact, @corner-compact, @corner-small); +@chip-pad-hor: if($isCompact, 3, @space-4); +@chip-pad-ver: if($isCompact, 2, 6dp); + +// Page indicator (lvgl_page_indicator_create): a dot per page, the current page's dot is filled +@page-indicator-color: @color-on-surface-variant; +@page-indicator-dot-size: if($isCompact, 4, 6dp); +@page-indicator-gap: if($isCompact, 3, 6dp); + +// Badge (lvgl_badge_create): a small dot that marks an item, e.g. a favourite +@badge-color: @color-secondary; +@badge-size: if($isCompact, 4, 6dp); + +// Scrollbar +@scrollbar-color: @color-outline; +@scrollbar-opacity: 40%; +@scrollbar-scrolled-opacity: 70%; +@scrollbar-width: if($isCompact, 2, @space-1); +@scrollbar-pad: if($isCompact, 1, @space-1); + +// Button: Material 3 filled button +@button-container-color: @color-primary; +@button-label-color: @color-on-primary; +@button-pressed-container-color: @state-pressed-on-primary; +@button-checked-container-color: @color-secondary; +@button-checked-label-color: @color-on-secondary; +@button-checked-pressed-container-color: @state-pressed-on-secondary; +@button-disabled-container-color: @color-on-surface; +@button-disabled-container-opacity: @opacity-disabled-container; +@button-disabled-label-color: @color-on-surface; +@button-disabled-label-opacity: @opacity-disabled-content; +@button-shape: @shape-control; +@button-pad-hor: @density-control-pad-hor; +@button-pad-ver: @density-control-pad-ver; +@button-gap: @density-control-gap; + +// Text button (dialog actions, menu header buttons) +@text-button-label-color: @color-primary; +@text-button-pressed-container-color: @color-primary; +@text-button-pad-hor: if($isCompact, 3, @space-3); + +// Icon button (dialog and window header buttons) +@icon-button-color: @color-on-surface; + +// Filled and tonal icon buttons: Material 3 large icon buttons with a container +@icon-button-container-shape: if($isCompact, @corner-compact, @corner-large); +@icon-button-filled-color: @color-primary; +@icon-button-filled-icon-color: @color-on-primary; +@icon-button-filled-pressed-color: @state-pressed-on-primary; +@icon-button-tonal-color: @color-secondary-container; +@icon-button-tonal-icon-color: @color-on-secondary-container; +@icon-button-tonal-pressed-color: mix(@color-on-secondary-container, @color-secondary-container, @opacity-state-pressed); + +// Toolbar: Material 2 top app bar, filled with a dark neutral tone for contrast with the content. +// The tone is the same in light and dark mode. The statusbar uses a darker neutral tone with the same tint. +@app-bar-color: mix($colorPrimary, @neutral-22, @tint-neutral); +@app-bar-content-color: white; +@app-bar-pressed-opacity: 20%; +@app-bar-pad-hor: if($isCompact, 0, @space-1); +@app-bar-gap: if($isCompact, 2, @space-1); +// The focus ring is drawn inside toolbar buttons, as there's little room around them +@app-bar-focus-width: @focus-inset-width; + +// Focus ring +@focus-ring-color: @focus-color; +@focus-ring-width: @focus-width; +@focus-ring-offset: @focus-offset; +@edit-ring-color: @edit-color; + +// Switch +@switch-width: if($isCompact, 25, 52dp); +@switch-height: if($isCompact, 15, 32dp); +@switch-track-color: @color-surface-container-highest; +@switch-track-border-color: @color-outline; +@switch-track-border-width: if($isCompact, 1, 2dp); +@switch-track-selected-color: @color-primary; +@switch-handle-color: @color-outline; +@switch-handle-selected-color: @color-on-primary; +@switch-handle-pad: if($isCompact, -3, -8dp); +@switch-handle-selected-pad: if($isCompact, -2, -4dp); +@switch-handle-pressed-pad: if($isCompact, -1, -2dp); + +// Checkbox +@checkbox-gap: if($isCompact, 3, @space-3); +@checkbox-box-color: @color-surface; +@checkbox-box-opacity: transp; +@checkbox-box-border-color: @color-on-surface-variant; +@checkbox-box-border-width: if($isCompact, 1, 2dp); +@checkbox-box-shape: if($isCompact, 2, 2dp); +@checkbox-box-pad: if($isCompact, 1, 2dp); +@checkbox-box-selected-color: @color-primary; +@checkbox-icon-color: @color-on-primary; + +// Slider, bar (linear progress) +@slider-track-color: @color-secondary-container; +@slider-active-track-color: @color-primary; +@slider-handle-color: @color-primary; +// The track is half of LVGL's default thickness (LV_DPI_DEF / 10). The knob is the track height plus its padding on both +// sides, so the padding includes the removed thickness to keep the knob's size. +@slider-track-height: 8dp; +@slider-handle-pad: if($isCompact, 2, 6dp) + 4dp; +// Sliderbox: in the normal layout, the gap between the slider and its buttons leaves room for the knob, which sticks out at the slider's ends +@sliderbox-gap: if($isCompact, 4, (@slider-track-height / 2) + @slider-handle-pad + @space-2); +@progress-track-color: @color-secondary-container; +@progress-indicator-color: @color-primary; + +// Arc, spinner (circular progress) +@arc-track-color: @color-secondary-container; +@arc-indicator-color: @color-primary; +@arc-width: if($isCompact, 3, @space-1); +@arc-handle-color: @color-primary; +@arc-handle-pad: if($isCompact, 2, @space-1); + +// Text field: Material 3 outlined text field (textarea, spinbox, dropdown) +@field-color: @color-surface; +@field-opacity: transp; +@field-text-color: @color-on-surface; +@field-border-color: @color-outline; +@field-border-width: @density-border; +@field-focused-border-color: @color-primary; +@field-focused-border-width: if($isCompact, 1, 2dp); +@field-shape: @shape-field; +@field-pad-hor: @density-field-pad-hor; +@field-pad-ver: @density-field-pad-ver; +@field-placeholder-color: @color-on-surface-variant; +@field-cursor-color: @color-primary; +@field-cursor-width: if($isCompact, 1, 2dp); +@field-selection-color: @color-primary-container; +@field-selection-text-color: @color-on-primary-container; +@spinbox-cursor-color: @color-primary; +@spinbox-cursor-text-color: @color-on-primary; + +// Menu: Material 3 menu (dropdown list) +@menu-color: @color-surface-container; +@menu-text-color: @color-on-surface; +@menu-shape: @shape-menu; +@menu-pad: if($isCompact, 1, @space-2); +@menu-max-height: dpi-def * 2; +@menu-item-selected-color: @color-secondary-container; +@menu-item-selected-text-color: @color-on-secondary-container; +@menu-item-line-space: if($isCompact, 4, @space-4); + +// Roller +@roller-color: @color-surface-container; +@roller-text-color: @color-on-surface-variant; +@roller-selected-color: @color-secondary-container; +@roller-selected-text-color: @color-on-secondary-container; +@roller-line-space: if($isCompact, 4, @space-4); + +// Button matrix and keyboard: keys are tonal buttons +@keys-color: @color-surface-container; +@keys-pad: @density-key-gap; +@keys-gap: @density-key-gap; +@key-color: @color-surface-container-highest; +@key-text-color: @color-on-surface; +@key-shape: @shape-key; +@key-checked-color: @color-secondary-container; +@key-checked-text-color: @color-on-secondary-container; +@key-selected-color: @color-primary; +@key-selected-text-color: @color-on-primary; + +// List +@list-color: @color-surface; +@list-pad: @density-list-pad; +@list-item-color: @color-surface; +@list-item-opacity: transp; +@list-item-text-color: @color-on-surface; +@list-item-pad-hor: @density-list-item-pad-hor; +@list-item-pad-ver: @density-list-item-pad-ver; +@list-item-gap: if($isCompact, 3, @space-4); +@list-item-focused-color: @color-secondary-container; +@list-item-focused-text-color: @color-on-secondary-container; +@list-divider-color: @color-outline-variant; +@list-divider-width: @density-border; +@list-subheader-text-color: @color-on-surface-variant; + +// Tabs: Material 3 primary tabs +@tabs-divider-color: @color-outline-variant; +@tab-label-color: @color-on-surface-variant; +@tab-selected-label-color: @color-primary; +@tab-indicator-color: @color-primary; +@tab-indicator-width: if($isCompact, 2, 3dp); +@tab-page-pad: @density-container-pad; + +// Dialog (message box) +@dialog-color: @color-surface-container-high; +@dialog-text-color: @color-on-surface-variant; +@dialog-title-color: @color-on-surface; +@dialog-shape: @shape-dialog; +@dialog-pad: @density-dialog-pad; +@dialog-gap: if($isCompact, 2, @space-2); +@dialog-scrim-color: @color-scrim; +@dialog-scrim-opacity: @opacity-scrim; + +// Settings menu (lv_menu) +@settings-color: @color-surface; +@settings-section-color: @color-surface-container-low; +@settings-section-shape: @shape-container; +@settings-item-pad-hor: @density-list-item-pad-hor; +@settings-item-pad-ver: @density-list-item-pad-ver; +@settings-item-gap: @density-container-gap; +@settings-divider-color: @color-outline-variant; +@settings-item-selected-color: @color-secondary-container; +@settings-item-selected-text-color: @color-on-secondary-container; + +// Window +@window-header-color: @color-surface-container; +@window-header-pad: if($isCompact, 2, @space-2); +@window-content-pad: @density-container-pad; + +// Table +@table-color: @color-surface; +@table-cell-text-color: @color-on-surface; +@table-cell-pad: if($isCompact, 2, @space-3); +@table-divider-color: @color-outline-variant; + +// Chart +@chart-color: @color-surface-container-low; +@chart-grid-color: @color-outline-variant; +@chart-series-width: if($isCompact, 1, 2dp); +@chart-point-size: if($isCompact, 3, 6dp); +@chart-indicator-color: @color-primary; + +// Calendar +@calendar-color: @color-surface-container-high; +@calendar-shape: if($isCompact, @corner-compact, @corner-large); +@calendar-pad: if($isCompact, 2, @space-2); +@calendar-day-text-color: @color-on-surface; +@calendar-header-pad: if($isCompact, 2, @space-3); + +// Scale +@scale-color: @color-on-surface-variant; +@scale-width: @density-border; +@scale-tick-length: if($isCompact, 3, 6dp); + +// Divider (lv_line) +@divider-color: @color-outline-variant; +@divider-width: @density-border; + +// LED +@led-glow-width: if($isCompact, 4, 15dp); +@led-glow-spread: if($isCompact, 1, 5dp); + +// ============================================================ +// Transitions +// ============================================================ + +@transition state-enter { + props: bgColor, bgOpa, borderColor, textColor, outlineWidth, outlineOpa; + path: ease-out; + duration: @motion-duration-short; + delay: 0; +} + +@transition state-exit { + props: bgColor, bgOpa, borderColor, textColor, outlineWidth, outlineOpa; + path: ease-out; + duration: @motion-duration-short; + delay: 70; +} + +// ============================================================ +// Styles: shared states +// ============================================================ + +transition_enter { + @if ($animationsEnabled) { + transition: state-enter; + } +} + +transition_exit { + @if ($animationsEnabled) { + transition: state-exit; + } +} + +focus_ring { + outlineColor: @focus-ring-color; + outlineWidth: @focus-ring-width; + outlinePad: @focus-ring-offset; + outlineOpa: cover; +} + +edit_ring { + outlineColor: @edit-ring-color; + outlineWidth: @focus-ring-width; + outlinePad: @focus-ring-offset; + outlineOpa: cover; +} + +focus_ring_inset { + outlineColor: @focus-ring-color; + outlineWidth: @focus-inset-width; + outlinePad: -@focus-inset-width; + outlineOpa: cover; +} + +edit_ring_inset { + outlineColor: @edit-ring-color; + outlineWidth: @focus-inset-width; + outlinePad: -@focus-inset-width; + outlineOpa: cover; +} + +// State layer for transparent containers +state_pressed { + bgColor: @state-layer-color; + bgOpa: @opacity-state-pressed; +} + +disabled { + opa: @opacity-disabled-content; +} + +scrollbar { + bgColor: @scrollbar-color; + bgOpa: @scrollbar-opacity; + radius: circle; + width: @scrollbar-width; + padAll: @scrollbar-pad; +} + +scrollbar_scrolled { + bgOpa: @scrollbar-scrolled-opacity; +} + +// Page-like widgets show the shared app background instead of painting their own +page { + bgOpa: transp; + borderWidth: 0; + radius: 0; + padAll: 0; + padGap: 0; +} + +// ============================================================ +// Styles: widgets +// ============================================================ + +screen { + bgColor: @screen-color; + bgOpa: cover; + textColor: @screen-text-color; + textFont: $fontNormal; + padRow: @screen-gap; + padColumn: @screen-gap; + rotarySensitivity: dpi / 4 * 256; +} + +container { + bgColor: @container-color; + bgOpa: cover; + textColor: @container-text-color; + borderColor: @container-border-color; + borderWidth: @container-border-width; + borderPost: true; + radius: @container-shape; + padAll: @container-pad; + padGap: @container-gap; + lineColor: @divider-color; + lineWidth: @divider-width; +} + +card { + bgColor: @card-color; + bgOpa: cover; + textColor: @card-text-color; + borderColor: @card-border-color; + borderWidth: @card-border-width; + radius: @card-shape; + padAll: @card-pad; + padGap: @card-gap; +} + +page_indicator { + bgOpa: transp; + borderWidth: 0; + padAll: 0; + padGap: @page-indicator-gap; +} + +page_indicator_label { + textColor: @page-indicator-color; +} + +page_indicator_dot { + width: @page-indicator-dot-size; + height: @page-indicator-dot-size; + radius: circle; + bgColor: @page-indicator-color; + bgOpa: transp; + borderColor: @page-indicator-color; + borderWidth: 1; +} + +page_indicator_dot_current { + bgOpa: cover; +} + +badge { + width: @badge-size; + height: @badge-size; + radius: circle; + bgColor: @badge-color; + bgOpa: cover; +} + +chip { + bgOpa: transp; + textColor: @chip-label-color; + borderColor: @chip-border-color; + borderWidth: @density-border; + radius: @chip-shape; + padHor: @chip-pad-hor; + padVer: @chip-pad-ver; +} + +chip_selected { + bgColor: @chip-selected-color; + bgOpa: cover; + textColor: @chip-selected-label-color; + borderColor: @chip-selected-color; +} + +button { + bgColor: @button-container-color; + bgOpa: cover; + textColor: @button-label-color; + radius: @button-shape; + padHor: @button-pad-hor; + padVer: @button-pad-ver; + padGap: @button-gap; +} + +button_pressed { + bgColor: @button-pressed-container-color; +} + +button_checked { + bgColor: @button-checked-container-color; + textColor: @button-checked-label-color; +} + +button_checked_pressed { + bgColor: @button-checked-pressed-container-color; +} + +button_disabled { + bgColor: @button-disabled-container-color; + bgOpa: @button-disabled-container-opacity; + textColor: @button-disabled-label-color; + textOpa: @button-disabled-label-opacity; +} + +text_button { + bgOpa: transp; + textColor: @text-button-label-color; + radius: @button-shape; + padHor: @text-button-pad-hor; + padVer: @button-pad-ver; + padGap: @button-gap; +} + +text_button_pressed { + bgColor: @text-button-pressed-container-color; + bgOpa: @opacity-state-pressed; +} + +icon_button { + bgOpa: transp; + textColor: @icon-button-color; + radius: @button-shape; + padAll: @button-pad-ver; +} + +icon_button_filled { + bgColor: @icon-button-filled-color; + bgOpa: cover; + textColor: @icon-button-filled-icon-color; + radius: @icon-button-container-shape; + padAll: @button-pad-ver; +} + +icon_button_filled_pressed { + bgColor: @icon-button-filled-pressed-color; +} + +icon_button_tonal { + bgColor: @icon-button-tonal-color; + bgOpa: cover; + textColor: @icon-button-tonal-icon-color; + radius: @icon-button-container-shape; + padAll: @button-pad-ver; +} + +icon_button_tonal_pressed { + bgColor: @icon-button-tonal-pressed-color; +} + +app_bar { + bgColor: @app-bar-color; + bgOpa: cover; + textColor: @app-bar-content-color; + radius: 0; + borderWidth: 0; + padHor: @app-bar-pad-hor; + padVer: 0; + padGap: @app-bar-gap; +} + +app_bar_button { + padAll: 0; + textColor: @app-bar-content-color; +} + +app_bar_button_pressed { + bgColor: @app-bar-content-color; + bgOpa: @app-bar-pressed-opacity; +} + +app_bar_focus_ring { + outlineColor: @focus-ring-color; + outlineWidth: @app-bar-focus-width; + outlinePad: -@app-bar-focus-width; + outlineOpa: cover; +} + +app_bar_field { + textColor: @app-bar-content-color; + borderColor: @app-bar-content-color; +} + +switch_track { + width: @switch-width; + height: @switch-height; + bgColor: @switch-track-color; + bgOpa: cover; + borderColor: @switch-track-border-color; + borderWidth: @switch-track-border-width; + radius: circle; + animDuration: @motion-duration-short; +} + +switch_track_selected { + borderColor: @switch-track-selected-color; +} + +switch_indicator_selected { + bgColor: @switch-track-selected-color; + bgOpa: cover; + radius: circle; +} + +switch_handle { + bgColor: @switch-handle-color; + bgOpa: cover; + radius: circle; + padAll: @switch-handle-pad; +} + +switch_handle_selected { + bgColor: @switch-handle-selected-color; + padAll: @switch-handle-selected-pad; +} + +switch_handle_pressed { + padAll: @switch-handle-pressed-pad; +} + +checkbox { + padGap: @checkbox-gap; + textColor: @color-on-surface; +} + +checkbox_box { + bgColor: @checkbox-box-color; + bgOpa: @checkbox-box-opacity; + borderColor: @checkbox-box-border-color; + borderWidth: @checkbox-box-border-width; + radius: @checkbox-box-shape; + padAll: @checkbox-box-pad; + textFont: $fontSmall; + textColor: @checkbox-icon-color; +} + +checkbox_box_selected { + bgColor: @checkbox-box-selected-color; + bgOpa: cover; + borderColor: @checkbox-box-selected-color; + bgImageSrc: symbol(ok); +} + +sliderbox { + padColumn: @sliderbox-gap; +} + +slider_track_size { + height: @slider-track-height; +} + +// The knob extends beyond the slider by its padding, so the slider reserves that room above and below it in layouts. +// Not left and right: flex grow (e.g. in the sliderbox) doesn't take a growing item's own margins into account. +slider_knob_room { + marginVer: @slider-handle-pad; +} + +slider_track { + bgColor: @slider-track-color; + bgOpa: cover; + radius: circle; +} + +slider_active_track { + bgColor: @slider-active-track-color; + bgOpa: cover; + radius: circle; +} + +slider_handle { + bgColor: @slider-handle-color; + bgOpa: cover; + radius: circle; + padAll: @slider-handle-pad; +} + +progress_track { + bgColor: @progress-track-color; + bgOpa: cover; + radius: circle; +} + +progress_indicator { + bgColor: @progress-indicator-color; + bgOpa: cover; + radius: circle; +} + +arc_track { + arcColor: @arc-track-color; + arcWidth: @arc-width; + arcRounded: true; +} + +arc_indicator { + arcColor: @arc-indicator-color; + arcWidth: @arc-width; + arcRounded: true; +} + +arc_handle { + bgColor: @arc-handle-color; + bgOpa: cover; + radius: circle; + padAll: @arc-handle-pad; +} + +field { + bgColor: @field-color; + bgOpa: @field-opacity; + textColor: @field-text-color; + borderColor: @field-border-color; + borderWidth: @field-border-width; + radius: @field-shape; + padHor: @field-pad-hor; + padVer: @field-pad-ver; + padGap: @field-pad-ver; +} + +field_focused { + borderColor: @field-focused-border-color; + borderWidth: @field-focused-border-width; +} + +field_placeholder { + textColor: @field-placeholder-color; +} + +field_cursor { + borderColor: @field-cursor-color; + borderWidth: @field-cursor-width; + borderSide: left; + padLeft: -1; + animDuration: @motion-duration-long; +} + +field_selection { + bgColor: @field-selection-color; + textColor: @field-selection-text-color; +} + +spinbox_cursor { + bgColor: @spinbox-cursor-color; + bgOpa: cover; + textColor: @spinbox-cursor-text-color; +} + +menu { + bgColor: @menu-color; + bgOpa: cover; + textColor: @menu-text-color; + radius: @menu-shape; + padAll: @menu-pad; + textLineSpace: @menu-item-line-space; + maxHeight: @menu-max-height; + clipCorner: true; +} + +menu_item { + bgOpa: transp; +} + +menu_item_selected { + bgColor: @menu-item-selected-color; + bgOpa: cover; + textColor: @menu-item-selected-text-color; +} + +roller { + bgColor: @roller-color; + bgOpa: cover; + textColor: @roller-text-color; + radius: @shape-container; + textLineSpace: @roller-line-space; + textAlign: center; + animDuration: @motion-duration-medium; +} + +roller_selected { + bgColor: @roller-selected-color; + bgOpa: cover; + textColor: @roller-selected-text-color; +} + +keys { + bgColor: @keys-color; + bgOpa: cover; + radius: @shape-container; + padAll: @keys-pad; + padGap: @keys-gap; +} + +key { + bgColor: @key-color; + bgOpa: cover; + textColor: @key-text-color; + radius: @key-shape; +} + +key_pressed { + bgColor: mix(@state-layer-color, @key-color, @opacity-state-pressed); +} + +key_checked { + bgColor: @key-checked-color; + textColor: @key-checked-text-color; +} + +key_selected { + bgColor: @key-selected-color; + textColor: @key-selected-text-color; +} + +list { + bgColor: @list-color; + bgOpa: cover; + padHor: 0; + padVer: @list-pad; + padGap: 0; + clipCorner: true; +} + +list_item { + bgColor: @list-item-color; + bgOpa: @list-item-opacity; + textColor: @list-item-text-color; + padHor: @list-item-pad-hor; + padVer: @list-item-pad-ver; + padColumn: @list-item-gap; + borderColor: @list-divider-color; + borderWidth: @list-divider-width; + borderSide: bottom; +} + +list_item_focused { + bgColor: @list-item-focused-color; + bgOpa: cover; + textColor: @list-item-focused-text-color; +} + +list_subheader { + textColor: @list-subheader-text-color; + textFont: $fontSmall; + padHor: @list-item-pad-hor; + padTop: @list-item-pad-ver; + padBottom: @list-pad; +} + +tabs { + bgOpa: transp; + borderColor: @tabs-divider-color; + borderWidth: @density-border; + borderSide: bottom; +} + +tab { + bgOpa: transp; + textColor: @tab-label-color; + radius: 0; +} + +tab_selected { + textColor: @tab-selected-label-color; + borderColor: @tab-indicator-color; + borderWidth: @tab-indicator-width; + borderSide: bottom; +} + +tab_page { + padAll: @tab-page-pad; +} + +dialog { + bgColor: @dialog-color; + bgOpa: cover; + textColor: @dialog-text-color; + radius: @dialog-shape; + padAll: 0; + padGap: 0; + clipCorner: true; +} + +dialog_header { + padHor: @dialog-pad; + padTop: @dialog-pad; + padBottom: @dialog-gap; + padGap: @dialog-gap; +} + +dialog_title { + textColor: @dialog-title-color; + textFont: $fontLarge; +} + +dialog_content { + padHor: @dialog-pad; + padVer: @dialog-gap; +} + +dialog_actions { + padAll: @dialog-pad; + padTop: @dialog-gap; + padGap: @dialog-gap; +} + +dialog_scrim { + bgColor: @dialog-scrim-color; + bgOpa: @dialog-scrim-opacity; +} + +settings { + bgColor: @settings-color; + bgOpa: cover; + padAll: 0; + padGap: 0; + radius: 0; + clipCorner: true; +} + +settings_sidebar { + padAll: 0; + padGap: 0; + borderColor: @settings-divider-color; + borderWidth: @density-border; + borderSide: right; +} + +settings_content { + padAll: 0; + padGap: 0; +} + +settings_page { + padHor: 0; + padGap: 0; +} + +settings_header { + padHor: @settings-item-pad-hor; + padVer: @density-list-pad; + padGap: @settings-item-gap; +} + +settings_section { + bgColor: @settings-section-color; + bgOpa: cover; + radius: @settings-section-shape; + clipCorner: true; +} + +settings_item { + padHor: @settings-item-pad-hor; + padVer: @settings-item-pad-ver; + padGap: @settings-item-gap; +} + +settings_item_selected { + bgColor: @settings-item-selected-color; + bgOpa: cover; + textColor: @settings-item-selected-text-color; +} + +settings_separator { + bgOpa: transp; + padVer: @density-list-pad; +} + +window_header { + bgColor: @window-header-color; + bgOpa: cover; + padAll: @window-header-pad; + padGap: @window-header-pad; +} + +window_title { + textFont: $fontLarge; +} + +window_content { + bgOpa: transp; + padAll: @window-content-pad; + padGap: @density-container-gap; +} + +table { + bgColor: @table-color; + bgOpa: cover; + padAll: 0; + radius: 0; + borderColor: @table-divider-color; + borderWidth: @density-border; +} + +table_cell { + bgOpa: transp; + textColor: @table-cell-text-color; + padAll: @table-cell-pad; + borderColor: @table-divider-color; + borderWidth: @density-border; + borderSide: bottom; +} + +chart { + bgColor: @chart-color; + bgOpa: cover; + radius: @shape-container; + padAll: @density-container-pad; + lineColor: @chart-grid-color; + borderPost: false; +} + +chart_series { + lineWidth: @chart-series-width; + size: @chart-point-size, @chart-point-size; + radius: @chart-point-size; +} + +chart_indicator { + bgColor: @chart-indicator-color; + bgOpa: cover; + radius: circle; + size: @chart-point-size, @chart-point-size; +} + +calendar { + bgColor: @calendar-color; + bgOpa: cover; + radius: @calendar-shape; + padAll: @calendar-pad; + padGap: @calendar-pad; +} + +calendar_days { + bgOpa: transp; + padAll: 0; + padGap: @density-key-gap; +} + +calendar_day { + bgOpa: transp; + textColor: @calendar-day-text-color; + radius: circle; +} + +calendar_header { + padAll: @calendar-header-pad; + padGap: @calendar-header-pad; +} + +scale { + lineColor: @scale-color; + lineWidth: @scale-width; + arcColor: @scale-color; + arcWidth: @scale-width; + textColor: @scale-color; + length: @scale-tick-length; +} + +divider { + lineColor: @divider-color; + lineWidth: @divider-width; +} + +led { + bgColor: white; + bgOpa: cover; + bgGradColor: palette(grey); + radius: circle; + shadowWidth: @led-glow-width; + shadowColor: white; + shadowSpread: @led-glow-spread; +} + +// ============================================================ +// Apply rules +// ============================================================ + +apply(null > *) { + screen: default; + scrollbar: part.scrollbar; + scrollbar_scrolled: part.scrollbar, state.scrolled; +} + +// Base object + +apply(tabview > obj[1]) { + // Tab content area: no styles +} + +apply(tabview > obj[0]) { + tabs: default; +} + +apply(tabview > * > obj) { + tab_page: default; + scrollbar: part.scrollbar; + scrollbar_scrolled: part.scrollbar, state.scrolled; +} + +apply(win > obj[0]) { + window_header: default; +} + +apply(win > obj[1]) { + window_content: default; + scrollbar: part.scrollbar; + scrollbar_scrolled: part.scrollbar, state.scrolled; +} + +apply(calendar > obj) { + // No styles +} + +apply(obj) { + container: default; + scrollbar: part.scrollbar; + scrollbar_scrolled: part.scrollbar, state.scrolled; +} + +// Buttons + +apply(tabview > *[0] > button) { + tab: default; + transition_exit: default; + state_pressed: state.pressed; + transition_enter: state.pressed; + tab_selected: state.checked; + focus_ring: state.focusKey; + edit_ring: state.edited; +} + +apply(menu_sidebar_header_cont > button, menu_main_header_cont > button, win > obj[0] > button) { + icon_button: default; + transition_exit: default; + state_pressed: state.pressed; + transition_enter: state.pressed; + focus_ring: state.focusKey; + disabled: state.disabled; +} + +apply(button) { + button: default; + transition_exit: default; + button_pressed: state.pressed; + transition_enter: state.pressed; + button_checked: state.checked; + button_checked_pressed: state.checked, state.pressed; + focus_ring: state.focusKey; + edit_ring: state.edited; + button_disabled: state.disabled; +} + +apply(toolbar > icon_button) { + icon_button: default; + app_bar_button: default; + transition_exit: default; + app_bar_button_pressed: state.pressed; + transition_enter: state.pressed; + app_bar_focus_ring: state.focusKey; + app_bar_focus_ring: state.edited; + disabled: state.disabled; +} + +apply(toolbar > dropdown) { + @include dropdown; + app_bar_field: default; + app_bar_focus_ring: state.focusKey; +} + +apply(toolbar) { + app_bar: default; +} + +apply(icon_button) { + icon_button: default; + transition_exit: default; + state_pressed: state.pressed; + transition_enter: state.pressed; + focus_ring_inset: state.focusKey; + edit_ring_inset: state.edited; + disabled: state.disabled; +} + +apply(icon_button_filled) { + icon_button_filled: default; + transition_exit: default; + icon_button_filled_pressed: state.pressed; + transition_enter: state.pressed; + focus_ring: state.focusKey; + edit_ring: state.edited; + button_disabled: state.disabled; +} + +apply(icon_button_tonal) { + icon_button_tonal: default; + transition_exit: default; + icon_button_tonal_pressed: state.pressed; + transition_enter: state.pressed; + focus_ring: state.focusKey; + edit_ring: state.edited; + button_disabled: state.disabled; +} + +apply(msgbox_header_button) { + icon_button: default; + transition_exit: default; + state_pressed: state.pressed; + transition_enter: state.pressed; + focus_ring: state.focusKey; + disabled: state.disabled; +} + +apply(msgbox_footer_button) { + text_button: default; + transition_exit: default; + text_button_pressed: state.pressed; + transition_enter: state.pressed; + focus_ring: state.focusKey; + disabled: state.disabled; +} + +// Labels + +apply(msgbox_header > label) { + dialog_title: default; +} + +apply(win > obj[0] > label) { + window_title: default; +} + +apply(textarea > label) { + field_selection: part.selected; +} + +// Selection controls + +apply(switch) { + switch_track: default; + switch_track_selected: state.checked; + switch_indicator_selected: part.indicator, state.checked; + switch_handle: part.knob; + switch_handle_selected: part.knob, state.checked; + switch_handle_pressed: part.knob, state.pressed; + focus_ring: state.focusKey; + edit_ring: state.edited; + disabled: state.disabled; +} + +apply(checkbox) { + checkbox: default; + focus_ring: state.focusKey; + checkbox_box: part.indicator; + checkbox_box_selected: part.indicator, state.checked; + transition_exit: part.indicator; + transition_enter: part.indicator, state.pressed; + disabled: state.disabled; +} + +apply(page_indicator) { + page_indicator: default; +} + +apply(page_indicator > label) { + page_indicator_label: default; +} + +apply(page_indicator_dot) { + page_indicator_dot: default; + page_indicator_dot_current: state.checked; +} + +apply(badge) { + badge: default; +} + +// Focusable images, e.g. the statusbar icons +apply(image) { + focus_ring_inset: state.focusKey; +} + +apply(chip) { + chip: default; + transition_exit: default; + state_pressed: state.pressed; + transition_enter: state.pressed; + chip_selected: state.checked; + focus_ring: state.focusKey; + edit_ring: state.edited; + disabled: state.disabled; +} + +apply(card) { + card: default; + scrollbar: part.scrollbar; +} + +apply(sliderbox) { + sliderbox: default; +} + +apply(slider) { + slider_track: default; + slider_track_size: default; + slider_knob_room: default; + focus_ring: state.focusKey; + edit_ring: state.edited; + slider_active_track: part.indicator; + slider_handle: part.knob; + disabled: state.disabled; +} + +apply(bar) { + slider_track_size: default; + progress_track: default; + progress_indicator: part.indicator; + focus_ring: state.focusKey; +} + +apply(arc) { + arc_track: default; + arc_indicator: part.indicator; + arc_handle: part.knob; + focus_ring: state.focusKey; + edit_ring: state.edited; +} + +apply(spinner) { + arc_track: default; + arc_indicator: part.indicator; +} + +// Text input + +apply(textarea) { + field: default; + field_focused: state.focused; + focus_ring: state.focusKey; + edit_ring: state.edited; + disabled: state.disabled; + scrollbar: part.scrollbar; + scrollbar_scrolled: part.scrollbar, state.scrolled; + field_cursor: part.cursor, state.focused; + field_placeholder: part.textareaPlaceholder; +} + +apply(spinbox) { + field: default; + field_focused: state.focused; + focus_ring: state.focusKey; + edit_ring: state.edited; + spinbox_cursor: part.cursor; +} + +apply(dropdown) { + field: default; + transition_exit: default; + state_pressed: state.pressed; + transition_enter: state.pressed; + focus_ring: state.focusKey; + edit_ring: state.edited; + disabled: state.disabled; +} + +apply(dropdownlist) { + menu: default; + scrollbar: part.scrollbar; + scrollbar_scrolled: part.scrollbar, state.scrolled; + menu_item: part.selected; + menu_item_selected: part.selected, state.checked; + state_pressed: part.selected, state.pressed; +} + +apply(roller) { + roller: default; + roller_selected: part.selected; + focus_ring: state.focusKey; + edit_ring: state.edited; +} + +// Button matrix and keyboard + +apply(calendar > buttonmatrix) { + calendar_days: default; + calendar_day: part.items; + state_pressed: part.items, state.pressed; + key_selected: part.items, state.checked; + focus_ring: part.items, state.focusKey; + edit_ring: part.items, state.edited; + disabled: part.items, state.disabled; +} + +apply(buttonmatrix) { + keys: default; + focus_ring: state.focusKey; + edit_ring: state.edited; + key: part.items; + key_pressed: part.items, state.pressed; + key_selected: part.items, state.checked; + focus_ring: part.items, state.focusKey; + disabled: part.items, state.disabled; +} + +apply(keyboard) { + keys: default; + focus_ring: state.focusKey; + edit_ring: state.edited; + key: part.items; + key_pressed: part.items, state.pressed; + key_checked: part.items, state.checked; + key_selected: part.items, state.focusKey; + disabled: part.items, state.disabled; +} + +// Lists + +apply(list) { + list: default; + scrollbar: part.scrollbar; + scrollbar_scrolled: part.scrollbar, state.scrolled; +} + +apply(list_text) { + list_subheader: default; +} + +apply(list_button) { + list_item: default; + transition_exit: default; + state_pressed: state.pressed; + transition_enter: state.pressed; + list_item_focused: state.focusKey; + disabled: state.disabled; +} + +apply(table) { + table: default; + focus_ring: state.focusKey; + edit_ring: state.edited; + scrollbar: part.scrollbar; + scrollbar_scrolled: part.scrollbar, state.scrolled; + table_cell: part.items; + state_pressed: part.items, state.pressed; + list_item_focused: part.items, state.focusKey; +} + +// Settings menu + +apply(menu) { + settings: default; +} + +apply(menu_sidebar_cont) { + settings_sidebar: default; + scrollbar: part.scrollbar; + scrollbar_scrolled: part.scrollbar, state.scrolled; +} + +apply(menu_main_cont) { + settings_content: default; + scrollbar: part.scrollbar; + scrollbar_scrolled: part.scrollbar, state.scrolled; +} + +apply(menu_page) { + settings_page: default; + scrollbar: part.scrollbar; + scrollbar_scrolled: part.scrollbar, state.scrolled; +} + +apply(menu_sidebar_header_cont, menu_main_header_cont) { + settings_header: default; +} + +apply(menu_section) { + settings_section: default; +} + +apply(menu_cont) { + settings_item: default; + state_pressed: state.pressed; + settings_item_selected: state.checked; + settings_item_selected: state.focusKey; +} + +apply(menu_separator) { + settings_separator: default; +} + +// Dialog + +apply(msgbox) { + dialog: default; +} + +apply(msgbox_backdrop) { + dialog_scrim: default; +} + +apply(msgbox_header) { + dialog_header: default; +} + +apply(msgbox_content) { + dialog_content: default; + scrollbar: part.scrollbar; + scrollbar_scrolled: part.scrollbar, state.scrolled; +} + +apply(msgbox_footer) { + dialog_actions: default; +} + +// Containers + +apply(tabview, win) { + page: default; +} + +apply(tileview) { + page: default; + scrollbar: part.scrollbar; + scrollbar_scrolled: part.scrollbar, state.scrolled; +} + +apply(tileview_tile) { + scrollbar: part.scrollbar; + scrollbar_scrolled: part.scrollbar, state.scrolled; +} + +// Data display + +apply(chart) { + chart: default; + scrollbar: part.scrollbar; + scrollbar_scrolled: part.scrollbar, state.scrolled; + chart_series: part.items; + chart_indicator: part.indicator; + chart_series: part.cursor; +} + +apply(calendar) { + calendar: default; +} + +apply(calendar_header_arrow, calendar_header_dropdown) { + calendar_header: default; +} + +apply(scale) { + scale: part.main; + scale: part.indicator; + scale: part.items; +} + +apply(line) { + divider: default; +} + +apply(led) { + led: default; +} diff --git a/Modules/lvgl-module/themes/material_mono.lss b/Modules/lvgl-module/themes/material_mono.lss new file mode 100644 index 000000000..8869e5972 --- /dev/null +++ b/Modules/lvgl-module/themes/material_mono.lss @@ -0,0 +1,1334 @@ +// Material 3 theme for monochrome and greyscale displays (OLED, e-paper) +// +// Tokens are layered: +// 1. Base tokens: raw values without meaning +// 2. Semantic tokens: Material 3 roles, mapped onto ink and paper +// 3. Widget tokens: per widget, built from semantic tokens +// Styles only use widget tokens, or semantic tokens for shared state styles. +// +// A 1-bit display can't show opacity or tints, so states are shown with inversion and borders. + +@theme material_mono; + +@config { + isDark: bool = false; + isCompact: bool = false; + animationsEnabled: bool = true; + colorPrimary: color = black; + colorSecondary: color = black; + fontSmall: font = raw("LV_FONT_DEFAULT"); + fontNormal: font = raw("LV_FONT_DEFAULT"); + fontLarge: font = raw("LV_FONT_DEFAULT"); +} + +// Tactility widgets +@widget toolbar: lvgl_toolbar_class; +@widget icon_button: lvgl_icon_button_class; +@widget icon_button_filled: lvgl_icon_button_filled_class; +@widget icon_button_tonal: lvgl_icon_button_tonal_class; +@widget sliderbox: lvgl_sliderbox_class; +@widget card: lvgl_card_class; +@widget chip: lvgl_chip_class; +@widget badge: lvgl_badge_class; +@widget page_indicator: lvgl_page_indicator_class; +@widget page_indicator_dot: lvgl_page_indicator_dot_class; + +// ============================================================ +// 1. Base tokens +// ============================================================ + +@black: black; +@white: white; + +// Shape scale +@corner-none: 0; +@corner-small: 4dp; +@corner-medium: 8dp; +@corner-large: 12dp; +@corner-full: circle; +@corner-compact: 2; + +// Spacing scale +@space-1: 2dp; +@space-2: 4dp; +@space-3: 6dp; +@space-4: 8dp; +@space-6: 12dp; + +// Line widths +@line-thin: 1; +@line-medium: 2; +@line-thick: 3; + +// Motion +@duration-short: 100; +@duration-medium: 250; +@duration-long: 500; + +// ============================================================ +// 2. Semantic tokens +// ============================================================ + +// Ink is the foreground, paper the background +@ink: if($isDark, @white, @black); +@paper: if($isDark, @black, @white); + +@color-primary: @ink; +@color-on-primary: @paper; +@color-surface: @paper; +@color-on-surface: @ink; +@color-outline: @ink; + +// Shapes +@shape-control: if($isCompact, @corner-compact, @corner-full); +@shape-container: if($isCompact, @corner-compact, @corner-medium); +@shape-field: if($isCompact, @corner-compact, @corner-small); +@shape-key: if($isCompact, @corner-compact, @corner-small); +@shape-dialog: if($isCompact, @corner-compact, @corner-large); + +// Density +@density-container-pad: if($isCompact, 2, @space-4); +@density-container-gap: if($isCompact, 2, @space-3); +@density-control-pad-hor: if($isCompact, 3, @space-6); +@density-control-pad-ver: if($isCompact, 2, @space-3); +@density-control-gap: if($isCompact, 2, @space-2); +@density-field-pad: if($isCompact, 2, @space-3); +@density-list-pad: if($isCompact, 1, @space-2); +@density-list-item-pad-hor: if($isCompact, 2, @space-4); +@density-list-item-pad-ver: if($isCompact, 2, @space-3); +@density-dialog-pad: if($isCompact, 2, @space-6); +@density-key-gap: if($isCompact, 1, @space-1); + +// Borders and indicators +@border: @line-thin; +@focus-width: if($isCompact, @line-thin, @line-medium); +@focus-offset: if($isCompact, @line-thin, @line-medium); +@edit-width: if($isCompact, @line-medium, @line-thick); +// Drawn inside the widget, for widgets with little room around them (icon buttons) +@focus-inset-width: if($isCompact, @line-thin, @line-medium); + +// Motion +@motion-duration-short: if($animationsEnabled, @duration-short, 0); +@motion-duration-medium: if($animationsEnabled, @duration-medium, 0); +@motion-duration-long: if($animationsEnabled, @duration-long, 0); + +// ============================================================ +// 3. Widget tokens +// ============================================================ + +// Screen +@screen-color: @color-surface; +@screen-text-color: @color-on-surface; +@screen-gap: @density-container-gap; + +// Container (lv_obj): Material 3 outlined card +@container-color: @color-surface; +@container-text-color: @color-on-surface; +@container-border-color: @color-outline; +@container-border-width: @border; +@container-shape: @shape-container; +@container-pad: @density-container-pad; +@container-gap: @density-container-gap; + +// Card (lvgl_card_create): containers are already outlined, so a card has a thicker outline to stand out +@card-color: @color-surface; +@card-text-color: @color-on-surface; +@card-border-color: @color-outline; +@card-border-width: @line-medium; +@card-shape: @shape-container; +@card-pad: @density-container-pad; +@card-gap: @density-container-gap; + +// Chip (lvgl_chip_create): outlined, inverted when selected +@chip-shape: if($isCompact, @corner-compact, @corner-small); +@chip-pad-hor: if($isCompact, 3, @space-4); +@chip-pad-ver: if($isCompact, 2, @space-2); + +// Page indicator (lvgl_page_indicator_create): a dot per page, the current page's dot is filled +@page-indicator-color: @color-on-surface; +@page-indicator-dot-size: if($isCompact, 4, 6dp); +@page-indicator-gap: if($isCompact, 3, 6dp); + +// Badge (lvgl_badge_create): a small dot that marks an item, e.g. a favourite +@badge-color: @color-on-surface; +@badge-size: if($isCompact, 4, 6dp); + +// Scrollbar +@scrollbar-color: @color-on-surface; +@scrollbar-width: if($isCompact, 2, 3dp); +@scrollbar-pad: 1; + +// Button: Material 3 outlined button, inverted when pressed or checked +@button-container-color: @color-surface; +@button-label-color: @color-on-surface; +@button-border-color: @color-outline; +@button-border-width: @border; +@button-inverted-container-color: @color-primary; +@button-inverted-label-color: @color-on-primary; +@button-shape: @shape-control; +@button-pad-hor: @density-control-pad-hor; +@button-pad-ver: @density-control-pad-ver; +@button-gap: @density-control-gap; + +// Filled and tonal icon buttons: filled is ink, tonal is outlined +@icon-button-container-shape: if($isCompact, @corner-compact, @corner-large); +@icon-button-filled-color: @color-primary; +@icon-button-filled-icon-color: @color-on-primary; +@icon-button-tonal-color: @color-surface; +@icon-button-tonal-icon-color: @color-on-surface; +@icon-button-tonal-border-color: @color-outline; + +// Toolbar: top app bar with a divider, which shows the background like the statusbar does +@app-bar-title-color: @color-on-surface; +@app-bar-divider-color: @color-outline; +@app-bar-pad-hor: if($isCompact, 0, @space-1); +@app-bar-gap: if($isCompact, 2, @space-1); +// The focus ring is drawn inside toolbar buttons, as there's little room around them +@app-bar-focus-width: @focus-inset-width; + +// Focus ring +@focus-ring-color: @color-on-surface; +@focus-ring-width: @focus-width; +@focus-ring-offset: @focus-offset; +@edit-ring-width: @edit-width; + +// Switch +@switch-width: if($isCompact, 25, 40dp); +@switch-height: if($isCompact, 15, 24dp); +@switch-track-color: @color-surface; +@switch-track-border-color: @color-outline; +@switch-track-border-width: @border; +@switch-track-selected-color: @color-primary; +@switch-handle-color: @color-on-surface; +@switch-handle-selected-color: @color-on-primary; +@switch-handle-pad: if($isCompact, -3, -5dp); +@switch-handle-selected-pad: if($isCompact, -2, -3dp); + +// Checkbox +@checkbox-gap: if($isCompact, 3, @space-4); +@checkbox-box-color: @color-surface; +@checkbox-box-border-color: @color-outline; +@checkbox-box-border-width: @border; +@checkbox-box-shape: 2; +@checkbox-box-pad: 1; +@checkbox-box-selected-color: @color-primary; +@checkbox-icon-color: @color-on-primary; + +// Slider, bar, arc, spinner +@slider-track-color: @color-surface; +@slider-track-border-color: @color-outline; +@slider-active-track-color: @color-primary; +@slider-handle-color: @color-primary; +// The track is half of LVGL's default thickness (LV_DPI_DEF / 10). The knob is the track height plus its padding on both +// sides, so the padding includes the removed thickness to keep the knob's size. +@slider-track-height: 8dp; +@slider-handle-pad: if($isCompact, 1, 3dp) + 4dp; +// Sliderbox: in the normal layout, the gap between the slider and its buttons leaves room for the knob, which sticks out at the slider's ends +@sliderbox-gap: if($isCompact, 4, (@slider-track-height / 2) + @slider-handle-pad + @space-2); +@arc-track-color: @color-on-surface; +@arc-track-width: @line-thin; +@arc-indicator-color: @color-primary; +@arc-indicator-width: if($isCompact, @line-medium, 4dp); +@arc-handle-color: @color-primary; +@arc-handle-pad: if($isCompact, 1, 3dp); + +// Text field (textarea, spinbox, dropdown) +@field-color: @color-surface; +@field-text-color: @color-on-surface; +@field-border-color: @color-outline; +@field-border-width: @border; +@field-focused-border-width: @line-medium; +@field-shape: @shape-field; +@field-pad: @density-field-pad; +@field-cursor-color: @color-on-surface; +@field-cursor-width: @line-medium; +@field-selection-color: @color-primary; +@field-selection-text-color: @color-on-primary; + +// Menu (dropdown list), roller +@menu-color: @color-surface; +@menu-border-color: @color-outline; +@menu-shape: @shape-field; +@menu-max-height: dpi-def * 2; +@menu-item-line-space: if($isCompact, 2, @space-3); +@roller-line-space: if($isCompact, 2, @space-3); + +// Button matrix and keyboard +@keys-pad: @density-key-gap; +@keys-gap: @density-key-gap; +@key-color: @color-surface; +@key-text-color: @color-on-surface; +@key-border-color: @color-outline; +@key-shape: @shape-key; + +// List +@list-pad: @density-list-pad; +@list-item-pad-hor: @density-list-item-pad-hor; +@list-item-pad-ver: @density-list-item-pad-ver; +@list-item-gap: if($isCompact, 3, @space-4); +@list-divider-color: @color-outline; + +// Tabs +@tabs-divider-color: @color-outline; +@tab-indicator-color: @color-primary; +@tab-indicator-width: if($isCompact, @line-medium, @line-thick); +@tab-page-pad: @density-container-pad; + +// Dialog +@dialog-color: @color-surface; +@dialog-border-color: @color-outline; +@dialog-border-width: @line-medium; +@dialog-shape: @shape-dialog; +@dialog-pad: @density-dialog-pad; +@dialog-gap: if($isCompact, 2, @space-2); + +// Window +@window-header-border-color: @color-outline; +@window-header-pad: if($isCompact, 2, @space-2); +@window-content-pad: @density-container-pad; + +// Table, chart, calendar, scale, divider +@table-cell-pad: if($isCompact, 2, @space-3); +@table-divider-color: @color-outline; +@chart-grid-color: @color-on-surface; +@chart-point-size: if($isCompact, 3, 6dp); +@calendar-pad: if($isCompact, 2, @space-2); +@scale-color: @color-on-surface; +@scale-tick-length: if($isCompact, 3, 6dp); +@divider-color: @color-outline; + +// ============================================================ +// Styles: shared states +// ============================================================ + +focus_ring { + outlineColor: @focus-ring-color; + outlineWidth: @focus-ring-width; + outlinePad: @focus-ring-offset; + outlineOpa: cover; +} + +edit_ring { + outlineColor: @focus-ring-color; + outlineWidth: @edit-ring-width; + outlinePad: @focus-ring-offset; + outlineOpa: cover; +} + +focus_ring_inset { + outlineColor: @focus-ring-color; + outlineWidth: @focus-inset-width; + outlinePad: -@focus-inset-width; + outlineOpa: cover; +} + +edit_ring_inset { + outlineColor: @focus-ring-color; + outlineWidth: @edit-ring-width; + outlinePad: -@edit-ring-width; + outlineOpa: cover; +} + +inverted { + bgColor: @button-inverted-container-color; + bgOpa: cover; + textColor: @button-inverted-label-color; + borderColor: @button-inverted-label-color; + lineColor: @button-inverted-label-color; + arcColor: @button-inverted-label-color; + outlineColor: @button-inverted-label-color; +} + +disabled { + borderWidth: 0; +} + +scrollbar { + bgColor: @scrollbar-color; + bgOpa: cover; + radius: circle; + width: @scrollbar-width; + padAll: @scrollbar-pad; +} + +// Page-like widgets show the shared app background instead of painting their own +page { + bgOpa: transp; + borderWidth: 0; + radius: 0; + padAll: 0; + padGap: 0; +} + +// ============================================================ +// Styles: widgets +// ============================================================ + +screen { + bgColor: @screen-color; + bgOpa: cover; + textColor: @screen-text-color; + textFont: $fontNormal; + padRow: @screen-gap; + padColumn: @screen-gap; +} + +container { + bgColor: @container-color; + bgOpa: cover; + textColor: @container-text-color; + borderColor: @container-border-color; + borderWidth: @container-border-width; + radius: @container-shape; + padAll: @container-pad; + padGap: @container-gap; + lineColor: @divider-color; + lineWidth: @border; + arcColor: @color-on-surface; + arcWidth: @line-medium; +} + +card { + bgColor: @card-color; + bgOpa: cover; + textColor: @card-text-color; + borderColor: @card-border-color; + borderWidth: @card-border-width; + radius: @card-shape; + padAll: @card-pad; + padGap: @card-gap; +} + +page_indicator { + bgOpa: transp; + borderWidth: 0; + padAll: 0; + padGap: @page-indicator-gap; +} + +page_indicator_label { + textColor: @page-indicator-color; +} + +page_indicator_dot { + width: @page-indicator-dot-size; + height: @page-indicator-dot-size; + radius: circle; + bgColor: @page-indicator-color; + bgOpa: transp; + borderColor: @page-indicator-color; + borderWidth: 1; +} + +page_indicator_dot_current { + bgOpa: cover; +} + +badge { + width: @badge-size; + height: @badge-size; + radius: circle; + bgColor: @badge-color; + bgOpa: cover; +} + +chip { + bgColor: @color-surface; + bgOpa: cover; + textColor: @color-on-surface; + borderColor: @color-outline; + borderWidth: @border; + radius: @chip-shape; + padHor: @chip-pad-hor; + padVer: @chip-pad-ver; +} + +button { + bgColor: @button-container-color; + bgOpa: cover; + textColor: @button-label-color; + borderColor: @button-border-color; + borderWidth: @button-border-width; + radius: @button-shape; + padHor: @button-pad-hor; + padVer: @button-pad-ver; + padGap: @button-gap; +} + +icon_button { + bgOpa: transp; + textColor: @color-on-surface; + borderWidth: 0; + radius: @button-shape; + padAll: @button-pad-ver; +} + +icon_button_filled { + bgColor: @icon-button-filled-color; + bgOpa: cover; + textColor: @icon-button-filled-icon-color; + borderWidth: 0; + radius: @icon-button-container-shape; + padAll: @button-pad-ver; +} + +// The filled button is already ink, so pressing it shows the outlined look +icon_button_filled_pressed { + bgColor: @icon-button-tonal-color; + textColor: @icon-button-tonal-icon-color; + borderColor: @icon-button-tonal-border-color; + borderWidth: @border; +} + +icon_button_tonal { + bgColor: @icon-button-tonal-color; + bgOpa: cover; + textColor: @icon-button-tonal-icon-color; + borderColor: @icon-button-tonal-border-color; + borderWidth: @border; + radius: @icon-button-container-shape; + padAll: @button-pad-ver; +} + +app_bar { + bgOpa: transp; + textColor: @app-bar-title-color; + radius: 0; + borderColor: @app-bar-divider-color; + borderWidth: @border; + borderSide: bottom; + padHor: @app-bar-pad-hor; + padVer: 0; + padGap: @app-bar-gap; +} + +app_bar_button { + padAll: 0; +} + +app_bar_focus_ring { + outlineColor: @focus-ring-color; + outlineWidth: @app-bar-focus-width; + outlinePad: -@app-bar-focus-width; + outlineOpa: cover; +} + +switch_track { + width: @switch-width; + height: @switch-height; + bgColor: @switch-track-color; + bgOpa: cover; + borderColor: @switch-track-border-color; + borderWidth: @switch-track-border-width; + radius: circle; + padAll: 0; + animDuration: @motion-duration-short; +} + +switch_indicator_selected { + bgColor: @switch-track-selected-color; + bgOpa: cover; + radius: circle; +} + +switch_handle { + bgColor: @switch-handle-color; + bgOpa: cover; + radius: circle; + padAll: @switch-handle-pad; +} + +switch_handle_selected { + bgColor: @switch-handle-selected-color; + padAll: @switch-handle-selected-pad; +} + +checkbox { + padGap: @checkbox-gap; +} + +checkbox_box { + bgColor: @checkbox-box-color; + bgOpa: cover; + borderColor: @checkbox-box-border-color; + borderWidth: @checkbox-box-border-width; + radius: @checkbox-box-shape; + padAll: @checkbox-box-pad; + textFont: $fontSmall; + textColor: @checkbox-icon-color; +} + +checkbox_box_selected { + bgColor: @checkbox-box-selected-color; + bgImageSrc: symbol(ok); +} + +sliderbox { + padColumn: @sliderbox-gap; +} + +slider_track_size { + height: @slider-track-height; +} + +// The knob extends beyond the slider by its padding, so the slider reserves that room above and below it in layouts. +// Not left and right: flex grow (e.g. in the sliderbox) doesn't take a growing item's own margins into account. +slider_knob_room { + marginVer: @slider-handle-pad; +} + +slider_track { + bgColor: @slider-track-color; + bgOpa: cover; + borderColor: @slider-track-border-color; + borderWidth: @border; + radius: circle; + padAll: 0; +} + +slider_active_track { + bgColor: @slider-active-track-color; + bgOpa: cover; + radius: circle; +} + +slider_handle { + bgColor: @slider-handle-color; + bgOpa: cover; + radius: circle; + padAll: @slider-handle-pad; +} + +arc_track { + arcColor: @arc-track-color; + arcWidth: @arc-track-width; +} + +arc_indicator { + arcColor: @arc-indicator-color; + arcWidth: @arc-indicator-width; + arcRounded: true; +} + +arc_handle { + bgColor: @arc-handle-color; + bgOpa: cover; + radius: circle; + padAll: @arc-handle-pad; +} + +field { + bgColor: @field-color; + bgOpa: cover; + textColor: @field-text-color; + borderColor: @field-border-color; + borderWidth: @field-border-width; + radius: @field-shape; + padAll: @field-pad; + padGap: @field-pad; +} + +field_focused { + borderWidth: @field-focused-border-width; +} + +field_cursor { + borderColor: @field-cursor-color; + borderWidth: @field-cursor-width; + borderSide: left; + bgOpa: transp; + animDuration: @motion-duration-long; +} + +field_selection { + bgColor: @field-selection-color; + bgOpa: cover; + textColor: @field-selection-text-color; +} + +menu { + bgColor: @menu-color; + bgOpa: cover; + borderColor: @menu-border-color; + borderWidth: @border; + radius: @menu-shape; + textLineSpace: @menu-item-line-space; + maxHeight: @menu-max-height; + clipCorner: true; +} + +menu_item { + bgOpa: transp; +} + +roller { + bgColor: @color-surface; + bgOpa: cover; + textColor: @color-on-surface; + borderColor: @color-outline; + borderWidth: @border; + radius: @shape-container; + textLineSpace: @roller-line-space; + textAlign: center; + animDuration: @motion-duration-medium; +} + +keys { + bgOpa: transp; + borderWidth: 0; + padAll: @keys-pad; + padGap: @keys-gap; +} + +key { + bgColor: @key-color; + bgOpa: cover; + textColor: @key-text-color; + borderColor: @key-border-color; + borderWidth: @border; + radius: @key-shape; +} + +list { + bgColor: @color-surface; + bgOpa: cover; + borderColor: @color-outline; + borderWidth: @border; + radius: @shape-container; + padHor: 0; + padVer: @list-pad; + padGap: 0; + clipCorner: true; +} + +list_item { + bgOpa: transp; + textColor: @color-on-surface; + borderColor: @list-divider-color; + borderWidth: @border; + borderSide: bottom; + radius: 0; + padHor: @list-item-pad-hor; + padVer: @list-item-pad-ver; + padColumn: @list-item-gap; +} + +list_subheader { + textFont: $fontSmall; + textDecor: underline; + padHor: @list-item-pad-hor; + padTop: @list-item-pad-ver; + padBottom: @list-pad; +} + +tabs { + bgOpa: transp; + borderColor: @tabs-divider-color; + borderWidth: @border; + borderSide: bottom; +} + +tab { + bgOpa: transp; + textColor: @color-on-surface; + borderWidth: 0; + radius: 0; +} + +tab_selected { + borderColor: @tab-indicator-color; + borderWidth: @tab-indicator-width; + borderSide: bottom; +} + +tab_page { + padAll: @tab-page-pad; +} + +dialog { + bgColor: @dialog-color; + bgOpa: cover; + textColor: @color-on-surface; + borderColor: @dialog-border-color; + borderWidth: @dialog-border-width; + radius: @dialog-shape; + padAll: 0; + padGap: 0; + clipCorner: true; +} + +dialog_header { + padHor: @dialog-pad; + padTop: @dialog-pad; + padBottom: @dialog-gap; + padGap: @dialog-gap; +} + +dialog_title { + textFont: $fontLarge; +} + +dialog_content { + padHor: @dialog-pad; + padVer: @dialog-gap; +} + +dialog_actions { + padAll: @dialog-pad; + padTop: @dialog-gap; + padGap: @dialog-gap; +} + +settings { + bgColor: @color-surface; + bgOpa: cover; + padAll: 0; + padGap: 0; + radius: 0; + clipCorner: true; +} + +settings_sidebar { + padAll: 0; + padGap: 0; + borderColor: @color-outline; + borderWidth: @border; + borderSide: right; +} + +settings_content { + padAll: 0; + padGap: 0; +} + +settings_page { + padHor: 0; + padGap: 0; +} + +settings_header { + padHor: @density-list-item-pad-hor; + padVer: @density-list-pad; + padGap: @density-container-gap; +} + +settings_section { + bgColor: @color-surface; + bgOpa: cover; + borderColor: @color-outline; + borderWidth: @border; + radius: @shape-container; + clipCorner: true; +} + +settings_item { + padHor: @density-list-item-pad-hor; + padVer: @density-list-item-pad-ver; + padGap: @density-container-gap; +} + +settings_separator { + bgOpa: transp; + padVer: @density-list-pad; +} + +window_header { + bgColor: @color-surface; + bgOpa: cover; + borderColor: @window-header-border-color; + borderWidth: @border; + borderSide: bottom; + padAll: @window-header-pad; + padGap: @window-header-pad; +} + +window_title { + textFont: $fontLarge; +} + +window_content { + bgOpa: transp; + padAll: @window-content-pad; + padGap: @density-container-gap; +} + +table { + bgColor: @color-surface; + bgOpa: cover; + borderColor: @color-outline; + borderWidth: @border; + radius: 0; + padAll: 0; +} + +table_cell { + bgOpa: transp; + textColor: @color-on-surface; + padAll: @table-cell-pad; + borderColor: @table-divider-color; + borderWidth: @border; + borderSide: bottom; +} + +chart { + bgColor: @color-surface; + bgOpa: cover; + borderColor: @color-outline; + borderWidth: @border; + radius: @shape-container; + padAll: @density-container-pad; + lineColor: @chart-grid-color; +} + +chart_series { + lineColor: @color-on-surface; + lineWidth: @line-medium; + size: @chart-point-size, @chart-point-size; +} + +chart_indicator { + bgColor: @color-primary; + bgOpa: cover; + radius: circle; + size: @chart-point-size, @chart-point-size; +} + +calendar { + bgColor: @color-surface; + bgOpa: cover; + borderColor: @color-outline; + borderWidth: @border; + radius: @shape-container; + padAll: @calendar-pad; + padGap: @calendar-pad; +} + +calendar_day { + bgOpa: transp; + borderWidth: 0; + radius: circle; +} + +scale { + lineColor: @scale-color; + lineWidth: @border; + arcColor: @scale-color; + arcWidth: @border; + textColor: @scale-color; + length: @scale-tick-length; +} + +divider { + lineColor: @divider-color; + lineWidth: @border; +} + +led { + bgColor: @color-on-surface; + bgOpa: cover; + radius: circle; + borderColor: @color-outline; + borderWidth: @border; +} + +// ============================================================ +// Apply rules +// ============================================================ + +apply(null > *) { + screen: default; + scrollbar: part.scrollbar; +} + +// Base object + +apply(tabview > obj[1]) { + // Tab content area: no styles +} + +apply(tabview > obj[0]) { + tabs: default; +} + +apply(tabview > * > obj) { + tab_page: default; + scrollbar: part.scrollbar; +} + +apply(win > obj[0]) { + window_header: default; +} + +apply(win > obj[1]) { + window_content: default; + scrollbar: part.scrollbar; +} + +apply(calendar > obj) { + // No styles +} + +apply(obj) { + container: default; + scrollbar: part.scrollbar; +} + +// Buttons + +apply(tabview > *[0] > button) { + tab: default; + inverted: state.pressed; + tab_selected: state.checked; + focus_ring: state.focusKey; + edit_ring: state.edited; +} + +apply(toolbar > icon_button) { + icon_button: default; + app_bar_button: default; + inverted: state.pressed; + app_bar_focus_ring: state.focusKey; + app_bar_focus_ring: state.edited; + disabled: state.disabled; +} + +apply(toolbar) { + app_bar: default; +} + +apply(icon_button) { + icon_button: default; + inverted: state.pressed; + focus_ring_inset: state.focusKey; + edit_ring_inset: state.edited; + disabled: state.disabled; +} + +apply(icon_button_filled) { + icon_button_filled: default; + icon_button_filled_pressed: state.pressed; + focus_ring: state.focusKey; + edit_ring: state.edited; + disabled: state.disabled; +} + +apply(icon_button_tonal) { + icon_button_tonal: default; + inverted: state.pressed; + focus_ring: state.focusKey; + edit_ring: state.edited; + disabled: state.disabled; +} + +apply(menu_sidebar_header_cont > button, menu_main_header_cont > button, win > obj[0] > button, msgbox_header_button) { + icon_button: default; + inverted: state.pressed; + focus_ring: state.focusKey; +} + +apply(button, msgbox_footer_button) { + button: default; + inverted: state.pressed; + inverted: state.checked; + focus_ring: state.focusKey; + edit_ring: state.edited; + disabled: state.disabled; +} + +// Labels + +apply(msgbox_header > label) { + dialog_title: default; +} + +apply(win > obj[0] > label) { + window_title: default; +} + +apply(textarea > label) { + field_selection: part.selected; +} + +// Selection controls + +apply(switch) { + switch_track: default; + switch_indicator_selected: part.indicator, state.checked; + switch_handle: part.knob; + switch_handle_selected: part.knob, state.checked; + focus_ring: state.focusKey; + edit_ring: state.edited; + disabled: state.disabled; +} + +apply(checkbox) { + checkbox: default; + focus_ring: state.focusKey; + checkbox_box: part.indicator; + checkbox_box_selected: part.indicator, state.checked; + disabled: part.indicator, state.disabled; +} + +apply(page_indicator) { + page_indicator: default; +} + +apply(page_indicator > label) { + page_indicator_label: default; +} + +apply(page_indicator_dot) { + page_indicator_dot: default; + page_indicator_dot_current: state.checked; +} + +apply(badge) { + badge: default; +} + +apply(chip) { + chip: default; + inverted: state.checked; + inverted: state.pressed; + focus_ring: state.focusKey; + edit_ring: state.edited; + disabled: state.disabled; +} + +apply(card) { + card: default; + scrollbar: part.scrollbar; +} + +apply(sliderbox) { + sliderbox: default; +} + +apply(slider) { + slider_track: default; + slider_track_size: default; + slider_knob_room: default; + focus_ring: state.focusKey; + edit_ring: state.edited; + slider_active_track: part.indicator; + slider_handle: part.knob; +} + +apply(bar) { + slider_track_size: default; + slider_track: default; + slider_active_track: part.indicator; + focus_ring: state.focusKey; +} + +apply(arc) { + arc_track: default; + arc_indicator: part.indicator; + arc_handle: part.knob; + focus_ring: state.focusKey; + edit_ring: state.edited; +} + +apply(spinner) { + arc_track: default; + arc_indicator: part.indicator; +} + +// Text input + +apply(textarea) { + field: default; + field_focused: state.focused; + focus_ring: state.focusKey; + edit_ring: state.edited; + disabled: state.disabled; + scrollbar: part.scrollbar; + field_cursor: part.cursor, state.focused; +} + +apply(spinbox) { + field: default; + field_focused: state.focused; + focus_ring: state.focusKey; + edit_ring: state.edited; + inverted: part.cursor; +} + +apply(dropdown) { + field: default; + inverted: state.pressed; + focus_ring: state.focusKey; + edit_ring: state.edited; + disabled: state.disabled; +} + +apply(dropdownlist) { + menu: default; + scrollbar: part.scrollbar; + menu_item: part.selected; + inverted: part.selected, state.checked; + inverted: part.selected, state.pressed; +} + +apply(roller) { + roller: default; + inverted: part.selected; + focus_ring: state.focusKey; + edit_ring: state.edited; +} + +// Button matrix and keyboard + +apply(calendar > buttonmatrix) { + keys: default; + calendar_day: part.items; + inverted: part.items, state.pressed; + inverted: part.items, state.checked; + focus_ring: part.items, state.focusKey; + disabled: part.items, state.disabled; +} + +apply(buttonmatrix, keyboard) { + keys: default; + focus_ring: state.focusKey; + edit_ring: state.edited; + key: part.items; + inverted: part.items, state.pressed; + inverted: part.items, state.checked; + focus_ring: part.items, state.focusKey; + disabled: part.items, state.disabled; +} + +// Lists + +apply(list) { + list: default; + scrollbar: part.scrollbar; +} + +apply(list_text) { + list_subheader: default; +} + +apply(list_button) { + list_item: default; + inverted: state.pressed; + inverted: state.focusKey; + disabled: state.disabled; +} + +apply(table) { + table: default; + focus_ring: state.focusKey; + edit_ring: state.edited; + scrollbar: part.scrollbar; + table_cell: part.items; + inverted: part.items, state.pressed; + inverted: part.items, state.focusKey; +} + +// Settings menu + +apply(menu) { + settings: default; +} + +apply(menu_sidebar_cont) { + settings_sidebar: default; + scrollbar: part.scrollbar; +} + +apply(menu_main_cont) { + settings_content: default; + scrollbar: part.scrollbar; +} + +apply(menu_page) { + settings_page: default; + scrollbar: part.scrollbar; +} + +apply(menu_sidebar_header_cont, menu_main_header_cont) { + settings_header: default; +} + +apply(menu_section) { + settings_section: default; +} + +apply(menu_cont) { + settings_item: default; + inverted: state.pressed; + inverted: state.checked; + inverted: state.focusKey; +} + +apply(menu_separator) { + settings_separator: default; +} + +// Dialog + +apply(msgbox) { + dialog: default; +} + +apply(msgbox_backdrop) { + // A 1-bit display can't show a scrim +} + +apply(msgbox_header) { + dialog_header: default; +} + +apply(msgbox_content) { + dialog_content: default; + scrollbar: part.scrollbar; +} + +apply(msgbox_footer) { + dialog_actions: default; +} + +// Containers + +apply(tabview, win) { + page: default; +} + +apply(tileview) { + page: default; + scrollbar: part.scrollbar; +} + +apply(tileview_tile) { + scrollbar: part.scrollbar; +} + +// Data display + +apply(chart) { + chart: default; + scrollbar: part.scrollbar; + chart_series: part.items; + chart_indicator: part.indicator; + chart_series: part.cursor; +} + +apply(calendar) { + calendar: default; +} + +apply(scale) { + scale: part.main; + scale: part.indicator; + scale: part.items; +} + +apply(line) { + divider: default; +} + +apply(led) { + led: default; +} diff --git a/README.md b/README.md index 87eab9de5..53b793ae0 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,14 @@ ## Overview Tactility is an operating system that focuses on the ESP32 microcontroller family. +It can load apps from SD card that were built with native C/C++ SDKs. See [https://tactilityproject.org](https://tactilityproject.org) for more information. -![photo of devices running Tactility](Documentation/pics/tactility-devices.webp)  ![screenshot of launcher app](Documentation/pics/screenshot-Launcher.png) - -You can run built-in apps or start them from an SD card. It's easy to manage system settings: - -![screenshot off app list app](Documentation/pics/screenshot-AppList.png)  ![screenshot of settings app](Documentation/pics/screenshot-Settings.png) +
+
screenshot of launcher app   screenshot of settings app
+
screenshot of terminal app   screenshot of files app
+
## License diff --git a/Tactility/Include/Tactility/app/selectiondialog/SelectionDialog.h b/Tactility/Include/Tactility/app/selectiondialog/SelectionDialog.h index 095220b0c..6e8ddf793 100644 --- a/Tactility/Include/Tactility/app/selectiondialog/SelectionDialog.h +++ b/Tactility/Include/Tactility/app/selectiondialog/SelectionDialog.h @@ -17,8 +17,7 @@ namespace tt::app::selectiondialog { * @a callerAppInstanceId (a new-model app - see app/manager.h). The caller receives the * result as an APP_EVENT_RESULT in its own event loop: result is the selected item's index * (>= 0), -1 if 0 items were provided (an error - the dialog auto-closes without showing - * anything), or a value not matching any item (currently always 1) if the dialog was - * dismissed without a selection. No result_bundle. If exactly 1 item is provided, the dialog + * anything), or -2 if the dialog was dismissed without a selection. No result_bundle. If exactly 1 item is provided, the dialog * auto-closes with result index 0 without showing anything. The caller is responsible for * calling app_manager_stop() on the returned instance id once it has handled the result. * @return the new dialog's app instance id diff --git a/Tactility/Include/Tactility/lvgl/Statusbar.h b/Tactility/Include/Tactility/lvgl/Statusbar.h index 302f28646..59f53b5cd 100644 --- a/Tactility/Include/Tactility/lvgl/Statusbar.h +++ b/Tactility/Include/Tactility/lvgl/Statusbar.h @@ -26,6 +26,13 @@ void statusbar_icon_set_image(int8_t id, const std::string& image); /** Update the visibility for an icon on the statusbar. Does not need to be called with LVGL lock. */ void statusbar_icon_set_visibility(int8_t id, bool visible); +/** + * Sets the app that starts when the icon is clicked. Does not need to be called with LVGL lock. + * @param[in] id the icon id + * @param[in] appId the app id, or an empty string to make the icon not clickable + */ +void statusbar_icon_set_app(int8_t id, const std::string& appId); + int statusbar_get_height(); } // namespace diff --git a/Tactility/Include/Tactility/settings/AppearanceSettings.h b/Tactility/Include/Tactility/settings/AppearanceSettings.h index 437b590cd..5e39c246a 100644 --- a/Tactility/Include/Tactility/settings/AppearanceSettings.h +++ b/Tactility/Include/Tactility/settings/AppearanceSettings.h @@ -1,10 +1,18 @@ #pragma once #include +#include #include namespace tt::settings::appearance { +enum class ThemeMode { + /** Light or dark, as configured for the device */ + DeviceDefault, + Light, + Dark +}; + struct AppearanceSettings { /** Default text font size in pixels, or 0 for the device's default size */ uint16_t fontSize = 0; @@ -12,6 +20,14 @@ struct AppearanceSettings { std::string regularFontPath; /** TrueType font for monospace text, or empty for the system font */ std::string monoFontPath; + ThemeMode themeMode = ThemeMode::DeviceDefault; + /** Use the monochrome theme, also on colour displays */ + bool monoTheme = false; + bool animationsEnabled = true; + /** Theme colours as 0xRRGGBB, or empty for the theme's default colour */ + std::optional primaryColor; + std::optional secondaryColor; + std::optional errorColor; bool operator==(const AppearanceSettings&) const = default; }; diff --git a/Tactility/Private/Tactility/app/AppGrid.h b/Tactility/Private/Tactility/app/AppGrid.h index fd2b6a5ed..36da50549 100644 --- a/Tactility/Private/Tactility/app/AppGrid.h +++ b/Tactility/Private/Tactility/app/AppGrid.h @@ -19,7 +19,7 @@ struct AppGridItem { }; /** - * Paged, non-scrolling grid of app tiles (icon above name) with "<" and ">" toolbar buttons. + * Paged, non-scrolling grid of app tiles (icon above name) with "<" and ">" page buttons. * Must outlive the widgets it creates. */ class AppGrid final { @@ -39,8 +39,36 @@ class AppGrid final { explicit AppGrid(const Callbacks& callbacks) : callbacks(callbacks) {} - /** Adds the paging buttons to the toolbar, creates the grid below it and populates it. */ - void createWidgets(lv_obj_t* parent, lv_obj_t* toolbar); + enum class IconColor { + /** The theme's icon colour */ + Default, + /** The theme's primary colour */ + Primary, + /** The theme's secondary colour */ + Secondary + }; + + /** Sets the colour of the tile icons. Call before creating the widgets. */ + void setIconColor(IconColor color) { iconColor = color; } + + /** Swiping left or right over the grid goes to the next or previous page. Call before creating the widgets. */ + void setSwipeNavigation(bool enabled) { swipeNavigation = enabled; } + + + /** + * Creates the grid and populates it, with a bar below it: buttons on the left (see addBarButton()), + * a page indicator in the center and page buttons on the right. The page indicator and buttons are + * hidden when there's only one page, and the page buttons wrap around. + * @param[in] parent the container for the grid and the bar + */ + void createWidgetsWithBottomBar(lv_obj_t* parent); + + /** + * Adds an icon button to the left side of the bottom bar. Call right after createWidgetsWithBottomBar(). + * @param[in] icon a shared icon (see lvgl/icons/shared.h) + * @return the button + */ + lv_obj_t* addBarButton(const char* icon, lv_event_cb_t onClicked, void* userData); /** * Rebuilds the grid asynchronously, so it is safe to call from a tile's own event. @@ -53,22 +81,33 @@ class AppGrid final { Callbacks callbacks; std::vector items; uint32_t page = 0; + uint32_t pageCount = 1; + IconColor iconColor = IconColor::Default; + bool swipeNavigation = false; bool keepSelectionOnRepopulate = true; lv_obj_t* grid = nullptr; lv_obj_t* prevButton = nullptr; lv_obj_t* nextButton = nullptr; + lv_obj_t* pageIndicator = nullptr; + lv_obj_t* pageButtons = nullptr; + lv_obj_t* barButtons = nullptr; + void createGrid(lv_obj_t* parent); + void goToPreviousPage(); + void goToNextPage(); void populate(); static void onDeferredRepopulate(void* userData); + static void onDeferredNextPage(void* userData); + static void onDeferredPreviousPage(void* userData); static void onGridDeleted(lv_event_t* e); static void onGridSizeChanged(lv_event_t* e); static void onPrevPressed(lv_event_t* e); static void onNextPressed(lv_event_t* e); + static void onGridGesture(lv_event_t* e); static void onTileClicked(lv_event_t* e); static void onTileLongPressed(lv_event_t* e); static void onTileKey(lv_event_t* e); - static void onTileFocusChanged(lv_event_t* e); }; } diff --git a/Tactility/Private/Tactility/app/applist/Favourites.h b/Tactility/Private/Tactility/app/launcher/Favourites.h similarity index 94% rename from Tactility/Private/Tactility/app/applist/Favourites.h rename to Tactility/Private/Tactility/app/launcher/Favourites.h index c029b7983..1bf364884 100644 --- a/Tactility/Private/Tactility/app/applist/Favourites.h +++ b/Tactility/Private/Tactility/app/launcher/Favourites.h @@ -9,7 +9,7 @@ #include #include -namespace tt::app::applist { +namespace tt::app::launcher { /** Favourited app ids, persisted one per line under the data root. */ class Favourites final { @@ -81,7 +81,7 @@ class Favourites final { static std::string filePath() { char path[128]; - if (app_paths_get_user_data_path("tactility.applist", "favourites", path, sizeof(path)) != ERROR_NONE) { + if (app_paths_get_user_data_path("tactility.launcher", "favourites", path, sizeof(path)) != ERROR_NONE) { return ""; } return path; diff --git a/Tactility/Private/Tactility/lvgl/Theme.h b/Tactility/Private/Tactility/lvgl/Theme.h new file mode 100644 index 000000000..09c5c35cd --- /dev/null +++ b/Tactility/Private/Tactility/lvgl/Theme.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +namespace tt::lvgl { + +/** + * Sets the theme settings from the appearance settings. + * They take effect for displays that are added afterwards, so call this before LVGL (re)starts. + */ +void configureTheme(const settings::appearance::AppearanceSettings& settings); + +} diff --git a/Tactility/Source/InitApps.cpp b/Tactility/Source/InitApps.cpp index ec362a8b4..53c100bd7 100644 --- a/Tactility/Source/InitApps.cpp +++ b/Tactility/Source/InitApps.cpp @@ -42,7 +42,6 @@ namespace app { namespace appearancesettings { extern const ::AppManifest manifest; } namespace apphubdetails { extern const ::AppManifest manifest; } namespace apppackagedetails { extern const ::AppManifest manifest; } - namespace applist { extern const ::AppManifest manifest; } namespace apppackagelist { extern const ::AppManifest manifest; } namespace audiosettings { extern const ::AppManifest manifest; } namespace boot { extern const ::AppManifest manifest; } @@ -110,7 +109,6 @@ static void registerInternalApps() { app_manager_add(&app::apppackagedetails::manifest); app_manager_add(&app::apphub::manifest); app_manager_add(&app::apphubdetails::manifest); - app_manager_add(&app::applist::manifest); app_manager_add(&app::apppackagelist::manifest); if (service::audio::isAvailable()) { app_manager_add(&app::audiosettings::manifest); diff --git a/Tactility/Source/app/AppGrid.cpp b/Tactility/Source/app/AppGrid.cpp index 4af67ffa9..2d7a0fb77 100644 --- a/Tactility/Source/app/AppGrid.cpp +++ b/Tactility/Source/app/AppGrid.cpp @@ -2,7 +2,11 @@ #include #include -#include +#include +#include +#include +#include +#include #include #include @@ -12,8 +16,6 @@ namespace tt::app { namespace { constexpr int32_t TILE_GAP = 6; -constexpr uint32_t ICON_COLOR_SELECTED = 0x33BBFF; -constexpr uint32_t ICON_COLOR_HIGHLIGHTED = 0xF876E9; // Minimum label width, as a multiple of the label's font size constexpr uint32_t MIN_LABEL_WIDTH_IN_FONT_SIZES = 6; @@ -49,24 +51,71 @@ PageLayout computePageLayout(lv_obj_t* grid) { }; } -lv_color_t getIconColor(bool focused, bool highlighted) { - if (focused) { - return lv_color_hex(ICON_COLOR_SELECTED); +const AppGridItem* getTileItem(lv_event_t* e) { + return static_cast(lv_obj_get_user_data(lv_event_get_target_obj(e))); +} + + +lv_obj_t* createBarSection(lv_obj_t* parent, lv_flex_align_t align) { + auto* section = lv_obj_create(parent); + lv_obj_set_size(section, LV_SIZE_CONTENT, LV_SIZE_CONTENT); + lv_obj_set_style_pad_all(section, 0, LV_STATE_DEFAULT); + lv_obj_set_style_border_width(section, 0, LV_STATE_DEFAULT); + lv_obj_set_flex_flow(section, LV_FLEX_FLOW_ROW); + lv_obj_set_flex_align(section, align, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + lv_obj_remove_flag(section, LV_OBJ_FLAG_SCROLLABLE); + return section; +} + +// The width that a section's visible children need, including the gaps between them +int32_t getContentWidth(lv_obj_t* section) { + int32_t width = 0; + uint32_t visible = 0; + const uint32_t count = lv_obj_get_child_count(section); + for (uint32_t i = 0; i < count; i++) { + lv_obj_t* child = lv_obj_get_child(section, static_cast(i)); + if (!lv_obj_has_flag(child, LV_OBJ_FLAG_HIDDEN)) { + width += lv_obj_get_width(child); + visible++; + } } - return highlighted ? lv_color_hex(ICON_COLOR_HIGHLIGHTED) : lv_color_white(); + return visible > 1 ? width + static_cast(visible - 1) * lv_obj_get_style_pad_column(section, LV_PART_MAIN) : width; } -// Accent colours can't be rendered in 1bpp, so tiles use theme colours and the theme's focus outline instead -bool isMonochrome(lv_obj_t* obj) { - return lv_display_get_color_format(lv_obj_get_display(obj)) == LV_COLOR_FORMAT_I1; +// The page indicator gets the space between the side sections, which grow equally to keep it centered +void updatePageIndicatorWidth(void* data) { + auto* bottom_bar = static_cast(data); + auto* left_section = lv_obj_get_child(bottom_bar, 0); + auto* indicator_section = lv_obj_get_child(bottom_bar, 1); + auto* page_buttons = lv_obj_get_child(bottom_bar, 2); + auto* indicator = lv_obj_get_child(indicator_section, 0); + if (indicator == nullptr) { + return; + } + const int32_t side_width = std::max(getContentWidth(left_section), getContentWidth(page_buttons)); + const int32_t gaps = 2 * lv_obj_get_style_pad_column(bottom_bar, LV_PART_MAIN); + const int32_t available = lv_obj_get_content_width(bottom_bar) - 2 * side_width - gaps; + lv_obj_set_style_max_width(indicator, std::max(0, available), LV_STATE_DEFAULT); } -const AppGridItem* getTileItem(lv_event_t* e) { - return static_cast(lv_obj_get_user_data(lv_event_get_target_obj(e))); +void onBottomBarSizeChanged(lv_event_t* event) { + lv_async_call(updatePageIndicatorWidth, lv_event_get_target_obj(event)); +} + +void onBottomBarDeleted(lv_event_t* event) { + lv_async_call_cancel(updatePageIndicatorWidth, lv_event_get_target_obj(event)); } } // namespace +void AppGrid::onDeferredNextPage(void* userData) { + static_cast(userData)->goToNextPage(); +} + +void AppGrid::onDeferredPreviousPage(void* userData) { + static_cast(userData)->goToPreviousPage(); +} + void AppGrid::onDeferredRepopulate(void* userData) { static_cast(userData)->populate(); } @@ -74,6 +123,8 @@ void AppGrid::onDeferredRepopulate(void* userData) { void AppGrid::onGridDeleted(lv_event_t* e) { // Cancels a still-pending repopulate scheduled right before the grid itself was deleted (e.g. the app closing) lv_async_call_cancel(onDeferredRepopulate, lv_event_get_user_data(e)); + lv_async_call_cancel(onDeferredNextPage, lv_event_get_user_data(e)); + lv_async_call_cancel(onDeferredPreviousPage, lv_event_get_user_data(e)); } // Deferred: page size depends on the grid size, which is final only after the layout pass that triggered this. @@ -81,18 +132,42 @@ void AppGrid::onGridSizeChanged(lv_event_t* e) { lv_async_call(onDeferredRepopulate, lv_event_get_user_data(e)); } +// Changing the page doesn't keep the selected app selected: that would return to the selected app's page +void AppGrid::goToPreviousPage() { + page = page > 0 ? page - 1 : pageCount - 1; + keepSelectionOnRepopulate = false; + populate(); +} + +void AppGrid::goToNextPage() { + page = page + 1 < pageCount ? page + 1 : 0; + keepSelectionOnRepopulate = false; + populate(); +} + void AppGrid::onPrevPressed(lv_event_t* e) { - auto* self = static_cast(lv_event_get_user_data(e)); - if (self->page > 0) { - self->page--; - self->populate(); - } + static_cast(lv_event_get_user_data(e))->goToPreviousPage(); } void AppGrid::onNextPressed(lv_event_t* e) { + static_cast(lv_event_get_user_data(e))->goToNextPage(); +} + +// Swipes that start on a tile reach the grid too, as tiles pass their gestures on to the grid +void AppGrid::onGridGesture(lv_event_t* e) { auto* self = static_cast(lv_event_get_user_data(e)); - self->page++; - self->populate(); + lv_indev_t* indev = lv_indev_active(); + if (indev == nullptr || self->pageCount <= 1) { + return; + } + const lv_dir_t direction = lv_indev_get_gesture_dir(indev); + if (direction != LV_DIR_LEFT && direction != LV_DIR_RIGHT) { + return; + } + // Keeps the tile where the swipe started from being clicked when the finger is lifted + lv_indev_wait_release(indev); + // Deferred: changing the page deletes the tiles, possibly including the one this event came from + lv_async_call(direction == LV_DIR_LEFT ? onDeferredNextPage : onDeferredPreviousPage, self); } void AppGrid::onTileClicked(lv_event_t* e) { @@ -110,26 +185,21 @@ void AppGrid::onTileKey(lv_event_t* e) { self->callbacks.onKey(getTileItem(e)->manifest, lv_event_get_key(e), self->callbacks.userData); } -void AppGrid::onTileFocusChanged(lv_event_t* e) { - lv_obj_t* tile = lv_event_get_target_obj(e); - lv_obj_t* icon = lv_obj_get_child(tile, 0); - const bool focused = lv_event_get_code(e) == LV_EVENT_FOCUSED; - lv_obj_set_style_text_color(icon, getIconColor(focused, getTileItem(e)->highlighted), LV_STATE_DEFAULT); -} - void AppGrid::requestRepopulate(bool keepSelection) { keepSelectionOnRepopulate = keepSelection; lv_async_call(onDeferredRepopulate, this); } void AppGrid::populate() { - // Captured before lv_obj_clean() deletes the currently focused tile below: LVGL moves - // focus elsewhere as it leaves the group, and creating replacement tiles doesn't restore - // it, so the app id is remembered here and re-focused on its new tile once rebuilt. + // Captured before lv_obj_clean() deletes the currently focused tile below: creating + // replacement tiles doesn't restore the focus, so the app id is remembered here and + // re-focused on its new tile once rebuilt. std::string focusedAppId; + const bool keepSelection = keepSelectionOnRepopulate; lv_group_t* group = lv_group_get_default(); - if (group != nullptr && keepSelectionOnRepopulate) { - lv_obj_t* focused = lv_group_get_focused(group); + if (group != nullptr && keepSelection) { + // Grid navigation focuses the grid in the group, and a tile within the grid + lv_obj_t* focused = lv_group_get_focused(group) == grid ? lvgl_grid_navigation_get_focused(grid) : nullptr; if (focused != nullptr && lv_obj_get_parent(focused) == grid) { focusedAppId = static_cast(lv_obj_get_user_data(focused))->manifest.id; } @@ -157,30 +227,24 @@ void AppGrid::populate() { } } page = std::min(page, page_count - 1); + pageCount = page_count; - lv_obj_set_state(prevButton, LV_STATE_DISABLED, page == 0); - lv_obj_set_state(nextButton, LV_STATE_DISABLED, page + 1 >= page_count); - // The toolbar wraps each action button, so the wrapper is hidden to free its space too - lv_obj_set_flag(lv_obj_get_parent(prevButton), LV_OBJ_FLAG_HIDDEN, page_count <= 1); - lv_obj_set_flag(lv_obj_get_parent(nextButton), LV_OBJ_FLAG_HIDDEN, page_count <= 1); + lv_obj_set_flag(pageIndicator, LV_OBJ_FLAG_HIDDEN, page_count <= 1); + lv_obj_set_flag(pageButtons, LV_OBJ_FLAG_HIDDEN, page_count <= 1); + lvgl_page_indicator_set_page_count(pageIndicator, page_count); + lvgl_page_indicator_set_page(pageIndicator, page); lv_obj_t* focusedTile = nullptr; - const bool monochrome = isMonochrome(grid); const uint32_t first = page * layout.pageSize; const uint32_t last = std::min(item_count, first + layout.pageSize); for (uint32_t i = first; i < last; i++) { const auto& item = items[i]; - lv_obj_t* tile = lv_button_create(grid); + // The theme styles the tile as an icon button, including its focus and pressed states + lv_obj_t* tile = lvgl_icon_button_create(grid); lv_obj_set_size(tile, layout.tileWidth, layout.tileHeight); lv_obj_set_style_pad_all(tile, layout.pad, LV_STATE_DEFAULT); lv_obj_set_style_pad_row(tile, layout.pad / 2, LV_STATE_DEFAULT); - lv_obj_set_style_shadow_width(tile, 0, LV_STATE_DEFAULT); - lv_obj_set_style_bg_opa(tile, LV_OPA_TRANSP, LV_STATE_DEFAULT); - if (!monochrome) { - lv_obj_set_style_outline_width(tile, 0, LV_STATE_FOCUSED); - lv_obj_set_style_outline_width(tile, 0, LV_STATE_FOCUS_KEY); - } lv_obj_set_flex_flow(tile, LV_FLEX_FLOW_COLUMN); lv_obj_set_flex_align(tile, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); lv_obj_set_user_data(tile, const_cast(&item)); @@ -189,10 +253,12 @@ void AppGrid::populate() { lv_obj_set_style_text_font(icon, lvgl_get_shared_icon_large_font(), LV_STATE_DEFAULT); lv_obj_set_style_text_align(icon, LV_TEXT_ALIGN_CENTER, LV_STATE_DEFAULT); lv_obj_set_size(icon, layout.iconSize, layout.iconSize); - if (!monochrome) { - lv_obj_set_style_text_color(icon, getIconColor(false, item.highlighted), LV_STATE_DEFAULT); - } lv_label_set_text(icon, item.icon); + if (iconColor == IconColor::Primary) { + lv_obj_set_style_text_color(icon, lv_theme_get_color_primary(icon), LV_STATE_DEFAULT); + } else if (iconColor == IconColor::Secondary) { + lv_obj_set_style_text_color(icon, lv_theme_get_color_secondary(icon), LV_STATE_DEFAULT); + } lv_obj_t* label = lv_label_create(tile); lv_obj_set_style_text_font(label, lvgl_get_text_font(FONT_SIZE_SMALL), LV_STATE_DEFAULT); @@ -202,6 +268,12 @@ void AppGrid::populate() { lv_label_set_long_mode(label, LV_LABEL_LONG_MODE_DOTS); lv_label_set_text(label, item.manifest.name); + // A badge marks favourites, as it doesn't look like the focus indicator + if (item.highlighted) { + auto* badge = lvgl_badge_create(tile); + lv_obj_align(badge, LV_ALIGN_TOP_RIGHT, 0, 0); + } + lv_obj_add_event_cb(tile, onTileClicked, LV_EVENT_SHORT_CLICKED, this); if (callbacks.onLongPressed != nullptr) { lv_obj_add_event_cb(tile, onTileLongPressed, LV_EVENT_LONG_PRESSED, this); @@ -209,10 +281,6 @@ void AppGrid::populate() { if (callbacks.onKey != nullptr) { lv_obj_add_event_cb(tile, onTileKey, LV_EVENT_KEY, this); } - if (!monochrome) { - lv_obj_add_event_cb(tile, onTileFocusChanged, LV_EVENT_FOCUSED, nullptr); - lv_obj_add_event_cb(tile, onTileFocusChanged, LV_EVENT_DEFOCUSED, nullptr); - } if (!focusedAppId.empty() && focusedAppId == item.manifest.id) { focusedTile = tile; @@ -220,15 +288,16 @@ void AppGrid::populate() { } if (focusedTile != nullptr) { - lv_group_focus_obj(focusedTile); - lv_obj_add_state(focusedTile, LV_STATE_FOCUS_KEY); + lv_group_focus_obj(grid); + lv_gridnav_set_focused(grid, focusedTile, LV_ANIM_OFF); + } else if (!keepSelection && group != nullptr && lv_group_get_focused(group) == grid) { + // Grid navigation selects the first new tile when the grid is focused, but a page change shows no selection. + // The next key that moves the focus selects a tile again. + lvgl_focus_hide_key_selection(group); } } -void AppGrid::createWidgets(lv_obj_t* parent, lv_obj_t* toolbar) { - prevButton = lvgl_toolbar_add_text_button_action(toolbar, "<", onPrevPressed, this); - nextButton = lvgl_toolbar_add_text_button_action(toolbar, ">", onNextPressed, this); - +void AppGrid::createGrid(lv_obj_t* parent) { grid = lv_obj_create(parent); lv_obj_set_width(grid, LV_PCT(100)); lv_obj_set_flex_grow(grid, 1); @@ -238,20 +307,61 @@ void AppGrid::createWidgets(lv_obj_t* parent, lv_obj_t* toolbar) { lv_obj_set_style_pad_column(grid, TILE_GAP, LV_STATE_DEFAULT); lv_obj_set_style_border_width(grid, 0, LV_STATE_DEFAULT); lv_obj_remove_flag(grid, LV_OBJ_FLAG_SCROLLABLE); + // The arrow keys move between the tiles in rows and columns + lvgl_grid_navigation_add(grid); + lv_obj_add_event_cb(grid, onGridDeleted, LV_EVENT_DELETE, this); + if (swipeNavigation) { + // LVGL sends a gesture to the first object, starting at the touched one, that doesn't pass it on to its parent + lv_obj_remove_flag(grid, LV_OBJ_FLAG_GESTURE_BUBBLE); + lv_obj_add_event_cb(grid, onGridGesture, LV_EVENT_GESTURE, this); + } +} + +void AppGrid::createWidgetsWithBottomBar(lv_obj_t* parent) { + // The side sections grow equally, so the page indicator stays centered + auto* bottom_bar = createBarSection(parent, LV_FLEX_ALIGN_START); + lv_obj_set_width(bottom_bar, LV_PCT(100)); + lvgl_obj_add_edge_padding(bottom_bar); + barButtons = createBarSection(bottom_bar, LV_FLEX_ALIGN_START); + lv_obj_set_flex_grow(barButtons, 1); + auto* indicator_section = createBarSection(bottom_bar, LV_FLEX_ALIGN_CENTER); + pageButtons = createBarSection(bottom_bar, LV_FLEX_ALIGN_END); + lv_obj_set_flex_grow(pageButtons, 1); + lv_obj_add_event_cb(bottom_bar, onBottomBarSizeChanged, LV_EVENT_SIZE_CHANGED, nullptr); + lv_obj_add_event_cb(bottom_bar, onBottomBarDeleted, LV_EVENT_DELETE, nullptr); + + createGrid(parent); + lv_obj_move_foreground(bottom_bar); + + pageIndicator = lvgl_page_indicator_create(indicator_section); + prevButton = lvgl_icon_button_create(pageButtons); + lv_label_set_text(lv_label_create(prevButton), "<"); + lv_obj_add_event_cb(prevButton, onPrevPressed, LV_EVENT_SHORT_CLICKED, this); + nextButton = lvgl_icon_button_create(pageButtons); + lv_label_set_text(lv_label_create(nextButton), ">"); + lv_obj_add_event_cb(nextButton, onNextPressed, LV_EVENT_SHORT_CLICKED, this); + // The arrow keys move between the page buttons + lvgl_grid_navigation_add(pageButtons); // Resolves the grid's flex_grow height, which the page size is derived from. lv_obj_update_layout(parent); - - lv_obj_add_event_cb(grid, onGridDeleted, LV_EVENT_DELETE, this); lv_obj_add_event_cb(grid, onGridSizeChanged, LV_EVENT_SIZE_CHANGED, this); populate(); - const bool next_visible = !lv_obj_has_flag(lv_obj_get_parent(nextButton), LV_OBJ_FLAG_HIDDEN); - if (next_visible && !lvgl_indev_exists(LV_INDEV_TYPE_POINTER)) { - lv_group_focus_obj(nextButton); - lv_obj_add_state(nextButton, LV_STATE_FOCUS_KEY); + if (!lvgl_indev_exists(LV_INDEV_TYPE_POINTER) && lv_obj_get_child_count(grid) > 0) { + lv_group_focus_obj(grid); + lv_gridnav_set_focused(grid, lv_obj_get_child(grid, 0), LV_ANIM_OFF); } } +lv_obj_t* AppGrid::addBarButton(const char* icon, lv_event_cb_t onClicked, void* userData) { + auto* button = lvgl_icon_button_create(barButtons); + auto* label = lv_label_create(button); + lv_obj_set_style_text_font(label, lvgl_get_shared_icon_default_font(), LV_STATE_DEFAULT); + lv_label_set_text(label, icon); + lv_obj_add_event_cb(button, onClicked, LV_EVENT_SHORT_CLICKED, userData); + return button; +} + } diff --git a/Tactility/Source/app/appearancesettings/AppearanceSettings.cpp b/Tactility/Source/app/appearancesettings/AppearanceSettings.cpp index 0c64cf4b2..d1513ecfd 100644 --- a/Tactility/Source/app/appearancesettings/AppearanceSettings.cpp +++ b/Tactility/Source/app/appearancesettings/AppearanceSettings.cpp @@ -1,7 +1,9 @@ #include +#include #include #include #include +#include #include #include @@ -19,6 +21,9 @@ #include #include +#include +#include +#include #include #include @@ -42,6 +47,41 @@ enum class FontSlot { Mono }; +struct NamedColor { + const char* name; + lv_palette_t palette; +}; + +constexpr NamedColor PALETTE_COLORS[] = { + { "Red", LV_PALETTE_RED }, + { "Pink", LV_PALETTE_PINK }, + { "Purple", LV_PALETTE_PURPLE }, + { "Deep purple", LV_PALETTE_DEEP_PURPLE }, + { "Indigo", LV_PALETTE_INDIGO }, + { "Blue", LV_PALETTE_BLUE }, + { "Light blue", LV_PALETTE_LIGHT_BLUE }, + { "Cyan", LV_PALETTE_CYAN }, + { "Teal", LV_PALETTE_TEAL }, + { "Green", LV_PALETTE_GREEN }, + { "Light green", LV_PALETTE_LIGHT_GREEN }, + { "Lime", LV_PALETTE_LIME }, + { "Yellow", LV_PALETTE_YELLOW }, + { "Amber", LV_PALETTE_AMBER }, + { "Orange", LV_PALETTE_ORANGE }, + { "Deep orange", LV_PALETTE_DEEP_ORANGE }, + { "Brown", LV_PALETTE_BROWN }, + { "Blue grey", LV_PALETTE_BLUE_GREY }, + { "Grey", LV_PALETTE_GREY }, +}; + +constexpr size_t COLOR_COUNT = 3; +constexpr const char* COLOR_TITLES[COLOR_COUNT] = { "Primary color", "Secondary color", "Error color" }; + +struct ColorRowWidgets { + lv_obj_t* row = nullptr; + lv_obj_t* swatch = nullptr; +}; + struct FontRowWidgets { lv_obj_t* defaultButton = nullptr; lv_obj_t* fileLabel = nullptr; @@ -53,6 +93,7 @@ struct Context { TaskEventGroup* eventGroup = nullptr; uint32_t selectRegularBit = 0; uint32_t selectMonoBit = 0; + uint32_t selectColorBit = 0; uint32_t applyBit = 0; uint32_t cacheUpdatedBit = 0; @@ -70,6 +111,9 @@ struct Context { std::atomic cacheUpdateSucceeded = false; uint32_t selectLaunchId = 0; + /** The colour that the selection dialog is for */ + size_t selectColorIndex = 0; + uint32_t colorSelectLaunchId = 0; FontSlot selectSlot = FontSlot::Regular; AppStream selectStream {}; uint8_t selectBuffer[256] {}; @@ -80,6 +124,13 @@ struct Context { FontRowWidgets regularRow; FontRowWidgets monoRow; lv_obj_t* errorLabel = nullptr; + lv_obj_t* lightChip = nullptr; + lv_obj_t* darkChip = nullptr; + lv_obj_t* regularThemeChip = nullptr; + lv_obj_t* monoThemeChip = nullptr; + ColorRowWidgets colorRows[COLOR_COUNT]; + /** The display can only show the mono theme */ + bool isMonoDisplay = false; }; lvgl::FontConfiguration toFontConfiguration(const settings::appearance::AppearanceSettings& settings) { @@ -120,6 +171,50 @@ void updateFontRow(const FontRowWidgets& row, const std::string& path, size_t ch setHidden(row.warningLabel, !large); } +bool isDeviceDefaultDark() { + LvglThemeSettings defaults; + lvgl_theme_get_default_settings(&defaults); + return defaults.is_dark; +} + +bool isDark(const settings::appearance::AppearanceSettings& settings) { + switch (settings.themeMode) { + case settings::appearance::ThemeMode::Light: + return false; + case settings::appearance::ThemeMode::Dark: + return true; + default: + return isDeviceDefaultDark(); + } +} + +void setChecked(lv_obj_t* object, bool checked) { + if (checked) { + lv_obj_add_state(object, LV_STATE_CHECKED); + } else { + lv_obj_remove_state(object, LV_STATE_CHECKED); + } +} + +void updateColorRow(Context* ctx, size_t index); + +void updateThemeWidgets(Context* ctx) { + const bool dark = isDark(ctx->pendingSettings); + setChecked(ctx->lightChip, !dark); + setChecked(ctx->darkChip, dark); + if (ctx->regularThemeChip != nullptr) { + setChecked(ctx->regularThemeChip, !ctx->pendingSettings.monoTheme); + setChecked(ctx->monoThemeChip, ctx->pendingSettings.monoTheme); + } + // The mono theme doesn't use colours + for (size_t i = 0; i < COLOR_COUNT; i++) { + if (ctx->colorRows[i].row != nullptr) { + setHidden(ctx->colorRows[i].row, ctx->pendingSettings.monoTheme); + updateColorRow(ctx, i); + } + } +} + /** Updates the widgets to the pending settings. Requires the LVGL lock. */ void updateWidgets(Context* ctx) { if (ctx->applyButton == nullptr) { @@ -131,6 +226,81 @@ void updateWidgets(Context* ctx) { updateFontRow(ctx->regularRow, ctx->pendingSettings.regularFontPath, ctx->regularCharacterCount); updateFontRow(ctx->monoRow, ctx->pendingSettings.monoFontPath, ctx->monoCharacterCount); setHidden(ctx->errorLabel, !ctx->applyFailed); + updateThemeWidgets(ctx); +} + +void setDark(Context* ctx, bool dark) { + // The device default is kept when it matches, so it doesn't count as a change + if (dark == isDeviceDefaultDark()) { + ctx->pendingSettings.themeMode = settings::appearance::ThemeMode::DeviceDefault; + } else { + ctx->pendingSettings.themeMode = dark ? settings::appearance::ThemeMode::Dark : settings::appearance::ThemeMode::Light; + } + updateWidgets(ctx); +} + +void onLightPressed(lv_event_t* event) { + setDark(static_cast(lv_event_get_user_data(event)), false); +} + +void onDarkPressed(lv_event_t* event) { + setDark(static_cast(lv_event_get_user_data(event)), true); +} + +void onRegularThemePressed(lv_event_t* event) { + auto* ctx = static_cast(lv_event_get_user_data(event)); + ctx->pendingSettings.monoTheme = false; + updateWidgets(ctx); +} + +void onMonoThemePressed(lv_event_t* event) { + auto* ctx = static_cast(lv_event_get_user_data(event)); + ctx->pendingSettings.monoTheme = true; + updateWidgets(ctx); +} + +void onAnimationsChanged(lv_event_t* event) { + auto* ctx = static_cast(lv_event_get_user_data(event)); + auto* checkbox = static_cast(lv_event_get_target(event)); + ctx->pendingSettings.animationsEnabled = lv_obj_has_state(checkbox, LV_STATE_CHECKED); + updateWidgets(ctx); +} + +uint32_t getPaletteColor(lv_palette_t palette) { + return lv_color_to_u32(lv_palette_main(palette)) & 0xFFFFFF; +} + +constexpr size_t PALETTE_COLOR_COUNT = sizeof(PALETTE_COLORS) / sizeof(PALETTE_COLORS[0]); + +std::optional& getColor(settings::appearance::AppearanceSettings& settings, size_t index) { + switch (index) { + case 0: + return settings.primaryColor; + case 1: + return settings.secondaryColor; + default: + return settings.errorColor; + } +} + +uint32_t getDefaultColor(size_t index) { + LvglThemeSettings defaults; + lvgl_theme_get_default_settings(&defaults); + const lv_color_t colors[COLOR_COUNT] = { defaults.color_primary, defaults.color_secondary, defaults.color_error }; + return lv_color_to_u32(colors[index]) & 0xFFFFFF; +} + +void updateColorRow(Context* ctx, size_t index) { + const auto& widgets = ctx->colorRows[index]; + const auto& color = getColor(ctx->pendingSettings, index); + lv_obj_set_style_bg_color(widgets.swatch, lv_color_hex(color.value_or(getDefaultColor(index))), LV_STATE_DEFAULT); +} + +void onColorButtonPressed(lv_event_t* event) { + auto* ctx = static_cast(lv_event_get_user_data(event)); + auto* button = static_cast(lv_event_get_target(event)); + ctx->selectColorIndex = reinterpret_cast(lv_obj_get_user_data(button)); + task_event_group_signal(ctx->eventGroup, ctx->selectColorBit); } void onBackPressed(lv_event_t* event) { @@ -194,20 +364,102 @@ void onDefaultMonoPressed(lv_event_t* event) { updateWidgets(ctx); } +/** An unstyled row, so it doesn't paint over the card. Its items are spaced like the card's content. */ lv_obj_t* createRow(lv_obj_t* parent) { auto* row = lv_obj_create(parent); + lv_obj_remove_style_all(row); lv_obj_set_size(row, LV_PCT(100), LV_SIZE_CONTENT); lv_obj_set_flex_flow(row, LV_FLEX_FLOW_ROW); lv_obj_set_flex_align(row, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); - lv_obj_set_style_pad_all(row, 0, LV_STATE_DEFAULT); + lv_obj_set_style_pad_column(row, lv_obj_get_style_pad_column(parent, LV_PART_MAIN), LV_STATE_DEFAULT); // Room for the focus outline of the buttons, which is clipped to the row in compact mode lv_obj_set_style_pad_ver(row, 3, LV_STATE_DEFAULT); lv_obj_set_style_pad_hor(row, 3, LV_STATE_DEFAULT); - lv_obj_set_style_border_width(row, 0, LV_STATE_DEFAULT); lv_obj_remove_flag(row, LV_OBJ_FLAG_SCROLLABLE); return row; } +/** A title above a card, and the card */ +lv_obj_t* createSection(lv_obj_t* parent, const char* title) { + auto* title_label = lv_label_create(parent); + lv_label_set_text(title_label, title); + + auto* card = lvgl_card_create(parent); + lv_obj_set_size(card, LV_PCT(100), LV_SIZE_CONTENT); + lv_obj_set_flex_flow(card, LV_FLEX_FLOW_COLUMN); + return card; +} + +lv_obj_t* createLabeledRow(lv_obj_t* parent, const char* text) { + auto* row = createRow(parent); + auto* label = lv_label_create(row); + lv_label_set_text(label, text); + lv_obj_set_flex_grow(label, 1); + return row; +} + +lv_obj_t* createChip(lv_obj_t* parent, const char* text, lv_event_cb_t callback, Context* ctx) { + auto* chip = lvgl_chip_create(parent); + auto* label = lv_label_create(chip); + lv_label_set_text(label, text); + lv_obj_add_event_cb(chip, callback, LV_EVENT_SHORT_CLICKED, ctx); + return chip; +} + +ColorRowWidgets createColorRow(lv_obj_t* parent, size_t index, Context* ctx) { + ColorRowWidgets widgets; + // "Title [swatch] [Change]" + widgets.row = createLabeledRow(parent, COLOR_TITLES[index]); + + // The colour preview: its colour is the setting, its border uses the theme's text colour + widgets.swatch = lv_obj_create(widgets.row); + lv_obj_remove_style_all(widgets.swatch); + lv_obj_remove_flag(widgets.swatch, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_remove_flag(widgets.swatch, LV_OBJ_FLAG_CLICKABLE); + lv_obj_set_style_bg_opa(widgets.swatch, LV_OPA_COVER, LV_STATE_DEFAULT); + lv_obj_set_style_border_width(widgets.swatch, 1, LV_STATE_DEFAULT); + lv_obj_set_style_border_color(widgets.swatch, lv_obj_get_style_text_color(widgets.swatch, LV_PART_MAIN), LV_STATE_DEFAULT); + lv_obj_set_style_radius(widgets.swatch, LV_DPX(4), LV_STATE_DEFAULT); + + auto* button = lv_button_create(widgets.row); + auto* button_label = lv_label_create(button); + lv_label_set_text(button_label, "Change"); + lv_obj_set_user_data(button, reinterpret_cast(static_cast(index))); + lv_obj_add_event_cb(button, onColorButtonPressed, LV_EVENT_SHORT_CLICKED, ctx); + + // As high as the button + lv_obj_update_layout(button); + const int32_t swatch_size = lv_obj_get_height(button); + lv_obj_set_size(widgets.swatch, swatch_size, swatch_size); + return widgets; +} + +void createThemeCard(lv_obj_t* parent, Context* ctx) { + auto* card = createSection(parent, "Theme"); + + auto* mode_row = createLabeledRow(card, "Mode"); + ctx->lightChip = createChip(mode_row, "Light", onLightPressed, ctx); + ctx->darkChip = createChip(mode_row, "Dark", onDarkPressed, ctx); + + // Monochrome and greyscale displays always use the mono theme, and it doesn't use colours + if (!ctx->isMonoDisplay) { + auto* style_row = createLabeledRow(card, "Style"); + ctx->regularThemeChip = createChip(style_row, "Regular", onRegularThemePressed, ctx); + ctx->monoThemeChip = createChip(style_row, "Mono", onMonoThemePressed, ctx); + } + + auto* animations_checkbox = lv_checkbox_create(card); + lv_checkbox_set_text(animations_checkbox, "Animations"); + setChecked(animations_checkbox, ctx->pendingSettings.animationsEnabled); + lv_obj_add_event_cb(animations_checkbox, onAnimationsChanged, LV_EVENT_VALUE_CHANGED, ctx); + + if (!ctx->isMonoDisplay) { + for (size_t i = 0; i < COLOR_COUNT; i++) { + ctx->colorRows[i] = createColorRow(card, i, ctx); + } + } +} + lv_obj_t* createButton(lv_obj_t* parent, const char* text, lv_event_cb_t callback, Context* ctx) { auto* button = lv_button_create(parent); auto* label = lv_label_create(button); @@ -257,7 +509,12 @@ void createWidgets(lv_obj_t* parent, void* userData) { lv_obj_set_flex_flow(content, LV_FLEX_FLOW_COLUMN); lv_obj_set_style_border_width(content, 0, LV_STATE_DEFAULT); - auto* font_size_row = createRow(content); + const auto color_format = lv_display_get_color_format(lv_obj_get_display(parent)); + ctx->isMonoDisplay = color_format == LV_COLOR_FORMAT_I1 || color_format == LV_COLOR_FORMAT_L8; + createThemeCard(content, ctx); + + auto* fonts_card = createSection(content, "Fonts"); + auto* font_size_row = createRow(fonts_card); auto* font_size_label = lv_label_create(font_size_row); lv_label_set_text(font_size_label, "Font size"); lv_obj_set_flex_grow(font_size_label, 1); @@ -266,10 +523,18 @@ void createWidgets(lv_obj_t* parent, void* userData) { lv_spinbox_set_range(font_size_spinbox, MIN_FONT_SIZE, MAX_FONT_SIZE); lv_spinbox_set_digit_format(font_size_spinbox, 2, 0); lv_spinbox_set_step(font_size_spinbox, 1); + // Wide enough for the digits and the room that the text field keeps for its cursor, so it never scrolls the digits out of view. + // The size is set before the value, because setting the value scrolls to the cursor using the current size. + const lv_font_t* font_size_font = lv_obj_get_style_text_font(font_size_spinbox, LV_PART_MAIN); + lv_point_t digits_size; + lv_text_get_size(&digits_size, "00", font_size_font, 0, 0, LV_COORD_MAX, LV_TEXT_FLAG_NONE); + const int32_t font_size_content_width = digits_size.x + lv_font_get_line_height(font_size_font); + lv_obj_set_width(font_size_spinbox, font_size_content_width + + lv_obj_get_style_pad_left(font_size_spinbox, LV_PART_MAIN) + lv_obj_get_style_pad_right(font_size_spinbox, LV_PART_MAIN) + + 2 * lv_obj_get_style_border_width(font_size_spinbox, LV_PART_MAIN)); + lv_obj_set_style_text_align(font_size_spinbox, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN); const uint16_t font_size = ctx->pendingSettings.fontSize != 0 ? ctx->pendingSettings.fontSize : static_cast(TT_FONT_DEFAULT_SIZE); lv_spinbox_set_value(font_size_spinbox, font_size); - lv_obj_set_width(font_size_spinbox, lv_font_get_line_height(lv_obj_get_style_text_font(font_size_spinbox, LV_PART_MAIN)) * 3); - lv_obj_set_style_text_align(font_size_spinbox, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN); // The value is changed with the buttons, so the digit cursor isn't shown lv_obj_set_style_bg_opa(font_size_spinbox, LV_OPA_TRANSP, LV_PART_CURSOR); lv_obj_add_event_cb(font_size_spinbox, onFontSizeChanged, LV_EVENT_VALUE_CHANGED, ctx); @@ -277,10 +542,10 @@ void createWidgets(lv_obj_t* parent, void* userData) { lv_obj_move_to_index(decrement_button, lv_obj_get_index(font_size_spinbox)); createStepButton(font_size_row, LV_SYMBOL_PLUS, onFontSizeIncrementPressed, font_size_spinbox); - ctx->regularRow = createFontRow(content, "Regular font", onSelectRegularPressed, onDefaultRegularPressed, ctx); - ctx->monoRow = createFontRow(content, "Mono font", onSelectMonoPressed, onDefaultMonoPressed, ctx); + ctx->regularRow = createFontRow(fonts_card, "Regular font", onSelectRegularPressed, onDefaultRegularPressed, ctx); + ctx->monoRow = createFontRow(fonts_card, "Mono font", onSelectMonoPressed, onDefaultMonoPressed, ctx); - ctx->errorLabel = lv_label_create(content); + ctx->errorLabel = lv_label_create(fonts_card); lv_obj_set_width(ctx->errorLabel, LV_PCT(100)); lv_label_set_long_mode(ctx->errorLabel, LV_LABEL_LONG_MODE_WRAP); lv_label_set_text(ctx->errorLabel, "Failed to create the fonts. Check that the selected files are TrueType fonts."); @@ -296,6 +561,13 @@ void destroyWidgets(void* userData) { ctx->regularRow = {}; ctx->monoRow = {}; ctx->errorLabel = nullptr; + ctx->lightChip = nullptr; + ctx->darkChip = nullptr; + ctx->regularThemeChip = nullptr; + ctx->monoThemeChip = nullptr; + for (auto& row : ctx->colorRows) { + row = {}; + } } int32_t applyThreadMain(void* context) { @@ -313,6 +585,8 @@ void cancelApply(Context* ctx) { lvgl_unlock(); } +void finishApply(Context* ctx); + void startApply(Context* ctx) { if (ctx->applying || ctx->applyThread != nullptr) { return; @@ -320,11 +594,19 @@ void startApply(Context* ctx) { lvgl_lock(); ctx->applyConfiguration = toFontConfiguration(ctx->pendingSettings); + const bool fonts_changed = ctx->applyConfiguration != toFontConfiguration(ctx->savedSettings); ctx->applying = true; ctx->applyFailed = false; updateWidgets(ctx); lvgl_unlock(); + // Theme changes don't need new fonts + if (!fonts_changed) { + ctx->cacheUpdateSucceeded = true; + finishApply(ctx); + return; + } + ctx->applyThread = thread_alloc(); if (ctx->applyThread == nullptr) { LOG_E(TAG, "Failed to create thread"); @@ -378,8 +660,9 @@ void finishApply(Context* ctx) { lvgl_unlock(); // The window is rebuilt from the saved settings when LVGL starts again - LOG_I(TAG, "Restarting LVGL with the new fonts"); + LOG_I(TAG, "Restarting LVGL with the new settings"); lvgl::stop(); + lvgl::configureTheme(applied_settings); lvgl::loadFonts(ctx->applyConfiguration); lvgl::start(); } @@ -392,6 +675,37 @@ void startFileSelection(Context* ctx, FontSlot slot) { ctx->selectLaunchId = fileselection::startForExistingFile(ctx->appInstanceId, ctx->selectStream, ctx->selectBuffer, sizeof(ctx->selectBuffer), ctx->eventGroup); } +void startColorSelection(Context* ctx) { + if (ctx->colorSelectLaunchId != 0 || ctx->applying) { + return; + } + std::vector items; + items.reserve(PALETTE_COLOR_COUNT + 1); + items.emplace_back("Default"); + for (const auto& named_color : PALETTE_COLORS) { + items.emplace_back(named_color.name); + } + ctx->colorSelectLaunchId = selectiondialog::start(ctx->appInstanceId, COLOR_TITLES[ctx->selectColorIndex], items); +} + +void onColorSelectionResult(Context* ctx, const AppEvent& event) { + ctx->colorSelectLaunchId = 0; + // Index 0 is "Default", the others are the palette colours. Other results mean that the dialog was dismissed. + const int32_t selected = event.result.result; + if (selected >= 0 && selected <= static_cast(PALETTE_COLOR_COUNT)) { + lvgl_lock(); + auto& color = getColor(ctx->pendingSettings, ctx->selectColorIndex); + if (selected == 0) { + color.reset(); + } else { + color = getPaletteColor(PALETTE_COLORS[selected - 1].palette); + } + updateWidgets(ctx); + lvgl_unlock(); + } + app_manager_stop(event.result.launch_id); +} + void onFileSelectionResult(Context* ctx, const AppEvent& event) { ctx->selectLaunchId = 0; if (event.result.result == 0 /* Ok */) { @@ -426,6 +740,7 @@ int32_t appMain(int argc, char* argv[]) { ctx.eventGroup = &event_group; check(task_event_group_claim_bit(&event_group, &ctx.selectRegularBit) == ERROR_NONE); check(task_event_group_claim_bit(&event_group, &ctx.selectMonoBit) == ERROR_NONE); + check(task_event_group_claim_bit(&event_group, &ctx.selectColorBit) == ERROR_NONE); check(task_event_group_claim_bit(&event_group, &ctx.applyBit) == ERROR_NONE); check(task_event_group_claim_bit(&event_group, &ctx.cacheUpdatedBit) == ERROR_NONE); @@ -445,6 +760,9 @@ int32_t appMain(int argc, char* argv[]) { if (flags & ctx.selectMonoBit) { startFileSelection(&ctx, FontSlot::Mono); } + if (flags & ctx.selectColorBit) { + startColorSelection(&ctx); + } if (flags & ctx.applyBit) { startApply(&ctx); } @@ -458,6 +776,8 @@ int32_t appMain(int argc, char* argv[]) { should_close = true; } else if (event.type == APP_EVENT_RESULT && event.result.launch_id == ctx.selectLaunchId) { onFileSelectionResult(&ctx, event); + } else if (event.type == APP_EVENT_RESULT && event.result.launch_id == ctx.colorSelectLaunchId) { + onColorSelectionResult(&ctx, event); } } } diff --git a/Tactility/Source/app/applist/AppList.cpp b/Tactility/Source/app/applist/AppList.cpp deleted file mode 100644 index 007097b4b..000000000 --- a/Tactility/Source/app/applist/AppList.cpp +++ /dev/null @@ -1,219 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include - -#include -#include - -#include -#include -#include -#include - -#include -#include -#include - -namespace tt::app::applist { - -constexpr auto* TAG = "AppList"; - -namespace { - -struct IconEntry { - const char* id; - const char* icon; -}; - -constexpr IconEntry ICONS[] = { - {"tactility.apphub", LVGL_ICON_SHARED_DOWNLOAD}, - {"tactility.camera", LVGL_ICON_SHARED_CAMERA}, - {"tactility.chat", LVGL_ICON_SHARED_FORUM}, - {"tactility.i2cscanner", LVGL_ICON_SHARED_CABLE}, - {"tactility.notes", LVGL_ICON_SHARED_EDIT_NOTE}, - {"tactility.screenshot", LVGL_ICON_SHARED_IMAGE}, - {"tactility.systeminfo", LVGL_ICON_SHARED_DEVICES}, - {"tactility.terminal", LVGL_ICON_SHARED_TERMINAL}, - {"tactility.webserversettings", LVGL_ICON_SHARED_CLOUD}, -}; - -const char* appIcon(const ::AppManifest* manifest) { - for (const auto& entry : ICONS) { - if (!strcmp(manifest->id, entry.id)) return entry.icon; - } - return LVGL_ICON_SHARED_DEPLOYED_CODE; -} - -void collectManifest(const ::AppManifest* manifest, void* context) { - auto* manifests = static_cast*>(context); - manifests->push_back(*manifest); -} - -// Apps from installed packages this device can't run (wrong device, too little RAM) -void collectIncompatibleAppIds(const AppPackage* package, void* context) { - if (app_package_manifest_is_compatible(&package->package)) { - return; - } - auto* ids = static_cast*>(context); - for (size_t i = 0; i < package->app_id_count; i++) { - ids->emplace_back(package->app_ids[i]); - } -} - -std::vector collectItems(void* userData); -void onAppClicked(const ::AppManifest& manifest, void* userData); -void onAppLongPressed(const ::AppManifest& manifest, void* userData); -void onAppKey(const ::AppManifest& manifest, uint32_t key, void* userData); - -struct Context { - uint32_t appInstanceId; - Favourites favourites; - AppGrid grid { AppGrid::Callbacks { - .collect = collectItems, - .onClicked = onAppClicked, - .onLongPressed = onAppLongPressed, - .onKey = onAppKey, - .userData = this - } }; -}; - -std::vector collectItems(void* userData) { - const auto* ctx = static_cast(userData); - const std::vector favouriteIds = ctx->favourites.load(); - - std::vector<::AppManifest> collected; - app_manager_for_each_manifest(collectManifest, &collected); - std::vector incompatibleIds; - app_manager_for_each_package(collectIncompatibleAppIds, &incompatibleIds); - std::erase_if(collected, [&](const ::AppManifest& manifest) { - const bool is_valid_category = (manifest.category == APP_CATEGORY_USER) || (manifest.category == APP_CATEGORY_SYSTEM); - if (!is_valid_category || (manifest.flags & APP_MANIFEST_FLAG_HIDDEN) != 0) { - return true; - } - return std::ranges::find(incompatibleIds, std::string(manifest.id)) != incompatibleIds.end(); - }); - - std::vector items; - items.reserve(collected.size()); - for (const auto& manifest : collected) { - items.push_back({ manifest, appIcon(&manifest), Favourites::contains(favouriteIds, manifest.id) }); - } - std::ranges::sort(items, [](const AppGridItem& a, const AppGridItem& b) { - if (a.highlighted != b.highlighted) { - return a.highlighted; - } - return strcmp(a.manifest.name, b.manifest.name) < 0; - }); - return items; -} - -void onAppClicked(const ::AppManifest& manifest, void* userData) { - const auto* ctx = static_cast(userData); - uint32_t instanceId = 0; - AppStartContext context; - if (app_start_context_from_id(manifest.id, &context) == ERROR_NONE && app_start_with_context(&context, &instanceId) == ERROR_NONE) { - app_event_emit_close(ctx->appInstanceId); - } -} - -void toggleFavourite(Context* ctx, const char* appId, bool keepSelection) { - if (!ctx->favourites.toggle(appId)) { - LOG_E(TAG, "Failed to save favourites"); - } - ctx->grid.requestRepopulate(keepSelection); -} - -void onAppLongPressed(const ::AppManifest& manifest, void* userData) { - toggleFavourite(static_cast(userData), manifest.id, false); -} - -void onAppKey(const ::AppManifest& manifest, uint32_t key, void* userData) { - if (key == 'f' || key == 'F') { - toggleFavourite(static_cast(userData), manifest.id, true); - } -} - -void onBackPressed(lv_event_t* event) { - auto* ctx = static_cast(lv_event_get_user_data(event)); - app_event_emit_close(ctx->appInstanceId); -} - -void onHelpPressed(lv_event_t* event) { - auto* ctx = static_cast(lv_event_get_user_data(event)); - alertdialog::start(ctx->appInstanceId, "Help", "Long-press an app, or press F, to toggle it as a favorite."); -} - -void createWidgets(lv_obj_t* parent, void* userData) { - auto* ctx = static_cast(userData); - - // Flex column + flex_grow so the toolbar/grid split recomputes on layout instead of going stale after a resize. - lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN); - lv_obj_set_style_pad_row(parent, 0, LV_STATE_DEFAULT); - - auto* toolbar = lvgl_toolbar_create(parent, "Apps"); - lvgl_toolbar_set_nav_action(toolbar, LV_SYMBOL_CLOSE, onBackPressed, ctx); - ctx->grid.createWidgets(parent, toolbar); - auto* help_button = lvgl_toolbar_add_text_button_action(toolbar, LVGL_ICON_SHARED_HELP, onHelpPressed, ctx); - lv_obj_set_style_text_font(help_button, lvgl_get_shared_icon_default_font(), LV_STATE_DEFAULT); -} - -int32_t appMain(int argc, char* argv[]) { - uint32_t appInstanceId = app_scheduler_current_app_id(); - Context ctx { appInstanceId }; - - TaskEventGroup event_group {}; - task_event_group_construct(&event_group); - - AppEventSubscription sub {}; - check(app_event_subscribe(&sub, &event_group) == ERROR_NONE); - - WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx); - - while (true) { - task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY); - - bool shouldClose = false; - AppEvent event {}; - while (app_event_poll(&sub, &event) == ERROR_NONE) { - switch (event.type) { - case APP_EVENT_CLOSE: - shouldClose = true; - break; - case APP_EVENT_RESULT: - app_manager_stop(event.result.launch_id); - break; - default: - break; - } - if (shouldClose) break; - } - if (shouldClose) break; - } - - window_manager_remove(window); - check(app_event_unsubscribe(&sub) == ERROR_NONE); - task_event_group_destruct(&event_group); - return 0; -} - -} // namespace - -extern const ::AppManifest manifest = { - .id = "tactility.applist", - .name = "Apps", - .category = APP_CATEGORY_SYSTEM, - .location = { .type = APP_LOCATION_MEMORY, .location = reinterpret_cast(appMain) }, - .flags = APP_MANIFEST_FLAG_HIDDEN, - .stack = { .depth = 5120, .desired_memory_capability = 0 }, -}; - -} // namespace diff --git a/Tactility/Source/app/apppackagelist/AppPackageList.cpp b/Tactility/Source/app/apppackagelist/AppPackageList.cpp index 3a5d05a3f..b48d1eb0f 100644 --- a/Tactility/Source/app/apppackagelist/AppPackageList.cpp +++ b/Tactility/Source/app/apppackagelist/AppPackageList.cpp @@ -64,8 +64,8 @@ void collectPackage(const ::AppPackage* pkg, void* context) { void createWidgets(lv_obj_t* parent, void* userData) { auto* ctx = static_cast(userData); - // Flex column + flex_grow; see AppList.cpp's createWidgets() for why a fixed height computed - // once from lv_obj_get_content_height(parent) goes stale. + // Flex column + flex_grow, because a fixed height computed once from + // lv_obj_get_content_height(parent) goes stale after a resize. lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN); lv_obj_set_style_pad_row(parent, 0, LV_STATE_DEFAULT); diff --git a/Tactility/Source/app/audiosettings/AudioSettings.cpp b/Tactility/Source/app/audiosettings/AudioSettings.cpp index ab5653346..bcc61daf6 100644 --- a/Tactility/Source/app/audiosettings/AudioSettings.cpp +++ b/Tactility/Source/app/audiosettings/AudioSettings.cpp @@ -12,6 +12,7 @@ #include #include +#include #include #include @@ -26,10 +27,12 @@ struct Context { PubSub::SubscriptionHandle audioSubscription = nullptr; lv_obj_t* inputEnabledSwitch = nullptr; + lv_obj_t* inputCard = nullptr; lv_obj_t* inputMuteSwitch = nullptr; lv_obj_t* inputVolumeSlider = nullptr; lv_obj_t* outputEnabledSwitch = nullptr; + lv_obj_t* outputCard = nullptr; lv_obj_t* outputMuteSwitch = nullptr; lv_obj_t* outputVolumeSlider = nullptr; }; @@ -41,15 +44,19 @@ void onBackPressed(lv_event_t* event) { } void onInputEnabledSwitch(lv_event_t* event) { + auto* ctx = static_cast(lv_event_get_user_data(event)); auto* sw = static_cast(lv_event_get_target(event)); bool enabled = lv_obj_has_state(sw, LV_STATE_CHECKED); service::audio::setInputEnabled(enabled); + lv_obj_set_flag(ctx->inputCard, LV_OBJ_FLAG_HIDDEN, !enabled); } void onOutputEnabledSwitch(lv_event_t* event) { + auto* ctx = static_cast(lv_event_get_user_data(event)); auto* sw = static_cast(lv_event_get_target(event)); bool enabled = lv_obj_has_state(sw, LV_STATE_CHECKED); service::audio::setOutputEnabled(enabled); + lv_obj_set_flag(ctx->outputCard, LV_OBJ_FLAG_HIDDEN, !enabled); } void onInputMuteSwitch(lv_event_t* event) { @@ -76,24 +83,42 @@ void onOutputVolumeSlider(lv_event_t* event) { service::audio::setOutputVolume(percent); } -lv_obj_t* createSection(lv_obj_t* parent, const char* title) { - auto* wrapper = lv_obj_create(parent); - lv_obj_set_size(wrapper, LV_PCT(100), LV_SIZE_CONTENT); - lv_obj_set_flex_flow(wrapper, LV_FLEX_FLOW_COLUMN); - lv_obj_set_style_pad_hor(wrapper, 0, LV_STATE_DEFAULT); - lv_obj_set_style_border_width(wrapper, 0, LV_STATE_DEFAULT); +// An unstyled container, so the rows don't paint over the card +lv_obj_t* createRow(lv_obj_t* parent) { + auto* row = lv_obj_create(parent); + lv_obj_remove_style_all(row); + lv_obj_set_size(row, LV_PCT(100), LV_SIZE_CONTENT); + return row; +} + +struct Section { + lv_obj_t* enabledSwitch; + lv_obj_t* card; +}; + +// The title has the device's enable switch on its right. The card with the device's settings is hidden while it's disabled. +Section createSection(lv_obj_t* parent, const char* title, bool enabled, lv_event_cb_t onEnabledChanged, void* userData) { + auto* header = createRow(parent); - auto* title_label = lv_label_create(wrapper); + auto* title_label = lv_label_create(header); lv_label_set_text(title_label, title); + lv_obj_align(title_label, LV_ALIGN_LEFT_MID, 0, 0); - return wrapper; + auto* sw = lv_switch_create(header); + lv_obj_align(sw, LV_ALIGN_RIGHT_MID, 0, 0); + lv_obj_set_state(sw, LV_STATE_CHECKED, enabled); + lv_obj_add_event_cb(sw, onEnabledChanged, LV_EVENT_VALUE_CHANGED, userData); + + auto* card = lvgl_card_create(parent); + lv_obj_set_size(card, LV_PCT(100), LV_SIZE_CONTENT); + lv_obj_set_flex_flow(card, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flag(card, LV_OBJ_FLAG_HIDDEN, !enabled); + + return { sw, card }; } lv_obj_t* createSwitchRow(lv_obj_t* parent, const char* label, lv_event_cb_t cb, void* userData) { - auto* row = lv_obj_create(parent); - lv_obj_set_size(row, LV_PCT(100), LV_SIZE_CONTENT); - lv_obj_set_style_pad_all(row, 0, LV_STATE_DEFAULT); - lv_obj_set_style_border_width(row, 0, LV_STATE_DEFAULT); + auto* row = createRow(parent); auto* row_label = lv_label_create(row); lv_label_set_text(row_label, label); @@ -107,10 +132,7 @@ lv_obj_t* createSwitchRow(lv_obj_t* parent, const char* label, lv_event_cb_t cb, } lv_obj_t* createSliderRow(lv_obj_t* parent, const char* label, int32_t initialValue, lv_event_cb_t cb, void* userData) { - auto* row = lv_obj_create(parent); - lv_obj_set_size(row, LV_PCT(100), LV_SIZE_CONTENT); - lv_obj_set_style_pad_all(row, 0, LV_STATE_DEFAULT); - lv_obj_set_style_border_width(row, 0, LV_STATE_DEFAULT); + auto* row = createRow(parent); auto* row_label = lv_label_create(row); lv_label_set_text(row_label, label); @@ -126,8 +148,9 @@ lv_obj_t* createSliderRow(lv_obj_t* parent, const char* label, int32_t initialVa void refresh(Context* ctx) { if (ctx->inputEnabledSwitch) { - if (service::audio::isInputEnabled()) lv_obj_add_state(ctx->inputEnabledSwitch, LV_STATE_CHECKED); - else lv_obj_remove_state(ctx->inputEnabledSwitch, LV_STATE_CHECKED); + const bool enabled = service::audio::isInputEnabled(); + lv_obj_set_state(ctx->inputEnabledSwitch, LV_STATE_CHECKED, enabled); + lv_obj_set_flag(ctx->inputCard, LV_OBJ_FLAG_HIDDEN, !enabled); } if (ctx->inputMuteSwitch) { if (service::audio::isInputMuted()) lv_obj_add_state(ctx->inputMuteSwitch, LV_STATE_CHECKED); @@ -138,8 +161,9 @@ void refresh(Context* ctx) { } if (ctx->outputEnabledSwitch) { - if (service::audio::isOutputEnabled()) lv_obj_add_state(ctx->outputEnabledSwitch, LV_STATE_CHECKED); - else lv_obj_remove_state(ctx->outputEnabledSwitch, LV_STATE_CHECKED); + const bool enabled = service::audio::isOutputEnabled(); + lv_obj_set_state(ctx->outputEnabledSwitch, LV_STATE_CHECKED, enabled); + lv_obj_set_flag(ctx->outputCard, LV_OBJ_FLAG_HIDDEN, !enabled); } if (ctx->outputMuteSwitch) { if (service::audio::isOutputMuted()) lv_obj_add_state(ctx->outputMuteSwitch, LV_STATE_CHECKED); @@ -176,17 +200,19 @@ void createWidgets(lv_obj_t* parent, void* userData) { // device (e.g. a dedicated input codec with no output codec bound) should // only show the section it actually has, not a dead section for the other. if (service::audio::isInputAvailable()) { - auto* input_section = createSection(main_wrapper, "Microphone"); - ctx->inputEnabledSwitch = createSwitchRow(input_section, "Enabled", onInputEnabledSwitch, ctx); - ctx->inputMuteSwitch = createSwitchRow(input_section, "Mute", onInputMuteSwitch, ctx); - ctx->inputVolumeSlider = createSliderRow(input_section, "Volume", static_cast(service::audio::getInputVolume()), onInputVolumeSlider, ctx); + const auto input_section = createSection(main_wrapper, "Microphone", service::audio::isInputEnabled(), onInputEnabledSwitch, ctx); + ctx->inputEnabledSwitch = input_section.enabledSwitch; + ctx->inputCard = input_section.card; + ctx->inputMuteSwitch = createSwitchRow(input_section.card, "Mute", onInputMuteSwitch, ctx); + ctx->inputVolumeSlider = createSliderRow(input_section.card, "Volume", static_cast(service::audio::getInputVolume()), onInputVolumeSlider, ctx); } if (service::audio::isOutputAvailable()) { - auto* output_section = createSection(main_wrapper, "Speaker"); - ctx->outputEnabledSwitch = createSwitchRow(output_section, "Enabled", onOutputEnabledSwitch, ctx); - ctx->outputMuteSwitch = createSwitchRow(output_section, "Mute", onOutputMuteSwitch, ctx); - ctx->outputVolumeSlider = createSliderRow(output_section, "Volume", static_cast(service::audio::getOutputVolume()), onOutputVolumeSlider, ctx); + const auto output_section = createSection(main_wrapper, "Speaker", service::audio::isOutputEnabled(), onOutputEnabledSwitch, ctx); + ctx->outputEnabledSwitch = output_section.enabledSwitch; + ctx->outputCard = output_section.card; + ctx->outputMuteSwitch = createSwitchRow(output_section.card, "Mute", onOutputMuteSwitch, ctx); + ctx->outputVolumeSlider = createSliderRow(output_section.card, "Volume", static_cast(service::audio::getOutputVolume()), onOutputVolumeSlider, ctx); } // isAvailable() only reflects that the audio-stream device exists, not that any diff --git a/Tactility/Source/app/boot/BootInit.cpp b/Tactility/Source/app/boot/BootInit.cpp index 9238cfc74..ab92b153e 100644 --- a/Tactility/Source/app/boot/BootInit.cpp +++ b/Tactility/Source/app/boot/BootInit.cpp @@ -23,6 +23,8 @@ #include #include #include +#include +#include #include #include @@ -271,6 +273,7 @@ bool bootInit(TickType_t startTime) { LOG_I(TAG, "Loading fonts"); lvgl::loadFonts(lvgl::loadFontConfiguration()); + lvgl::configureTheme(settings::appearance::loadOrGetDefault()); screen.end(); diff --git a/Tactility/Source/app/btmanage/View.cpp b/Tactility/Source/app/btmanage/View.cpp index 1ef9baa1f..b089f1750 100644 --- a/Tactility/Source/app/btmanage/View.cpp +++ b/Tactility/Source/app/btmanage/View.cpp @@ -163,11 +163,17 @@ void View::updateScanning() { void View::updatePeerList() { lv_obj_clean(peers_list); - // Enable on boot row - auto* enable_on_boot_wrapper = lv_obj_create(peers_list); + // Enable on boot + + // An unstyled row insets the wrapper like the list items' text: margins would make a full-width item overflow the list + auto* enable_on_boot_row = lv_obj_create(peers_list); + lv_obj_remove_style_all(enable_on_boot_row); + lv_obj_set_size(enable_on_boot_row, LV_PCT(100), LV_SIZE_CONTENT); + const int32_t enable_on_boot_inset = (lvgl_get_ui_density() == LVGL_UI_DENSITY_COMPACT) ? 2 : LV_DPX(16); + lv_obj_set_style_pad_hor(enable_on_boot_row, enable_on_boot_inset, LV_STATE_DEFAULT); + + auto* enable_on_boot_wrapper = lv_obj_create(enable_on_boot_row); lv_obj_set_size(enable_on_boot_wrapper, LV_PCT(100), LV_SIZE_CONTENT); - lv_obj_set_style_pad_all(enable_on_boot_wrapper, 0, LV_STATE_DEFAULT); - lv_obj_set_style_border_width(enable_on_boot_wrapper, 0, LV_STATE_DEFAULT); auto* enable_label = lv_label_create(enable_on_boot_wrapper); lv_label_set_text(enable_label, "Enable on boot"); @@ -178,12 +184,6 @@ void View::updatePeerList() { lv_obj_add_event_cb(enable_on_boot_switch, onEnableOnBootSwitchChanged, LV_EVENT_VALUE_CHANGED, nullptr); lv_obj_add_event_cb(enable_on_boot_wrapper, onEnableOnBootParentClicked, LV_EVENT_SHORT_CLICKED, enable_on_boot_switch); - if (lvgl_get_ui_density() == LVGL_UI_DENSITY_COMPACT) { - lv_obj_set_style_pad_ver(enable_on_boot_wrapper, 2, LV_STATE_DEFAULT); - } else { - lv_obj_set_style_pad_ver(enable_on_boot_wrapper, 8, LV_STATE_DEFAULT); - } - updateEnableOnBootToggle(); using enum bluetooth::RadioState; diff --git a/Tactility/Source/app/display/Display.cpp b/Tactility/Source/app/display/Display.cpp index e23623f9f..2423fdc31 100644 --- a/Tactility/Source/app/display/Display.cpp +++ b/Tactility/Source/app/display/Display.cpp @@ -177,7 +177,6 @@ void createWidgets(lv_obj_t* parent, void* userData) { auto* ctx = static_cast(userData); ctx->displaySettings = settings::display::loadOrGetDefault(); - auto ui_density = lvgl_get_ui_density(); bool has_imu = device_exists_of_type(&IMU_TYPE); lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN); @@ -205,9 +204,6 @@ void createWidgets(lv_obj_t* parent, void* userData) { lv_obj_set_size(brightness_wrapper, LV_PCT(100), LV_SIZE_CONTENT); lv_obj_set_style_pad_hor(brightness_wrapper, 0, LV_STATE_DEFAULT); lv_obj_set_style_border_width(brightness_wrapper, 0, LV_STATE_DEFAULT); - if (ui_density != LVGL_UI_DENSITY_COMPACT) { - lv_obj_set_style_pad_ver(brightness_wrapper, 4, LV_STATE_DEFAULT); - } auto* brightness_label = lv_label_create(brightness_wrapper); lv_label_set_text(brightness_label, "Brightness"); diff --git a/Tactility/Source/app/files/State.cpp b/Tactility/Source/app/files/State.cpp index 07668f319..7049a43a0 100644 --- a/Tactility/Source/app/files/State.cpp +++ b/Tactility/Source/app/files/State.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -14,7 +15,10 @@ namespace tt::app::files { constexpr auto* TAG = "Files"; State::State() { - setEntriesForPath("/"); + char data_root[FILE_MAX_PATH_STRING_LENGTH]; + if (paths_get_data_root_path(data_root, sizeof(data_root)) != ERROR_NONE || !setEntriesForPath(data_root)) { + setEntriesForPath("/"); + } } std::string State::getSelectedChildPath() const { diff --git a/Tactility/Source/app/files/View.cpp b/Tactility/Source/app/files/View.cpp index 90e8840e4..d510ca6b6 100644 --- a/Tactility/Source/app/files/View.cpp +++ b/Tactility/Source/app/files/View.cpp @@ -552,15 +552,15 @@ void View::update(size_t start_index) { }); if (is_root) { - lv_obj_add_flag(lv_obj_get_parent(navigate_up_button), LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(navigate_up_button, LV_OBJ_FLAG_HIDDEN); } else { - lv_obj_remove_flag(lv_obj_get_parent(navigate_up_button), LV_OBJ_FLAG_HIDDEN); + lv_obj_remove_flag(navigate_up_button, LV_OBJ_FLAG_HIDDEN); } if (state->hasClipboard() && !is_root) { - lv_obj_remove_flag(lv_obj_get_parent(paste_button), LV_OBJ_FLAG_HIDDEN); + lv_obj_remove_flag(paste_button, LV_OBJ_FLAG_HIDDEN); } else { - lv_obj_add_flag(lv_obj_get_parent(paste_button), LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(paste_button, LV_OBJ_FLAG_HIDDEN); } lvgl_unlock(); @@ -579,7 +579,7 @@ void View::init(uint32_t appInstanceId, lv_obj_t* parent) { new_file_button = lvgl_toolbar_add_image_button_action(toolbar, LV_SYMBOL_FILE, &onNewFilePressedCallback, this); new_folder_button = lvgl_toolbar_add_image_button_action(toolbar, LV_SYMBOL_DIRECTORY, &onNewFolderPressedCallback, this); paste_button = lvgl_toolbar_add_image_button_action(toolbar, LV_SYMBOL_PASTE, &onPastePressedCallback, this); - lv_obj_add_flag(lv_obj_get_parent(paste_button), LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(paste_button, LV_OBJ_FLAG_HIDDEN); auto* wrapper = lv_obj_create(parent); lv_obj_set_width(wrapper, LV_PCT(100)); diff --git a/Tactility/Source/app/gpssettings/GpsSettings.cpp b/Tactility/Source/app/gpssettings/GpsSettings.cpp index df6c658b5..d1a2a09b1 100644 --- a/Tactility/Source/app/gpssettings/GpsSettings.cpp +++ b/Tactility/Source/app/gpssettings/GpsSettings.cpp @@ -239,23 +239,16 @@ void createWidgets(lv_obj_t* parent, void* userData) { lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN); lv_obj_set_style_pad_row(parent, 0, LV_STATE_DEFAULT); - uint8_t margin = (lvgl_get_ui_density() == LVGL_UI_DENSITY_COMPACT) ? 2 : 8; - auto* toolbar = lvgl_toolbar_create(parent, "GPS"); // The global toolbar nav callback only knows how to stop old-model apps. lvgl_toolbar_set_nav_action(toolbar, LV_SYMBOL_CLOSE, onBackPressed, ctx); lvgl_toolbar_add_text_button_action(toolbar, LV_SYMBOL_PLUS, onAddGpsPressed, ctx); - lv_obj_set_style_margin_bottom(toolbar, margin, LV_STATE_DEFAULT); ctx->deviceListWrapper = lv_obj_create(parent); lv_obj_set_size(ctx->deviceListWrapper, LV_PCT(100), LV_SIZE_CONTENT); lv_obj_set_flex_flow(ctx->deviceListWrapper, LV_FLEX_FLOW_COLUMN); lv_obj_set_flex_grow(ctx->deviceListWrapper, 1); lv_obj_set_style_border_width(ctx->deviceListWrapper, 0, 0); - lv_obj_set_style_pad_hor(ctx->deviceListWrapper, margin, 0); - lv_obj_set_style_pad_top(ctx->deviceListWrapper, 0, 0); - lv_obj_set_style_pad_bottom(ctx->deviceListWrapper, margin, 0); - lv_obj_set_style_pad_row(ctx->deviceListWrapper, margin, 0); rebuildDeviceList(ctx); updateDeviceStates(ctx); diff --git a/Tactility/Source/app/launcher/Launcher.cpp b/Tactility/Source/app/launcher/Launcher.cpp index a969eee56..636f3c442 100644 --- a/Tactility/Source/app/launcher/Launcher.cpp +++ b/Tactility/Source/app/launcher/Launcher.cpp @@ -1,27 +1,28 @@ #include #include #include +#include #include #include +#include #include +#include +#include #include -#include -#include -#include +#include +#include #include #include #include -#include #include -#include -#include #include -#include +#include +#include #include #include #include @@ -32,21 +33,105 @@ constexpr auto* TAG = "Launcher"; namespace { -uint32_t getButtonPadding(UiDensity density, uint32_t buttonSize) { - if (density == LVGL_UI_DENSITY_COMPACT) { - return 0; - } else { - return buttonSize / 8; +struct IconEntry { + const char* id; + const char* icon; +}; + +constexpr IconEntry ICONS[] = { + {"tactility.apphub", LVGL_ICON_SHARED_DOWNLOAD}, + {"tactility.camera", LVGL_ICON_SHARED_CAMERA}, + {"tactility.chat", LVGL_ICON_SHARED_FORUM}, + {"tactility.files", LVGL_ICON_SHARED_FOLDER}, + {"tactility.i2cscanner", LVGL_ICON_SHARED_CABLE}, + {"tactility.notes", LVGL_ICON_SHARED_EDIT_NOTE}, + {"tactility.screenshot", LVGL_ICON_SHARED_IMAGE}, + {"tactility.systeminfo", LVGL_ICON_SHARED_DEVICES}, + {"tactility.terminal", LVGL_ICON_SHARED_TERMINAL}, + {"tactility.webserversettings", LVGL_ICON_SHARED_CLOUD}, +}; + +// Hidden apps that are still shown in the launcher +constexpr const char* SHOWN_HIDDEN_APP_IDS[] = { + "tactility.files", +}; + +const char* appIcon(const ::AppManifest* manifest) { + for (const auto& entry : ICONS) { + if (!strcmp(manifest->id, entry.id)) return entry.icon; + } + return LVGL_ICON_SHARED_DEPLOYED_CODE; +} + +bool isShown(const ::AppManifest& manifest) { + if (manifest.category != APP_CATEGORY_USER && manifest.category != APP_CATEGORY_SYSTEM) { + return false; + } + if ((manifest.flags & APP_MANIFEST_FLAG_HIDDEN) == 0) { + return true; + } + return std::ranges::any_of(SHOWN_HIDDEN_APP_IDS, [&](const char* id) { return strcmp(manifest.id, id) == 0; }); +} + +void collectManifest(const ::AppManifest* manifest, void* context) { + auto* manifests = static_cast*>(context); + manifests->push_back(*manifest); +} + +// Apps from installed packages this device can't run (wrong device, too little RAM) +void collectIncompatibleAppIds(const AppPackage* package, void* context) { + if (app_package_manifest_is_compatible(&package->package)) { + return; + } + auto* ids = static_cast*>(context); + for (size_t i = 0; i < package->app_id_count; i++) { + ids->emplace_back(package->app_ids[i]); } } -int32_t computeButtonMargin(int32_t available_span, int32_t total_button_size) { - const int32_t usable = std::max(0, available_span - (3 * total_button_size)); - return std::min(usable / 16, total_button_size / 2); +std::vector collectItems(void* userData); +void onAppClicked(const ::AppManifest& manifest, void* userData); +void onAppLongPressed(const ::AppManifest& manifest, void* userData); +void onAppKey(const ::AppManifest& manifest, uint32_t key, void* userData); + +struct Context { + Favourites favourites; + AppGrid grid { AppGrid::Callbacks { + .collect = collectItems, + .onClicked = onAppClicked, + .onLongPressed = onAppLongPressed, + .onKey = onAppKey, + .userData = this + } }; +}; + +std::vector collectItems(void* userData) { + const auto* ctx = static_cast(userData); + const std::vector favouriteIds = ctx->favourites.load(); + + std::vector<::AppManifest> collected; + app_manager_for_each_manifest(collectManifest, &collected); + std::vector incompatibleIds; + app_manager_for_each_package(collectIncompatibleAppIds, &incompatibleIds); + std::erase_if(collected, [&](const ::AppManifest& manifest) { + return !isShown(manifest) || std::ranges::find(incompatibleIds, std::string(manifest.id)) != incompatibleIds.end(); + }); + + std::vector items; + items.reserve(collected.size()); + for (const auto& manifest : collected) { + items.push_back({ manifest, appIcon(&manifest), Favourites::contains(favouriteIds, manifest.id) }); + } + std::ranges::sort(items, [](const AppGridItem& a, const AppGridItem& b) { + if (a.highlighted != b.highlighted) { + return a.highlighted; + } + return strcmp(a.manifest.name, b.manifest.name) < 0; + }); + return items; } -void onAppPressed(lv_event_t* e) { - auto* appId = static_cast(lv_event_get_user_data(e)); +void startApp(const char* appId) { uint32_t instance_id = 0; AppStartContext context; if (app_start_context_from_id(appId, &context) == ERROR_NONE) { @@ -54,40 +139,39 @@ void onAppPressed(lv_event_t* e) { } } -lv_obj_t* createAppButton(lv_obj_t* parent, UiDensity uiDensity, const char* imageFile, const char* appId, int32_t itemMargin, bool isLandscape) { - const auto button_size = lvgl_get_launcher_icon_font_height(); - const auto button_padding = getButtonPadding(uiDensity, button_size); - auto* apps_button = lv_button_create(parent); +void onAppClicked(const ::AppManifest& manifest, void*) { + startApp(manifest.id); +} - lv_obj_set_style_pad_all(apps_button, static_cast(button_padding), LV_STATE_DEFAULT); - if (isLandscape) { - lv_obj_set_style_margin_hor(apps_button, itemMargin, LV_STATE_DEFAULT); - } else { - lv_obj_set_style_margin_ver(apps_button, itemMargin, LV_STATE_DEFAULT); +void toggleFavourite(Context* ctx, const char* appId, bool keepSelection) { + if (!ctx->favourites.toggle(appId)) { + LOG_E(TAG, "Failed to save favourites"); } + ctx->grid.requestRepopulate(keepSelection); +} - lv_obj_set_style_shadow_width(apps_button, 0, LV_STATE_DEFAULT); - lv_obj_set_style_bg_opa(apps_button, 0, LV_STATE_DEFAULT); - - // create the image first - auto* button_image = lv_image_create(apps_button); - lv_obj_set_style_text_font(button_image, lvgl_get_launcher_icon_font(), LV_STATE_DEFAULT); - lv_image_set_src(button_image, imageFile); - lv_obj_set_style_text_color(button_image, lv_theme_get_color_primary(button_image), LV_STATE_DEFAULT); - lv_obj_set_style_image_recolor(button_image, lv_theme_get_color_primary(parent), LV_STATE_DEFAULT); - lv_obj_set_style_image_recolor_opa(button_image, LV_OPA_COVER, LV_STATE_DEFAULT); +void onAppLongPressed(const ::AppManifest& manifest, void* userData) { + toggleFavourite(static_cast(userData), manifest.id, false); +} - // Ensure it's square (Material Symbols are slightly wider than tall) - lv_obj_set_size(button_image, button_size, button_size); +void onAppKey(const ::AppManifest& manifest, uint32_t key, void* userData) { + if (key == 'f' || key == 'F') { + toggleFavourite(static_cast(userData), manifest.id, true); + } +} - lv_obj_add_event_cb(apps_button, onAppPressed, LV_EVENT_SHORT_CLICKED, (void*)appId); +void onShortcutPressed(lv_event_t* e) { + startApp(static_cast(lv_event_get_user_data(e))); +} - return apps_button; +bool isAppRegistered(const char* appId) { + ::AppManifest manifest; + return app_manager_find_manifest(appId, &manifest) == ERROR_NONE; } -bool shouldShowPowerButton() { - bool show_power_button = false; - device_for_each_of_type(&POWER_SUPPLY_TYPE, &show_power_button, [](Device* device, void* context) { +bool supportsPowerOff() { + bool supported = false; + device_for_each_of_type(&POWER_SUPPLY_TYPE, &supported, [](Device* device, void* context) { if (device_is_ready(device) && power_supply_supports_power_off(device)) { *static_cast(context) = true; return false; // stop iterating @@ -95,118 +179,24 @@ bool shouldShowPowerButton() { return true; // continue iterating } }); - return show_power_button; + return supported; } -void onButtonsWrapperResized(lv_event_t* e); - -// The screen object outlives this window's own widgets (lvgl-window-manager deletes and -// recreates only the topmost window's widget on every app switch, not the screen itself), so -// the LV_EVENT_SIZE_CHANGED callback registered on it must be removed once buttons_wrapper is -// destroyed, to avoid a dangling user-data pointer the next time the display rotates while a -// different window is topmost. -void onButtonsWrapperDeleted(lv_event_t* e) { - auto* buttons_wrapper = lv_event_get_target_obj(e); - auto* screen = lv_obj_get_screen(buttons_wrapper); - lv_obj_remove_event_cb_with_user_data(screen, onButtonsWrapperResized, buttons_wrapper); -} - -// Re-applies the flex direction and per-button margins when the display orientation changes -// while the launcher is the visible window (these are decided once at createWidgets() based on -// the resolution at that time, so a later rotation needs this to catch up). -void onButtonsWrapperResized(lv_event_t* e) { - auto* buttons_wrapper = static_cast(lv_event_get_user_data(e)); - const auto* display = lv_obj_get_display(buttons_wrapper); - - const auto button_size = lvgl_get_launcher_icon_font_height(); - const auto button_padding = getButtonPadding(lvgl_get_ui_density(), button_size); - const auto total_button_size = button_size + (button_padding * 2); - - const auto horizontal_px = lv_display_get_horizontal_resolution(display); - const auto vertical_px = lv_display_get_vertical_resolution(display); - const bool is_landscape_display = horizontal_px >= vertical_px; - const auto current_flow = lv_obj_get_style_flex_flow(buttons_wrapper, LV_PART_MAIN); - const bool was_landscape = current_flow == LV_FLEX_FLOW_ROW; - if (is_landscape_display == was_landscape) { - return; - } +void createWidgets(lv_obj_t* parent, void* userData) { + auto* ctx = static_cast(userData); - lv_obj_set_flex_flow(buttons_wrapper, is_landscape_display ? LV_FLEX_FLOW_ROW : LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN); + lv_obj_set_style_pad_row(parent, 0, LV_STATE_DEFAULT); - const int32_t margin = is_landscape_display - ? computeButtonMargin(horizontal_px, total_button_size) - : computeButtonMargin(vertical_px, total_button_size); - - const uint32_t child_count = lv_obj_get_child_count(buttons_wrapper); - for (uint32_t i = 0; i < child_count; i++) { - auto* button = lv_obj_get_child(buttons_wrapper, i); - lv_obj_set_style_margin_hor(button, is_landscape_display ? margin : 0, LV_STATE_DEFAULT); - lv_obj_set_style_margin_ver(button, is_landscape_display ? 0 : margin, LV_STATE_DEFAULT); + // The monochrome theme's primary color is black, also on a black background + if (!lvgl_theme_is_mono()) { + ctx->grid.setIconColor(AppGrid::IconColor::Primary); } -} - -void createWidgets(lv_obj_t* parent, void*) { - auto* buttons_wrapper = lv_obj_create(parent); - - auto ui_density = lvgl_get_ui_density(); - const auto button_size = lvgl_get_launcher_icon_font_height(); - const auto button_padding = getButtonPadding(ui_density, button_size); - const auto total_button_size = button_size + (button_padding * 2); - - lv_obj_align(buttons_wrapper, LV_ALIGN_CENTER, 0, 0); - lv_obj_set_size(buttons_wrapper, LV_SIZE_CONTENT, LV_SIZE_CONTENT); - lv_obj_set_style_border_width(buttons_wrapper, 0, LV_STATE_DEFAULT); - lv_obj_set_flex_grow(buttons_wrapper, 1); - - // Fix for button selection - lv_obj_set_style_pad_all(buttons_wrapper, 6, LV_STATE_DEFAULT); - - const auto* display = lv_obj_get_display(parent); - const auto horizontal_px = lv_display_get_horizontal_resolution(display); - const auto vertical_px = lv_display_get_vertical_resolution(display); - const bool is_landscape_display = horizontal_px >= vertical_px; - if (is_landscape_display) { - lv_obj_set_flex_flow(buttons_wrapper, LV_FLEX_FLOW_ROW); - } else { - lv_obj_set_flex_flow(buttons_wrapper, LV_FLEX_FLOW_COLUMN); - } - - const int32_t margin = is_landscape_display - ? computeButtonMargin(lv_display_get_horizontal_resolution(display), total_button_size) - : computeButtonMargin(lv_display_get_vertical_resolution(display), total_button_size); - - auto* app_list_button = createAppButton(buttons_wrapper, ui_density, LVGL_ICON_LAUNCHER_APPS, "tactility.applist", margin, is_landscape_display); - createAppButton(buttons_wrapper, ui_density, LVGL_ICON_LAUNCHER_FOLDER, "tactility.files", margin, is_landscape_display); - createAppButton(buttons_wrapper, ui_density, LVGL_ICON_LAUNCHER_SETTINGS, "tactility.settings", margin, is_landscape_display); - - // The launcher's container is several levels below the screen, and LVGL only sends - // LV_EVENT_SIZE_CHANGED to the screen object itself on a resolution change - so the - // handler is attached there, with buttons_wrapper passed through as user data. - lv_obj_add_event_cb(lv_obj_get_screen(parent), onButtonsWrapperResized, LV_EVENT_SIZE_CHANGED, buttons_wrapper); - lv_obj_add_event_cb(buttons_wrapper, onButtonsWrapperDeleted, LV_EVENT_DELETE, nullptr); - - // Some devices (e.g. T-Lora Pager) have no other way to power off, so the - // button stays in the launcher; the confirmation flow lives in the PowerOff app. - if (shouldShowPowerButton()) { - auto* power_button = lv_button_create(parent); - lv_obj_set_style_pad_all(power_button, 8, 0); - lv_obj_align(power_button, LV_ALIGN_BOTTOM_MID, 0, -10); - lv_obj_add_event_cb(power_button, onAppPressed, LV_EVENT_SHORT_CLICKED, (void*)"tactility.poweroff"); - lv_obj_set_style_shadow_width(power_button, 0, LV_STATE_DEFAULT); - lv_obj_set_style_bg_opa(power_button, 0, LV_PART_MAIN); - - auto* power_label = lv_label_create(power_button); - lv_label_set_text(power_label, LV_SYMBOL_POWER); - lv_obj_set_style_text_color(power_label, lv_theme_get_color_primary(parent), LV_STATE_DEFAULT); - } - - // If we don't have a touch device, we assume there's some other kind of input like a keyboard, an encoder or button control - // In that scenario we want to automatically have the app list button selected so the user doesn't have to press the widget selection - // an extra time. - if (!device_has_active_by_type(&POINTER_TYPE)) { - // lv_obj_update_layout(parent); // Resolve flex layout first, so focus/state invalidate against final coords - lv_group_focus_obj(app_list_button); - lv_obj_add_state(app_list_button, LV_STATE_FOCUS_KEY); + ctx->grid.setSwipeNavigation(true); + ctx->grid.createWidgetsWithBottomBar(parent); + ctx->grid.addBarButton(LVGL_ICON_SHARED_SETTINGS, onShortcutPressed, const_cast("tactility.settings")); + if (isAppRegistered("tactility.poweroff") && supportsPowerOff()) { + ctx->grid.addBarButton(LVGL_ICON_SHARED_POWER_SETTINGS_NEW, onShortcutPressed, const_cast("tactility.poweroff")); } } @@ -238,30 +228,6 @@ void runAutoStart() { } } -constexpr size_t AUTO_START_STACK_DEPTH = 4096 / sizeof(StackType_t); - -struct AutoStartTaskContext { - SemaphoreHandle_t done; -}; - -void autoStartTaskMain(void* param) { - auto* context = static_cast(param); - runAutoStart(); - xSemaphoreGive(context->done); - vTaskDelete(nullptr); -} - -// runAutoStart() reads from flash, and apps run on PSRAM when available. -// Launcher stays in memory, so we prefer to keep the PSRAM task and temporarily run an IRAM task for auto start logic. -void runAutoStartIsolated() { - AutoStartTaskContext context { .done = xSemaphoreCreateBinary() }; - check(context.done != nullptr); - TaskHandle_t auto_start_task = nullptr; - check(xTaskCreate(autoStartTaskMain, "LauncherAutoStart", AUTO_START_STACK_DEPTH, &context, tskIDLE_PRIORITY, &auto_start_task) == pdPASS); - xSemaphoreTake(context.done, portMAX_DELAY); - vSemaphoreDelete(context.done); -} - int32_t appMain(int argc, char* argv[]) { uint32_t appInstanceId = app_scheduler_current_app_id(); @@ -271,9 +237,10 @@ int32_t appMain(int argc, char* argv[]) { AppEventSubscription sub {}; check(app_event_subscribe(&sub, &event_group) == ERROR_NONE); - WindowId window = window_manager_create(appInstanceId, createWidgets, nullptr); + Context ctx; + WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx); - runAutoStartIsolated(); + runAutoStart(); // The launcher is meant to stay resident (it's the home screen) - it only gives up its // thread when app-module's scheduler asks it to (e.g. another new-model app is started). @@ -305,8 +272,7 @@ extern const ::AppManifest manifest = { .category = APP_CATEGORY_SYSTEM, .location = { .type = APP_LOCATION_MEMORY, .location = reinterpret_cast(appMain) }, .flags = APP_MANIFEST_FLAG_HIDDEN, - // No file IO, so callstack can be in external RAM - .stack = { .depth = 3072 , .desired_memory_capability = MEMORY_CAPABILITY_EXTERNAL } + .stack = { .depth = 5120, .desired_memory_capability = 0 } }; // Kept for Tactility/Private/Tactility/app/launcher/Launcher.h's existing declaration (still diff --git a/Tactility/Source/app/power/Power.cpp b/Tactility/Source/app/power/Power.cpp index 81a7c7bd7..1b6662e60 100644 --- a/Tactility/Source/app/power/Power.cpp +++ b/Tactility/Source/app/power/Power.cpp @@ -14,6 +14,7 @@ #include #include +#include #include #include @@ -170,10 +171,14 @@ void createWidgets(lv_obj_t* parent, void* userData) { entry.device = device; lv_obj_t* header = lv_label_create(wrapper); - lv_label_set_text_fmt(header, "%s:", device->name); + lv_label_set_text(header, device->name); + + lv_obj_t* card = lvgl_card_create(wrapper); + lv_obj_set_size(card, LV_PCT(100), LV_SIZE_CONTENT); + lv_obj_set_flex_flow(card, LV_FLEX_FLOW_COLUMN); if (power_supply_supports_charge_control(device)) { - lv_obj_t* switch_container = lv_obj_create(wrapper); + lv_obj_t* switch_container = lv_obj_create(card); lv_obj_set_width(switch_container, LV_PCT(100)); lv_obj_set_height(switch_container, LV_SIZE_CONTENT); lv_obj_set_style_pad_all(switch_container, 0, 0); @@ -193,7 +198,7 @@ void createWidgets(lv_obj_t* parent, void* userData) { } if (power_supply_supports_quick_charge(device)) { - lv_obj_t* qc_container = lv_obj_create(wrapper); + lv_obj_t* qc_container = lv_obj_create(card); lv_obj_set_width(qc_container, LV_PCT(100)); lv_obj_set_height(qc_container, LV_SIZE_CONTENT); lv_obj_set_style_pad_all(qc_container, 0, 0); @@ -215,8 +220,7 @@ void createWidgets(lv_obj_t* parent, void* userData) { PowerSupplyPropertyValue value; for (auto property : DISPLAYED_PROPERTIES) { if (power_supply_get_property(device, property, &value) == ERROR_NONE) { - lv_obj_t* label = lv_label_create(wrapper); - lv_obj_set_style_margin_left(label, 24, LV_STATE_DEFAULT); + lv_obj_t* label = lv_label_create(card); setPropertyLabelText(label, property, value); entry.propertyWidgets.push_back({ property, label }); } diff --git a/Tactility/Source/app/selectiondialog/SelectionDialog.cpp b/Tactility/Source/app/selectiondialog/SelectionDialog.cpp index 588406813..564e23280 100644 --- a/Tactility/Source/app/selectiondialog/SelectionDialog.cpp +++ b/Tactility/Source/app/selectiondialog/SelectionDialog.cpp @@ -32,7 +32,7 @@ struct Context { // The eventual appMain() return value - see AlertDialog.cpp's Context::result for why this // is a plain (non-atomic) field safely shared between the LVGL thread (writer, before // emitting APP_EVENT_CLOSE) and this dialog's own thread (reader, after waking from it). - int32_t result = 1; // Cancelled - safety-net default if closed without selecting an item + int32_t result = -2; // Cancelled - safety-net default if closed without selecting an item }; struct ItemContext { diff --git a/Tactility/Source/app/settings/Settings.cpp b/Tactility/Source/app/settings/Settings.cpp index 6c25f9be4..4e8883c7e 100644 --- a/Tactility/Source/app/settings/Settings.cpp +++ b/Tactility/Source/app/settings/Settings.cpp @@ -9,7 +9,7 @@ #include #include -#include +#include #include #include @@ -55,7 +55,7 @@ const char* appIcon(const ::AppManifest* manifest) { } void onAppClicked(const ::AppManifest& manifest, void*) { - // Fire-and-forget top-level navigation, same as AppList's own app-launch buttons. + // Fire-and-forget top-level navigation, same as the Launcher's own app-launch buttons. uint32_t instanceId = 0; // Re-resolved by id against the live ledger, not the (possibly stale) cached manifest above: // fails gracefully if the app was uninstalled since this button was built, instead of handing @@ -109,9 +109,12 @@ void createWidgets(lv_obj_t* parent, void* userData) { lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN); lv_obj_set_style_pad_row(parent, 0, LV_STATE_DEFAULT); - auto* toolbar = lvgl_toolbar_create(parent, "Settings"); - lvgl_toolbar_set_nav_action(toolbar, LV_SYMBOL_CLOSE, onBackPressed, ctx); - ctx->grid.createWidgets(parent, toolbar); + if (!lvgl_theme_is_mono()) { + ctx->grid.setIconColor(AppGrid::IconColor::Secondary); + } + ctx->grid.setSwipeNavigation(true); + ctx->grid.createWidgetsWithBottomBar(parent); + ctx->grid.addBarButton(LVGL_ICON_SHARED_CLOSE, onBackPressed, ctx); } int32_t appMain(int argc, char* argv[]) { @@ -154,7 +157,7 @@ extern const ::AppManifest manifest = { .category = APP_CATEGORY_SYSTEM, .location = { .type = APP_LOCATION_MEMORY, .location = reinterpret_cast(appMain) }, .flags = APP_MANIFEST_FLAG_HIDDEN, - .stack = { .depth = 4000, .desired_memory_capability = 0 }, + .stack = { .depth = 5120, .desired_memory_capability = 0 }, }; } // namespace diff --git a/Tactility/Source/app/systeminfo/SystemInfo.cpp b/Tactility/Source/app/systeminfo/SystemInfo.cpp index 0d8ba505d..cf1c545bb 100644 --- a/Tactility/Source/app/systeminfo/SystemInfo.cpp +++ b/Tactility/Source/app/systeminfo/SystemInfo.cpp @@ -131,6 +131,7 @@ MemoryBarWidgets createMemoryBar(lv_obj_t* parent, const char* label) { lv_obj_set_style_pad_all(container, 0, LV_STATE_DEFAULT); lv_obj_set_style_border_width(container, 0, LV_STATE_DEFAULT); lv_obj_set_flex_flow(container, LV_FLEX_FLOW_ROW); + lv_obj_set_flex_align(container, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); lv_obj_set_style_bg_opa(container, 0, LV_STATE_DEFAULT); auto* left_label = lv_label_create(container); @@ -145,12 +146,6 @@ MemoryBarWidgets createMemoryBar(lv_obj_t* parent, const char* label) { lv_obj_set_width(bottom_label, LV_PCT(100)); lv_obj_set_style_text_align(bottom_label, LV_TEXT_ALIGN_RIGHT, 0); - if (lvgl_get_ui_density() == LVGL_UI_DENSITY_COMPACT) { - lv_obj_set_style_pad_bottom(bottom_label, 2, LV_STATE_DEFAULT); - } else { - lv_obj_set_style_pad_bottom(bottom_label, 12, LV_STATE_DEFAULT); - } - return {bar, bottom_label}; } diff --git a/Tactility/Source/app/terminal/main.cpp b/Tactility/Source/app/terminal/main.cpp index 57b3c7efa..95d6ab67f 100644 --- a/Tactility/Source/app/terminal/main.cpp +++ b/Tactility/Source/app/terminal/main.cpp @@ -40,6 +40,11 @@ void onClosePressed(lv_event_t* event) { app_event_emit_close(ctx->appInstanceId); } +void onCanvasClicked(lv_event_t* event) { + auto* toolbar = static_cast(lv_event_get_user_data(event)); + lv_obj_set_flag(toolbar, LV_OBJ_FLAG_HIDDEN, !lv_obj_has_flag(toolbar, LV_OBJ_FLAG_HIDDEN)); +} + void createWidgets(lv_obj_t* root, void* userData) { auto* ctx = static_cast(userData); @@ -47,16 +52,21 @@ void createWidgets(lv_obj_t* root, void* userData) { lv_obj_set_style_pad_all(root, 0, LV_STATE_DEFAULT); lv_obj_set_style_pad_row(root, 0, LV_STATE_DEFAULT); - // The toolbar only offers a touch close button, so it is left out to give the terminal more room + auto* canvas = lv_canvas_create(root); + lv_obj_set_width(canvas, LV_PCT(100)); + lv_obj_set_flex_grow(canvas, 1); + + // The toolbar only offers a touch close button. It's hidden to give the terminal all the room, and touching the terminal shows it on top. if (lvgl_indev_exists(LV_INDEV_TYPE_POINTER)) { auto* toolbar = lvgl_toolbar_create(root, "Terminal"); lvgl_toolbar_set_nav_action(toolbar, LV_SYMBOL_CLOSE, onClosePressed, ctx); + lv_obj_add_flag(toolbar, LV_OBJ_FLAG_FLOATING); + lv_obj_align(toolbar, LV_ALIGN_TOP_MID, 0, 0); + lv_obj_add_flag(toolbar, LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(canvas, LV_OBJ_FLAG_CLICKABLE); + lv_obj_add_event_cb(canvas, onCanvasClicked, LV_EVENT_SHORT_CLICKED, toolbar); } - auto* canvas = lv_canvas_create(root); - lv_obj_set_width(canvas, LV_PCT(100)); - lv_obj_set_flex_grow(canvas, 1); - ctx->renderer.attachCanvas(canvas); } diff --git a/Tactility/Source/app/trackball/TrackballSettings.cpp b/Tactility/Source/app/trackball/TrackballSettings.cpp index 152500e0b..e5d2249a2 100644 --- a/Tactility/Source/app/trackball/TrackballSettings.cpp +++ b/Tactility/Source/app/trackball/TrackballSettings.cpp @@ -25,13 +25,13 @@ extern const ::AppManifest manifest; constexpr auto* TAG = "TrackballSettings"; -// Convert mode to dropdown index (dropdown order: Encoder=0, Pointer=1) +// Convert mode to dropdown index (dropdown order: Keys=0, Pointer=1) static uint32_t modeToDropdownIndex(LvglTrackballMode mode) { switch (mode) { - case LVGL_TRACKBALL_MODE_ENCODER: return 0; + case LVGL_TRACKBALL_MODE_KEYS: return 0; case LVGL_TRACKBALL_MODE_POINTER: return 1; } - return 0; // default to Encoder + return 0; // default to Keys } static lv_indev_t* findFirstTrackballIndev() { @@ -65,7 +65,7 @@ struct Context { lv_indev_t* trackballIndev = nullptr; lv_obj_t* switchTrackball = nullptr; lv_obj_t* trackballModeDropdown = nullptr; - lv_obj_t* encoderSensitivitySlider = nullptr; + lv_obj_t* keySensitivitySlider = nullptr; lv_obj_t* pointerSensitivitySlider = nullptr; }; @@ -97,11 +97,11 @@ void onTrackballSwitch(lv_event_t* e) { // Enable/disable controls based on trackball state if (enabled) { if (ctx->trackballModeDropdown) lv_obj_clear_state(ctx->trackballModeDropdown, LV_STATE_DISABLED); - if (ctx->encoderSensitivitySlider) lv_obj_clear_state(ctx->encoderSensitivitySlider, LV_STATE_DISABLED); + if (ctx->keySensitivitySlider) lv_obj_clear_state(ctx->keySensitivitySlider, LV_STATE_DISABLED); if (ctx->pointerSensitivitySlider) lv_obj_clear_state(ctx->pointerSensitivitySlider, LV_STATE_DISABLED); } else { if (ctx->trackballModeDropdown) lv_obj_add_state(ctx->trackballModeDropdown, LV_STATE_DISABLED); - if (ctx->encoderSensitivitySlider) lv_obj_add_state(ctx->encoderSensitivitySlider, LV_STATE_DISABLED); + if (ctx->keySensitivitySlider) lv_obj_add_state(ctx->keySensitivitySlider, LV_STATE_DISABLED); if (ctx->pointerSensitivitySlider) lv_obj_add_state(ctx->pointerSensitivitySlider, LV_STATE_DISABLED); } } @@ -110,10 +110,10 @@ void onTrackballModeChanged(lv_event_t* e) { auto* ctx = static_cast(lv_event_get_user_data(e)); uint32_t selected = lv_dropdown_get_selected(ctx->trackballModeDropdown); - // Validate selection matches expected enum values (dropdown order: Encoder=0, Pointer=1) + // Validate selection matches expected enum values (dropdown order: Keys=0, Pointer=1) LvglTrackballMode mode; switch (selected) { - case 0: mode = LVGL_TRACKBALL_MODE_ENCODER; break; + case 0: mode = LVGL_TRACKBALL_MODE_KEYS; break; case 1: mode = LVGL_TRACKBALL_MODE_POINTER; break; default: return; // Invalid selection, ignore } @@ -125,10 +125,10 @@ void onTrackballModeChanged(lv_event_t* e) { applyLive(ctx); } -void onEncoderSensitivityChanged(lv_event_t* e) { +void onKeySensitivityChanged(lv_event_t* e) { auto* ctx = static_cast(lv_event_get_user_data(e)); - int32_t value = lv_slider_get_value(ctx->encoderSensitivitySlider); - ctx->tbSettings.encoder_sensitivity = static_cast(value); + int32_t value = lv_slider_get_value(ctx->keySensitivitySlider); + ctx->tbSettings.key_sensitivity = static_cast(value); ctx->updated = true; // Apply immediately @@ -149,7 +149,6 @@ void createWidgets(lv_obj_t* parent, void* userData) { auto* ctx = static_cast(userData); ctx->tbSettings = settings::trackball::loadOrGetDefault(); - auto ui_density = lvgl_get_ui_density(); ctx->updated = false; ctx->trackballIndev = findFirstTrackballIndev(); @@ -196,7 +195,7 @@ void createWidgets(lv_obj_t* parent, void* userData) { lv_obj_align(tb_mode_label, LV_ALIGN_LEFT_MID, 0, 0); ctx->trackballModeDropdown = lv_dropdown_create(tb_mode_wrapper); - lv_dropdown_set_options(ctx->trackballModeDropdown, "Encoder\nPointer"); + lv_dropdown_set_options(ctx->trackballModeDropdown, "Keys\nPointer"); lv_obj_align(ctx->trackballModeDropdown, LV_ALIGN_RIGHT_MID, 0, 0); lv_dropdown_set_selected(ctx->trackballModeDropdown, modeToDropdownIndex(ctx->tbSettings.mode)); lv_obj_add_event_cb(ctx->trackballModeDropdown, onTrackballModeChanged, LV_EVENT_VALUE_CHANGED, ctx); @@ -206,28 +205,25 @@ void createWidgets(lv_obj_t* parent, void* userData) { lv_obj_add_state(ctx->trackballModeDropdown, LV_STATE_DISABLED); } - // Encoder sensitivity slider - auto* enc_sens_wrapper = lv_obj_create(main_wrapper); - lv_obj_set_size(enc_sens_wrapper, LV_PCT(100), LV_SIZE_CONTENT); - lv_obj_set_style_pad_hor(enc_sens_wrapper, 0, LV_STATE_DEFAULT); - lv_obj_set_style_border_width(enc_sens_wrapper, 0, LV_STATE_DEFAULT); - if (ui_density != LVGL_UI_DENSITY_COMPACT) { - lv_obj_set_style_pad_ver(enc_sens_wrapper, 4, LV_STATE_DEFAULT); - } + // Key sensitivity slider + auto* key_sens_wrapper = lv_obj_create(main_wrapper); + lv_obj_set_size(key_sens_wrapper, LV_PCT(100), LV_SIZE_CONTENT); + lv_obj_set_style_pad_hor(key_sens_wrapper, 0, LV_STATE_DEFAULT); + lv_obj_set_style_border_width(key_sens_wrapper, 0, LV_STATE_DEFAULT); - auto* enc_sens_label = lv_label_create(enc_sens_wrapper); - lv_label_set_text(enc_sens_label, "Encoder Speed"); - lv_obj_align(enc_sens_label, LV_ALIGN_LEFT_MID, 0, 0); + auto* key_sens_label = lv_label_create(key_sens_wrapper); + lv_label_set_text(key_sens_label, "Key Speed"); + lv_obj_align(key_sens_label, LV_ALIGN_LEFT_MID, 0, 0); - ctx->encoderSensitivitySlider = lv_slider_create(enc_sens_wrapper); - lv_slider_set_range(ctx->encoderSensitivitySlider, 1, 10); - lv_slider_set_value(ctx->encoderSensitivitySlider, ctx->tbSettings.encoder_sensitivity, LV_ANIM_OFF); - lv_obj_set_width(ctx->encoderSensitivitySlider, LV_PCT(50)); - lv_obj_align(ctx->encoderSensitivitySlider, LV_ALIGN_RIGHT_MID, 0, 0); - lv_obj_add_event_cb(ctx->encoderSensitivitySlider, onEncoderSensitivityChanged, LV_EVENT_VALUE_CHANGED, ctx); + ctx->keySensitivitySlider = lv_slider_create(key_sens_wrapper); + lv_slider_set_range(ctx->keySensitivitySlider, 1, 10); + lv_slider_set_value(ctx->keySensitivitySlider, ctx->tbSettings.key_sensitivity, LV_ANIM_OFF); + lv_obj_set_width(ctx->keySensitivitySlider, LV_PCT(50)); + lv_obj_align(ctx->keySensitivitySlider, LV_ALIGN_RIGHT_MID, 0, 0); + lv_obj_add_event_cb(ctx->keySensitivitySlider, onKeySensitivityChanged, LV_EVENT_VALUE_CHANGED, ctx); if (!ctx->tbSettings.enabled) { - lv_obj_add_state(ctx->encoderSensitivitySlider, LV_STATE_DISABLED); + lv_obj_add_state(ctx->keySensitivitySlider, LV_STATE_DISABLED); } // Pointer sensitivity slider @@ -235,9 +231,6 @@ void createWidgets(lv_obj_t* parent, void* userData) { lv_obj_set_size(ptr_sens_wrapper, LV_PCT(100), LV_SIZE_CONTENT); lv_obj_set_style_pad_hor(ptr_sens_wrapper, 0, LV_STATE_DEFAULT); lv_obj_set_style_border_width(ptr_sens_wrapper, 0, LV_STATE_DEFAULT); - if (ui_density != LVGL_UI_DENSITY_COMPACT) { - lv_obj_set_style_pad_ver(ptr_sens_wrapper, 4, LV_STATE_DEFAULT); - } auto* ptr_sens_label = lv_label_create(ptr_sens_wrapper); lv_label_set_text(ptr_sens_label, "Pointer Speed"); diff --git a/Tactility/Source/app/wifimanage/View.cpp b/Tactility/Source/app/wifimanage/View.cpp index 0c9c2f31e..ff51025d5 100644 --- a/Tactility/Source/app/wifimanage/View.cpp +++ b/Tactility/Source/app/wifimanage/View.cpp @@ -142,10 +142,15 @@ void View::updateNetworkList() { // Enable on boot - auto* enable_on_boot_wrapper = lv_obj_create(networks_list); + // An unstyled row insets the wrapper like the list items' text: margins would make a full-width item overflow the list + auto* enable_on_boot_row = lv_obj_create(networks_list); + lv_obj_remove_style_all(enable_on_boot_row); + lv_obj_set_size(enable_on_boot_row, LV_PCT(100), LV_SIZE_CONTENT); + const int32_t enable_on_boot_inset = (lvgl_get_ui_density() == LVGL_UI_DENSITY_COMPACT) ? 2 : LV_DPX(16); + lv_obj_set_style_pad_hor(enable_on_boot_row, enable_on_boot_inset, LV_STATE_DEFAULT); + + auto* enable_on_boot_wrapper = lv_obj_create(enable_on_boot_row); lv_obj_set_size(enable_on_boot_wrapper, LV_PCT(100), LV_SIZE_CONTENT); - lv_obj_set_style_pad_all(enable_on_boot_wrapper, 0, LV_STATE_DEFAULT); - lv_obj_set_style_border_width(enable_on_boot_wrapper, 0, LV_STATE_DEFAULT); auto* enable_label = lv_label_create(enable_on_boot_wrapper); lv_label_set_text(enable_label, "Enable on boot"); @@ -156,12 +161,6 @@ void View::updateNetworkList() { lv_obj_add_event_cb(enable_on_boot_switch, onEnableOnBootSwitchChanged, LV_EVENT_VALUE_CHANGED, bindings); lv_obj_add_event_cb(enable_on_boot_wrapper, onEnableOnBootParentClicked, LV_EVENT_SHORT_CLICKED, enable_on_boot_switch); - if (lvgl_get_ui_density() == LVGL_UI_DENSITY_COMPACT) { - lv_obj_set_style_pad_ver(enable_on_boot_wrapper, 2, LV_STATE_DEFAULT); - } else { - lv_obj_set_style_pad_ver(enable_on_boot_wrapper, 8, LV_STATE_DEFAULT); - } - updateEnableOnBootToggle(); switch (state->getRadioState()) { diff --git a/Tactility/Source/app/wifimanage/WifiManage.cpp b/Tactility/Source/app/wifimanage/WifiManage.cpp index bbd295b68..de009a531 100644 --- a/Tactility/Source/app/wifimanage/WifiManage.cpp +++ b/Tactility/Source/app/wifimanage/WifiManage.cpp @@ -6,7 +6,6 @@ #include #include -#include #include #include #include diff --git a/Tactility/Source/lvgl/Lvgl.cpp b/Tactility/Source/lvgl/Lvgl.cpp index 9a34ed553..6e084aeca 100644 --- a/Tactility/Source/lvgl/Lvgl.cpp +++ b/Tactility/Source/lvgl/Lvgl.cpp @@ -1,5 +1,7 @@ #include +#include + #ifdef ESP_PLATFORM #include #endif @@ -22,6 +24,7 @@ #include #include +#include #include #include #include @@ -74,12 +77,13 @@ static lv_obj_t* windowManagerScreenInit(lv_obj_t* root) { lv_obj_set_flex_flow(vertical_container, LV_FLEX_FLOW_COLUMN); lv_obj_set_style_pad_all(vertical_container, 0, LV_STATE_DEFAULT); lv_obj_set_style_pad_gap(vertical_container, 0, LV_STATE_DEFAULT); - lv_obj_set_style_bg_color(vertical_container, lv_color_black(), LV_STATE_DEFAULT); + lv_obj_set_style_bg_opa(vertical_container, LV_OPA_TRANSP, LV_STATE_DEFAULT); lv_obj_set_style_border_width(vertical_container, 0, LV_STATE_DEFAULT); lv_obj_set_style_radius(vertical_container, 0, LV_STATE_DEFAULT); lv_obj_remove_flag(vertical_container, LV_OBJ_FLAG_SCROLLABLE); - lvgl::statusbar_create(vertical_container); + // The statusbar fits itself into the top of round and rounded displays + auto* statusbar = lvgl::statusbar_create(vertical_container); auto* app_container = lv_obj_create(vertical_container); lv_obj_set_style_pad_all(app_container, 0, LV_STATE_DEFAULT); @@ -89,6 +93,21 @@ static lv_obj_t* windowManagerScreenInit(lv_obj_t* root) { lv_obj_set_flex_flow(app_container, LV_FLEX_FLOW_COLUMN); lv_obj_remove_flag(app_container, LV_OBJ_FLAG_SCROLLABLE); + // Keeps the apps inside the visible area of round displays. + // On rounded displays, the widgets along the edges move their content inward from the corners instead. + lv_display_t* display = lv_obj_get_display(root); + DisplayShape shape; + lvgl_display_get_shape(display, &shape); + if (shape.shape == DISPLAY_SHAPE_CIRCLE) { + LvglInsets insets; + lvgl_display_get_insets(display, &insets); + lv_obj_update_layout(statusbar); + lv_obj_set_style_pad_top(app_container, std::max(0, insets.top - lv_obj_get_height(statusbar)), LV_STATE_DEFAULT); + lv_obj_set_style_pad_bottom(app_container, insets.bottom, LV_STATE_DEFAULT); + lv_obj_set_style_pad_left(app_container, insets.left, LV_STATE_DEFAULT); + lv_obj_set_style_pad_right(app_container, insets.right, LV_STATE_DEFAULT); + } + // Parented to root (not app_container/vertical_container) so it overlays on top of // everything, including the statusbar, regardless of which app is showing. Hidden until a // focused textarea shows it (see lvgl_keyboard_add_textarea()/textarea_show_keyboard()). diff --git a/Tactility/Source/lvgl/Statusbar.cpp b/Tactility/Source/lvgl/Statusbar.cpp index 9199e05d0..9d00a2e49 100644 --- a/Tactility/Source/lvgl/Statusbar.cpp +++ b/Tactility/Source/lvgl/Statusbar.cpp @@ -12,9 +12,16 @@ #include #include +#include + +#include #include +#include +#include #include +#include +#include #include #ifdef ESP_PLATFORM @@ -35,6 +42,7 @@ static void onUpdateTime(); struct StatusbarIcon { std::string image; + std::string appId; bool visible = false; bool claimed = false; }; @@ -159,6 +167,22 @@ static void update_icon(lv_obj_t* image, const StatusbarIcon* icon) { } else { lv_obj_add_flag(image, LV_OBJ_FLAG_HIDDEN); } + lv_obj_set_flag(image, LV_OBJ_FLAG_CLICKABLE, !icon->appId.empty()); +} + +static void onIconClicked(lv_event_t* event) { + const auto index = reinterpret_cast(lv_event_get_user_data(event)); + statusbar_data.mutex.lock(); + const std::string app_id = statusbar_data.icons[index].appId; + statusbar_data.mutex.unlock(); + if (app_id.empty()) { + return; + } + AppStartContext context; + uint32_t instance_id = 0; + if (app_start_context_from_id(app_id.c_str(), &context) == ERROR_NONE) { + app_start_with_context(&context, &instance_id); + } } lv_obj_t* statusbar_create(lv_obj_t* parent) { @@ -168,53 +192,81 @@ lv_obj_t* statusbar_create(lv_obj_t* parent) { auto* statusbar = reinterpret_cast(obj); - const auto fg_color = STATUSBAR_COLORS_INVERTED ? lv_color_black() : lv_color_white(); - const auto bg_color = STATUSBAR_COLORS_INVERTED ? lv_color_white() : lv_color_black(); - - lv_obj_set_style_bg_color(obj, bg_color, LV_STATE_DEFAULT); - lv_obj_set_style_bg_opa(obj, LV_OPA_COVER, LV_STATE_DEFAULT); - - // Inverted statusbar creates a separator between statusbar and the rest of the UI below it - if (STATUSBAR_COLORS_INVERTED) { - // Show border as a horizontal line on the bottom - lv_obj_set_style_border_color(obj, fg_color, LV_STATE_DEFAULT); - lv_obj_set_style_border_side(obj, LV_BORDER_SIDE_BOTTOM, LV_STATE_DEFAULT); - lv_obj_set_style_border_width(obj, 1, LV_STATE_DEFAULT); - } else { - // No border - lv_obj_set_style_border_color(obj, bg_color, LV_STATE_DEFAULT); - } - lv_obj_set_width(obj, LV_PCT(100)); lv_obj_set_style_pad_ver(obj, 0, LV_STATE_DEFAULT); lv_obj_set_style_pad_hor(obj, 2, LV_STATE_DEFAULT); lv_obj_center(obj); - lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_ROW); - lv_obj_set_flex_align(obj, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); auto icon_size = lvgl_get_statusbar_icon_font_height(); auto ui_density = lvgl_get_ui_density(); auto icon_padding = (ui_density != LVGL_UI_DENSITY_COMPACT) ? static_cast(icon_size * 0.2f) : 2; - lv_obj_set_style_pad_column(obj, icon_padding, LV_STATE_DEFAULT); - statusbar->time = lv_label_create(obj); - lv_obj_set_style_text_color(statusbar->time, fg_color, LV_STATE_DEFAULT); - lv_obj_set_style_margin_left(statusbar->time, 4, LV_STATE_DEFAULT); - update_time(statusbar); + lv_display_t* display = lv_obj_get_display(obj); + DisplayShape shape; + lvgl_display_get_shape(display, &shape); + + // The parent of the icons, which grid navigation moves through + lv_obj_t* icon_row; + if (shape.shape == DISPLAY_SHAPE_CIRCLE) { + // Two centered rows, as the top of a circle is narrow: the time, and the icons below it + const int32_t diameter = std::min(lv_display_get_horizontal_resolution(display), lv_display_get_vertical_resolution(display)); + lv_obj_set_height(obj, LV_SIZE_CONTENT); + lv_obj_set_style_pad_top(obj, diameter / 24, LV_STATE_DEFAULT); + lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_align(obj, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + + statusbar->time = lv_label_create(obj); + update_time(statusbar); + + icon_row = lv_obj_create(obj); + lv_obj_remove_style_all(icon_row); + lv_obj_set_size(icon_row, LV_SIZE_CONTENT, LV_SIZE_CONTENT); + lv_obj_set_style_pad_ver(icon_row, static_cast(icon_size / 10), LV_STATE_DEFAULT); + lv_obj_set_style_pad_column(icon_row, icon_padding, LV_STATE_DEFAULT); + lv_obj_set_flex_flow(icon_row, LV_FLEX_FLOW_ROW); + lv_obj_set_flex_align(icon_row, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + } else { + lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_ROW); + lv_obj_set_flex_align(obj, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + lv_obj_set_style_pad_column(obj, icon_padding, LV_STATE_DEFAULT); + + if (shape.shape == DISPLAY_SHAPE_ROUNDED) { + // Moves the content inward from the rounded corners, measured at the top of the icons + const int32_t content_top = (statusbar_get_height() - static_cast(icon_size)) / 2; + lv_obj_set_style_pad_hor(obj, 2 + lvgl_display_get_row_inset(display, content_top), LV_STATE_DEFAULT); + } - auto* left_spacer = lv_obj_create(obj); - lv_obj_remove_flag(left_spacer, LV_OBJ_FLAG_SCROLLABLE); - lv_obj_set_size(left_spacer, 1, 1); - lv_obj_set_flex_grow(left_spacer, 1); - lv_obj_set_style_bg_opa(left_spacer, LV_OPA_0, LV_STATE_DEFAULT); - lv_obj_set_style_border_opa(left_spacer, LV_OPA_0, LV_STATE_DEFAULT); + statusbar->time = lv_label_create(obj); + lv_obj_set_style_margin_left(statusbar->time, 4, LV_STATE_DEFAULT); + update_time(statusbar); + + auto* left_spacer = lv_obj_create(obj); + lv_obj_remove_flag(left_spacer, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_remove_flag(left_spacer, LV_OBJ_FLAG_CLICKABLE); + lv_obj_set_size(left_spacer, 1, 1); + lv_obj_set_flex_grow(left_spacer, 1); + lv_obj_set_style_bg_opa(left_spacer, LV_OPA_0, LV_STATE_DEFAULT); + lv_obj_set_style_border_opa(left_spacer, LV_OPA_0, LV_STATE_DEFAULT); + + icon_row = obj; + } + + // The monochrome theme's focus ring doesn't contrast with the statusbar, so the icons get their own + const bool own_focus_ring = lvgl_theme_is_mono(); + const int32_t focus_width = (ui_density != LVGL_UI_DENSITY_COMPACT) ? 2 : 1; + const lv_color_t focus_color = lv_obj_get_style_text_color(obj, LV_PART_MAIN); statusbar_data.mutex.lock(MAX_TICKS); for (int i = 0; i < STATUSBAR_ICON_LIMIT; ++i) { - auto* image = lv_image_create(obj); + auto* image = lv_image_create(icon_row); lv_obj_set_size(image, icon_size, icon_size); // regular padding doesn't work lv_obj_set_style_text_font(image, lvgl_get_statusbar_icon_font(), LV_STATE_DEFAULT); - lv_obj_set_style_text_color(image, fg_color, LV_STATE_DEFAULT); lv_obj_set_style_pad_all(image, 0, LV_STATE_DEFAULT); + if (own_focus_ring) { + lv_obj_set_style_outline_color(image, focus_color, LV_STATE_FOCUS_KEY); + lv_obj_set_style_outline_width(image, focus_width, LV_STATE_FOCUS_KEY); + lv_obj_set_style_outline_pad(image, -focus_width, LV_STATE_FOCUS_KEY); + } + lv_obj_add_event_cb(image, onIconClicked, LV_EVENT_SHORT_CLICKED, reinterpret_cast(static_cast(i))); statusbar->icons[i] = image; update_icon(image, &(statusbar_data.icons[i])); @@ -229,6 +281,15 @@ lv_obj_t* statusbar_create(lv_obj_t* parent) { }); statusbar_data.mutex.unlock(); + // Without a touchscreen, the icons are selected with the keys + if (lvgl_indev_exists(LV_INDEV_TYPE_KEYPAD)) { + lvgl_grid_navigation_add(icon_row); + } + // Only the icon row is focused when it isn't the statusbar itself + if (icon_row != obj && lv_obj_get_group(obj) != nullptr) { + lv_group_remove_obj(obj); + } + return obj; } @@ -298,6 +359,7 @@ void statusbar_icon_remove(int8_t id) { icon->claimed = false; icon->visible = false; icon->image = ""; + icon->appId = ""; statusbar_data.mutex.unlock(); statusbar_data.pubsub->publish(nullptr); } @@ -328,6 +390,16 @@ void statusbar_icon_set_visibility(int8_t id, bool visible) { statusbar_data.pubsub->publish(nullptr); } +void statusbar_icon_set_app(int8_t id, const std::string& appId) { + check(id >= 0 && id < STATUSBAR_ICON_LIMIT); + statusbar_data.mutex.lock(); + StatusbarIcon* icon = &statusbar_data.icons[id]; + check(icon->claimed); + icon->appId = appId; + statusbar_data.mutex.unlock(); + statusbar_data.pubsub->publish(nullptr); +} + int statusbar_get_height() { const auto icon_size = lvgl_get_statusbar_icon_font_height(); const auto vertical_padding = static_cast((static_cast(icon_size) * 0.1f)); diff --git a/Tactility/Source/lvgl/Theme.cpp b/Tactility/Source/lvgl/Theme.cpp new file mode 100644 index 000000000..103b5096c --- /dev/null +++ b/Tactility/Source/lvgl/Theme.cpp @@ -0,0 +1,27 @@ +#include + +#include + +namespace tt::lvgl { + +void configureTheme(const settings::appearance::AppearanceSettings& settings) { + LvglThemeSettings theme_settings; + lvgl_theme_get_default_settings(&theme_settings); + if (settings.themeMode != settings::appearance::ThemeMode::DeviceDefault) { + theme_settings.is_dark = settings.themeMode == settings::appearance::ThemeMode::Dark; + } + theme_settings.is_mono = settings.monoTheme; + theme_settings.animations_enabled = settings.animationsEnabled; + if (settings.primaryColor.has_value()) { + theme_settings.color_primary = lv_color_hex(*settings.primaryColor); + } + if (settings.secondaryColor.has_value()) { + theme_settings.color_secondary = lv_color_hex(*settings.secondaryColor); + } + if (settings.errorColor.has_value()) { + theme_settings.color_error = lv_color_hex(*settings.errorColor); + } + lvgl_theme_set_settings(&theme_settings); +} + +} diff --git a/Tactility/Source/lvgl/wrappers/button.cpp b/Tactility/Source/lvgl/wrappers/button.cpp deleted file mode 100644 index 6d9c0d718..000000000 --- a/Tactility/Source/lvgl/wrappers/button.cpp +++ /dev/null @@ -1,24 +0,0 @@ -#ifdef ESP_PLATFORM - -#include - -#include - -extern "C" { - -extern lv_obj_t* __real_lv_button_create(lv_obj_t* parent); - -lv_obj_t* __wrap_lv_button_create(lv_obj_t* parent) { - auto button = __real_lv_button_create(parent); - - if (lvgl_get_ui_density() == LVGL_UI_DENSITY_COMPACT) { - lv_obj_set_style_pad_all(button, 3, LV_STATE_DEFAULT); - lv_obj_set_style_radius(button, 3, LV_STATE_DEFAULT); - } - - return button; -} - -} - -#endif // ESP_PLATFORM \ No newline at end of file diff --git a/Tactility/Source/lvgl/wrappers/dropdown.cpp b/Tactility/Source/lvgl/wrappers/dropdown.cpp deleted file mode 100644 index ad15e4704..000000000 --- a/Tactility/Source/lvgl/wrappers/dropdown.cpp +++ /dev/null @@ -1,30 +0,0 @@ -#ifdef ESP_PLATFORM - -#include - -#include - -extern "C" { - -extern lv_obj_t* __real_lv_dropdown_create(lv_obj_t* parent); - -lv_obj_t* __wrap_lv_dropdown_create(lv_obj_t* parent) { - auto dropdown = __real_lv_dropdown_create(parent); - - if (lvgl_get_ui_density() == LVGL_UI_DENSITY_COMPACT) { - lv_obj_set_style_pad_all(dropdown, 2, LV_STATE_DEFAULT); - } - - lv_obj_set_style_border_width(dropdown, 1, LV_PART_MAIN); - if (lv_display_get_color_format(lv_obj_get_display(parent)) == LV_COLOR_FORMAT_L8) { - lv_obj_set_style_border_color(dropdown, lv_theme_get_color_secondary(parent), LV_PART_MAIN); - } else { - lv_obj_set_style_border_color(dropdown, lv_color_hex(0xFAFAFA), LV_PART_MAIN); - } - - return dropdown; -} - -} - -#endif // ESP_PLATFORM \ No newline at end of file diff --git a/Tactility/Source/lvgl/wrappers/list.cpp b/Tactility/Source/lvgl/wrappers/list.cpp deleted file mode 100644 index d297f5292..000000000 --- a/Tactility/Source/lvgl/wrappers/list.cpp +++ /dev/null @@ -1,36 +0,0 @@ -#ifdef ESP_PLATFORM - -#include - -#include - -extern "C" { - -extern lv_obj_t* __real_lv_list_create(lv_obj_t* parent); -extern lv_obj_t* __real_lv_list_add_button(lv_obj_t* list, const void* icon, const char* txt); - -lv_obj_t* __wrap_lv_list_create(lv_obj_t* parent) { - auto* list = __real_lv_list_create(parent); - - if (lvgl_get_ui_density() == LVGL_UI_DENSITY_COMPACT) { - lv_obj_set_style_pad_row(list, 2, LV_STATE_DEFAULT); - lv_obj_set_style_pad_column(list, 2, LV_STATE_DEFAULT); - lv_obj_set_style_pad_all(list, 2, LV_STATE_DEFAULT); - } - - return list; -} - -lv_obj_t* __wrap_lv_list_add_button(lv_obj_t* list, const void* icon, const char* txt) { - auto* button = __real_lv_list_add_button(list, icon, txt); - - if (lvgl_get_ui_density() == LVGL_UI_DENSITY_COMPACT) { - lv_obj_set_style_pad_ver(button, 2, LV_STATE_DEFAULT); - } - - return button; -} - -} - -#endif // ESP_PLATFORM \ No newline at end of file diff --git a/Tactility/Source/lvgl/wrappers/obj.cpp b/Tactility/Source/lvgl/wrappers/obj.cpp deleted file mode 100644 index 5df116501..000000000 --- a/Tactility/Source/lvgl/wrappers/obj.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#ifdef ESP_PLATFORM - -#include - -#include - -extern "C" { - -extern void __real_lv_obj_set_flex_flow(lv_obj_t* obj, lv_flex_flow_t flow); -extern lv_obj_t* __real_lv_obj_create(lv_obj_t* parent); - -void __wrap_lv_obj_set_flex_flow(lv_obj_t* obj, lv_flex_flow_t flow) { - __real_lv_obj_set_flex_flow(obj, flow); - - if (lvgl_get_ui_density() == LVGL_UI_DENSITY_COMPACT) { - lv_obj_set_style_pad_gap(obj, 4, LV_STATE_DEFAULT); - } -} - -lv_obj_t* __wrap_lv_obj_create(lv_obj_t* parent) { - auto obj = __real_lv_obj_create(parent); - // Screens and display layers (a null parent, also used by LVGL's own display setup) must stay borderless and unpadded - if (parent != nullptr && lvgl_get_ui_density() == LVGL_UI_DENSITY_COMPACT) { - lv_obj_set_style_pad_all(obj, 2, LV_STATE_DEFAULT); - lv_obj_set_style_pad_gap(obj, 2, LV_STATE_DEFAULT); - lv_obj_set_style_radius(obj, 3, LV_STATE_DEFAULT); - lv_obj_set_style_border_width(obj, 1, LV_STATE_DEFAULT); - } - return obj; -} - -} - -#endif // ESP_PLATFORM \ No newline at end of file diff --git a/Tactility/Source/lvgl/wrappers/switch.cpp b/Tactility/Source/lvgl/wrappers/switch.cpp deleted file mode 100644 index f11d8eb0e..000000000 --- a/Tactility/Source/lvgl/wrappers/switch.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#ifdef ESP_PLATFORM - -#include - -#include - -extern "C" { - -extern lv_obj_t* __real_lv_switch_create(lv_obj_t* parent); - -lv_obj_t* __wrap_lv_switch_create(lv_obj_t* parent) { - auto widget = __real_lv_switch_create(parent); - - if (lvgl_get_ui_density() == LVGL_UI_DENSITY_COMPACT) { - lv_obj_set_style_size(widget, 25, 15, LV_STATE_DEFAULT); - } - - if (lv_display_get_color_format(lv_obj_get_display(parent)) == LV_COLOR_FORMAT_L8) { - lv_obj_set_style_bg_color(widget, lv_theme_get_color_secondary(parent), LV_PART_MAIN); - } - - return widget; -} - -} - -#endif // ESP_PLATFORM diff --git a/Tactility/Source/lvgl/wrappers/textarea.cpp b/Tactility/Source/lvgl/wrappers/textarea.cpp index f37f0f5d3..2327fcaeb 100644 --- a/Tactility/Source/lvgl/wrappers/textarea.cpp +++ b/Tactility/Source/lvgl/wrappers/textarea.cpp @@ -10,23 +10,14 @@ extern lv_obj_t* __real_lv_textarea_create(lv_obj_t* parent); lv_obj_t* __wrap_lv_textarea_create(lv_obj_t* parent) { auto textarea = __real_lv_textarea_create(parent); - if (lvgl_get_ui_density() == LVGL_UI_DENSITY_COMPACT) { - lv_obj_set_style_pad_all(textarea, 2, LV_STATE_DEFAULT); - } - auto* software_keyboard = lvgl_software_keyboard_get_last(); if (software_keyboard != nullptr) { lvgl_keyboard_add_textarea(software_keyboard, textarea); } - if (lv_display_get_color_format(lv_obj_get_display(parent)) == LV_COLOR_FORMAT_L8) { - lv_obj_set_style_border_width(textarea, 1, LV_PART_MAIN); - lv_obj_set_style_border_color(textarea, lv_theme_get_color_secondary(parent), LV_PART_MAIN); - } - return textarea; } } -#endif // ESP_PLATFORM \ No newline at end of file +#endif // ESP_PLATFORM diff --git a/Tactility/Source/service/memorychecker/MemoryCheckerService.cpp b/Tactility/Source/service/memorychecker/MemoryCheckerService.cpp index 2c907bac6..d0cbf3a84 100644 --- a/Tactility/Source/service/memorychecker/MemoryCheckerService.cpp +++ b/Tactility/Source/service/memorychecker/MemoryCheckerService.cpp @@ -55,6 +55,7 @@ bool MemoryCheckerService::onStart(ServiceContext& service) { lock.lock(); statusbarIconId = lvgl::statusbar_icon_add(LVGL_ICON_STATUSBAR_MEMORY, false); + lvgl::statusbar_icon_set_app(statusbarIconId, "tactility.systeminfo"); lvgl::statusbar_icon_set_visibility(statusbarIconId, false); timer.setCallbackPriority(Thread::Priority::Lower); diff --git a/Tactility/Source/service/statusbar/Statusbar.cpp b/Tactility/Source/service/statusbar/Statusbar.cpp index d12fe8df5..507f724f5 100644 --- a/Tactility/Source/service/statusbar/Statusbar.cpp +++ b/Tactility/Source/service/statusbar/Statusbar.cpp @@ -85,7 +85,7 @@ static const char* getBluetoothStatusIcon(tt::bluetooth::RadioState state, bool using enum tt::bluetooth::RadioState; case Off: case OffPending: - return nullptr; // hidden when off + return LVGL_ICON_STATUSBAR_BLUETOOTH_DISABLED; case OnPending: case On: if (connected) return LVGL_ICON_STATUSBAR_BLUETOOTH_CONNECTED; @@ -144,7 +144,7 @@ class StatusbarService final : public Service { Mutex mutex; std::unique_ptr updateTimer; int8_t gps_icon_id; - bool gps_last_state = false; + const char* gps_last_icon = nullptr; int8_t bt_icon_id; const char* bt_last_icon = nullptr; int8_t wifi_icon_id; @@ -165,15 +165,20 @@ class StatusbarService final : public Service { } void updateGpsIcon() { - bool show_icon = device_has_active_by_type(&GPS_TYPE); - if (gps_last_state != show_icon) { - if (show_icon) { - lvgl::statusbar_icon_set_image(gps_icon_id, LVGL_ICON_STATUSBAR_LOCATION_ON); + const char* icon = nullptr; + if (device_has_active_by_type(&GPS_TYPE)) { + icon = LVGL_ICON_STATUSBAR_LOCATION_ON; + } else if (device_exists_of_type(&GPS_TYPE)) { + icon = LVGL_ICON_STATUSBAR_LOCATION_OFF; + } + if (gps_last_icon != icon) { + if (icon != nullptr) { + lvgl::statusbar_icon_set_image(gps_icon_id, icon); lvgl::statusbar_icon_set_visibility(gps_icon_id, true); } else { lvgl::statusbar_icon_set_visibility(gps_icon_id, false); } - gps_last_state = show_icon; + gps_last_icon = icon; } } @@ -337,6 +342,12 @@ class StatusbarService final : public Service { sdcard_icon_id = lvgl::statusbar_icon_add(); wifi_icon_id = lvgl::statusbar_icon_add(); power_icon_id = lvgl::statusbar_icon_add(); + lvgl::statusbar_icon_set_app(gps_icon_id, "tactility.gpssettings"); + lvgl::statusbar_icon_set_app(bt_icon_id, "tactility.btmanage"); + lvgl::statusbar_icon_set_app(usb_icon_id, "tactility.usbsettings"); + lvgl::statusbar_icon_set_app(sdcard_icon_id, "tactility.files"); + lvgl::statusbar_icon_set_app(wifi_icon_id, "tactility.wifimanage"); + lvgl::statusbar_icon_set_app(power_icon_id, "tactility.power"); } ~StatusbarService() override { diff --git a/Tactility/Source/service/webserver/WebServerService.cpp b/Tactility/Source/service/webserver/WebServerService.cpp index d0dd196c0..a13894b05 100644 --- a/Tactility/Source/service/webserver/WebServerService.cpp +++ b/Tactility/Source/service/webserver/WebServerService.cpp @@ -206,6 +206,7 @@ bool WebServerService::onStart(ServiceContext& service) { // Create statusbar icon (hidden initially, shown when server actually starts) statusbarIconId = lvgl::statusbar_icon_add(); + lvgl::statusbar_icon_set_app(statusbarIconId, "tactility.webserversettings"); lvgl::statusbar_icon_set_visibility(statusbarIconId, false); // Load and cache settings once at boot diff --git a/Tactility/Source/settings/AppearanceSettings.cpp b/Tactility/Source/settings/AppearanceSettings.cpp index 968fda084..0f751a275 100644 --- a/Tactility/Source/settings/AppearanceSettings.cpp +++ b/Tactility/Source/settings/AppearanceSettings.cpp @@ -5,6 +5,9 @@ #include +#include +#include +#include #include #include @@ -13,6 +16,49 @@ namespace tt::settings::appearance { constexpr auto* SETTINGS_KEY_FONT_SIZE = "fontSize"; constexpr auto* SETTINGS_KEY_REGULAR_FONT_PATH = "regularFontPath"; constexpr auto* SETTINGS_KEY_MONO_FONT_PATH = "monoFontPath"; +constexpr auto* SETTINGS_KEY_THEME_MODE = "themeMode"; +constexpr auto* SETTINGS_KEY_MONO_THEME = "monoTheme"; +constexpr auto* SETTINGS_KEY_ANIMATIONS = "animations"; +constexpr auto* SETTINGS_KEY_PRIMARY_COLOR = "primaryColor"; +constexpr auto* SETTINGS_KEY_SECONDARY_COLOR = "secondaryColor"; +constexpr auto* SETTINGS_KEY_ERROR_COLOR = "errorColor"; + +static std::optional parseColor(const std::map& map, const char* key) { + const auto entry = map.find(key); + if (entry == map.end() || entry->second.empty()) { + return std::nullopt; + } + // An invalid value (e.g. from a hand-edited file) counts as unset, rather than as black + const std::string& text = entry->second; + uint32_t color = 0; + const auto [end, error] = std::from_chars(text.data(), text.data() + text.size(), color, 16); + if (error != std::errc {} || end != text.data() + text.size() || color > 0xFFFFFF) { + return std::nullopt; + } + return color; +} + +// Saving only updates the keys that are written, so an unset colour is written as an empty value +static void putColor(std::map& map, const char* key, std::optional color) { + if (color.has_value()) { + char text[8]; + std::snprintf(text, sizeof(text), "%06" PRIX32, *color); + map[key] = text; + } else { + map[key] = ""; + } +} + +static const char* toString(ThemeMode mode) { + switch (mode) { + case ThemeMode::Light: + return "light"; + case ThemeMode::Dark: + return "dark"; + default: + return "default"; + } +} static std::string getSettingsFilePath() { char path[256]; @@ -43,6 +89,22 @@ bool load(AppearanceSettings& settings) { if (const auto entry = map.find(SETTINGS_KEY_MONO_FONT_PATH); entry != map.end()) { settings.monoFontPath = entry->second; } + if (const auto entry = map.find(SETTINGS_KEY_THEME_MODE); entry != map.end()) { + if (entry->second == "light") { + settings.themeMode = ThemeMode::Light; + } else if (entry->second == "dark") { + settings.themeMode = ThemeMode::Dark; + } + } + if (const auto entry = map.find(SETTINGS_KEY_MONO_THEME); entry != map.end()) { + settings.monoTheme = entry->second == "true"; + } + if (const auto entry = map.find(SETTINGS_KEY_ANIMATIONS); entry != map.end()) { + settings.animationsEnabled = entry->second != "false"; + } + settings.primaryColor = parseColor(map, SETTINGS_KEY_PRIMARY_COLOR); + settings.secondaryColor = parseColor(map, SETTINGS_KEY_SECONDARY_COLOR); + settings.errorColor = parseColor(map, SETTINGS_KEY_ERROR_COLOR); return true; } @@ -63,6 +125,12 @@ bool save(const AppearanceSettings& settings) { map[SETTINGS_KEY_FONT_SIZE] = std::to_string(settings.fontSize); map[SETTINGS_KEY_REGULAR_FONT_PATH] = settings.regularFontPath; map[SETTINGS_KEY_MONO_FONT_PATH] = settings.monoFontPath; + map[SETTINGS_KEY_THEME_MODE] = toString(settings.themeMode); + map[SETTINGS_KEY_MONO_THEME] = settings.monoTheme ? "true" : "false"; + map[SETTINGS_KEY_ANIMATIONS] = settings.animationsEnabled ? "true" : "false"; + putColor(map, SETTINGS_KEY_PRIMARY_COLOR, settings.primaryColor); + putColor(map, SETTINGS_KEY_SECONDARY_COLOR, settings.secondaryColor); + putColor(map, SETTINGS_KEY_ERROR_COLOR, settings.errorColor); return file::savePropertiesFile(settings_path, map); } diff --git a/Tactility/Source/settings/TrackballSettings.cpp b/Tactility/Source/settings/TrackballSettings.cpp index 564f7a885..da975039e 100644 --- a/Tactility/Source/settings/TrackballSettings.cpp +++ b/Tactility/Source/settings/TrackballSettings.cpp @@ -26,8 +26,8 @@ constexpr auto* KEY_TRACKBALL_MODE = "trackballMode"; constexpr auto* KEY_ENCODER_SENSITIVITY = "encoderSensitivity"; constexpr auto* KEY_POINTER_SENSITIVITY = "pointerSensitivity"; -constexpr uint8_t MIN_ENCODER_SENSITIVITY = 1; -constexpr uint8_t MAX_ENCODER_SENSITIVITY = 10; +constexpr uint8_t MIN_KEY_SENSITIVITY = 1; +constexpr uint8_t MAX_KEY_SENSITIVITY = 10; constexpr uint8_t MIN_POINTER_SENSITIVITY = 1; constexpr uint8_t MAX_POINTER_SENSITIVITY = 10; @@ -64,13 +64,13 @@ bool load(LvglTrackballSettings& settings) { return s == "1" || s == "true" || s == "True" || s == "TRUE"; }; settings.enabled = (tb_enabled != map.end()) ? isTrueValue(tb_enabled->second) : true; - settings.mode = (tb_mode != map.end() && tb_mode->second == "1") ? LVGL_TRACKBALL_MODE_POINTER : LVGL_TRACKBALL_MODE_ENCODER; + settings.mode = (tb_mode != map.end() && tb_mode->second == "1") ? LVGL_TRACKBALL_MODE_POINTER : LVGL_TRACKBALL_MODE_KEYS; auto default_settings = lvgl_trackball_settings_get_default(); - settings.encoder_sensitivity = (enc_sens != map.end()) ? safeParseUint8(enc_sens->second, default_settings.encoder_sensitivity) : default_settings.encoder_sensitivity; + settings.key_sensitivity = (enc_sens != map.end()) ? safeParseUint8(enc_sens->second, default_settings.key_sensitivity) : default_settings.key_sensitivity; settings.pointer_sensitivity = (ptr_sens != map.end()) ? safeParseUint8(ptr_sens->second, default_settings.pointer_sensitivity) : default_settings.pointer_sensitivity; // Clamp values to valid ranges - settings.encoder_sensitivity = std::clamp(settings.encoder_sensitivity, MIN_ENCODER_SENSITIVITY, MAX_ENCODER_SENSITIVITY); + settings.key_sensitivity = std::clamp(settings.key_sensitivity, MIN_KEY_SENSITIVITY, MAX_KEY_SENSITIVITY); settings.pointer_sensitivity = std::clamp(settings.pointer_sensitivity, MIN_POINTER_SENSITIVITY, MAX_POINTER_SENSITIVITY); return true; @@ -92,7 +92,7 @@ bool save(const LvglTrackballSettings& settings) { std::map map; map[KEY_TRACKBALL_ENABLED] = settings.enabled ? "1" : "0"; map[KEY_TRACKBALL_MODE] = (settings.mode == LVGL_TRACKBALL_MODE_POINTER) ? "1" : "0"; - map[KEY_ENCODER_SENSITIVITY] = std::to_string(std::clamp(settings.encoder_sensitivity, MIN_ENCODER_SENSITIVITY, MAX_ENCODER_SENSITIVITY)); + map[KEY_ENCODER_SENSITIVITY] = std::to_string(std::clamp(settings.key_sensitivity, MIN_KEY_SENSITIVITY, MAX_KEY_SENSITIVITY)); map[KEY_POINTER_SENSITIVITY] = std::to_string(std::clamp(settings.pointer_sensitivity, MIN_POINTER_SENSITIVITY, MAX_POINTER_SENSITIVITY)); auto settings_path = getSettingsFilePath(); if (!file::findOrCreateParentDirectory(settings_path, 0755)) { diff --git a/Tactility/idf_component.yml b/Tactility/idf_component.yml index ea8299b45..d06a00ed6 100644 --- a/Tactility/idf_component.yml +++ b/Tactility/idf_component.yml @@ -81,7 +81,7 @@ dependencies: rules: - if: "target in [esp32s3, esp32p4]" espressif/esp_lvgl_port: "2.7.2" - lvgl/lvgl: "9.3.0" + lvgl/lvgl: "9.4.0" epdiy: git: https://github.com/vroland/epdiy.git version: 2.1.3 diff --git a/TactilityKernel/bindings/display.yaml b/TactilityKernel/bindings/display.yaml new file mode 100644 index 000000000..5e6a4f918 --- /dev/null +++ b/TactilityKernel/bindings/display.yaml @@ -0,0 +1,11 @@ +description: Common display properties + +properties: + shape: + type: int + default: DISPLAY_SHAPE_RECTANGLE + description: Shape of the visible area (DISPLAY_SHAPE_RECTANGLE, DISPLAY_SHAPE_ROUNDED or DISPLAY_SHAPE_CIRCLE) + corner-radius: + type: int + default: 0 + description: Corner radius in pixels, for DISPLAY_SHAPE_ROUNDED diff --git a/TactilityKernel/include/tactility/drivers/display.h b/TactilityKernel/include/tactility/drivers/display.h index 1bd2b768d..e692e5a3f 100644 --- a/TactilityKernel/include/tactility/drivers/display.h +++ b/TactilityKernel/include/tactility/drivers/display.h @@ -10,6 +10,27 @@ extern "C" { #include #include +/** + * @brief The shape of the display's visible area. + */ +enum DisplayShapeType { + DISPLAY_SHAPE_RECTANGLE = 0, + /** A rectangle with rounded corners */ + DISPLAY_SHAPE_ROUNDED = 1, + /** A circle with the diameter of the shortest side, centered on the panel */ + DISPLAY_SHAPE_CIRCLE = 2, +}; + +/** + * @brief The visible area of a display. Display bindings that include display.yaml get it from the devicetree. + */ +struct DisplayShape { + /** A DisplayShapeType value */ + uint8_t shape; + /** The corner radius in pixels, for DISPLAY_SHAPE_ROUNDED */ + uint16_t corner_radius; +}; + /** * @brief Optional capabilities of the display. */ @@ -255,6 +276,14 @@ struct DisplayApi { * @return true if all specified capabilities are available for this device instance */ bool (*has_capability)(struct Device* device, uint32_t capability); + + /** + * @brief Gets the shape of the display's visible area. + * @warning Nullable. When null, display_get_shape() reports a rectangle. + * @param[in] device the display device + * @param[out] shape the shape + */ + void (*get_shape)(struct Device* device, struct DisplayShape* shape); }; /** @@ -387,6 +416,13 @@ uint8_t display_get_frame_buffer_count(struct Device* device); */ error_t display_get_backlight(struct Device* device, struct Device** backlight); +/** + * @brief Gets the shape of the display's visible area. + * @param[in] device the display device + * @param[out] shape the shape: a rectangle when the driver doesn't report one + */ +void display_get_shape(struct Device* device, struct DisplayShape* shape); + extern const struct DeviceType DISPLAY_TYPE; #ifdef __cplusplus diff --git a/TactilityKernel/include/tactility/drivers/keyboard.h b/TactilityKernel/include/tactility/drivers/keyboard.h index 9f9e1b127..98578392d 100644 --- a/TactilityKernel/include/tactility/drivers/keyboard.h +++ b/TactilityKernel/include/tactility/drivers/keyboard.h @@ -30,6 +30,10 @@ typedef enum { CODEPOINT_ARROW_DOWN = 0x2193, CODEPOINT_HOME = 0x21F1, CODEPOINT_END = 0x21F2, + /** Moves the focus to the next widget, for devices that can only step through widgets (e.g. an encoder wheel) */ + CODEPOINT_FOCUS_NEXT = 0x21E5, + /** Moves the focus to the previous widget, for devices that can only step through widgets (e.g. an encoder wheel) */ + CODEPOINT_FOCUS_PREVIOUS = 0x21E4, } CodePoint; /** diff --git a/TactilityKernel/include/tactility/paths.h b/TactilityKernel/include/tactility/paths.h index 0e6e54616..48b20e4c4 100644 --- a/TactilityKernel/include/tactility/paths.h +++ b/TactilityKernel/include/tactility/paths.h @@ -12,6 +12,16 @@ extern "C" { #define FILE_MAX_PATH_LENGTH 255 #define FILE_MAX_PATH_STRING_LENGTH (FILE_MAX_PATH_LENGTH + 1) +/** + * @brief Get the root path of the storage that holds the user data (e.g. the SD card). + * @param[out] out_path buffer to store the path (no trailing "/") + * @param[in] out_path_size size of the output buffer + * @retval ERROR_NOT_FOUND if the configured storage location isn't available (e.g. no SD card) + * @retval ERROR_BUFFER_OVERFLOW if out_path_size is too small + * @retval ERROR_NONE on success + */ +error_t paths_get_data_root_path(char* out_path, size_t out_path_size); + /** * @brief Get the root path for user data. Survives OS upgrades. * @param[out] out_path buffer to store the path (no trailing "/") diff --git a/TactilityKernel/source/drivers/display.cpp b/TactilityKernel/source/drivers/display.cpp index 912d6f408..cf6615cbc 100644 --- a/TactilityKernel/source/drivers/display.cpp +++ b/TactilityKernel/source/drivers/display.cpp @@ -157,6 +157,16 @@ error_t display_get_backlight(Device* device, Device** backlight) { return error; } +void display_get_shape(Device* device, DisplayShape* shape) { + const auto* driver = device_get_driver(device); + const auto* api = DISPLAY_DRIVER_API(driver); + if (api->get_shape == nullptr) { + *shape = { .shape = DISPLAY_SHAPE_RECTANGLE, .corner_radius = 0 }; + return; + } + api->get_shape(device, shape); +} + const struct DeviceType DISPLAY_TYPE { .name = "display" }; diff --git a/TactilityKernel/source/paths.cpp b/TactilityKernel/source/paths.cpp index 1c0379c2e..a89aaa076 100644 --- a/TactilityKernel/source/paths.cpp +++ b/TactilityKernel/source/paths.cpp @@ -8,7 +8,9 @@ #include #include -static error_t paths_get_data_root_path(char* out_path, size_t out_path_size) { +extern "C" { + +error_t paths_get_data_root_path(char* out_path, size_t out_path_size) { #if defined(CONFIG_TT_USER_DATA_LOCATION_INTERNAL) // Registered by platform-esp32/platform-posix's own module start() (see file_system_add()'s // "data" name), rather than a literal here: on POSIX that filesystem's real path is resolved @@ -38,8 +40,6 @@ static error_t paths_get_data_root_path(char* out_path, size_t out_path_size) { #endif } -extern "C" { - error_t paths_get_data_path(char* out_path, size_t out_path_size) { // Sized for a real, realpath()-resolved absolute path (see paths_get_data_root_path()'s // CONFIG_TT_USER_DATA_LOCATION_INTERNAL branch), not just the short literal this used to be. diff --git a/TactilityKernel/source/symbols.c b/TactilityKernel/source/symbols.c index 3ea20c0a4..7481a24f6 100644 --- a/TactilityKernel/source/symbols.c +++ b/TactilityKernel/source/symbols.c @@ -173,6 +173,7 @@ const struct ModuleSymbol KERNEL_SYMBOLS[] = { DEFINE_MODULE_SYMBOL(display_get_frame_buffer), DEFINE_MODULE_SYMBOL(display_get_frame_buffer_count), DEFINE_MODULE_SYMBOL(display_get_backlight), + DEFINE_MODULE_SYMBOL(display_get_shape), DEFINE_MODULE_SYMBOL(DISPLAY_TYPE), // file system DEFINE_MODULE_SYMBOL(file_system_mount), @@ -271,6 +272,7 @@ const struct ModuleSymbol KERNEL_SYMBOLS[] = { DEFINE_MODULE_SYMBOL(led_strip_get_length), DEFINE_MODULE_SYMBOL(LED_STRIP_TYPE), // drivers/paths + DEFINE_MODULE_SYMBOL(paths_get_data_root_path), DEFINE_MODULE_SYMBOL(paths_get_data_path), DEFINE_MODULE_SYMBOL(paths_get_temp_path), // drivers/pointer diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index e68b63ee0..3d46db621 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -14,6 +14,7 @@ add_subdirectory(${CMAKE_SOURCE_DIR}/Modules/app-posix-module/tests ${CMAKE_CURR add_subdirectory(${CMAKE_SOURCE_DIR}/Modules/http-module/tests ${CMAKE_CURRENT_BINARY_DIR}/http-module) add_subdirectory(${CMAKE_SOURCE_DIR}/Modules/binfont-module/tests ${CMAKE_CURRENT_BINARY_DIR}/binfont-module) add_subdirectory(${CMAKE_SOURCE_DIR}/Modules/lodepng-module/tests ${CMAKE_CURRENT_BINARY_DIR}/lodepng-module) +add_subdirectory(${CMAKE_SOURCE_DIR}/Modules/lvgl-module/tests ${CMAKE_CURRENT_BINARY_DIR}/lvgl-module) add_custom_target(build-tests) add_dependencies(build-tests ServiceModuleTests) @@ -27,3 +28,4 @@ add_dependencies(build-tests AppPosixModuleTests) add_dependencies(build-tests HttpModuleTests) add_dependencies(build-tests BinFontModuleTests) add_dependencies(build-tests LodePngModuleTests) +add_dependencies(build-tests LvglModuleTests) diff --git a/device.py b/device.py index 75773ee85..78f163d27 100755 --- a/device.py +++ b/device.py @@ -292,20 +292,13 @@ def write_lvgl_variables(output_file, device_properties: dict): has_statusbar_colors_inverted = get_boolean_property_or_false(device_properties, "lvgl.statusbarColorsInverted") if has_statusbar_colors_inverted: output_file.write("CONFIG_TT_LVGL_STATUSBAR_COLORS_INVERTED=y\n") + # Tactility uses its own themes (Modules/lvgl-module), and lvgl.theme only selects the default light or dark mode theme = get_property_or_default(device_properties, "lvgl.theme", "DefaultDark") - if theme == "DefaultDark": - output_file.write("CONFIG_LV_THEME_DEFAULT_DARK=y\n") - elif theme == "DefaultLight": - output_file.write("CONFIG_LV_THEME_DEFAULT_LIGHT=y\n") - elif theme == "Mono": - output_file.write("CONFIG_LV_USE_THEME_MONO=y\n") - # LVGL selects the theme with #if LV_USE_THEME_DEFAULT / #elif LV_USE_THEME_SIMPLE / - # #elif LV_USE_THEME_MONO, and the Kconfig defaults enable DEFAULT+SIMPLE for any - # non-1bpp color depth so MONO only takes effect once those are disabled. - output_file.write("CONFIG_LV_USE_THEME_DEFAULT=n\n") - output_file.write("CONFIG_LV_USE_THEME_SIMPLE=n\n") - else: + if theme not in ("DefaultDark", "DefaultLight", "Mono"): exit_with_error(f"Unknown theme: {theme}") + output_file.write("CONFIG_LV_USE_THEME_DEFAULT=n\n") + output_file.write("CONFIG_LV_USE_THEME_SIMPLE=n\n") + output_file.write("CONFIG_LV_USE_THEME_MONO=n\n") font_size_text = get_property_or_none(device_properties, "font.defaultSize") if font_size_text is None: font_size_text = get_property_or_default(device_properties, "lvgl.fontSize", "14") diff --git a/lv_conf.h b/lv_conf.h index a9f933c9d..3496f96d0 100644 --- a/lv_conf.h +++ b/lv_conf.h @@ -557,22 +557,13 @@ * THEMES *==================*/ -/*A simple, impressive and very complete theme*/ -#define LV_USE_THEME_DEFAULT 1 -#if LV_USE_THEME_DEFAULT - - /*0: Light mode; 1: Dark mode*/ - #define LV_THEME_DEFAULT_DARK 1 +/*Tactility uses its own themes (Modules/lvgl-module), so LVGL's themes are disabled*/ - /*1: Enable grow on press*/ - #define LV_THEME_DEFAULT_GROW 1 - - /*Default transition time in [ms]*/ - #define LV_THEME_DEFAULT_TRANSITION_TIME 80 -#endif /*LV_USE_THEME_DEFAULT*/ +/*A simple, impressive and very complete theme*/ +#define LV_USE_THEME_DEFAULT 0 /*A very simple theme that is a good starting point for a custom theme*/ -#define LV_USE_THEME_SIMPLE 1 +#define LV_USE_THEME_SIMPLE 0 /*A theme designed for monochrome displays*/ #define LV_USE_THEME_MONO 0 @@ -782,7 +773,7 @@ #define LV_USE_MONKEY 0 /*1: Enable grid navigation*/ -#define LV_USE_GRIDNAV 0 +#define LV_USE_GRIDNAV 1 /*1: Enable lv_obj fragment*/ #define LV_USE_FRAGMENT 0