diff --git a/.gitee/ISSUE_TEMPLATE/001_bug_report.yml b/.gitee/ISSUE_TEMPLATE/001_bug_report.yml
new file mode 100644
index 0000000000000000000000000000000000000000..7107bc233c838b0738b53c3960912b09710fc480
--- /dev/null
+++ b/.gitee/ISSUE_TEMPLATE/001_bug_report.yml
@@ -0,0 +1,40 @@
+name: 缺陷反馈 | Bug
+description: 当您发现了一个缺陷,需要向社区反馈时,请使用此模板。
+title: "[BUG] <标题>"
+labels: [👀 needs triage, "Type: Bug"]
+body:
+ - type: markdown
+ attributes:
+ value: |
+ 感谢您对 openvela 社区的支持与关注,欢迎反馈缺陷。
+
+ - type: textarea
+ attributes:
+ label: "重现问题的步骤"
+ description: "简洁地描述错误是什么,为什么您认为它是一个错误,以及如何重现它的步骤"
+ placeholder: |
+ 重现问题的步骤,可能包括日志和截图。
+ 1. 步骤 1
+ 2. 步骤 2
+ validations:
+ required: true
+
+ - type: dropdown
+ id: architecture
+ attributes:
+ label: Issue Architecture
+ multiple: true
+ options:
+ - "Arch: arm"
+ - "Arch: arm64"
+ - "Arch: x86_64"
+ validations:
+ required: true
+
+ - type: markdown
+ attributes:
+ value: |
+ 提交前请确认您已遵循以下步骤:
+ - 确认问题在 [**dev**](../) 上可重现。
+ - 遇到构建问题时运行 `make distclean`。
+ - 搜索 [现有问题](./)
diff --git a/.gitee/ISSUE_TEMPLATE/002_feature_request.yml b/.gitee/ISSUE_TEMPLATE/002_feature_request.yml
new file mode 100644
index 0000000000000000000000000000000000000000..8b500152e7f080e30baa628bdc8c079b93be3d25
--- /dev/null
+++ b/.gitee/ISSUE_TEMPLATE/002_feature_request.yml
@@ -0,0 +1,34 @@
+name: 新需求 | Feature
+description: 当您需要反馈或实现一个新需求时,使用此模板。
+title: "[FEATURE] <标题>"
+body:
+ - type: markdown
+ attributes:
+ value: |
+ 感谢您对 openvela 社区的支持与关注。
+
+ - type: textarea
+ id: question-description
+ attributes:
+ label: 您的需求是否和问题相关?
+ description: 请简单描述问题,并提供issue链接。
+ validations:
+ required: true
+
+ - type: textarea
+ id: solution
+ attributes:
+ label: 请描述您想要的解决方案
+ validations:
+ required: true
+
+ - type: textarea
+ id: 替代方案
+ attributes:
+ label: 请描述您考虑过的替代解决方案
+
+ - type: markdown
+ attributes:
+ value: |
+ 提交前请搜索 [现有功能需求](./)
+
diff --git a/.gitee/ISSUE_TEMPLATE/003_help.yml b/.gitee/ISSUE_TEMPLATE/003_help.yml
new file mode 100644
index 0000000000000000000000000000000000000000..bde95adf3d07da0e687eef3d960a10b26b2a97a9
--- /dev/null
+++ b/.gitee/ISSUE_TEMPLATE/003_help.yml
@@ -0,0 +1,22 @@
+name: 问题咨询
+title: "[问题咨询]"
+body:
+ - type: markdown
+ attributes:
+ value: |
+ 感谢您对 openvela 社区的支持与关注。
+
+ - type: textarea
+ id: question-description
+ attributes:
+ label: 描述
+ description: 请解释您的问题的背景或上下文,这有助于其他人更好地理解您的问题或疑问。
+ validations:
+ required: true
+
+ - type: markdown
+ attributes:
+ value: |
+ 提交前请确认您已遵循以下步骤:
+ - 我已搜索 [openvela 文档](../),但未找到问题的答案。
+ - 已搜索 [现有问题](./)
diff --git a/.gitee/ISSUE_TEMPLATE/config.yml b/.gitee/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 0000000000000000000000000000000000000000..3ba13e0cec6cbbfd462e9ebf529dd2093148cd69
--- /dev/null
+++ b/.gitee/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1 @@
+blank_issues_enabled: false
diff --git a/.gitee/PULL_REQUEST_TEMPLATE.md b/.gitee/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000000000000000000000000000000000000..cd8b2bc3a7690b37298e12d71c7b12189d26fae8
--- /dev/null
+++ b/.gitee/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,11 @@
+## 概要
+
+*在此部分更新信息,说明更改的必要性、具体做了什么以及如何实现的,如果有新功能出现,请提供参考资料(依赖关系、类似问题和解决方案等)。*
+
+## 影响
+
+*在此部分更新信息(如适用),说明更改如何影响用户、构建过程、硬件、文档、安全性、兼容性等。*
+
+## 测试
+
+*在此部分更新信息,详细说明如何验证更改,使用什么主机进行构建(操作系统、CPU、编译器等),使用什么目标进行验证(架构、板子:配置等)。提供更改前后的构建和运行日志将非常有帮助。*
\ No newline at end of file
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 0000000000000000000000000000000000000000..99ea0de3c436dbab51e3599c804dfc16aa373762
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1 @@
+* @xiaoxiang781216 @GUIDINGLI @terry0012 @jianglianfang
diff --git a/.github/ISSUE_TEMPLATE/001_bug_report.yml b/.github/ISSUE_TEMPLATE/001_bug_report.yml
new file mode 100644
index 0000000000000000000000000000000000000000..48e823db004f97019d883d13fd419084b650b4df
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/001_bug_report.yml
@@ -0,0 +1,60 @@
+# 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.
+#
+name: Bug report
+description: Report a bug to improve openvela stability
+title: "[BUG]
"
+labels: [👀 needs triage, "Type: Bug"]
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Hello openvela Community member! Please keep things tidy by putting your post in the proper place:
+
+ Reporting a bug: use this form.
+ Asking a question or getting help: use the [General Help](./new?assignees=&labels=Community%3A+Question&projects=&template=003_help.yml&title=%5BHELP%5D+%3Ctitle%3E) form.
+ Requesting a new feature: use the [Feature request](./new?assignees=&labels=Type%3A+Enhancement&projects=&template=002_feature_request.yml&title=%5BFEATURE%5D+%3Ctitle%3E) form.
+ - type: textarea
+ attributes:
+ label: "Description / Steps to reproduce the issue"
+ description: "A clear and concise description of what the bug is, and why you consider it to be a bug, and steps for how to reproduce it"
+ placeholder: |
+ A description with steps to reproduce the issue.
+ May include logs, images, or videos.
+ 1. Step 1
+ 2. Step 2
+ validations:
+ required: true
+
+ - type: dropdown
+ id: architecture
+ attributes:
+ label: Issue Architecture
+ description: What architecture(s) are you seeing the problem on?
+ multiple: true
+ options:
+ - "[Arch: arm]"
+ - "[Arch: arm64]"
+ - "[Arch: x86_64]"
+ validations:
+ required: true
+
+ - type: markdown
+ attributes:
+ value: |
+ ### Before You Submit
+
+ Please verify that you've followed these steps:
+ - Confirm the problem is reproducible on [**dev**](../).
+ - Run `make distclean` when encountering build issues.
+ - Search [existing issues](./) (including [closed](./?q=is%3Aissue+is%3Aclosed))
+
diff --git a/.github/ISSUE_TEMPLATE/002_feature_request.yml b/.github/ISSUE_TEMPLATE/002_feature_request.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d5f410895687730a42836aa65c321c13e59100c5
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/002_feature_request.yml
@@ -0,0 +1,55 @@
+# 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.
+#
+name: Feature request
+description: Request an enhancement for openvela
+title: "[FEATURE] "
+labels: ["Type: Enhancement"]
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Hello openvela Community member! Please keep things tidy by putting your post in the proper place:
+
+ Requesting a new feature: use this form.
+ Asking a question or getting help: use the [General Help](./new?assignees=&labels=Community%3A+Question&projects=&template=003_help.yml&title=%5BHELP%5D+%3Ctitle%3E) form.
+ Reporting a bug: use the [Bug report](./new?assignees=&labels=%F0%9F%91%80+needs+triage%2CType%3A+Bug&projects=&template=001_bug_report.yml&title=%5BBUG%5D+%3Ctitle%3E) form.
+
+ - type: textarea
+ id: question-description
+ attributes:
+ label: Is your feature request related to a problem? Please describe.
+ description: Please provide a clear and concise description of what the problem is. Add relevant issue link.
+ validations:
+ required: true
+
+ - type: textarea
+ id: solution
+ attributes:
+ label: Describe the solution you'd like
+ description: Please provide a clear and concise description of what you want to happen.
+ validations:
+ required: true
+
+ - type: textarea
+ id: alternatives
+ attributes:
+ label: Describe alternatives you've considered
+ description: Please provide a clear and concise description of any alternative solutions or features you've considered.
+
+ - type: markdown
+ attributes:
+ value: |
+ ### Before You Submit
+
+ Please verify that you've followed these steps:
+ - Search [existing feature requests](./) (including [closed](./?q=is%3Aissue+is%3Aclosed))
diff --git a/.github/ISSUE_TEMPLATE/003_help.yml b/.github/ISSUE_TEMPLATE/003_help.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ff1e1bf627ad1674eef3a4e20ed808e416beb0ec
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/003_help.yml
@@ -0,0 +1,47 @@
+# 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.
+#
+name: General Help
+description: Get general support regarding openvela
+title: "[HELP] "
+labels: ["Community: Question"]
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Hello openvela Community member! Please keep things tidy by putting your post in the proper place:
+
+ Asking a question or getting help: use this form.
+ Reporting a bug: use the [Bug report](./new?assignees=&labels=%F0%9F%91%80+needs+triage%2CType%3A+Bug&projects=&template=001_bug_report.yml&title=%5BBUG%5D+%3Ctitle%3E) form.
+ Requesting a new feature: use the [Feature request](./new?assignees=&labels=Type%3A+Enhancement&projects=&template=002_feature_request.yml&title=%5BFEATURE%5D+%3Ctitle%3E) form
+
+ - type: markdown
+ attributes:
+ value: |
+ ### Whether you're a beginner or an experienced developer, openvela Help is here to assist you with all your openvela questions and concerns.
+
+ - type: textarea
+ id: question-description
+ attributes:
+ label: Description
+ description: Explain the background or context of your question. This helps others understand your problem or inquiry better.
+ validations:
+ required: true
+
+ - type: markdown
+ attributes:
+ value: |
+ ### Before You Submit
+
+ Please verify that you've followed these steps:
+ - I have searched [openvela Documentation](../) and didn't find an answer to my question.
+ - Search [existing issues](./) (including [closed](./?q=is%3Aissue+is%3Aclosed))
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 0000000000000000000000000000000000000000..3ba13e0cec6cbbfd462e9ebf529dd2093148cd69
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1 @@
+blank_issues_enabled: false
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000000000000000000000000000000000000..cfe55eb61d3334245c04325ee92abc7fdd1ebc07
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,20 @@
+*Note: Please adhere to [Contributing Guidelines](https://github.com/open-vela/docs/blob/dev/CONTRIBUTING.md).*
+
+## Summary
+
+*Update this section with information on why change is necessary,
+ what it exactly does and how, if new feature shows up, provide
+ references (dependencies, similar problems and solutions), etc.*
+
+## Impact
+
+*Update this section, where applicable, on how change affects users,
+ build process, hardware, documentation, security, compatibility, etc.*
+
+## Testing
+
+*Update this section with details on how did you verify the change,
+ what Host was used for build (OS, CPU, compiler, ..), what Target was
+ used for verification (arch, board:config, ..), etc. Providing build
+ and runtime logs from before and after change is highly appreciated.*
+
diff --git a/.github/workflows/checkpatch.yml b/.github/workflows/checkpatch.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4d987aabb43c2effff6197beea063b44a4ad9b08
--- /dev/null
+++ b/.github/workflows/checkpatch.yml
@@ -0,0 +1,14 @@
+# This is a basic workflow to help you get started with Actions
+
+name: checkpatch
+
+# Controls when the workflow will run
+on:
+ pull_request:
+ types: [opened, reopened, synchronize]
+
+# A workflow run is made up of one or more jobs that can run sequentially or in parallel
+jobs:
+ checkpatch:
+ uses: open-vela/public-actions/.github/workflows/checkpatch.yml@trunk
+ secrets: inherit
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..7a034a21157b0ebc785fb5cca1a31013a9e86f3a
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,14 @@
+# This is a basic workflow to help you get started with Actions
+
+name: CI
+
+# Controls when the workflow will run
+on:
+ pull_request_target:
+ types: [opened, reopened, synchronize]
+
+# A workflow run is made up of one or more jobs that can run sequentially or in parallel
+jobs:
+ ci:
+ uses: open-vela/public-actions/.github/workflows/ci.yml@trunk
+ secrets: inherit
diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml
new file mode 100644
index 0000000000000000000000000000000000000000..8f8fc3cb81bad3b121f1e6aa23009cea0ee6cd4f
--- /dev/null
+++ b/.github/workflows/stale.yml
@@ -0,0 +1,11 @@
+name: 'Close stale issues and PR'
+on:
+ schedule:
+ - cron: '30 1 * * *'
+ workflow_dispatch: # 允许手动触发
+
+jobs:
+ stale:
+ uses: open-vela/public-actions/.github/workflows/stale.yml@trunk
+ secrets: inherit
+
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..a52a2693e9c604f6b374e62d2948a313bb78f9e1
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,6 @@
+*.o
+.built
+.depend
+.kconfig
+/Kconfig
+Make.dep
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..bbe81b91d4736f0e27422b1f19acdd99606d3161
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,10 @@
+Copyright (C) 2024 Xiaomi Corporation
+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/Make.defs b/Make.defs
new file mode 100644
index 0000000000000000000000000000000000000000..5ea525a1ca8d0611085a3bc2aaff928a3183f569
--- /dev/null
+++ b/Make.defs
@@ -0,0 +1,36 @@
+############################################################################
+# packages/Make.defs
+# Adds selected frameworks to apps/ build
+#
+# Copyright (C) 2019 Xiaomi Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in
+# the documentation and/or other materials provided with the
+# distribution.
+# 3. Neither the name NuttX nor the names of its contributors may be
+# used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+############################################################################
+
+include $(wildcard $(APPDIR)/packages/demos/*/Make.defs)
\ No newline at end of file
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..949c23fb3090746f69b966fd700eb2984b31e320
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,37 @@
+############################################################################
+# packages/Makefile
+#
+# Copyright (C) 2019 Xiaomi Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in
+# the documentation and/or other materials provided with the
+# distribution.
+# 3. Neither the name NuttX nor the names of its contributors may be
+# used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+############################################################################
+
+MENUDESC = "Demos"
+
+include $(APPDIR)/Directory.mk
diff --git a/README.md b/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..5c56623c630f09bf617d755806c8d1be31d00852
--- /dev/null
+++ b/README.md
@@ -0,0 +1,16 @@
+# Demo
+
+\[ English | [简体中文](README_zh-cn.md) \]
+
+## Overview
+
+This repository implements examples of openvela native application code. Developers can quickly get started with development based on our documentation and examples.
+
+## Usage Instructions
+
+- [Music Player](../../../docs/blob/dev/en/demo/Music_Player_Example.md)
+- [Smart Band](../../../docs/blob/dev/en/demo/Smart_Band_Example.md)
+- [Bicycle Computer](../../../docs/blob/dev/en/demo/X_Track.md)
+- [Calculator](calculator/Readme.md)
+- [Relation Calculator](relation_calculator/Readme.md)
+- [Whackmole](Whackmole/Readme.md)
diff --git a/README_zh-cn.md b/README_zh-cn.md
new file mode 100644
index 0000000000000000000000000000000000000000..813367c3240bc1672f2c22fb7328e9137eebc4f3
--- /dev/null
+++ b/README_zh-cn.md
@@ -0,0 +1,16 @@
+# Demo
+
+\[ [English](README.md) | 简体中文 \]
+
+## 简介
+
+该仓库实现了 openvela native应用代码示例,开发者可以根据我们的文档和示例,快速上手开发。
+
+## 使用说明
+
+- [音乐播放器](../../../docs/blob/dev/zh-cn/demo/Music_Player_Example_zh-cn.md)
+- [智能手环](../../../docs/blob/dev/zh-cn/demo/Smart_Band_Example_zh-cn.md)
+- [自行车码表](../../../docs/blob/dev/zh-cn/demo/X_Track_zh-cn.md)
+- [计算器](calculator/Readme.md)
+- [亲戚计算器](relation_calculator/Readme.md)
+- [打地鼠](Whackmole/Readme.md)
diff --git a/Whackmole/Kconfig b/Whackmole/Kconfig
new file mode 100644
index 0000000000000000000000000000000000000000..8b7413aadc0255907702b9001f5476b64c34a3f2
--- /dev/null
+++ b/Whackmole/Kconfig
@@ -0,0 +1,14 @@
+
+config LVX_USE_DEMO_WHACKMOLE
+ bool "Whackmole"
+ default n
+ help
+ Enable the demo Whackmole application.
+
+if LVX_USE_DEMO_WHACKMOLE
+config LVX_WHACKMOLE_DATA_ROOT
+ string "Whackmole Data Root"
+ default "/sdcard"
+ help
+ Specify the data storage path for the Whackmole application.
+endif
diff --git a/Whackmole/Make.defs b/Whackmole/Make.defs
new file mode 100644
index 0000000000000000000000000000000000000000..f53ccdb4037e12345703ad8bfa3bcdf7c1c5208c
--- /dev/null
+++ b/Whackmole/Make.defs
@@ -0,0 +1,4 @@
+ # Make.defs
+ifneq ($(CONFIG_LVX_USE_DEMO_WHACKMOLE),)
+CONFIGURED_APPS += $(APPDIR)/packages/demos/Whackmole
+endif
diff --git a/Whackmole/Makefile b/Whackmole/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..aa70edf53b46339509266ed119229746f066bcfb
--- /dev/null
+++ b/Whackmole/Makefile
@@ -0,0 +1,15 @@
+# Makefile
+
+
+include $(APPDIR)/Make.defs
+
+ifeq ($(CONFIG_LVX_USE_DEMO_WHACKMOLE), y)
+PROGNAME = Whackmole
+PRIORITY = 100
+STACKSIZE = 32768
+MODULE = $(CONFIG_LVX_USE_DEMO_WHACKMOLE)
+CSRCS = Whackmole.c
+MAINSRC = Whackmole_main.c
+endif
+
+include $(APPDIR)/Application.mk
diff --git a/Whackmole/Readme.md b/Whackmole/Readme.md
new file mode 100644
index 0000000000000000000000000000000000000000..fd6a754290b38d01685cb7e2c171621e2da20f1c
--- /dev/null
+++ b/Whackmole/Readme.md
@@ -0,0 +1,110 @@
+# 打地鼠
+
+## 运行效果
+
+
+
+## 使用说明
+
+### 配置项目
+
+1. 切换到 openvela 仓库的根目录,执行如下命令来配置Whackmole。
+
+ 模拟器配置文件(defconfig)在 `vendor/openvela/boards/vela/configs/goldfish-armeabi-v7a-ap/` 目录下,使用 `build.sh` 配置和编译开发板的代码。
+
+ ```Bash
+ ./build.sh vendor/openvela/boards/vela/configs/goldfish-armeabi-v7a-ap menuconfig
+ ```
+
+2. 按下 `/` 键逐个搜索修改如下配置:
+
+ ```Bash
+ LVX_USE_DEMO_WHACKMOLE=y
+ LVX_WHACKMOLE_DATA_ROOT="/data"
+ ```
+3. 如果页面显示不流畅,在menuconfig中将lv_cache_def_size设置为20000000
+
+### 编译项目
+
+```Bash
+# 清理构建产物
+./build.sh vendor/openvela/boards/vela/configs/goldfish-armeabi-v7a-ap distclean -j6
+
+# 开始构建
+./build.sh vendor/openvela/boards/vela/configs/goldfish-armeabi-v7a-ap -j6
+```
+
+### 启动模拟器并推送资源
+
+1. 切换到 openvela 仓库的根目录,启动模拟器:
+
+ ```Bash
+ ./emulator.sh vela
+ ```
+
+2. 使用模拟器支持的 ADB 将资源推送到设备,在 openvela 仓库的根目录下打开一个新的终端,输入 adb push 后跟文件路径,即可将资源传输到相应位置。
+
+ ```Bash
+ # 安装adb
+ sudo apt install android-tools-adb
+
+ # 推送资源
+ adb push packages/demos/Whackmole/pic /data/
+
+ ```
+
+### 启动游戏
+
+在模拟器的终端环境 `openvela-ap>` 中输入如下命令:
+
+```Bash
+Whackmole
+```
+
+## 功能修改
+
+首先需要明确,新功能开发时只需要在`Whackmole.c`中增加/修改功能,`Whackmole_main.c`可当作通用开发模板。
+
+### 修改资源图片
+
+在`pic`下的`hammer.png`,`mole.png`,`grassland.png` 对应的位置修改图片资源,并重新推送资源 `adb push packages/demos/Whackmole/pic /data/`
+
+```C
+void init_whack_a_mole_game(lv_obj_t *parent) {
+ // Set random number seed
+ srand(time(NULL));
+ R.images.hammer = ICONS_ROOT "/hammer.png";
+ R.images.mole = ICONS_ROOT "/mole.png";
+ R.images.grassland = ICONS_ROOT "/grassland.png";
+ ...
+}
+```
+
+### 修改游戏难度值
+
+在此游戏中,当游戏时间game_time小于40时,设置地鼠出现的定时器周期为800毫秒;如果小于20,则设为600毫秒。
+
+```C
+// Gophers appear randomly
+static void pop_random_mole(lv_timer_t *timer) {
+ for (int i = 0; i < 9; i++) {
+ lv_obj_add_flag(moles[i], LV_OBJ_FLAG_HIDDEN);
+ }
+ int show_count = rand() % 2 + 1;
+ for (int i = 0; i < show_count; i++) {
+ int mole_idx = rand() % 9;
+ lv_obj_clear_flag(moles[mole_idx], LV_OBJ_FLAG_HIDDEN);
+ }
+
+ // Adjust the frequency of gophers
+ if (game_time < 40) {
+ lv_timer_set_period(timer, 800);
+ }
+ if (game_time < 20) {
+ lv_timer_set_period(timer, 600);
+ }
+}
+```
+如果我们想要调整游戏难度,可以通过增大或减小定时器周期来实现。
+
+
diff --git a/Whackmole/Whackmole.c b/Whackmole/Whackmole.c
new file mode 100644
index 0000000000000000000000000000000000000000..a921f95d59ae6824b0451a405c8b54cda690b4f5
--- /dev/null
+++ b/Whackmole/Whackmole.c
@@ -0,0 +1,346 @@
+#include "Whackmole.h"
+#include "src/display/lv_display.h"
+#include "src/font/lv_font.h"
+#include
+#include
+#include // for snprintf
+#include
+#include
+#include
+
+#define GAME_TIME 30
+
+static lv_obj_t* game_screen = NULL;
+static lv_obj_t* moles[9];
+static lv_obj_t* score_label;
+static lv_obj_t* time_label;
+static lv_obj_t* hit_effect = NULL;
+static lv_obj_t* hammer_cursor = NULL;
+
+typedef struct {
+ int x;
+ int y;
+} hole_pos_t;
+
+static hole_pos_t hole_positions[9] = { { 210, 210 }, { 550, 210 }, { 900, 210 },
+ { 150, 380 }, { 550, 380 }, { 900, 380 },
+ { 150, 560 }, { 550, 560 }, { 950, 560 } };
+
+static int score = 0;
+static int game_time = GAME_TIME;
+static lv_timer_t* game_timer = NULL;
+static lv_timer_t* mole_timer = NULL;
+static void start_game(lv_event_t* e);
+static void delete_effect_cb(lv_timer_t* timer);
+static void mole_click_event(lv_event_t* e);
+static void update_game_timer(lv_timer_t* timer);
+static void pop_random_mole(lv_timer_t* timer);
+static void pointer_event_cb(lv_event_t* e);
+struct resource_s R;
+
+static void end_msg_event_cb(lv_event_t* e)
+{
+ lv_obj_t* btn = lv_event_get_target(e);
+ lv_obj_t* footer = lv_obj_get_parent(btn);
+ lv_obj_t* mbox = lv_obj_get_parent(footer);
+ lv_obj_delete(mbox);
+ const char* txt = (const char*)lv_event_get_user_data(e);
+ if (strcmp(txt, "Again") == 0) {
+ start_game(NULL);
+ }
+}
+static bool init_resource(void)
+{
+ // Fonts
+ R.fonts.size_14.normal = lv_freetype_font_create(
+ FONTS_ROOT "/MiSans-Normal.ttf", LV_FREETYPE_FONT_RENDER_MODE_BITMAP, 14,
+ LV_FREETYPE_FONT_STYLE_NORMAL);
+ R.fonts.size_22.bold = lv_freetype_font_create(
+ FONTS_ROOT "/MiSans-Normal.ttf", LV_FREETYPE_FONT_RENDER_MODE_BITMAP, 22,
+ LV_FREETYPE_FONT_STYLE_NORMAL);
+ if (R.fonts.size_14.normal == NULL || R.fonts.size_22.bold == NULL || R.fonts.size_24.normal == NULL || R.fonts.size_28.normal == NULL || R.fonts.size_60.bold == NULL) {
+ return false;
+ }
+
+ // Styles
+ lv_style_init(&R.styles.button_default);
+ lv_style_init(&R.styles.button_pressed);
+ lv_style_set_opa(&R.styles.button_default, LV_OPA_COVER);
+ lv_style_set_opa(&R.styles.button_pressed, LV_OPA_70);
+
+ return true;
+}
+
+// INIT
+void init_whack_a_mole_game(lv_obj_t* parent)
+{
+ // Set random number seed
+ srand(time(NULL));
+ R.images.hammer = ICONS_ROOT "/hammer.png";
+ R.images.mole = ICONS_ROOT "/mole.png";
+ R.images.grassland = ICONS_ROOT "/grassland.png";
+ // create game screen
+ game_screen = lv_image_create(parent);
+ lv_image_set_src(game_screen, R.images.grassland);
+ lv_obj_set_align(game_screen, LV_ALIGN_CENTER);
+ lv_obj_set_size(game_screen, LV_PCT(100), LV_PCT(100));
+ lv_obj_align(game_screen, LV_ALIGN_CENTER, 0, 0);
+
+ // Disable scroll and click attributes
+ lv_obj_remove_flag(game_screen, LV_OBJ_FLAG_SCROLLABLE);
+ lv_obj_remove_flag(game_screen, LV_OBJ_FLAG_CLICKABLE);
+ lv_obj_remove_flag(game_screen, LV_OBJ_FLAG_SCROLL_ELASTIC);
+ lv_obj_remove_flag(game_screen, LV_OBJ_FLAG_SCROLL_MOMENTUM);
+
+ lv_obj_move_background(game_screen);
+ lv_obj_move_to_index(game_screen, 0);
+ // Add pointer events to track mouse position
+ lv_obj_add_event_cb(game_screen, pointer_event_cb, LV_EVENT_PRESSED, NULL);
+ lv_obj_add_event_cb(game_screen, pointer_event_cb, LV_EVENT_PRESSING, NULL);
+
+ static const lv_style_prop_t props[] = { LV_STYLE_TRANSFORM_ROTATION, 0 };
+ /* A special transition when going to pressed state
+ * Make it slow (500 ms) but start without delay*/
+ static lv_style_transition_dsc_t trans_pr;
+ lv_style_transition_dsc_init(&trans_pr, props, lv_anim_path_linear, 150, 0,
+ NULL);
+
+ // hammer cursor
+ hammer_cursor = lv_image_create(game_screen);
+ lv_image_set_src(hammer_cursor, R.images.hammer);
+ lv_obj_add_flag(hammer_cursor, LV_OBJ_FLAG_HIDDEN);
+ lv_obj_set_style_image_recolor(hammer_cursor, lv_color_black(), 0);
+ lv_obj_set_style_image_recolor_opa(hammer_cursor, LV_OPA_30, 0);
+ lv_obj_set_style_transform_scale_x(hammer_cursor, 60, 0);
+ lv_obj_set_style_transform_scale_y(hammer_cursor, 60, 0);
+ lv_obj_set_style_transform_rotation(hammer_cursor, 0, LV_STATE_DEFAULT);
+ lv_obj_set_style_transform_rotation(hammer_cursor, 300, LV_STATE_PRESSED);
+ lv_obj_set_style_transition(hammer_cursor, &trans_pr, LV_STATE_DEFAULT);
+ lv_obj_set_style_transition(hammer_cursor, &trans_pr, LV_STATE_PRESSED);
+ lv_obj_add_flag(hammer_cursor, LV_OBJ_FLAG_CLICKABLE);
+ lv_obj_remove_flag(hammer_cursor, LV_OBJ_FLAG_IGNORE_LAYOUT);
+
+ // title
+ lv_obj_t* title = lv_label_create(game_screen);
+ lv_label_set_text(title, "Whackmole");
+ lv_obj_set_style_text_font(title, R.fonts.size_22.bold, 0);
+ lv_obj_set_style_text_color(title, lv_color_hex(0xFFFFFF), 0);
+ lv_obj_set_align(title, LV_ALIGN_TOP_MID);
+ lv_obj_set_pos(title, 0, 10);
+
+ // score display
+ score_label = lv_label_create(game_screen);
+ lv_label_set_text_fmt(score_label, "score: %d", score);
+ lv_obj_set_style_text_font(score_label, R.fonts.size_22.bold, 0);
+ lv_obj_set_style_text_color(score_label, lv_color_hex(0xFFFFFF), 0);
+ lv_obj_set_align(score_label, LV_ALIGN_TOP_LEFT);
+
+ lv_obj_set_pos(score_label, 20, 10);
+ // Time display
+ time_label = lv_label_create(game_screen);
+ lv_label_set_text_fmt(time_label, "time: %d", game_time);
+ lv_obj_set_style_text_font(time_label, R.fonts.size_22.bold, 0);
+ lv_obj_set_style_text_color(time_label, lv_color_hex(0xFFFFFF), 0);
+ lv_obj_set_align(time_label, LV_ALIGN_TOP_RIGHT);
+ lv_obj_set_pos(time_label, -20, 10);
+
+ for (int i = 0; i < 9; i++) {
+ lv_obj_t* hole = lv_obj_create(game_screen);
+ lv_obj_set_size(hole, 200, 150);
+ lv_obj_set_pos(hole, hole_positions[i].x - 30, hole_positions[i].y + 5);
+ lv_obj_set_style_bg_color(hole, lv_color_hex(0x000000), 0);
+ lv_obj_set_style_radius(hole, 25, 0);
+ lv_obj_set_style_border_width(hole, 0, 0);
+ lv_obj_set_style_shadow_width(hole, 0, 0);
+ lv_obj_set_style_shadow_color(hole, lv_color_hex(0x000000), 0);
+ lv_obj_set_style_shadow_opa(hole, LV_OPA_30, 0);
+ lv_obj_set_style_bg_opa(hole, LV_OPA_TRANSP,
+ 0); // Set background to transparent
+
+ lv_obj_move_background(hole);
+ lv_obj_move_to_index(hole, 0);
+ // create moles
+ moles[i] = lv_image_create(hole);
+ lv_image_set_src(moles[i], R.images.mole);
+ // lv_obj_set_size(moles[i], LV_PCT(100), LV_PCT(100));
+ lv_obj_set_align(moles[i], LV_ALIGN_CENTER);
+ lv_obj_set_style_image_recolor(moles[i], lv_color_black(), 0);
+ lv_obj_set_style_image_recolor_opa(moles[i], LV_OPA_30, 0);
+ lv_obj_set_style_transform_scale_x(hammer_cursor, 40, 0);
+ lv_obj_set_style_transform_scale_y(hammer_cursor, 40, 0);
+ lv_obj_add_flag(moles[i], LV_OBJ_FLAG_HIDDEN);
+ lv_obj_set_style_image_opa(moles[i], LV_OPA_COVER, 0);
+ lv_obj_set_style_image_recolor_opa(moles[i], LV_OPA_0, 0);
+ lv_obj_set_style_transform_scale(moles[i], 200, 0);
+ lv_obj_add_flag(moles[i], LV_OBJ_FLAG_CLICKABLE);
+ lv_obj_set_style_image_recolor_opa(moles[i], 0, 0);
+ lv_obj_move_foreground(moles[i]);
+
+ // click event
+ lv_obj_add_event_cb(moles[i], mole_click_event, LV_EVENT_CLICKED, NULL);
+ // Hover event
+ lv_obj_add_event_cb(moles[i], pointer_event_cb, LV_EVENT_PRESSED, NULL);
+ lv_obj_add_event_cb(moles[i], pointer_event_cb, LV_EVENT_PRESSING, NULL);
+ lv_obj_add_event_cb(moles[i], pointer_event_cb, LV_EVENT_RELEASED, NULL);
+ }
+
+ // start button
+ lv_obj_t* start_btn = lv_btn_create(game_screen);
+ lv_obj_set_size(start_btn, 120, 40);
+ lv_obj_set_align(start_btn, LV_ALIGN_BOTTOM_MID);
+ lv_obj_set_pos(start_btn, 0, -20);
+ lv_obj_add_event_cb(start_btn, start_game, LV_EVENT_CLICKED, NULL);
+ lv_obj_set_style_bg_color(start_btn, lv_color_hex(0x228B22), 0);
+ lv_obj_t* btn_label = lv_label_create(start_btn);
+ lv_label_set_text(btn_label, "START");
+ lv_obj_set_style_text_font(start_btn, R.fonts.size_22.bold, 0);
+ lv_obj_set_style_text_color(start_btn, lv_color_hex(0xFFFFFF), 0);
+ lv_obj_center(btn_label);
+ lv_obj_move_foreground(hammer_cursor);
+ lv_obj_move_foreground(start_btn);
+ lv_obj_move_foreground(score_label);
+ lv_obj_move_foreground(time_label);
+ lv_obj_move_foreground(title);
+}
+static void pointer_event_cb(lv_event_t* e)
+{
+ lv_event_code_t code = lv_event_get_code(e);
+ lv_point_t pos;
+ lv_indev_t* indev = lv_indev_active();
+ if (indev == NULL)
+ return;
+ lv_indev_get_point(indev, &pos);
+ lv_obj_remove_flag(hammer_cursor, LV_OBJ_FLAG_HIDDEN);
+ lv_obj_set_pos(hammer_cursor, pos.x - 25, pos.y - 25);
+
+ if (code == LV_EVENT_PRESSED) {
+ lv_obj_set_state(hammer_cursor, LV_STATE_PRESSED, true);
+ } else if (code == LV_EVENT_PRESSING) {
+ } else {
+ lv_obj_set_state(hammer_cursor, LV_STATE_PRESSED, false);
+ }
+}
+
+// start game
+static void start_game(lv_event_t* e)
+{
+ score = 0;
+ game_time = GAME_TIME;
+ lv_label_set_text_fmt(score_label, "score: %d", score);
+ lv_label_set_text_fmt(time_label, "time: %d", game_time);
+ lv_obj_set_style_text_font(game_screen, R.fonts.size_22.bold, 0);
+ lv_obj_set_style_text_color(game_screen, lv_color_hex(0xFFFFFF), 0);
+ lv_obj_set_style_text_font(game_screen, R.fonts.size_22.bold, 0);
+ lv_obj_set_style_text_color(game_screen, lv_color_hex(0xFFFFFF), 0);
+
+ for (int i = 0; i < 9; i++) {
+ lv_obj_add_flag(moles[i], LV_OBJ_FLAG_HIDDEN);
+ }
+ if (game_timer) {
+ lv_timer_delete(game_timer);
+ game_timer = NULL;
+ }
+ game_timer = lv_timer_create(update_game_timer, 1000, NULL);
+
+ if (mole_timer) {
+ lv_timer_delete(mole_timer);
+ mole_timer = NULL;
+ }
+ mole_timer = lv_timer_create(pop_random_mole, 1000, NULL);
+}
+
+// Update the game timer
+static void update_game_timer(lv_timer_t* timer)
+{
+ game_time--;
+ lv_label_set_text_fmt(time_label, "time: %d", game_time);
+ if (game_time <= 0) {
+ lv_timer_delete(game_timer);
+ game_timer = NULL;
+
+ if (mole_timer) {
+ lv_timer_delete(mole_timer);
+ mole_timer = NULL;
+ }
+ for (int i = 0; i < 9; i++) {
+ lv_obj_add_flag(moles[i], LV_OBJ_FLAG_HIDDEN);
+ }
+ lv_obj_t* end_msg = lv_msgbox_create(lv_screen_active());
+ lv_msgbox_add_title(end_msg, "Game over!");
+ lv_msgbox_add_text(end_msg, "Congratulations on completing game!");
+ lv_obj_set_size(end_msg, 300, 120);
+ lv_msgbox_add_close_button(end_msg);
+ const char* btns[] = { "Again", "Close", NULL };
+ for (int i = 0; btns[i]; i++) {
+ lv_obj_t* btn = lv_msgbox_add_footer_button(end_msg, btns[i]);
+ lv_obj_set_style_text_font(btn, R.fonts.size_14.normal, 0);
+ lv_obj_add_event_cb(btn, end_msg_event_cb, LV_EVENT_CLICKED,
+ (void*)btns[i]);
+ }
+ return;
+ }
+}
+
+// Gophers appear randomly
+static void pop_random_mole(lv_timer_t* timer)
+{
+ for (int i = 0; i < 9; i++) {
+ lv_obj_add_flag(moles[i], LV_OBJ_FLAG_HIDDEN);
+ }
+ int show_count = rand() % 2 + 1;
+ for (int i = 0; i < show_count; i++) {
+ int mole_idx = rand() % 9;
+ lv_obj_remove_flag(moles[mole_idx], LV_OBJ_FLAG_HIDDEN);
+ }
+
+ // Adjust the frequency of gophers
+ if (game_time < 40) {
+ lv_timer_set_period(timer, 800);
+ }
+ if (game_time < 20) {
+ lv_timer_set_period(timer, 600);
+ }
+}
+
+// Gopher click events
+static void mole_click_event(lv_event_t* e)
+{
+ lv_obj_t* mole = lv_event_get_target(e);
+ LV_LOG_USER("clicked %d", lv_obj_has_flag(mole, LV_OBJ_FLAG_HIDDEN));
+ // Bonus points when the gopher is visible
+ if (!lv_obj_has_flag(mole, LV_OBJ_FLAG_HIDDEN)) {
+ score++;
+ lv_label_set_text_fmt(score_label, "score: %d", score);
+ lv_obj_add_flag(mole, LV_OBJ_FLAG_HIDDEN);
+ if (hit_effect != NULL) {
+ lv_obj_delete(hit_effect);
+ hit_effect = NULL;
+ }
+ hit_effect = lv_obj_create(lv_obj_get_parent(mole));
+ lv_obj_set_size(hit_effect, LV_PCT(100), LV_PCT(100));
+ lv_obj_set_align(hit_effect, LV_ALIGN_CENTER);
+ lv_obj_set_pos(hit_effect, 0, 0);
+ lv_obj_set_style_bg_color(hit_effect, lv_color_hex(0xFFFF00), 0);
+ lv_obj_set_style_bg_opa(hit_effect, LV_OPA_50, 0);
+ lv_timer_create(delete_effect_cb, 300, hit_effect);
+ }
+}
+
+// Delete click effects
+static void delete_effect_cb(lv_timer_t* timer)
+{
+ lv_obj_t* effect = timer->user_data;
+ if (effect) {
+ lv_obj_delete(effect);
+ hit_effect = NULL;
+ }
+ lv_timer_delete(timer);
+}
+
+// create app
+void app_create(void)
+{
+ lv_obj_t* scr = lv_screen_active();
+ init_resource();
+ init_whack_a_mole_game(scr);
+}
diff --git a/Whackmole/Whackmole.h b/Whackmole/Whackmole.h
new file mode 100644
index 0000000000000000000000000000000000000000..5a4562bb20a160f1e57e418431093acd0ee43786
--- /dev/null
+++ b/Whackmole/Whackmole.h
@@ -0,0 +1,68 @@
+#ifndef WHACKMOLE_H
+#define WHACKMOLE_H
+#include
+#define RES_ROOT CONFIG_LVX_WHACKMOLE_DATA_ROOT "/pic"
+#define ICONS_ROOT RES_ROOT "/icons"
+#define FONTS_ROOT RES_ROOT "/fonts"
+void init_whack_a_mole_game(lv_obj_t* parent);
+void app_create(void);
+struct resource_s {
+ struct {
+ lv_obj_t* time;
+ lv_obj_t* date;
+
+ lv_obj_t* player_group;
+
+ lv_obj_t* volume_bar;
+ lv_obj_t* volume_bar_indic;
+ lv_obj_t* audio;
+ lv_obj_t* playlist_base;
+
+ lv_obj_t* album_cover;
+ lv_obj_t* album_name;
+ lv_obj_t* album_artist;
+
+ lv_obj_t* play_btn;
+ lv_obj_t* playback_group;
+ lv_obj_t* playback_progress;
+ lv_span_t* playback_current_time;
+ lv_span_t* playback_total_time;
+
+ lv_obj_t* playlist;
+ } ui;
+
+ struct {
+ struct {
+ lv_font_t* normal;
+ } size_14;
+ struct {
+ lv_font_t* bold;
+ } size_22;
+ struct {
+ lv_font_t* normal;
+ } size_24;
+ struct {
+ lv_font_t* normal;
+ } size_28;
+ struct {
+ lv_font_t* bold;
+ } size_60;
+ } fonts;
+
+ struct {
+ lv_style_t button_default;
+ lv_style_t button_pressed;
+ lv_style_transition_dsc_t button_transition_dsc;
+ lv_style_transition_dsc_t transition_dsc;
+ } styles;
+
+ struct {
+ const char* hammer;
+ const char* mole;
+ const char* grassland;
+ } images;
+};
+struct ctx_s {
+ bool resource_healthy_check;
+};
+#endif // WHACKMOLE_H
diff --git a/Whackmole/Whackmole_main.c b/Whackmole/Whackmole_main.c
new file mode 100644
index 0000000000000000000000000000000000000000..42752c4a5e8edbfccd6cb5e8034b5cf58c34a543
--- /dev/null
+++ b/Whackmole/Whackmole_main.c
@@ -0,0 +1,63 @@
+// include NuttX headers
+#include
+#include
+#include
+
+// include lvgl headers
+#include
+#include "Whackmole.h"
+static void lv_nuttx_uv_loop(uv_loop_t* loop, lv_nuttx_result_t* result)
+{
+ lv_nuttx_uv_t uv_info;
+ void* data;
+
+ uv_loop_init(loop);
+
+ lv_memset(&uv_info, 0, sizeof(uv_info));
+ uv_info.loop = loop;
+ uv_info.disp = result->disp;
+ uv_info.indev = result->indev;
+#ifdef CONFIG_UINPUT_TOUCH
+ uv_info.uindev = result->utouch_indev;
+#endif
+
+ data = lv_nuttx_uv_init(&uv_info);
+ uv_run(loop, UV_RUN_DEFAULT);
+ lv_nuttx_uv_deinit(&data);
+}
+
+#include "Whackmole.h"
+
+int main(int argc, FAR char* argv[])
+{
+ // init lvgl
+ lv_nuttx_dsc_t info;
+ lv_nuttx_result_t result;
+ uv_loop_t ui_loop;
+ lv_memset(&ui_loop, 0, sizeof(uv_loop_t));
+
+ if (lv_is_initialized()) {
+ LV_LOG_ERROR("LVGL already initialized! aborting.");
+ return -1;
+ }
+
+ lv_init();
+
+ lv_nuttx_dsc_init(&info);
+ lv_nuttx_init(&info, &result);
+
+ if (result.disp == NULL) {
+ LV_LOG_ERROR("lv_demos initialization failure!");
+ return 1;
+ }
+
+ app_create();
+
+ // refresh lvgl ui
+ lv_nuttx_uv_loop(&ui_loop, &result);
+
+ lv_nuttx_deinit(&result);
+ lv_deinit();
+
+ return 0;
+}
diff --git a/Whackmole/img/whackmole.gif b/Whackmole/img/whackmole.gif
new file mode 100644
index 0000000000000000000000000000000000000000..72526e4bfa41ef0ebc6b36785d23b07463c9a4dd
Binary files /dev/null and b/Whackmole/img/whackmole.gif differ
diff --git a/Whackmole/pic/fonts/MiSans-Normal.ttf b/Whackmole/pic/fonts/MiSans-Normal.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..3199d464141d0599dd1269a3fef4264a829c6da0
Binary files /dev/null and b/Whackmole/pic/fonts/MiSans-Normal.ttf differ
diff --git a/Whackmole/pic/fonts/MiSans-Semibold.ttf b/Whackmole/pic/fonts/MiSans-Semibold.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..83c1a532632f582550d48bae117d758191ad1800
Binary files /dev/null and b/Whackmole/pic/fonts/MiSans-Semibold.ttf differ
diff --git a/Whackmole/pic/icons/grassland.png b/Whackmole/pic/icons/grassland.png
new file mode 100644
index 0000000000000000000000000000000000000000..bd460576b29f3d116cdf047d85b41a3d47b6940a
Binary files /dev/null and b/Whackmole/pic/icons/grassland.png differ
diff --git a/Whackmole/pic/icons/hammer.png b/Whackmole/pic/icons/hammer.png
new file mode 100644
index 0000000000000000000000000000000000000000..6eca66c53e71fb8af46c1a9e739cf212850d9fdc
Binary files /dev/null and b/Whackmole/pic/icons/hammer.png differ
diff --git a/Whackmole/pic/icons/mole.png b/Whackmole/pic/icons/mole.png
new file mode 100644
index 0000000000000000000000000000000000000000..4e61cbf4bef4e97213bc808c1304f913016aca90
Binary files /dev/null and b/Whackmole/pic/icons/mole.png differ
diff --git a/bandx/Kconfig b/bandx/Kconfig
new file mode 100644
index 0000000000000000000000000000000000000000..8691459f0ef9bdc00ac582b00ff860182b1d82d5
--- /dev/null
+++ b/bandx/Kconfig
@@ -0,0 +1,14 @@
+config LVX_USE_DEMO_BANDX
+ bool "Smart Band demo"
+ depends on GRAPHICS_LVGL && LV_USE_FRAGMENT && LV_USE_LIBPNG
+ default n
+ ---help---
+ Enable build bandx Demo programs
+
+if LVX_USE_DEMO_BANDX
+
+config BANDX_BASE_PATH
+ string "Base path in bandx example"
+ default "/data"
+
+endif # LVX_USE_DEMO_BANDX
diff --git a/bandx/Make.defs b/bandx/Make.defs
new file mode 100644
index 0000000000000000000000000000000000000000..c08e4edf3f1096fc8787fbdb956d6d1a5e544806
--- /dev/null
+++ b/bandx/Make.defs
@@ -0,0 +1,18 @@
+#
+# Copyright (C) 2023 Xiaomi Corporation
+#
+# 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.
+#
+ifneq ($(CONFIG_LVX_USE_DEMO_BANDX),)
+CONFIGURED_APPS += $(APPDIR)/packages/demos/bandx
+endif
diff --git a/bandx/Makefile b/bandx/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..191bc9f6d8cf2e798fe1f8c323c255dd1c9c86e7
--- /dev/null
+++ b/bandx/Makefile
@@ -0,0 +1,14 @@
+include $(APPDIR)/Make.defs
+
+ifeq ($(CONFIG_LVX_USE_DEMO_BANDX), y)
+PROGNAME += bandx
+PRIORITY = 100
+STACKSIZE = 32768
+MODULE = $(CONFIG_LVX_USE_DEMO_BANDX)
+
+MAINSRC += bandx_demo_main.c
+CSRCS += $(shell find -L ./ -name "*.c" | grep -v bandx_demo_main.c)
+
+endif
+
+include $(APPDIR)/Application.mk
diff --git a/bandx/bandx_demo.c b/bandx/bandx_demo.c
new file mode 100644
index 0000000000000000000000000000000000000000..0f07371e4f7826b4ce3c2035e4a0008efae0d3d0
--- /dev/null
+++ b/bandx/bandx_demo.c
@@ -0,0 +1,63 @@
+/**
+ * @file bandx_demo.c
+ *
+ */
+
+/*********************
+ * INCLUDES
+ *********************/
+#include "bandx_demo.h"
+
+#include "page/page.h"
+#include "resource/resource.h"
+
+/*********************
+ * DEFINES
+ *********************/
+#define ROOT_VER_RES 368
+#define ROOT_HOR_RES 194
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+/**********************
+ * STATIC PROTOTYPES
+ **********************/
+
+/**********************
+ * STATIC VARIABLES
+ **********************/
+
+/**********************
+ * MACROS
+ **********************/
+
+/**********************
+ * GLOBAL FUNCTIONS
+ **********************/
+
+void bandx_demo_create(void)
+{
+ lv_obj_set_style_bg_color(lv_scr_act(), lv_palette_main(LV_PALETTE_GREY), 0);
+
+ /* prepare base root */
+ static lv_obj_t* root;
+ root = lv_obj_create(lv_scr_act());
+ lv_obj_remove_style_all(root);
+ lv_obj_set_size(root, ROOT_HOR_RES, ROOT_VER_RES);
+ lv_obj_center(root);
+
+ resource_init();
+ page_init();
+
+ lv_fragment_manager_t* manager = lv_fragment_manager_create(NULL);
+
+ /* push first page */
+ lv_fragment_t* page = page_create("dialplate", NULL);
+ lv_fragment_manager_push(manager, page, &root);
+}
+
+/**********************
+ * STATIC FUNCTIONS
+ **********************/
diff --git a/bandx/bandx_demo.h b/bandx/bandx_demo.h
new file mode 100644
index 0000000000000000000000000000000000000000..1a82f1e1efc0e56f605e872b0ee166c32edcbf1c
--- /dev/null
+++ b/bandx/bandx_demo.h
@@ -0,0 +1,44 @@
+/**
+ * @file bandx_demo.h
+ *
+ */
+
+#ifndef BANDX_DEMO_H
+#define BANDX_DEMO_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*********************
+ * INCLUDES
+ *********************/
+
+#include
+
+/*********************
+ * DEFINES
+ *********************/
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+/**********************
+ * GLOBAL PROTOTYPES
+ **********************/
+
+/**
+ * Demo function for bandx.
+ */
+void bandx_demo_create(void);
+
+/**********************
+ * MACROS
+ **********************/
+
+#ifdef __cplusplus
+} /*extern "C"*/
+#endif
+
+#endif /*BANDX_DEMO_H*/
diff --git a/bandx/bandx_demo_main.c b/bandx/bandx_demo_main.c
new file mode 100644
index 0000000000000000000000000000000000000000..da2184ef35ae3b76a67a4f9db756021cce68b81f
--- /dev/null
+++ b/bandx/bandx_demo_main.c
@@ -0,0 +1,63 @@
+// include NuttX headers
+#include
+#include
+#include
+
+// include lvgl headers
+#include
+
+static void lv_nuttx_uv_loop(uv_loop_t* loop, lv_nuttx_result_t* result)
+{
+ lv_nuttx_uv_t uv_info;
+ void* data;
+
+ uv_loop_init(loop);
+
+ lv_memset(&uv_info, 0, sizeof(uv_info));
+ uv_info.loop = loop;
+ uv_info.disp = result->disp;
+ uv_info.indev = result->indev;
+#ifdef CONFIG_UINPUT_TOUCH
+ uv_info.uindev = result->utouch_indev;
+#endif
+
+ data = lv_nuttx_uv_init(&uv_info);
+ uv_run(loop, UV_RUN_DEFAULT);
+ lv_nuttx_uv_deinit(&data);
+}
+
+#include "bandx_demo.h"
+
+int main(int argc, FAR char* argv[])
+{
+ // init lvgl
+ lv_nuttx_dsc_t info;
+ lv_nuttx_result_t result;
+ uv_loop_t ui_loop;
+ lv_memset(&ui_loop, 0, sizeof(uv_loop_t));
+
+ if (lv_is_initialized()) {
+ LV_LOG_ERROR("LVGL already initialized! aborting.");
+ return -1;
+ }
+
+ lv_init();
+
+ lv_nuttx_dsc_init(&info);
+ lv_nuttx_init(&info, &result);
+
+ if (result.disp == NULL) {
+ LV_LOG_ERROR("bandx_demo initialization failure!");
+ return 1;
+ }
+
+ bandx_demo_create();
+
+ // refresh lvgl ui
+ lv_nuttx_uv_loop(&ui_loop, &result);
+
+ lv_nuttx_deinit(&result);
+ lv_deinit();
+
+ return 0;
+}
diff --git a/bandx/hal/backlight.c b/bandx/hal/backlight.c
new file mode 100755
index 0000000000000000000000000000000000000000..8fdf4087a8530c39050d348664e419601363d5eb
--- /dev/null
+++ b/bandx/hal/backlight.c
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2020 Xiaomi Corporation
+ *
+ * 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.
+ */
+
+/*********************
+ * INCLUDES
+ *********************/
+#include "backlight.h"
+
+/*********************
+ * DEFINES
+ *********************/
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+/**********************
+ * STATIC PROTOTYPES
+ **********************/
+
+/**********************
+ * STATIC VARIABLES
+ **********************/
+
+static uint16_t backlight_value = 0;
+
+/**********************
+ * MACROS
+ **********************/
+
+/**********************
+ * GLOBAL FUNCTIONS
+ **********************/
+
+uint16_t backlight_get_value(void)
+{
+ return backlight_value;
+}
+
+void backlight_set_value(int16_t val)
+{
+ backlight_value = val;
+}
+
+/**********************
+ * STATIC FUNCTIONS
+ **********************/
diff --git a/bandx/hal/backlight.h b/bandx/hal/backlight.h
new file mode 100755
index 0000000000000000000000000000000000000000..73c8c8ecbc682f66d8761bd8466f31d837c8e562
--- /dev/null
+++ b/bandx/hal/backlight.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2020 Xiaomi Corporation
+ *
+ * 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.
+ */
+#ifndef __BACKLIGHT_H
+#define __BACKLIGHT_H
+
+/*********************
+ * INCLUDES
+ *********************/
+
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*********************
+ * DEFINES
+ *********************/
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+/**********************
+ * GLOBAL PROTOTYPES
+ **********************/
+
+/**
+ * Get the current value of the backlight.
+ * @return The current backlight value.
+ */
+uint16_t backlight_get_value(void);
+
+/**
+ * Set the value of the backlight.
+ * @param val The value to set for the backlight.
+ */
+void backlight_set_value(int16_t val);
+
+/**********************
+ * MACROS
+ **********************/
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/bandx/hal/clock.c b/bandx/hal/clock.c
new file mode 100755
index 0000000000000000000000000000000000000000..d019038a85d14d14ae057cf0bd13fea766de936f
--- /dev/null
+++ b/bandx/hal/clock.c
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2020 Xiaomi Corporation
+ *
+ * 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.
+ */
+
+/*********************
+ * INCLUDES
+ *********************/
+
+#include "clock.h"
+#include
+
+/*********************
+ * DEFINES
+ *********************/
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+/**********************
+ * STATIC PROTOTYPES
+ **********************/
+
+/**********************
+ * STATIC VARIABLES
+ **********************/
+
+/**********************
+ * MACROS
+ **********************/
+
+/**********************
+ * GLOBAL FUNCTIONS
+ **********************/
+
+void clock_get_value(clock_value_t* clock_value)
+{
+ struct tm* t;
+ time_t tt;
+ time(&tt);
+ t = localtime(&tt);
+
+ clock_value->year = t->tm_year + 1900;
+ clock_value->month = t->tm_mon + 1;
+ clock_value->date = t->tm_mday;
+ clock_value->week = t->tm_wday;
+ clock_value->hour = t->tm_hour;
+ clock_value->min = t->tm_min;
+ clock_value->sec = t->tm_sec;
+ clock_value->ms = 0;
+}
+
+void clock_set_value(const clock_value_t* clock_value)
+{
+}
+
+/**********************
+ * STATIC FUNCTIONS
+ **********************/
diff --git a/bandx/hal/clock.h b/bandx/hal/clock.h
new file mode 100755
index 0000000000000000000000000000000000000000..55a7a5cc97fc551a2859a2741371dfb285afe370
--- /dev/null
+++ b/bandx/hal/clock.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2020 Xiaomi Corporation
+ *
+ * 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.
+ */
+#ifndef __CLOCK_H
+#define __CLOCK_H
+
+/*********************
+ * INCLUDES
+ *********************/
+
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*********************
+ * DEFINES
+ *********************/
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+typedef struct {
+ uint8_t hour;
+ uint8_t min;
+ uint8_t sec;
+ uint16_t ms;
+ uint16_t year;
+ uint8_t month;
+ uint8_t date;
+ uint8_t week;
+} clock_value_t;
+
+/**********************
+ * GLOBAL PROTOTYPES
+ **********************/
+
+/**
+ * Get the current time value from the clock.
+ * @param clock_value Pointer to a structure to store the current clock value.
+ */
+void clock_get_value(clock_value_t* clock_value);
+
+/**
+ * Set the clock to a specific time value.
+ * @param clock_value Pointer to a structure of clock_value_t value to set.
+ */
+void clock_set_value(const clock_value_t* clock_value);
+
+/**********************
+ * MACROS
+ **********************/
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/bandx/hal/hal.h b/bandx/hal/hal.h
new file mode 100755
index 0000000000000000000000000000000000000000..dfe456d92a3982c23c48f62061eb2b6a366cde0a
--- /dev/null
+++ b/bandx/hal/hal.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2020 Xiaomi Corporation
+ *
+ * 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.
+ */
+#ifndef __HAL_H
+#define __HAL_H
+
+/*********************
+ * INCLUDES
+ *********************/
+
+#include "backlight.h"
+#include "clock.h"
+#include "imu.h"
+#include "particle_sensor.h"
+#include "power.h"
+
+/*********************
+ * DEFINES
+ *********************/
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+/**********************
+ * GLOBAL PROTOTYPES
+ **********************/
+
+/**********************
+ * MACROS
+ **********************/
+
+#endif
diff --git a/bandx/hal/imu.c b/bandx/hal/imu.c
new file mode 100644
index 0000000000000000000000000000000000000000..b7557487f5b4bfe135fe618bb12c751bcca44849
--- /dev/null
+++ b/bandx/hal/imu.c
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2020 Xiaomi Corporation
+ *
+ * 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.
+ */
+
+/*********************
+ * INCLUDES
+ *********************/
+
+#include "imu.h"
+
+/*********************
+ * DEFINES
+ *********************/
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+/**********************
+ * STATIC PROTOTYPES
+ **********************/
+
+/**********************
+ * STATIC VARIABLES
+ **********************/
+
+static uint16_t step_cnt = 0;
+
+/**********************
+ * MACROS
+ **********************/
+
+/**********************
+ * GLOBAL FUNCTIONS
+ **********************/
+
+int16_t imu_get_steps(void)
+{
+ return step_cnt++;
+}
+
+/**********************
+ * STATIC FUNCTIONS
+ **********************/
diff --git a/bandx/hal/imu.h b/bandx/hal/imu.h
new file mode 100644
index 0000000000000000000000000000000000000000..177775e11ea3c02c79252fd02ddee0eb10548576
--- /dev/null
+++ b/bandx/hal/imu.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2020 Xiaomi Corporation
+ *
+ * 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.
+ */
+#ifndef __IMU_H
+#define __IMU_H
+
+/*********************
+ * INCLUDES
+ *********************/
+
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*********************
+ * DEFINES
+ *********************/
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+/**********************
+ * GLOBAL PROTOTYPES
+ **********************/
+
+/**
+ * Retrieve the number of steps recorded by the IMU.
+ * @return The number of steps as an integer value.
+ */
+int16_t imu_get_steps(void);
+
+/**********************
+ * MACROS
+ **********************/
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/bandx/hal/particle_sensor.c b/bandx/hal/particle_sensor.c
new file mode 100755
index 0000000000000000000000000000000000000000..7865e4f72b04865db0c45f2ef829558b227c6922
--- /dev/null
+++ b/bandx/hal/particle_sensor.c
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2020 Xiaomi Corporation
+ *
+ * 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.
+ */
+#include "particle_sensor.h"
+
+#include
+#include
+
+/*********************
+ * DEFINES
+ *********************/
+
+#define HR_BEATS_MAX 1000
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+/**********************
+ * STATIC PROTOTYPES
+ **********************/
+
+static void particle_sensor_update_range(float beats);
+
+/**********************
+ * STATIC VARIABLES
+ **********************/
+
+static int hr_last_beats = 600;
+static float hr_beats_min = HR_BEATS_MAX;
+static float hr_beats_max = 0;
+
+/**********************
+ * MACROS
+ **********************/
+
+/**********************
+ * GLOBAL FUNCTIONS
+ **********************/
+
+float particle_sensor_get_beats(void)
+{
+ float retval;
+ int breats = ((uint16_t)rand() % 600 + 400);
+ if (breats > hr_last_beats) {
+ hr_last_beats++;
+ } else if (breats < hr_last_beats) {
+ hr_last_beats--;
+ }
+ retval = hr_last_beats / 10.0f;
+
+ particle_sensor_update_range(retval);
+
+ return retval;
+}
+
+bool particle_sensor_get_beats_range(float* min, float* max)
+{
+ if (hr_beats_min > HR_BEATS_MAX - 1 || hr_beats_max < 1) {
+ return false;
+ }
+
+ *min = hr_beats_min;
+ *max = hr_beats_max;
+ return true;
+}
+
+void particle_sensor_reset_beats_range(void)
+{
+ hr_beats_min = HR_BEATS_MAX;
+ hr_beats_max = 0;
+}
+
+/**********************
+ * STATIC FUNCTIONS
+ **********************/
+
+static void particle_sensor_update_range(float beats)
+{
+ if (beats < 1) {
+ return;
+ }
+
+ if (beats < hr_beats_min) {
+ hr_beats_min = beats;
+ }
+ if (beats > hr_beats_max) {
+ hr_beats_max = beats;
+ }
+}
diff --git a/bandx/hal/particle_sensor.h b/bandx/hal/particle_sensor.h
new file mode 100755
index 0000000000000000000000000000000000000000..e92b3dc5e87d80f83ed4d27723a06bb216744690
--- /dev/null
+++ b/bandx/hal/particle_sensor.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2020 Xiaomi Corporation
+ *
+ * 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.
+ */
+#ifndef __PARTICLE_SENSOR_H
+#define __PARTICLE_SENSOR_H
+
+/*********************
+ * INCLUDES
+ *********************/
+
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*********************
+ * DEFINES
+ *********************/
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+/**********************
+ * GLOBAL PROTOTYPES
+ **********************/
+
+/**
+ * Get the current heartbeats detected by the particle sensor.
+ * @return The current number of beats as a float value.
+ */
+float particle_sensor_get_beats(void);
+
+/**
+ * Get the range of heartbeats detected by the particle sensor.
+ * @param min Pointer to a float to store the minimum beat value.
+ * @param max Pointer to a float to store the maximum beat value.
+ * @return Return true if the operation was successful, otherwise return false.
+ */
+bool particle_sensor_get_beats_range(float* min, float* max);
+
+/**
+ * Reset the beats range for the particle sensor.
+ */
+void particle_sensor_reset_beats_range(void);
+
+/**********************
+ * MACROS
+ **********************/
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/bandx/hal/power.c b/bandx/hal/power.c
new file mode 100755
index 0000000000000000000000000000000000000000..1d358f114418cd01d53cd80ab093c9711161fb81
--- /dev/null
+++ b/bandx/hal/power.c
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2020 Xiaomi Corporation
+ *
+ * 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.
+ */
+
+/*********************
+ * INCLUDES
+ *********************/
+
+#include "power.h"
+
+/*********************
+ * DEFINES
+ *********************/
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+/**********************
+ * STATIC PROTOTYPES
+ **********************/
+
+/**********************
+ * STATIC VARIABLES
+ **********************/
+
+/**********************
+ * MACROS
+ **********************/
+
+/**********************
+ * GLOBAL FUNCTIONS
+ **********************/
+
+uint8_t power_get_battery_usage(void)
+{
+ return 80;
+}
+
+bool power_get_battery_is_charging(void)
+{
+ return false;
+}
+
+/**********************
+ * STATIC FUNCTIONS
+ **********************/
diff --git a/bandx/hal/power.h b/bandx/hal/power.h
new file mode 100755
index 0000000000000000000000000000000000000000..34393808b5fcf0640c0cb6632bba87ff75163d3e
--- /dev/null
+++ b/bandx/hal/power.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2020 Xiaomi Corporation
+ *
+ * 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.
+ */
+#ifndef __POWER_H
+#define __POWER_H
+
+/*********************
+ * INCLUDES
+ *********************/
+
+#include
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*********************
+ * DEFINES
+ *********************/
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+/**********************
+ * GLOBAL PROTOTYPES
+ **********************/
+
+/**
+ * Get the current battery usage percentage.
+ * @return The current battery usage as an unsigned integer value.
+ */
+uint8_t power_get_battery_usage(void);
+
+/**
+ * Check if the battery is currently charging.
+ * @return Return true if the battery is charging, otherwise return false.
+ */
+bool power_get_battery_is_charging(void);
+
+/**********************
+ * MACROS
+ **********************/
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/bandx/page/_template.c b/bandx/page/_template.c
new file mode 100644
index 0000000000000000000000000000000000000000..66da47ba33ffda1b8fd3890141603dbefd9b6871
--- /dev/null
+++ b/bandx/page/_template.c
@@ -0,0 +1,100 @@
+/**
+ * @file _template.c
+ *
+ */
+
+/*********************
+ * INCLUDES
+ *********************/
+#include "page.h"
+
+/**********************
+ * TYPEDEFS
+ **********************/
+typedef struct {
+ lv_fragment_t base;
+ lv_obj_t* label;
+} page_ctx_t;
+
+/**********************
+ * STATIC PROTOTYPES
+ **********************/
+
+/**********************
+ * STATIC VARIABLES
+ **********************/
+
+/**********************
+ * MACROS
+ **********************/
+
+/**********************
+ * GLOBAL FUNCTIONS
+ **********************/
+
+/**********************
+ * STATIC FUNCTIONS
+ **********************/
+
+static void on_page_construct(lv_fragment_t* self, void* args)
+{
+ LV_LOG_USER("self: %p args: %p", self, args);
+}
+
+static void on_page_destruct(lv_fragment_t* self)
+{
+ LV_LOG_USER("self: %p", self);
+}
+
+static void on_page_attached(lv_fragment_t* self)
+{
+ LV_LOG_USER("self: %p", self);
+}
+
+static void on_page_detached(lv_fragment_t* self)
+{
+ LV_LOG_USER("self: %p", self);
+}
+
+static lv_obj_t* on_page_create(lv_fragment_t* self, lv_obj_t* container)
+{
+ LV_LOG_USER("self: %p container: %p", self, container);
+
+ lv_obj_t* root = lv_obj_create(container);
+ lv_obj_remove_style_all(root);
+ lv_obj_add_style(root, resource_get_style("root_def"), 0);
+ lv_obj_set_style_bg_color(root, lv_palette_main(LV_PALETTE_BLUE), 0);
+ return root;
+}
+
+static void on_page_created(lv_fragment_t* self, lv_obj_t* obj)
+{
+ LV_LOG_USER("self: %p obj: %p", self, obj);
+
+ lv_obj_t* label = lv_label_create(obj);
+ lv_obj_set_style_text_font(label, resource_get_font(BANDX_REGULAR_FONT "_38"), 0);
+ lv_label_set_text(label, "SPORT");
+ lv_obj_center(label);
+
+ lv_obj_t* img = lv_img_create(obj);
+ lv_img_set_src(img, resource_get_img("sport"));
+ lv_obj_align(img, LV_ALIGN_TOP_MID, 0, 20);
+}
+
+static void on_page_will_delete(lv_fragment_t* self, lv_obj_t* obj)
+{
+ LV_LOG_USER("self: %p obj: %p", self, obj);
+}
+
+static void on_page_deleted(lv_fragment_t* self, lv_obj_t* obj)
+{
+ LV_LOG_USER("self: %p obj: %p", self, obj);
+}
+
+static bool on_page_event(lv_fragment_t* self, int code, void* user_data)
+{
+ LV_LOG_USER("self: %p code: %d user_data: %p", self, code, user_data);
+ return false;
+}
+
+PAGE_CLASS_DEF(template);
diff --git a/bandx/page/dialplate.c b/bandx/page/dialplate.c
new file mode 100644
index 0000000000000000000000000000000000000000..d7b0735a920c016ecd94a70d6a3c359a0190cd43
--- /dev/null
+++ b/bandx/page/dialplate.c
@@ -0,0 +1,379 @@
+/**
+ * @file dialplate.c
+ *
+ */
+
+/*********************
+ * INCLUDES
+ *********************/
+#include "page.h"
+
+#include "../hal/hal.h"
+
+/*********************
+ * DEFINES
+ *********************/
+
+#define EVENT_CNT 1
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+typedef struct {
+ uint32_t color;
+ lv_coord_t y;
+ lv_coord_t bar_width;
+ lv_coord_t arc_radius;
+ const void* img_src;
+ lv_obj_t* label;
+ lv_obj_t* bar;
+ lv_obj_t* arc;
+} color_bar_t;
+
+enum color_bar_index {
+ COLOR_BAR_WEATHER,
+ COLOR_BAR_STEP,
+ COLOR_BAR_HEART,
+ COLOR_BAR_MAX
+};
+
+typedef struct {
+ lv_fragment_t base;
+ lv_span_t* span_date;
+ lv_span_t* span_month;
+ lv_span_t* span_week;
+ lv_obj_t* bar_batt;
+ lv_obj_t* label_time_hour;
+ lv_obj_t* label_time_min;
+ lv_timer_t* timer_label_time_update;
+ lv_timer_t* timer_color_bar_update;
+ clock_value_t clock_value;
+ color_bar_t color_bar_grp[COLOR_BAR_MAX];
+ lv_auto_event_data_t event_data[EVENT_CNT];
+ lv_auto_event_t* auto_event;
+} page_ctx_t;
+
+/**********************
+ * STATIC PROTOTYPES
+ **********************/
+
+/**********************
+ * STATIC VARIABLES
+ **********************/
+
+/**********************
+ * MACROS
+ **********************/
+
+/**********************
+ * GLOBAL FUNCTIONS
+ **********************/
+
+/**********************
+ * STATIC FUNCTIONS
+ **********************/
+static lv_obj_t* obj_batt_create(lv_obj_t* par)
+{
+ lv_obj_t* bar = lv_obj_create(par);
+ lv_obj_set_style_bg_color(bar, lv_color_white(), LV_PART_MAIN);
+ lv_obj_clear_flag(bar, LV_OBJ_FLAG_SCROLLABLE);
+ lv_obj_set_style_border_width(bar, 0, LV_PART_MAIN);
+ lv_obj_set_style_radius(bar, 0, LV_PART_MAIN);
+ lv_obj_set_size(bar, lv_obj_get_self_width(par) / 2, lv_obj_get_self_height(par) - 2);
+ lv_obj_align(bar, LV_ALIGN_LEFT_MID, 2, 0);
+
+ return bar;
+}
+
+static void topbar_create(lv_obj_t* par)
+{
+ page_ctx_t* ctx = (page_ctx_t*)lv_obj_get_user_data(par);
+
+ lv_obj_t* topbar = lv_obj_create(par);
+ lv_obj_set_size(topbar, LV_PCT(100), 25);
+ lv_obj_set_style_bg_color(topbar, lv_color_black(), LV_PART_MAIN);
+ lv_obj_set_style_border_width(topbar, 0, LV_PART_MAIN);
+ lv_obj_clear_flag(topbar, LV_OBJ_FLAG_SCROLLABLE);
+ lv_obj_align(topbar, LV_ALIGN_TOP_MID, 0, 0);
+
+ lv_obj_t* img1 = lv_img_create(topbar);
+ lv_img_set_src(img1, resource_get_img("icon_bluetooth"));
+ lv_obj_align(img1, LV_ALIGN_LEFT_MID, 20, 0);
+
+ lv_obj_t* img2 = lv_img_create(topbar);
+ lv_img_set_src(img2, resource_get_img("icon_battery"));
+ lv_obj_set_style_pad_all(topbar, 0, LV_PART_MAIN);
+ lv_obj_align(img2, LV_ALIGN_RIGHT_MID, -20, 0);
+
+ lv_obj_t* bar = obj_batt_create(img2);
+ ctx->bar_batt = bar;
+}
+
+static void label_date_create(lv_obj_t* par)
+{
+ page_ctx_t* ctx = (page_ctx_t*)lv_obj_get_user_data(par);
+
+ lv_obj_t* spangroup = lv_spangroup_create(par);
+ lv_obj_set_style_text_font(spangroup, resource_get_font(BANDX_BOLD_FONT "_23"), LV_PART_MAIN);
+ lv_obj_set_style_text_color(spangroup, lv_color_white(), LV_PART_MAIN);
+ lv_obj_align(spangroup, LV_ALIGN_TOP_MID, 0, 25);
+
+ lv_span_t* span1 = lv_spangroup_new_span(spangroup);
+ lv_span_t* span2 = lv_spangroup_new_span(spangroup);
+ lv_span_t* span3 = lv_spangroup_new_span(spangroup);
+ lv_style_set_text_color(&span2->style, lv_color_hex(0xF15A24));
+
+ ctx->span_month = span1;
+ ctx->span_date = span2;
+ ctx->span_week = span3;
+}
+
+static void label_time_create(lv_obj_t* par)
+{
+ page_ctx_t* ctx = (page_ctx_t*)lv_obj_get_user_data(par);
+
+ lv_obj_t* label = lv_label_create(par);
+ lv_obj_set_style_text_font(label, resource_get_font(BANDX_BOLD_FONT "_110"), LV_PART_MAIN);
+ lv_obj_set_style_text_color(label, lv_color_white(), LV_PART_MAIN);
+ lv_obj_align(label, LV_ALIGN_TOP_MID, 0, 33);
+ ctx->label_time_hour = label;
+
+ lv_obj_t* img = lv_img_create(par);
+ lv_img_set_src(img, resource_get_img("num_shadow"));
+ lv_obj_set_pos(img, 16, 136);
+
+ img = lv_img_create(par);
+ lv_img_set_src(img, resource_get_img("num_shadow"));
+ lv_obj_set_pos(img, 99, 136);
+
+ label = lv_label_create(par);
+ lv_obj_set_style_text_font(label, resource_get_font(BANDX_BOLD_FONT "_110"), LV_PART_MAIN);
+ lv_obj_set_style_text_color(label, lv_color_hex(0xF15A24), LV_PART_MAIN);
+ lv_obj_align(label, LV_ALIGN_TOP_MID, 0, 115);
+ ctx->label_time_min = label;
+}
+
+static void label_time_update(lv_timer_t* timer)
+{
+ page_ctx_t* ctx = lv_timer_get_user_data(timer);
+ clock_get_value(&ctx->clock_value);
+
+ char month_buf[32];
+ char date_buf[32];
+ const char* week_str[] = { "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" };
+ int week_index = ctx->clock_value.week % ARRAY_SIZE(week_str);
+
+ lv_snprintf(month_buf, sizeof(month_buf), "%02d-", ctx->clock_value.month);
+ lv_snprintf(date_buf, sizeof(date_buf), "%02d ", ctx->clock_value.date);
+ lv_span_set_text(ctx->span_month, month_buf);
+ lv_span_set_text(ctx->span_date, date_buf);
+ lv_span_set_text(ctx->span_week, week_str[week_index]);
+
+ lv_label_set_text_fmt(ctx->label_time_hour, "%02d", ctx->clock_value.hour);
+ lv_label_set_text_fmt(ctx->label_time_min, "%02d", ctx->clock_value.min);
+}
+
+static void color_bar_create(lv_obj_t* par, color_bar_t* color_bar, int len)
+{
+ for (int i = 0; i < len; i++) {
+ lv_color_t color = lv_color_hex(color_bar[i].color);
+ lv_coord_t y_pos = color_bar[i].y;
+ lv_coord_t bar_width = color_bar[i].bar_width;
+ lv_coord_t arc_size = color_bar[i].arc_radius * 2;
+
+ lv_obj_t* bar = lv_bar_create(par);
+ lv_bar_set_value(bar, 100, LV_ANIM_OFF);
+ lv_obj_set_style_bg_color(bar, color, LV_PART_INDICATOR);
+ lv_obj_set_style_bg_color(bar, color, LV_PART_MAIN);
+ lv_obj_set_size(bar, bar_width, 32);
+ lv_obj_set_pos(bar, -12, y_pos);
+
+ lv_obj_t* arc = lv_arc_create(par);
+ lv_obj_set_style_arc_color(arc, color, LV_PART_INDICATOR);
+ lv_obj_set_style_arc_width(
+ arc,
+ lv_obj_get_style_height(bar, 0),
+ LV_PART_INDICATOR);
+ lv_obj_set_style_bg_color(arc, color, LV_PART_KNOB);
+ lv_obj_set_style_pad_all(arc, 0, LV_PART_KNOB);
+ lv_obj_set_style_bg_opa(arc, LV_OPA_TRANSP, LV_PART_MAIN);
+ lv_obj_set_style_border_width(arc, 0, LV_PART_MAIN);
+ lv_arc_set_angles(arc, 270, 360);
+ lv_arc_set_bg_angles(arc, 270, 360);
+ lv_obj_set_size(arc, arc_size, arc_size);
+ lv_obj_clear_flag(arc, LV_OBJ_FLAG_CLICKABLE);
+
+ lv_obj_align_to(arc, bar, LV_ALIGN_OUT_RIGHT_TOP, -arc_size / 2 - 10, 0);
+
+ lv_obj_t* img = lv_img_create(bar);
+ lv_img_set_src(img, resource_get_img(color_bar[i].img_src));
+ lv_obj_align(img, LV_ALIGN_LEFT_MID, 20, 0);
+
+ if (i == COLOR_BAR_WEATHER) {
+ lv_obj_t* img_deg = lv_img_create(bar);
+ lv_img_set_src(img_deg, resource_get_img("centigrade"));
+ lv_obj_align(img_deg, LV_ALIGN_RIGHT_MID, 0, 0);
+ }
+
+ lv_obj_t* label = lv_label_create(bar);
+ lv_obj_set_style_text_font(
+ label,
+ resource_get_font(BANDX_BOLD_FONT "_28"),
+ LV_PART_MAIN);
+ lv_obj_set_style_text_color(
+ label,
+ lv_color_white(),
+ LV_PART_MAIN);
+ lv_obj_align_to(label, img, LV_ALIGN_OUT_RIGHT_MID, 10, 0);
+
+ lv_obj_move_foreground(bar);
+
+ color_bar[i].bar = bar;
+ color_bar[i].label = label;
+ color_bar[i].arc = arc;
+ }
+}
+
+static void color_bar_update(lv_timer_t* timer)
+{
+ page_ctx_t* ctx = lv_timer_get_user_data(timer);
+
+ lv_label_set_text_fmt(ctx->color_bar_grp[COLOR_BAR_WEATHER].label, "15");
+ lv_label_set_text_fmt(ctx->color_bar_grp[COLOR_BAR_STEP].label, "%d", imu_get_steps());
+ lv_label_set_text_fmt(
+ ctx->color_bar_grp[COLOR_BAR_HEART].label,
+ "%d",
+ (int)particle_sensor_get_beats());
+}
+
+static void auto_event_create(page_ctx_t* ctx)
+{
+ lv_auto_event_data_t ae_grp[EVENT_CNT] = {
+ { &ctx->base.obj, LV_EVENT_LEAVE, 2000 },
+ };
+ for (int i = 0; i < EVENT_CNT; i++) {
+ ctx->event_data[i] = ae_grp[i];
+ }
+ AUTO_EVENT_CREATE(ctx->auto_event, ctx->event_data);
+}
+
+static void on_root_event(lv_event_t* e)
+{
+ lv_obj_t* root = lv_event_get_current_target_obj(e);
+ lv_event_code_t code = lv_event_get_code(e);
+ page_ctx_t* ctx = lv_obj_get_user_data(root);
+
+ if (code == LV_EVENT_GESTURE) {
+ lv_dir_t dir = lv_indev_get_gesture_dir(lv_indev_get_act());
+ if (dir == LV_DIR_LEFT) {
+ lv_obj_send_event(root, LV_EVENT_LEAVE, NULL);
+ }
+ } else if (code == LV_EVENT_LEAVE) {
+ page_push(&ctx->base, "launcher", NULL);
+ page_set_last_page_dialplate(true);
+ }
+}
+
+static void on_page_construct(lv_fragment_t* self, void* args)
+{
+ LV_LOG_INFO("self: %p args: %p", self, args);
+
+ page_ctx_t* ctx = (page_ctx_t*)self;
+
+ color_bar_t color_bar_grp[COLOR_BAR_MAX] = {
+ { .color = 0xF7931E,
+ .y = 245,
+ .bar_width = 130,
+ .arc_radius = 120,
+ .img_src = "weather" },
+
+ { .color = 0x3FA9F5,
+ .y = 285,
+ .bar_width = 130,
+ .arc_radius = 80,
+ .img_src = "step" },
+
+ { .color = 0xED1C24,
+ .y = 325,
+ .bar_width = 130,
+ .arc_radius = 40,
+ .img_src = "heart" },
+ };
+
+ for (int i = 0; i < COLOR_BAR_MAX; i++) {
+ ctx->color_bar_grp[i] = color_bar_grp[i];
+ }
+}
+
+static void on_page_destruct(lv_fragment_t* self)
+{
+ LV_LOG_INFO("self: %p", self);
+}
+
+static void on_page_attached(lv_fragment_t* self)
+{
+ LV_LOG_INFO("self: %p", self);
+}
+
+static void on_page_detached(lv_fragment_t* self)
+{
+ LV_LOG_INFO("self: %p", self);
+}
+
+static lv_obj_t* on_page_create(lv_fragment_t* self, lv_obj_t* container)
+{
+ LV_LOG_INFO("self: %p container: %p", self, container);
+
+ lv_obj_t* root = lv_obj_create(container);
+ lv_obj_remove_style_all(root);
+ lv_obj_add_style(root, resource_get_style("root_def"), 0);
+ lv_obj_add_event(root, on_root_event, LV_EVENT_ALL, NULL);
+ lv_obj_clear_flag(root, LV_OBJ_FLAG_GESTURE_BUBBLE);
+ lv_obj_set_user_data(root, self);
+ return root;
+}
+
+static void on_page_created(lv_fragment_t* self, lv_obj_t* obj)
+{
+ LV_LOG_INFO("self: %p obj: %p", self, obj);
+
+ page_ctx_t* ctx = (page_ctx_t*)self;
+ lv_obj_clear_flag(obj, LV_OBJ_FLAG_SCROLLABLE);
+ clock_get_value(&ctx->clock_value);
+
+ topbar_create(obj);
+ label_date_create(obj);
+ label_time_create(obj);
+ color_bar_create(obj, ctx->color_bar_grp, COLOR_BAR_MAX);
+
+ ctx->timer_label_time_update = lv_timer_create(label_time_update, 100, ctx);
+ label_time_update(ctx->timer_label_time_update);
+
+ ctx->timer_color_bar_update = lv_timer_create(color_bar_update, 1000, ctx);
+ color_bar_update(ctx->timer_color_bar_update);
+ auto_event_create(ctx);
+}
+
+static void on_page_will_delete(lv_fragment_t* self, lv_obj_t* obj)
+{
+ LV_LOG_INFO("self: %p obj: %p", self, obj);
+
+ page_ctx_t* ctx = (page_ctx_t*)self;
+ lv_timer_del(ctx->timer_color_bar_update);
+ lv_timer_del(ctx->timer_label_time_update);
+ AUTO_EVENT_DELETE(ctx->auto_event);
+}
+
+static void on_page_deleted(lv_fragment_t* self, lv_obj_t* obj)
+{
+ LV_LOG_INFO("self: %p obj: %p", self, obj);
+}
+
+static bool on_page_event(lv_fragment_t* self, int code, void* user_data)
+{
+ LV_LOG_INFO("self: %p code: %d user_data: %p", self, code, user_data);
+ return false;
+}
+
+PAGE_CLASS_DEF(dialplate);
diff --git a/bandx/page/flashlight.c b/bandx/page/flashlight.c
new file mode 100755
index 0000000000000000000000000000000000000000..a08902a81ff58d93bafb041ffd41ddc6c32dca2c
--- /dev/null
+++ b/bandx/page/flashlight.c
@@ -0,0 +1,159 @@
+/**
+ * @file flashlight.c
+ *
+ */
+
+/*********************
+ * INCLUDES
+ *********************/
+#include "page.h"
+
+#include "../utils/lv_obj_ext_func.h"
+
+/*********************
+ * DEFINES
+ *********************/
+
+#define EVENT_CNT 3
+
+/**********************
+ * TYPEDEFS
+ **********************/
+typedef struct {
+ lv_fragment_t base;
+ lv_auto_event_data_t event_data[EVENT_CNT];
+ lv_auto_event_t* auto_event;
+} page_ctx_t;
+
+/**********************
+ * STATIC PROTOTYPES
+ **********************/
+
+/**********************
+ * STATIC VARIABLES
+ **********************/
+
+/**********************
+ * MACROS
+ **********************/
+
+/**********************
+ * GLOBAL FUNCTIONS
+ **********************/
+
+/**********************
+ * STATIC FUNCTIONS
+ **********************/
+
+static void auto_event_create(page_ctx_t* ctx)
+{
+ lv_auto_event_data_t ae_grp[EVENT_CNT] = {
+ { &ctx->base.obj, LV_EVENT_REFRESH, 1000 },
+ { &ctx->base.obj, LV_EVENT_REFRESH, 1000 },
+ { &ctx->base.obj, LV_EVENT_LEAVE, 1000 },
+ };
+ for (int i = 0; i < EVENT_CNT; i++) {
+ ctx->event_data[i] = ae_grp[i];
+ }
+ AUTO_EVENT_CREATE(ctx->auto_event, ctx->event_data);
+}
+
+static void on_root_event(lv_event_t* e)
+{
+ lv_obj_t* root = lv_event_get_current_target_obj(e);
+ lv_event_code_t code = lv_event_get_code(e);
+ page_ctx_t* ctx = lv_obj_get_user_data(root);
+
+ switch (code) {
+ case LV_EVENT_GESTURE: {
+ lv_dir_t dir = lv_indev_get_gesture_dir(lv_indev_get_act());
+ if (dir == LV_DIR_RIGHT) {
+ lv_obj_send_event(root, LV_EVENT_LEAVE, NULL);
+ }
+ } break;
+ case LV_EVENT_REFRESH: {
+ if (lv_obj_has_state(root, LV_STATE_CHECKED)) {
+ lv_obj_remove_state(root, LV_STATE_CHECKED);
+ } else {
+ lv_obj_add_state(root, LV_STATE_CHECKED);
+ }
+ } break;
+ case LV_EVENT_LEAVE: {
+ page_pop(&ctx->base);
+ } break;
+ default:
+ break;
+ }
+}
+
+static void on_page_construct(lv_fragment_t* self, void* args)
+{
+ LV_LOG_INFO("self: %p args: %p", self, args);
+}
+
+static void on_page_destruct(lv_fragment_t* self)
+{
+ LV_LOG_INFO("self: %p", self);
+}
+
+static void on_page_attached(lv_fragment_t* self)
+{
+ LV_LOG_INFO("self: %p", self);
+}
+
+static void on_page_detached(lv_fragment_t* self)
+{
+ LV_LOG_INFO("self: %p", self);
+}
+
+static lv_obj_t* on_page_create(lv_fragment_t* self, lv_obj_t* container)
+{
+ LV_LOG_INFO("self: %p container: %p", self, container);
+
+ lv_obj_t* root = lv_obj_create(container);
+ lv_obj_remove_style_all(root);
+ lv_obj_add_style(root, resource_get_style("root_def"), 0);
+ lv_obj_add_event(root, on_root_event, LV_EVENT_ALL, NULL);
+ lv_obj_clear_flag(root, LV_OBJ_FLAG_GESTURE_BUBBLE);
+ lv_obj_set_user_data(root, self);
+ return root;
+}
+
+static void on_page_created(lv_fragment_t* self, lv_obj_t* obj)
+{
+ LV_LOG_INFO("self: %p obj: %p", self, obj);
+
+ page_ctx_t* ctx = (page_ctx_t*)self;
+
+ lv_obj_add_flag(obj, LV_OBJ_FLAG_CHECKABLE);
+
+ static const lv_style_prop_t props[] = { LV_STYLE_BG_COLOR, LV_STYLE_PROP_INV };
+ static lv_style_transition_dsc_t dsc;
+ lv_style_transition_dsc_init(&dsc, props, lv_anim_path_ease_out, 200, 0, NULL);
+
+ lv_obj_set_style_bg_color(obj, lv_color_white(), LV_STATE_DEFAULT);
+ lv_obj_set_style_bg_color(obj, lv_color_mix(lv_color_white(), lv_color_black(), 200), LV_STATE_CHECKED);
+ lv_obj_set_style_transition(obj, &dsc, LV_STATE_DEFAULT);
+ lv_obj_set_style_transition(obj, &dsc, LV_STATE_CHECKED);
+ auto_event_create(ctx);
+}
+
+static void on_page_will_delete(lv_fragment_t* self, lv_obj_t* obj)
+{
+ LV_LOG_INFO("self: %p obj: %p", self, obj);
+ page_ctx_t* ctx = (page_ctx_t*)self;
+ AUTO_EVENT_DELETE(ctx->auto_event);
+}
+
+static void on_page_deleted(lv_fragment_t* self, lv_obj_t* obj)
+{
+ LV_LOG_INFO("self: %p obj: %p", self, obj);
+}
+
+static bool on_page_event(lv_fragment_t* self, int code, void* user_data)
+{
+ LV_LOG_INFO("self: %p code: %d user_data: %p", self, code, user_data);
+ return false;
+}
+
+PAGE_CLASS_DEF(flashlight);
diff --git a/bandx/page/heart_rate.c b/bandx/page/heart_rate.c
new file mode 100644
index 0000000000000000000000000000000000000000..2ccc4ceeb43e478cbff539dacba4561e9a0e6c37
--- /dev/null
+++ b/bandx/page/heart_rate.c
@@ -0,0 +1,481 @@
+/**
+ * @file heart_rate.c
+ *
+ */
+
+/*********************
+ * INCLUDES
+ *********************/
+#include "page.h"
+
+#include "../hal/hal.h"
+#include "../utils/lv_obj_ext_func.h"
+
+/*********************
+ * DEFINES
+ *********************/
+
+#define COLOR_BAR_CNT 4
+#define EVENT_CNT 4
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+typedef struct {
+ const char* text;
+ int value;
+ uint32_t color;
+ lv_coord_t y;
+} color_bar_t;
+
+typedef struct {
+ lv_fragment_t base;
+ lv_obj_t* img_down;
+ lv_obj_t* img_up;
+ lv_obj_t* btn_rst;
+ lv_obj_t* img_heart;
+ lv_obj_t* chart_hr;
+ lv_obj_t* cont_hr;
+ lv_obj_t* label_hr_current;
+ lv_obj_t* label_hr_record_range;
+ lv_obj_t* label_hr_record_time;
+ lv_chart_series_t* chart_hr_ser1;
+ lv_timer_t* timer_label_hr_update;
+ lv_style_t color_bar_style;
+ lv_style_t label_hr_style1;
+ lv_style_t label_hr_style2;
+ color_bar_t color_bar_grp[COLOR_BAR_CNT];
+ lv_auto_event_data_t event_data[EVENT_CNT];
+ lv_auto_event_t* auto_event;
+} page_ctx_t;
+
+/**********************
+ * STATIC PROTOTYPES
+ **********************/
+
+/**********************
+ * STATIC VARIABLES
+ **********************/
+
+/**********************
+ * MACROS
+ **********************/
+
+#define CONT_HR_MOVE_DOWN(down, cont_hr, img_heart) \
+ do { \
+ LV_OBJ_ADD_ANIM( \
+ cont_hr, \
+ y, \
+ (down) ? -PAGE_VER_RES : 0, LV_ANIM_TIME_DEFAULT); \
+ img_heart_anim_enable(img_heart, !down); \
+ } while (0)
+
+/**********************
+ * GLOBAL FUNCTIONS
+ **********************/
+
+/**********************
+ * STATIC FUNCTIONS
+ **********************/
+
+static void label_hr_current_update(lv_timer_t* timer)
+{
+ page_ctx_t* ctx = lv_timer_get_user_data(timer);
+
+ float hr_min, hr_max;
+ int hr_beats = (int)particle_sensor_get_beats();
+ if (!particle_sensor_get_beats_range(&hr_min, &hr_max)) {
+ return;
+ }
+
+ lv_label_set_text_fmt(ctx->label_hr_current, "%d", hr_beats);
+ lv_label_set_text_fmt(ctx->label_hr_record_range, "%d-%d", (int)hr_min, (int)hr_max);
+ lv_chart_set_next_value(ctx->chart_hr, ctx->chart_hr_ser1, hr_beats);
+}
+
+static void img_heart_anim_enable(lv_obj_t* img_heart, bool en)
+{
+ if (en) {
+ lv_anim_t a;
+ lv_anim_init(&a);
+ lv_anim_set_var(&a, img_heart);
+ lv_anim_set_exec_cb(&a, (lv_anim_exec_xcb_t)lv_img_set_zoom);
+ lv_anim_set_values(&a, 256, (int)(256 * 0.7f));
+ lv_anim_set_time(&a, 300);
+ lv_anim_set_playback_time(&a, 300);
+ lv_anim_set_repeat_count(&a, LV_ANIM_REPEAT_INFINITE);
+ lv_anim_set_repeat_delay(&a, 800);
+ lv_anim_set_path_cb(&a, lv_anim_path_overshoot);
+ lv_anim_start(&a);
+ } else {
+ lv_anim_del(img_heart, (lv_anim_exec_xcb_t)lv_img_set_zoom);
+ }
+}
+
+static void cont_hr_create(lv_obj_t* par)
+{
+ page_ctx_t* ctx = (page_ctx_t*)lv_obj_get_user_data(par);
+
+ lv_obj_t* obj = lv_obj_create(par);
+ lv_obj_remove_style_all(obj);
+ lv_obj_set_size(obj, PAGE_HOR_RES, PAGE_VER_RES * 2);
+ lv_obj_align(obj, LV_ALIGN_TOP_MID, 0, 0);
+ lv_obj_set_style_bg_color(obj, lv_color_black(), LV_PART_MAIN);
+ lv_obj_set_user_data(obj, ctx);
+
+ ctx->cont_hr = obj;
+}
+
+static void label_hr_current_create(lv_obj_t* par)
+{
+ page_ctx_t* ctx = (page_ctx_t*)lv_obj_get_user_data(par);
+
+ lv_obj_t* img = lv_img_create(par);
+ lv_img_set_src(img, resource_get_img("heart_color"));
+ lv_obj_align(img, LV_ALIGN_TOP_MID, 0, 28);
+
+ lv_obj_t* label1 = lv_label_create(par);
+ lv_obj_set_style_text_font(label1, resource_get_font(BANDX_REGULAR_FONT "_72"), LV_PART_MAIN);
+ lv_label_set_text(label1, "--");
+ lv_obj_set_style_text_color(label1, lv_color_white(), LV_PART_MAIN);
+ lv_obj_set_style_text_align(label1, LV_ALIGN_CENTER, LV_PART_MAIN);
+ lv_obj_align(label1, LV_ALIGN_TOP_MID, 0, 95);
+
+ lv_obj_t* label2 = lv_label_create(par);
+ lv_obj_set_style_text_font(
+ label2,
+ resource_get_font(BANDX_REGULAR_FONT "_20"),
+ LV_PART_MAIN);
+ lv_obj_set_style_text_color(label2, lv_palette_main(LV_PALETTE_GREY), LV_PART_MAIN);
+ lv_label_set_text(label2, "bpm");
+ lv_obj_align_to(label2, label1, LV_ALIGN_OUT_RIGHT_BOTTOM, 10, -20);
+
+ ctx->label_hr_current = label1;
+ ctx->img_heart = img;
+}
+
+static void label_hr_record_create(lv_obj_t* par)
+{
+ page_ctx_t* ctx = (page_ctx_t*)lv_obj_get_user_data(par);
+
+ lv_obj_t* label1 = lv_label_create(par);
+ lv_obj_add_style(label1, &ctx->label_hr_style1, LV_PART_MAIN);
+ lv_label_set_text(label1, "Recent updates");
+ lv_obj_align(label1, LV_ALIGN_TOP_MID, 0, 165);
+
+ lv_obj_t* label2 = lv_label_create(par);
+ lv_obj_add_style(label2, &ctx->label_hr_style2, LV_PART_MAIN);
+ lv_label_set_text(label2, "--:--");
+ lv_obj_align(label2, LV_ALIGN_TOP_MID, 0, 192);
+
+ lv_obj_t* label3 = lv_label_create(par);
+ lv_obj_add_style(label3, &ctx->label_hr_style1, LV_PART_MAIN);
+ lv_label_set_text(label3, "Range");
+ lv_obj_align(label3, LV_ALIGN_TOP_MID, 0, 222);
+
+ lv_obj_t* label4 = lv_label_create(par);
+ lv_obj_add_style(label4, &ctx->label_hr_style2, LV_PART_MAIN);
+ lv_label_set_text(label4, "");
+ lv_obj_align(label4, LV_ALIGN_TOP_MID, 0, 246);
+
+ ctx->label_hr_record_time = label2;
+ ctx->label_hr_record_range = label4;
+}
+
+static void label_hr_record_time_update(lv_obj_t* label)
+{
+ clock_value_t clock_value;
+ clock_get_value(&clock_value);
+ lv_label_set_text_fmt(label, "%02d:%02d", clock_value.hour, clock_value.min);
+}
+
+static void btn_rst_event_handler(lv_event_t* event)
+{
+ lv_event_code_t code = lv_event_get_code(event);
+ lv_obj_t* obj = lv_event_get_current_target_obj(event);
+ page_ctx_t* ctx = (page_ctx_t*)lv_event_get_user_data(event);
+
+ if (code == LV_EVENT_CLICKED) {
+ particle_sensor_reset_beats_range();
+ label_hr_record_time_update(ctx->label_hr_record_time);
+
+ lv_obj_t* img = lv_obj_get_child(obj, 0);
+ lv_anim_t a;
+ lv_anim_init(&a);
+ lv_anim_set_var(&a, img);
+ lv_anim_set_exec_cb(&a, (lv_anim_exec_xcb_t)lv_img_set_angle);
+ lv_anim_set_values(&a, 0, 3600);
+ lv_anim_set_time(&a, 1000);
+ lv_anim_set_path_cb(&a, lv_anim_path_ease_out);
+ lv_anim_start(&a);
+ }
+}
+
+static void btn_rst_create(lv_obj_t* par)
+{
+ page_ctx_t* ctx = (page_ctx_t*)lv_obj_get_user_data(par);
+
+ lv_obj_t* btn = lv_btn_create(par);
+ lv_obj_set_size(btn, 149, 44);
+ lv_obj_align(btn, LV_ALIGN_TOP_MID, 0, 279);
+ lv_obj_add_event(btn, btn_rst_event_handler, LV_EVENT_ALL, ctx);
+ lv_obj_set_style_bg_color(
+ btn,
+ lv_color_make(0xFF, 0x54, 0x2A),
+ LV_STATE_DEFAULT);
+ lv_obj_set_style_bg_color(
+ btn,
+ lv_palette_main(LV_PALETTE_GREY),
+ LV_STATE_PRESSED);
+
+ lv_style_t* trans_style = resource_get_style("btn_trans");
+ lv_obj_add_style(btn, trans_style, LV_STATE_PRESSED);
+ lv_obj_add_style(btn, trans_style, LV_STATE_DEFAULT);
+ lv_obj_set_style_radius(btn, 8, LV_PART_MAIN);
+ lv_obj_set_style_border_color(btn, lv_color_black(), LV_PART_MAIN);
+ lv_obj_set_style_border_width(btn, 0, LV_PART_MAIN);
+ lv_obj_set_style_outline_width(btn, 0, LV_PART_MAIN);
+
+ lv_obj_t* img = lv_img_create(btn);
+ lv_img_set_src(img, resource_get_img("icon_reset"));
+ lv_obj_align_to(img, btn, LV_ALIGN_CENTER, 0, 0);
+
+ ctx->btn_rst = btn;
+}
+
+static void img_arrow_event_handler(lv_event_t* event)
+{
+ lv_event_code_t code = lv_event_get_code(event);
+ lv_obj_t* obj = lv_event_get_current_target_obj(event);
+ page_ctx_t* ctx = (page_ctx_t*)lv_event_get_user_data(event);
+
+ if (code == LV_EVENT_CLICKED) {
+ bool is_down = (lv_strcmp(lv_img_get_src(obj), resource_get_img("arrow_down")) == 0);
+ CONT_HR_MOVE_DOWN(is_down, ctx->cont_hr, ctx->img_heart);
+ }
+}
+
+static void img_arrow_create(lv_obj_t* par)
+{
+ page_ctx_t* ctx = (page_ctx_t*)lv_obj_get_user_data(par);
+
+ lv_obj_t* img1 = lv_img_create(par);
+ lv_img_set_src(img1, resource_get_img("arrow_down"));
+ lv_obj_align(img1, LV_ALIGN_TOP_MID, 0, 339);
+ lv_obj_add_event(img1, img_arrow_event_handler, LV_EVENT_ALL, ctx);
+ lv_obj_add_flag(img1, LV_OBJ_FLAG_CLICKABLE);
+ ctx->img_down = img1;
+
+ lv_obj_t* img2 = lv_img_create(par);
+ lv_img_set_src(img2, resource_get_img("arrow_up"));
+ lv_obj_align(img2, LV_ALIGN_TOP_MID, 0, PAGE_VER_RES + 12);
+ lv_obj_add_event(img2, img_arrow_event_handler, LV_EVENT_ALL, ctx);
+ lv_obj_add_flag(img2, LV_OBJ_FLAG_CLICKABLE);
+ ctx->img_up = img2;
+}
+
+static void color_bar_create(lv_obj_t* par, color_bar_t* color_bar, int len)
+{
+ page_ctx_t* ctx = (page_ctx_t*)lv_obj_get_user_data(par);
+
+ for (int i = 0; i < len; i++) {
+ lv_obj_t* bar = lv_bar_create(par);
+ lv_bar_set_value(bar, color_bar[i].value, LV_ANIM_OFF);
+ lv_obj_set_size(bar, 156, 10);
+ lv_obj_set_style_bg_color(
+ bar,
+ lv_color_hex(color_bar[i].color),
+ LV_PART_INDICATOR);
+ lv_obj_align(bar, LV_ALIGN_TOP_MID, 0, PAGE_VER_RES + color_bar[i].y);
+
+ lv_obj_t* label = lv_label_create(par);
+ lv_obj_add_style(label, &ctx->color_bar_style, LV_PART_MAIN);
+ lv_label_set_text_fmt(label, "%s %d%%", color_bar[i].text, color_bar[i].value);
+ lv_obj_align_to(label, bar, LV_ALIGN_OUT_TOP_LEFT, 0, -2);
+ }
+}
+
+static void chart_hr_create(lv_obj_t* par)
+{
+ page_ctx_t* ctx = (page_ctx_t*)lv_obj_get_user_data(par);
+
+ lv_obj_t* chart = lv_chart_create(par);
+ lv_obj_set_size(chart, 156, 87);
+ lv_obj_clear_flag(chart, LV_OBJ_FLAG_CLICKABLE);
+ lv_obj_align(chart, LV_ALIGN_TOP_MID, 0, PAGE_VER_RES + 36);
+ lv_chart_set_type(chart, LV_CHART_TYPE_LINE);
+
+ lv_obj_set_style_bg_color(
+ chart,
+ lv_color_make(0x1A, 0x1A, 0x1A),
+ LV_PART_MAIN);
+ lv_obj_set_style_border_width(chart, 2, LV_PART_MAIN);
+ lv_obj_set_style_radius(chart, 5, LV_PART_MAIN);
+ lv_obj_set_style_border_color(
+ chart,
+ lv_color_make(0x66, 0x66, 0x66),
+ LV_PART_MAIN);
+
+ lv_obj_set_style_bg_opa(chart, LV_OPA_50, LV_PART_ITEMS);
+ lv_obj_set_style_bg_grad_dir(chart, LV_GRAD_DIR_VER, LV_PART_ITEMS);
+ lv_obj_set_style_bg_main_stop(chart, 255, LV_PART_ITEMS);
+ lv_obj_set_style_bg_grad_stop(chart, 0, LV_PART_ITEMS);
+ lv_obj_set_style_line_width(chart, 1, LV_PART_ITEMS);
+
+ lv_chart_set_div_line_count(chart, 0, 4);
+ lv_obj_set_style_line_color(
+ chart,
+ lv_color_make(0x66, 0x66, 0x66),
+ LV_PART_ITEMS);
+ lv_chart_set_range(chart, LV_CHART_AXIS_PRIMARY_Y, 50, 180);
+ lv_chart_set_point_count(chart, 100);
+
+ ctx->chart_hr_ser1 = lv_chart_add_series(chart, lv_color_make(0xfc, 0x5c, 0x00), 0);
+
+ ctx->chart_hr = chart;
+}
+
+static void auto_event_create(page_ctx_t* ctx)
+{
+ lv_auto_event_data_t ae_grp[EVENT_CNT] = {
+ { &ctx->btn_rst, LV_EVENT_CLICKED, 2000 },
+ { &ctx->img_down, LV_EVENT_CLICKED, 1000 },
+ { &ctx->img_up, LV_EVENT_CLICKED, 1000 },
+ { &ctx->base.obj, LV_EVENT_LEAVE, 1000 },
+ };
+ for (int i = 0; i < EVENT_CNT; i++) {
+ ctx->event_data[i] = ae_grp[i];
+ }
+
+ AUTO_EVENT_CREATE(ctx->auto_event, ctx->event_data);
+}
+
+static void on_root_event(lv_event_t* e)
+{
+ lv_obj_t* root = lv_event_get_current_target_obj(e);
+ lv_event_code_t code = lv_event_get_code(e);
+ page_ctx_t* ctx = lv_obj_get_user_data(root);
+
+ if (code == LV_EVENT_GESTURE) {
+ lv_dir_t dir = lv_indev_get_gesture_dir(lv_indev_get_act());
+ if (dir == LV_DIR_RIGHT) {
+ lv_obj_send_event(root, LV_EVENT_LEAVE, NULL);
+ } else if (dir == LV_DIR_BOTTOM) {
+ CONT_HR_MOVE_DOWN(false, ctx->cont_hr, ctx->img_heart);
+ } else if (dir == LV_DIR_TOP) {
+ CONT_HR_MOVE_DOWN(true, ctx->cont_hr, ctx->img_heart);
+ }
+ } else if (code == LV_EVENT_LEAVE) {
+ page_pop(&ctx->base);
+ }
+}
+
+static void on_page_construct(lv_fragment_t* self, void* args)
+{
+ LV_LOG_INFO("self: %p args: %p", self, args);
+
+ page_ctx_t* ctx = (page_ctx_t*)self;
+
+ color_bar_t color_bar_grp[COLOR_BAR_CNT] = {
+ { "Relaxation", 45, 0x3FA9F5, 158 },
+ { "Sports", 30, 0x7AC943, 212 },
+ { "Competition", 20, 0xF15A24, 266 },
+ { "Endurance", 5, 0xC1272D, 320 },
+ };
+
+ for (int i = 0; i < COLOR_BAR_CNT; i++) {
+ ctx->color_bar_grp[i] = color_bar_grp[i];
+ }
+ lv_style_init(&ctx->color_bar_style);
+ lv_style_set_text_font(&ctx->color_bar_style, resource_get_font(BANDX_REGULAR_FONT "_20"));
+ lv_style_set_text_color(&ctx->color_bar_style, lv_color_white());
+
+ lv_style_init(&ctx->label_hr_style1);
+ lv_style_set_text_font(&ctx->label_hr_style1, resource_get_font(BANDX_REGULAR_FONT "_20"));
+ lv_style_set_text_color(&ctx->label_hr_style1, lv_palette_main(LV_PALETTE_GREY));
+
+ lv_style_init(&ctx->label_hr_style2);
+ lv_style_set_text_font(&ctx->label_hr_style2, resource_get_font(BANDX_REGULAR_FONT "_20"));
+ lv_style_set_text_color(&ctx->label_hr_style2, lv_color_make(0xF1, 0x5A, 0x24));
+}
+
+static void on_page_destruct(lv_fragment_t* self)
+{
+ LV_LOG_INFO("self: %p", self);
+
+ page_ctx_t* ctx = (page_ctx_t*)self;
+
+ lv_style_reset(&ctx->color_bar_style);
+ lv_style_reset(&ctx->label_hr_style1);
+ lv_style_reset(&ctx->label_hr_style2);
+}
+
+static void on_page_attached(lv_fragment_t* self)
+{
+ LV_LOG_INFO("self: %p", self);
+}
+
+static void on_page_detached(lv_fragment_t* self)
+{
+ LV_LOG_INFO("self: %p", self);
+}
+
+static lv_obj_t* on_page_create(lv_fragment_t* self, lv_obj_t* container)
+{
+ LV_LOG_INFO("self: %p container: %p", self, container);
+
+ lv_obj_t* root = lv_obj_create(container);
+ lv_obj_remove_style_all(root);
+ lv_obj_add_style(root, resource_get_style("root_def"), 0);
+ lv_obj_add_event(root, on_root_event, LV_EVENT_ALL, NULL);
+ lv_obj_clear_flag(root, LV_OBJ_FLAG_GESTURE_BUBBLE);
+ lv_obj_clear_flag(root, LV_OBJ_FLAG_SCROLLABLE);
+ lv_obj_set_user_data(root, self);
+ return root;
+}
+
+static void on_page_created(lv_fragment_t* self, lv_obj_t* obj)
+{
+ LV_LOG_INFO("self: %p obj: %p", self, obj);
+
+ page_ctx_t* ctx = (page_ctx_t*)self;
+
+ cont_hr_create(obj);
+ label_hr_current_create(ctx->cont_hr);
+ label_hr_record_create(ctx->cont_hr);
+ label_hr_record_time_update(ctx->label_hr_record_time);
+ btn_rst_create(ctx->cont_hr);
+ img_arrow_create(ctx->cont_hr);
+ chart_hr_create(ctx->cont_hr);
+ color_bar_create(ctx->cont_hr, ctx->color_bar_grp, COLOR_BAR_CNT);
+
+ ctx->timer_label_hr_update = lv_timer_create(label_hr_current_update, 1000, ctx);
+
+ img_heart_anim_enable(ctx->img_heart, true);
+ auto_event_create(ctx);
+}
+
+static void on_page_will_delete(lv_fragment_t* self, lv_obj_t* obj)
+{
+ LV_LOG_INFO("self: %p obj: %p", self, obj);
+
+ page_ctx_t* ctx = (page_ctx_t*)self;
+
+ lv_timer_del(ctx->timer_label_hr_update);
+ img_heart_anim_enable(ctx->img_heart, false);
+ AUTO_EVENT_DELETE(ctx->auto_event);
+}
+
+static void on_page_deleted(lv_fragment_t* self, lv_obj_t* obj)
+{
+ LV_LOG_INFO("self: %p obj: %p", self, obj);
+}
+
+static bool on_page_event(lv_fragment_t* self, int code, void* user_data)
+{
+ LV_LOG_INFO("self: %p code: %d user_data: %p", self, code, user_data);
+ return false;
+}
+
+PAGE_CLASS_DEF(heart_rate);
diff --git a/bandx/page/launcher.c b/bandx/page/launcher.c
new file mode 100644
index 0000000000000000000000000000000000000000..578b20bb66d2d937c4fd3eee87848435fd9e2adb
--- /dev/null
+++ b/bandx/page/launcher.c
@@ -0,0 +1,336 @@
+/**
+ * @file launcher.c
+ *
+ */
+
+/*********************
+ * INCLUDES
+ *********************/
+#include "../utils/lv_obj_ext_func.h"
+#include "page.h"
+
+/*********************
+ * DEFINES
+ *********************/
+
+#define APP_ENTRY_CNT 7
+#define EVENT_CNT 3
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+typedef struct {
+ const void* name;
+ const char* text;
+ uint32_t bg_color;
+ lv_obj_t* icon;
+} app_entry_t;
+
+typedef struct {
+ lv_fragment_t base;
+ lv_style_t cont_style;
+ lv_style_t icon_style;
+ lv_style_t label_style;
+ app_entry_t app_entry[APP_ENTRY_CNT];
+ lv_timer_t* auto_show_timer;
+} page_ctx_t;
+
+/**********************
+ * STATIC PROTOTYPES
+ **********************/
+
+/**********************
+ * STATIC VARIABLES
+ **********************/
+
+/**********************
+ * MACROS
+ **********************/
+
+#define APP_ICON_SIZE 100
+#define APP_ICON_PAD_VER (APP_ICON_SIZE / 2)
+#define APP_ICON_ANIM_TIME 200
+
+/**********************
+ * GLOBAL FUNCTIONS
+ **********************/
+
+/**********************
+ * STATIC FUNCTIONS
+ **********************/
+
+static void app_icon_click_anim(lv_obj_t* img, bool ispress)
+{
+ lv_anim_t a;
+ lv_anim_init(&a);
+ lv_anim_set_var(&a, img);
+ lv_anim_set_exec_cb(&a, (lv_anim_exec_xcb_t)lv_img_set_zoom);
+ lv_anim_set_values(
+ &a,
+ lv_img_get_zoom(img),
+ ispress ? (int)(256 * 0.8f) : 256);
+ lv_anim_set_time(&a, APP_ICON_ANIM_TIME);
+
+ lv_anim_set_path_cb(&a, ispress ? lv_anim_path_ease_in_out : lv_anim_path_overshoot);
+
+ lv_anim_start(&a);
+}
+
+static void on_app_icon_event(lv_event_t* e)
+{
+ page_ctx_t* ctx = lv_event_get_user_data(e);
+ lv_obj_t* icon = lv_event_get_current_target(e);
+ lv_event_code_t code = lv_event_get_code(e);
+ lv_obj_t* img = lv_obj_get_child(icon, 0);
+
+ switch (code) {
+ case LV_EVENT_PRESSED:
+ app_icon_click_anim(img, true);
+ break;
+ case LV_EVENT_RELEASED:
+ case LV_EVENT_PRESS_LOST:
+ app_icon_click_anim(img, false);
+ break;
+ case LV_EVENT_CLICKED: {
+ app_entry_t* entry = lv_obj_get_user_data(icon);
+ page_set_last_page_dialplate(false);
+ page_push(&ctx->base, entry->name, NULL);
+ } break;
+ default:
+ break;
+ }
+}
+
+static void app_icon_create(page_ctx_t* ctx, lv_obj_t* par, app_entry_t* entry)
+{
+ lv_obj_t* cont = lv_obj_create(par);
+ lv_obj_remove_style_all(cont);
+ lv_obj_add_style(cont, &ctx->cont_style, 0);
+ lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_COLUMN);
+ lv_obj_set_flex_align(cont, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
+
+ lv_obj_t* icon = lv_obj_create(cont);
+ lv_style_t* trans_style = resource_get_style("btn_trans");
+ lv_obj_remove_style_all(icon);
+ lv_obj_add_style(icon, &ctx->icon_style, 0);
+
+ lv_obj_set_style_bg_color(icon, lv_palette_main(LV_PALETTE_GREY), LV_STATE_PRESSED);
+ lv_obj_set_style_transform_width(icon, 5, LV_STATE_PRESSED);
+ lv_obj_set_style_transform_height(icon, -5, LV_STATE_PRESSED);
+ lv_obj_add_style(icon, trans_style, LV_STATE_PRESSED);
+ lv_obj_add_style(icon, trans_style, LV_STATE_DEFAULT);
+
+ lv_obj_set_style_bg_color(icon, lv_color_hex(entry->bg_color), 0);
+ lv_obj_set_user_data(icon, (void*)entry);
+ lv_obj_add_event(icon, on_app_icon_event, LV_EVENT_ALL, ctx);
+ lv_obj_clear_flag(icon, LV_OBJ_FLAG_SCROLLABLE);
+
+ lv_obj_t* img = lv_img_create(icon);
+ lv_img_set_src(img, resource_get_img(entry->name));
+ lv_obj_center(img);
+
+ lv_obj_t* label = lv_label_create(cont);
+ lv_obj_add_style(label, &ctx->label_style, 0);
+ lv_label_set_text(label, entry->text);
+ entry->icon = icon;
+}
+
+static void auto_show_update(lv_timer_t* timer)
+{
+ page_ctx_t* ctx = lv_timer_get_user_data(timer);
+
+ static int current_show_app_index = -1;
+
+ if (current_show_app_index >= APP_ENTRY_CNT) {
+ current_show_app_index = -1;
+ }
+
+ enum state_type { IDLE,
+ FOCUS,
+ PRESSED,
+ PRESSED_LOST,
+ CLICK,
+ _END };
+ static uint8_t state = IDLE;
+ if (page_is_last_page_dialplate()) {
+ current_show_app_index = 0;
+ page_set_last_page_dialplate(false);
+ }
+
+ if (current_show_app_index >= 0 && current_show_app_index < APP_ENTRY_CNT) {
+ lv_obj_t* current_icon = ctx->app_entry[current_show_app_index].icon;
+
+ switch (state) {
+ case IDLE:
+ break;
+ case FOCUS:
+ lv_obj_scroll_to_view_recursive(current_icon, LV_ANIM_ON);
+ lv_obj_send_event(current_icon, LV_EVENT_FOCUSED, ctx);
+ break;
+ case PRESSED:
+ lv_obj_send_event(current_icon, LV_EVENT_PRESSED, ctx);
+ break;
+ case PRESSED_LOST:
+ lv_obj_send_event(current_icon, LV_EVENT_PRESS_LOST, ctx);
+ break;
+ case CLICK:
+ if (lv_strcmp(ctx->app_entry[current_show_app_index].text, "Settings") == 0) {
+ current_show_app_index = -1;
+ } else {
+ current_show_app_index++;
+ }
+ lv_obj_send_event(current_icon, LV_EVENT_CLICKED, ctx);
+ break;
+ case _END:
+ break;
+ default:
+ break;
+ }
+ state++;
+ state %= _END;
+ } else {
+ lv_obj_send_event(ctx->base.obj, LV_EVENT_LEAVE, NULL);
+ }
+}
+
+static void on_root_event(lv_event_t* e)
+{
+ lv_obj_t* root = lv_event_get_current_target_obj(e);
+ lv_event_code_t code = lv_event_get_code(e);
+ page_ctx_t* ctx = lv_obj_get_user_data(root);
+
+ if (code == LV_EVENT_GESTURE) {
+ lv_dir_t dir = lv_indev_get_gesture_dir(lv_indev_get_act());
+ if (dir == LV_DIR_RIGHT) {
+ lv_obj_send_event(root, LV_EVENT_LEAVE, NULL);
+ }
+ } else if (code == LV_EVENT_LEAVE) {
+ page_pop(&ctx->base);
+ }
+}
+
+static void on_page_construct(lv_fragment_t* self, void* args)
+{
+ LV_LOG_INFO("self: %p args: %p", self, args);
+ page_ctx_t* ctx = (page_ctx_t*)self;
+
+ lv_style_init(&ctx->cont_style);
+ lv_style_set_width(&ctx->cont_style, LV_PCT(100));
+ lv_style_set_height(&ctx->cont_style, LV_SIZE_CONTENT);
+
+ lv_style_init(&ctx->icon_style);
+ lv_style_set_bg_opa(&ctx->icon_style, LV_OPA_COVER);
+ lv_style_set_size(&ctx->icon_style, APP_ICON_SIZE, APP_ICON_SIZE);
+ lv_style_set_radius(&ctx->icon_style, 10);
+
+ lv_style_init(&ctx->label_style);
+ lv_style_set_text_font(&ctx->label_style, resource_get_font(BANDX_BOLD_FONT "_23"));
+ lv_style_set_text_color(&ctx->label_style, lv_color_white());
+
+ app_entry_t app_entry[APP_ENTRY_CNT] = {
+ { "heart_rate", "Heart", 0xFF542A },
+ { "music", "Music", 0xEE4C84 },
+ { "stop_watch", "StWatch", 0x3CFFA7 },
+ { "flashlight", "Light", 0xFFB428 },
+ { "sleep", "Sleep", 0x001BC8 },
+ { "sport", "Sport", 0x9B5DFF },
+ { "settings", "Settings", 0x0089FF },
+ };
+
+ for (int i = 0; i < APP_ENTRY_CNT; i++) {
+ ctx->app_entry[i] = app_entry[i];
+ }
+}
+
+static void on_page_destruct(lv_fragment_t* self)
+{
+ LV_LOG_INFO("self: %p", self);
+ page_ctx_t* ctx = (page_ctx_t*)self;
+ lv_style_reset(&ctx->cont_style);
+ lv_style_reset(&ctx->icon_style);
+ lv_style_reset(&ctx->label_style);
+}
+
+static void on_page_attached(lv_fragment_t* self)
+{
+ LV_LOG_INFO("self: %p", self);
+}
+
+static void on_page_detached(lv_fragment_t* self)
+{
+ LV_LOG_INFO("self: %p", self);
+}
+
+static lv_obj_t* on_page_create(lv_fragment_t* self, lv_obj_t* container)
+{
+ LV_LOG_INFO("self: %p container: %p", self, container);
+
+ lv_obj_t* root = lv_obj_create(container);
+ lv_obj_remove_style_all(root);
+ lv_obj_add_style(root, resource_get_style("root_def"), 0);
+ lv_obj_add_event(root, on_root_event, LV_EVENT_ALL, NULL);
+ lv_obj_clear_flag(root, LV_OBJ_FLAG_GESTURE_BUBBLE);
+ lv_obj_set_user_data(root, self);
+ return root;
+}
+
+static void on_page_created(lv_fragment_t* self, lv_obj_t* obj)
+{
+ LV_LOG_INFO("self: %p obj: %p", self, obj);
+
+ page_ctx_t* ctx = (page_ctx_t*)self;
+
+ /* root */
+ lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_COLUMN);
+ lv_obj_set_flex_align(obj, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER);
+ lv_obj_set_style_pad_row(obj, 20, 0);
+ lv_obj_set_style_pad_ver(obj, APP_ICON_PAD_VER, 0);
+
+ /* apps */
+ for (int i = 0; i < APP_ENTRY_CNT; i++) {
+ app_icon_create(
+ ctx,
+ obj,
+ &ctx->app_entry[i]);
+ }
+
+ /* shadow image */
+ {
+ lv_obj_t* img1 = lv_img_create(obj);
+ lv_img_set_src(img1, resource_get_img("icon_shadow_up"));
+ lv_obj_align(img1, LV_ALIGN_TOP_MID, 0, -APP_ICON_PAD_VER);
+ lv_obj_add_flag(img1, LV_OBJ_FLAG_FLOATING);
+
+ lv_obj_t* img2 = lv_img_create(obj);
+ lv_img_set_src(img2, resource_get_img("icon_shadow_down"));
+ lv_obj_align(img2, LV_ALIGN_BOTTOM_MID, 0, APP_ICON_PAD_VER);
+ lv_obj_add_flag(img2, LV_OBJ_FLAG_FLOATING);
+ }
+
+ if (page_get_autoshow_enable()) {
+ ctx->auto_show_timer = lv_timer_create(auto_show_update, 500, ctx);
+ }
+}
+
+static void on_page_will_delete(lv_fragment_t* self, lv_obj_t* obj)
+{
+ LV_LOG_INFO("self: %p obj: %p", self, obj);
+ page_ctx_t* ctx = (page_ctx_t*)self;
+ if (page_get_autoshow_enable()) {
+ lv_timer_del(ctx->auto_show_timer);
+ }
+}
+
+static void on_page_deleted(lv_fragment_t* self, lv_obj_t* obj)
+{
+ LV_LOG_INFO("self: %p obj: %p", self, obj);
+}
+
+static bool on_page_event(lv_fragment_t* self, int code, void* user_data)
+{
+ LV_LOG_INFO("self: %p code: %d user_data: %p", self, code, user_data);
+ return false;
+}
+
+PAGE_CLASS_DEF(launcher);
diff --git a/bandx/page/music.c b/bandx/page/music.c
new file mode 100644
index 0000000000000000000000000000000000000000..5a3bdda1dc1a84f25b982688624555ef007a6314
--- /dev/null
+++ b/bandx/page/music.c
@@ -0,0 +1,563 @@
+/**
+ * @file music.c
+ *
+ */
+
+/*********************
+ * INCLUDES
+ *********************/
+#include "page.h"
+
+/*********************
+ * DEFINES
+ *********************/
+
+#define MC_COLOR_GRAY lv_color_make(0x33, 0x33, 0x33)
+#define MC_COLOR_PINK lv_color_make(0xB9, 0x00, 0x5E)
+#define MC_COLOR_PINK_BG lv_color_make(0x17, 0x00, 0x0B)
+
+#define MUSIC_TIME_TO_MS(min, sec) ((min)*60 * 1000 + (sec)*1000)
+#define MUSIC_INFO_CNT 3
+#define EVENT_CNT 9
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+typedef struct {
+ const char* name;
+ uint32_t time_ms;
+} music_info_t;
+
+typedef struct {
+ lv_fragment_t base;
+ lv_style_t btn_style;
+ lv_style_t trans_style;
+ lv_obj_t* bar_volume;
+ lv_obj_t* arc_play;
+ lv_obj_t* obj_play;
+ lv_obj_t* label_music_info;
+ lv_obj_t* btn_volume_add;
+ lv_obj_t* btn_volume_reduce;
+ lv_obj_t* btn_music_ctrl_next;
+ lv_obj_t* btn_music_ctrl_prev;
+ lv_span_t* span_cur_time;
+ lv_span_t* span_end_time;
+ lv_timer_t* timer_music_update;
+ uint32_t music_current_time;
+ uint32_t music_end_time;
+ uint16_t music_current_index;
+ bool music_is_playing;
+ music_info_t music_info_grp[MUSIC_INFO_CNT];
+ lv_auto_event_data_t event_data[EVENT_CNT];
+ lv_auto_event_t* auto_event;
+} page_ctx_t;
+
+/**********************
+ * STATIC PROTOTYPES
+ **********************/
+
+/**********************
+ * STATIC VARIABLES
+ **********************/
+
+/**********************
+ * MACROS
+ **********************/
+
+/**********************
+ * GLOBAL FUNCTIONS
+ **********************/
+
+/**********************
+ * STATIC FUNCTIONS
+ **********************/
+
+static void on_root_event(lv_event_t* e)
+{
+ lv_obj_t* root = lv_event_get_target(e);
+ lv_event_code_t code = lv_event_get_code(e);
+ page_ctx_t* ctx = lv_obj_get_user_data(root);
+
+ if (code == LV_EVENT_GESTURE) {
+ lv_dir_t dir = lv_indev_get_gesture_dir(lv_indev_get_act());
+ if (dir == LV_DIR_RIGHT) {
+ lv_obj_send_event(root, LV_EVENT_LEAVE, NULL);
+ }
+ } else if (code == LV_EVENT_LEAVE) {
+ page_pop(&ctx->base);
+ }
+}
+
+static void bar_volume_set_value(int16_t value, lv_obj_t* bar_volume)
+{
+ lv_bar_set_value(bar_volume, value, LV_ANIM_ON);
+}
+
+static void bar_volume_create(lv_obj_t* par)
+{
+ page_ctx_t* ctx = lv_obj_get_user_data(par);
+
+ lv_obj_t* bar = lv_bar_create(par);
+ lv_obj_set_size(bar, 160, 3);
+ lv_obj_align(bar, LV_ALIGN_TOP_MID, 0, 70);
+ lv_obj_set_style_bg_color(bar, MC_COLOR_PINK, LV_PART_MAIN);
+ lv_obj_set_style_bg_color(bar, MC_COLOR_PINK, LV_PART_INDICATOR);
+
+ ctx->bar_volume = bar;
+}
+
+static void btn_volume_event_handler(lv_event_t* event)
+{
+ lv_obj_t* obj = lv_event_get_target(event);
+ lv_event_code_t code = lv_event_get_code(event);
+ page_ctx_t* ctx = lv_event_get_user_data(event);
+
+ if (code == LV_EVENT_CLICKED) {
+ lv_obj_t* img = lv_obj_get_child(obj, 0);
+ int16_t vol_value = lv_bar_get_value(ctx->bar_volume);
+
+ if (lv_strcmp(lv_img_get_src(img), resource_get_img("icon_volume_add")) == 0) {
+ bar_volume_set_value(vol_value + 10, ctx->bar_volume);
+ } else {
+ bar_volume_set_value(vol_value - 10, ctx->bar_volume);
+ }
+ }
+}
+
+static void btn_volume_create(lv_obj_t* par)
+{
+ page_ctx_t* ctx = lv_obj_get_user_data(par);
+
+ lv_obj_t* btn1 = lv_btn_create(par);
+ lv_obj_set_size(btn1, 85, 48);
+ lv_obj_add_style(btn1, &ctx->btn_style, LV_PART_MAIN);
+ lv_obj_add_style(btn1, &ctx->trans_style, LV_STATE_DEFAULT);
+ lv_obj_add_style(btn1, &ctx->trans_style, LV_STATE_PRESSED);
+ lv_obj_set_style_bg_color(btn1, lv_color_white(), LV_STATE_PRESSED);
+ lv_obj_align(btn1, LV_ALIGN_TOP_MID, -lv_obj_get_style_width(btn1, 0) / 2 - 3, 10);
+ lv_obj_add_event(btn1, btn_volume_event_handler, LV_EVENT_ALL, ctx);
+
+ lv_obj_t* btn2 = lv_btn_create(par);
+ lv_obj_set_size(btn2, 85, 48);
+ lv_obj_add_style(btn2, &ctx->btn_style, LV_PART_MAIN);
+ lv_obj_add_style(btn2, &ctx->trans_style, LV_STATE_DEFAULT);
+ lv_obj_add_style(btn2, &ctx->trans_style, LV_STATE_PRESSED);
+ lv_obj_set_style_bg_color(btn2, lv_color_white(), LV_STATE_PRESSED);
+ lv_obj_align(btn2, LV_ALIGN_TOP_MID, lv_obj_get_style_width(btn2, 0) / 2 + 3, 10);
+ lv_obj_add_event(btn2, btn_volume_event_handler, LV_EVENT_ALL, ctx);
+
+ lv_obj_t* img1 = lv_img_create(btn1);
+ lv_img_set_src(img1, resource_get_img("icon_volume_reduce"));
+ lv_obj_set_style_image_recolor_opa(img1, LV_OPA_COVER, LV_PART_MAIN);
+ lv_obj_set_style_image_recolor(img1, lv_color_black(), LV_PART_MAIN);
+ lv_obj_align(img1, LV_ALIGN_CENTER, 0, 0);
+
+ lv_obj_t* img2 = lv_img_create(btn2);
+ lv_img_set_src(img2, resource_get_img("icon_volume_add"));
+ lv_obj_set_style_image_recolor_opa(img2, LV_OPA_COVER, LV_PART_MAIN);
+ lv_obj_set_style_image_recolor(img2, lv_color_black(), LV_PART_MAIN);
+ lv_obj_align(img2, LV_ALIGN_CENTER, 0, 0);
+
+ ctx->btn_volume_reduce = btn1;
+ ctx->btn_volume_add = btn2;
+}
+
+static void obj_play_anim_ready_callback(lv_anim_t* a_p)
+{
+ page_ctx_t* ctx = lv_anim_get_user_data(a_p);
+
+ if (a_p->act_time == 0)
+ return;
+
+ lv_obj_t* img = (lv_obj_t*)a_p->var;
+ const char* img_src_next;
+ img_src_next = (lv_strcmp(lv_img_get_src(img), resource_get_img("icon_start")) == 0) ? resource_get_img("icon_pause") : resource_get_img("icon_start");
+ lv_img_set_src(img, img_src_next);
+
+ ctx->music_is_playing = (lv_strcmp(lv_img_get_src(img), resource_get_img("icon_pause")) == 0);
+
+ lv_anim_t a;
+ lv_anim_init(&a);
+ lv_anim_set_var(&a, img);
+ lv_anim_set_exec_cb(&a, (lv_anim_exec_xcb_t)lv_img_set_angle);
+ lv_anim_set_values(&a, 1800, 3600);
+ lv_anim_set_time(&a, 300);
+
+ lv_anim_set_path_cb(&a, lv_anim_path_ease_out);
+ lv_anim_start(&a);
+}
+
+static void obj_play_event_handler(lv_event_t* event)
+{
+ lv_event_code_t code = lv_event_get_code(event);
+ lv_obj_t* obj = lv_event_get_target(event);
+ page_ctx_t* ctx = lv_event_get_user_data(event);
+
+ if (code == LV_EVENT_CLICKED) {
+ lv_obj_t* img = lv_obj_get_child(obj, 0);
+
+ lv_anim_t a;
+ lv_anim_init(&a);
+ lv_anim_set_var(&a, img);
+ lv_anim_set_exec_cb(&a, (lv_anim_exec_xcb_t)lv_img_set_angle);
+ lv_anim_set_ready_cb(&a, obj_play_anim_ready_callback);
+ lv_anim_set_user_data(&a, ctx);
+ lv_anim_set_values(&a, 0, 1800);
+ lv_anim_set_time(&a, 300);
+ lv_anim_set_path_cb(&a, lv_anim_path_ease_in);
+ lv_anim_start(&a);
+ }
+}
+
+static void obj_play_create(lv_obj_t* par)
+{
+ page_ctx_t* ctx = lv_obj_get_user_data(par);
+
+ lv_obj_t* obj = lv_obj_create(ctx->arc_play);
+ lv_obj_set_size(obj, 104, 104);
+ lv_obj_add_style(obj, &ctx->btn_style, LV_PART_MAIN);
+ lv_obj_add_style(obj, &ctx->trans_style, LV_STATE_DEFAULT);
+ lv_obj_add_style(obj, &ctx->trans_style, LV_STATE_PRESSED);
+ lv_obj_set_style_bg_color(obj, lv_color_white(), LV_STATE_PRESSED);
+ lv_obj_add_event(obj, obj_play_event_handler, LV_EVENT_ALL, ctx);
+ lv_obj_set_style_radius(obj, LV_RADIUS_CIRCLE, LV_PART_MAIN);
+ lv_obj_set_style_bg_color(obj, MC_COLOR_GRAY, LV_PART_MAIN);
+ lv_obj_set_style_border_width(obj, 0, LV_PART_MAIN);
+ lv_obj_align(obj, LV_ALIGN_CENTER, 0, 0);
+
+ lv_obj_t* img = lv_img_create(obj);
+ lv_img_set_src(img, resource_get_img("icon_start"));
+ lv_obj_align(img, LV_ALIGN_CENTER, 0, 0);
+
+ ctx->obj_play = obj;
+}
+
+static void spangroup_time_update(page_ctx_t* ctx, uint32_t cur_ms, uint32_t end_ms)
+{
+ uint32_t cur_min = cur_ms / 60 / 1000;
+ uint32_t cur_sec = (cur_ms / 1000) % 60;
+
+ uint32_t end_min = end_ms / 60 / 1000;
+ uint32_t end_sec = (end_ms / 1000) % 60;
+
+ char cur_buf[32];
+ char end_buf[32];
+
+ lv_snprintf(cur_buf, sizeof(cur_buf), "%d:%02d", cur_min, cur_sec);
+ lv_snprintf(end_buf, sizeof(end_buf), " -- %d:%02d", end_min, end_sec);
+ lv_span_set_text(ctx->span_cur_time, cur_buf);
+ lv_span_set_text(ctx->span_end_time, end_buf);
+}
+
+static void arc_play_event_handler(lv_event_t* event)
+{
+ page_ctx_t* ctx = lv_event_get_user_data(event);
+ int32_t value = lv_arc_get_value(ctx->arc_play);
+ ctx->music_current_time = lv_map(value, lv_arc_get_min_value(ctx->arc_play), lv_arc_get_max_value(ctx->arc_play), 0, ctx->music_end_time);
+ spangroup_time_update(ctx, ctx->music_current_time, ctx->music_end_time);
+}
+
+static void arc_play_create(lv_obj_t* par)
+{
+ page_ctx_t* ctx = lv_obj_get_user_data(par);
+
+ lv_obj_t* arc = lv_arc_create(par);
+ lv_obj_set_style_arc_color(arc, MC_COLOR_PINK, LV_PART_INDICATOR);
+ lv_obj_set_style_arc_width(arc, 6, LV_PART_INDICATOR);
+
+ lv_obj_set_style_bg_color(arc, MC_COLOR_PINK, LV_PART_KNOB); // 设置 arc knob颜色
+ lv_obj_set_style_pad_all(arc, 0, LV_PART_KNOB); // 设置arc knob size
+
+ lv_obj_set_style_arc_color(arc, MC_COLOR_PINK_BG, LV_PART_MAIN);
+ lv_obj_set_style_arc_width(arc, 6, LV_PART_MAIN);
+
+ lv_obj_set_style_border_width(arc, 0, LV_PART_MAIN);
+ lv_obj_set_size(arc, 121, 121);
+ lv_obj_align(arc, LV_ALIGN_CENTER, 0, 0);
+ lv_obj_add_event_cb(arc, arc_play_event_handler, LV_EVENT_VALUE_CHANGED, ctx);
+
+ lv_arc_set_start_angle(arc, 270);
+ lv_arc_set_bg_angles(arc, 0, 360);
+ lv_arc_set_range(arc, 0, 1000);
+ lv_arc_set_rotation(arc, 270);
+
+ ctx->arc_play = arc;
+}
+
+static void spangroup_time_create(lv_obj_t* par)
+{
+ page_ctx_t* ctx = lv_obj_get_user_data(par);
+
+ lv_obj_t* label = lv_label_create(par);
+ lv_obj_set_style_text_font(label, resource_get_font(BANDX_REGULAR_FONT "_20"), LV_PART_MAIN);
+ lv_label_set_text(label, "--");
+ lv_obj_align(label, LV_ALIGN_TOP_MID, 0, 87);
+
+ lv_obj_t* spangroup = lv_spangroup_create(par);
+ lv_obj_set_style_text_font(spangroup, resource_get_font(BANDX_REGULAR_FONT "_20"), LV_PART_MAIN);
+
+ lv_span_t* cur_span = lv_spangroup_new_span(spangroup);
+ lv_style_set_text_color(&cur_span->style, lv_color_hex(0xB9005E));
+
+ lv_span_t* end_span = lv_spangroup_new_span(spangroup);
+ lv_style_set_text_color(&end_span->style, lv_color_hex(0x666666));
+ lv_span_set_text(end_span, "--");
+
+ lv_obj_align(spangroup, LV_ALIGN_TOP_MID, 0, 87);
+
+ ctx->span_cur_time = cur_span;
+ ctx->span_end_time = end_span;
+}
+
+static void label_music_info_create(lv_obj_t* par)
+{
+ page_ctx_t* ctx = lv_obj_get_user_data(par);
+
+ lv_obj_t* label = lv_label_create(par);
+ lv_obj_set_style_text_font(label, resource_get_font(BANDX_REGULAR_FONT "_20"), LV_PART_MAIN);
+ lv_obj_set_style_text_color(label, lv_color_white(), LV_PART_MAIN);
+ lv_label_set_text(label, "");
+ lv_label_set_long_mode(label, LV_LABEL_LONG_SCROLL_CIRCULAR);
+ lv_obj_set_align(label, LV_ALIGN_CENTER);
+ lv_obj_set_width(label, 166);
+ lv_obj_align(label, LV_ALIGN_TOP_MID, 0, 259);
+
+ ctx->label_music_info = label;
+}
+
+static void music_change_current(page_ctx_t* ctx, int index)
+{
+ index %= MUSIC_INFO_CNT;
+
+ ctx->music_current_time = 0;
+ ctx->music_end_time = ctx->music_info_grp[index].time_ms;
+
+ lv_label_set_text(ctx->label_music_info, ctx->music_info_grp[index].name);
+ lv_arc_set_value(ctx->arc_play, ctx->music_current_time * 1000 / ctx->music_end_time);
+ spangroup_time_update(ctx, ctx->music_current_time, ctx->music_end_time);
+}
+
+static void music_change_next(page_ctx_t* ctx, bool next)
+{
+ if (next) {
+ if (ctx->music_current_index >= MUSIC_INFO_CNT - 1) {
+ ctx->music_current_index = 0;
+ } else {
+ ctx->music_current_index++;
+ }
+ } else {
+ if (ctx->music_current_index == 0) {
+ ctx->music_current_index = MUSIC_INFO_CNT;
+ } else {
+ ctx->music_current_index--;
+ }
+ }
+
+ music_change_current(ctx, ctx->music_current_index);
+}
+
+static void btn_music_event_handler(lv_event_t* event)
+{
+ lv_obj_t* obj = lv_event_get_target(event);
+ lv_event_code_t code = lv_event_get_code(event);
+ page_ctx_t* ctx = lv_event_get_user_data(event);
+
+ if (code == LV_EVENT_CLICKED) {
+ lv_obj_t* img = lv_obj_get_child(obj, 0);
+
+ bool is_next = (lv_strcmp(lv_img_get_src(img), resource_get_img("icon_next")) == 0);
+ music_change_next(ctx, is_next);
+ }
+}
+
+static void btn_music_ctrl_create(lv_obj_t* par)
+{
+ page_ctx_t* ctx = lv_obj_get_user_data(par);
+
+ lv_obj_t* btn1 = lv_btn_create(par);
+ lv_obj_set_size(btn1, 85, 64);
+ lv_obj_add_style(btn1, &ctx->btn_style, LV_PART_MAIN);
+ lv_obj_set_style_bg_color(btn1, lv_color_white(), LV_STATE_PRESSED);
+ lv_obj_add_style(btn1, &ctx->trans_style, LV_STATE_DEFAULT);
+ lv_obj_add_style(btn1, &ctx->trans_style, LV_STATE_PRESSED);
+ lv_obj_align(btn1, LV_ALIGN_BOTTOM_MID, -lv_obj_get_style_width(btn1, 0) / 2 - 3, -10);
+ lv_obj_add_event(btn1, btn_music_event_handler, LV_EVENT_ALL, ctx);
+
+ lv_obj_t* btn2 = lv_btn_create(par);
+ lv_obj_set_size(btn2, 85, 64);
+ lv_obj_add_style(btn2, &ctx->btn_style, LV_PART_MAIN);
+ lv_obj_set_style_bg_color(btn2, lv_color_white(), LV_STATE_PRESSED);
+ lv_obj_add_style(btn2, &ctx->trans_style, LV_STATE_DEFAULT);
+ lv_obj_add_style(btn2, &ctx->trans_style, LV_STATE_PRESSED);
+ lv_obj_add_event(btn2, btn_music_event_handler, LV_EVENT_ALL, ctx);
+ lv_obj_align(btn2, LV_ALIGN_BOTTOM_MID, lv_obj_get_style_width(btn2, 0) / 2 + 3, -10);
+
+ lv_obj_t* img1 = lv_img_create(btn1);
+ lv_img_set_src(img1, resource_get_img("icon_prev"));
+ lv_obj_align(img1, LV_ALIGN_CENTER, 0, 0);
+
+ lv_obj_t* img2 = lv_img_create(btn2);
+ lv_img_set_src(img2, resource_get_img("icon_next"));
+ lv_obj_align(img2, LV_ALIGN_CENTER, 0, 0);
+
+ ctx->btn_music_ctrl_prev = btn1;
+ ctx->btn_music_ctrl_next = btn2;
+}
+
+static void music_update(lv_timer_t* timer)
+{
+ page_ctx_t* ctx = lv_timer_get_user_data(timer);
+
+ if (!ctx->music_is_playing) {
+ return;
+ }
+ ctx->music_current_time += timer->period;
+
+ if (ctx->music_current_time > ctx->music_end_time) {
+ music_change_next(ctx, true);
+ }
+
+ lv_arc_set_value(ctx->arc_play, ctx->music_current_time * 1000 / ctx->music_end_time);
+ spangroup_time_update(ctx, ctx->music_current_time, ctx->music_end_time);
+}
+
+static void auto_event_create(page_ctx_t* ctx)
+{
+ lv_auto_event_data_t ae_grp[EVENT_CNT] = {
+ { &ctx->obj_play, LV_EVENT_CLICKED, 1000 },
+ { &ctx->btn_volume_add, LV_EVENT_CLICKED, 500 },
+ { &ctx->btn_volume_add, LV_EVENT_CLICKED, 500 },
+ { &ctx->btn_volume_reduce, LV_EVENT_CLICKED, 500 },
+ { &ctx->btn_volume_reduce, LV_EVENT_CLICKED, 500 },
+ { &ctx->btn_music_ctrl_next, LV_EVENT_CLICKED, 2000 },
+ { &ctx->btn_music_ctrl_next, LV_EVENT_CLICKED, 2000 },
+ { &ctx->obj_play, LV_EVENT_CLICKED, 1000 },
+ { &ctx->base.obj, LV_EVENT_LEAVE, 1000 },
+ };
+ for (int i = 0; i < EVENT_CNT; i++) {
+ ctx->event_data[i] = ae_grp[i];
+ }
+ AUTO_EVENT_CREATE(ctx->auto_event, ctx->event_data);
+}
+
+static void on_page_construct(lv_fragment_t* self, void* args)
+{
+ LV_LOG_INFO("self: %p args: %p", self, args);
+
+ page_ctx_t* ctx = (page_ctx_t*)self;
+
+ lv_style_init(&ctx->btn_style);
+ lv_style_set_bg_color(&ctx->btn_style, MC_COLOR_GRAY);
+ lv_style_set_border_width(&ctx->btn_style, 0);
+ lv_style_set_outline_width(&ctx->btn_style, 0);
+ lv_style_set_radius(&ctx->btn_style, 10);
+
+ lv_style_init(&ctx->trans_style);
+ static lv_style_transition_dsc_t dsc;
+ static const lv_style_prop_t props[] = {
+ LV_STYLE_BG_COLOR,
+ LV_STYLE_TRANSFORM_WIDTH,
+ LV_STYLE_TRANSFORM_HEIGHT,
+ LV_STYLE_PROP_INV
+ };
+ lv_style_transition_dsc_init(
+ &dsc,
+ props,
+ lv_anim_path_ease_in_out,
+ 50,
+ 0,
+ NULL);
+ lv_style_set_transition(&ctx->trans_style, &dsc);
+
+ music_info_t music_info_grp[MUSIC_INFO_CNT] = {
+ { .name = "Wannabe (Instrumental)", MUSIC_TIME_TO_MS(3, 37) },
+ { .name = "River", MUSIC_TIME_TO_MS(3, 40) },
+ { .name = "I Feel Tired", MUSIC_TIME_TO_MS(3, 25) },
+ };
+
+ for (int i = 0; i < MUSIC_INFO_CNT; i++) {
+ ctx->music_info_grp[i] = music_info_grp[i];
+ }
+
+ ctx->music_current_time = 0;
+ ctx->music_end_time = 0;
+ ctx->music_current_index = 0;
+ ctx->music_is_playing = false;
+}
+
+static void on_page_destruct(lv_fragment_t* self)
+{
+ LV_LOG_INFO("self: %p", self);
+
+ page_ctx_t* ctx = (page_ctx_t*)self;
+ lv_style_reset(&ctx->btn_style);
+ lv_style_reset(&ctx->trans_style);
+}
+
+static void on_page_attached(lv_fragment_t* self)
+{
+ LV_LOG_INFO("self: %p", self);
+}
+
+static void on_page_detached(lv_fragment_t* self)
+{
+ LV_LOG_INFO("self: %p", self);
+}
+
+static lv_obj_t* on_page_create(lv_fragment_t* self, lv_obj_t* container)
+{
+ LV_LOG_INFO("self: %p container: %p", self, container);
+
+ lv_obj_t* root = lv_obj_create(container);
+ lv_obj_remove_style_all(root);
+ lv_obj_add_style(root, resource_get_style("root_def"), 0);
+ lv_obj_add_event(root, on_root_event, LV_EVENT_ALL, NULL);
+ lv_obj_clear_flag(root, LV_OBJ_FLAG_GESTURE_BUBBLE);
+ lv_obj_set_user_data(root, self);
+ return root;
+}
+
+static void on_page_created(lv_fragment_t* self, lv_obj_t* obj)
+{
+ LV_LOG_INFO("self: %p obj: %p", self, obj);
+
+ page_ctx_t* ctx = (page_ctx_t*)self;
+
+ btn_volume_create(obj);
+ bar_volume_create(obj);
+ bar_volume_set_value(50, ctx->bar_volume);
+
+ arc_play_create(obj);
+ obj_play_create(obj);
+ spangroup_time_create(obj);
+ label_music_info_create(obj);
+ btn_music_ctrl_create(obj);
+
+ ctx->music_is_playing = false;
+ music_change_current(ctx, ctx->music_current_index);
+ auto_event_create(ctx);
+ ctx->timer_music_update = lv_timer_create(music_update, 100, ctx);
+}
+
+static void on_page_will_delete(lv_fragment_t* self, lv_obj_t* obj)
+{
+ LV_LOG_INFO("self: %p obj: %p", self, obj);
+
+ page_ctx_t* ctx = (page_ctx_t*)self;
+ lv_timer_del(ctx->timer_music_update);
+ AUTO_EVENT_DELETE(ctx->auto_event);
+}
+
+static void on_page_deleted(lv_fragment_t* self, lv_obj_t* obj)
+{
+ LV_LOG_INFO("self: %p obj: %p", self, obj);
+}
+
+static bool on_page_event(lv_fragment_t* self, int code, void* user_data)
+{
+ LV_LOG_INFO("self: %p code: %d user_data: %p", self, code, user_data);
+ return false;
+}
+
+PAGE_CLASS_DEF(music);
diff --git a/bandx/page/page.c b/bandx/page/page.c
new file mode 100644
index 0000000000000000000000000000000000000000..5dd159d6df182b4ddcc7263cdff0d2b09a613b5e
--- /dev/null
+++ b/bandx/page/page.c
@@ -0,0 +1,113 @@
+/**
+ * @file page.c
+ *
+ */
+
+/*********************
+ * INCLUDES
+ *********************/
+
+#include "page.h"
+
+#include "lvgl/lvgl.h"
+
+/*********************
+ * DEFINES
+ *********************/
+
+#define PAGE_STYLE_MATCH(NAME) \
+ do { \
+ if (lv_strcmp(name, #NAME) == 0) { \
+ return &g_style_grp.NAME; \
+ } \
+ } while (0)
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+/**********************
+ * STATIC PROTOTYPES
+ **********************/
+
+static bool autoshow_enable = false;
+static bool is_last_page_dialplate = false;
+
+/**********************
+ * STATIC VARIABLES
+ **********************/
+
+/**********************
+ * MACROS
+ **********************/
+
+/**********************
+ * GLOBAL FUNCTIONS
+ **********************/
+
+void page_init(void)
+{
+}
+
+lv_fragment_t* page_create(const char* name, void* arg)
+{
+ LV_LOG_USER("name: %s arg: %p", name, arg);
+
+#define PAGE_DEF(NAME) \
+ do { \
+ if (lv_strcmp(name, #NAME) == 0) { \
+ extern const lv_fragment_class_t _page_##NAME##_cls; \
+ return lv_fragment_create(&_page_##NAME##_cls, arg); \
+ } \
+ } while (0);
+#include "page.inc"
+#undef PAGE_DEF
+
+ LV_LOG_WARN("NO match for %s", name);
+ return NULL;
+}
+
+bool page_push(lv_fragment_t* self, const char* name, void* arg)
+{
+ lv_fragment_t* page = page_create(name, NULL);
+
+ if (!page) {
+ LV_LOG_WARN("page push %s failed", name);
+ return false;
+ }
+
+ lv_fragment_manager_t* manager = lv_fragment_get_manager(self);
+ lv_obj_t* const* container = lv_fragment_get_container(self);
+ lv_fragment_manager_push(manager, page, container);
+ return true;
+}
+
+void page_pop(lv_fragment_t* self)
+{
+ lv_fragment_manager_t* manager = lv_fragment_get_manager(self);
+ lv_fragment_manager_pop(manager);
+}
+
+bool page_get_autoshow_enable(void)
+{
+ return autoshow_enable;
+}
+
+void page_set_autoshow_enable(bool en)
+{
+ autoshow_enable = en;
+}
+
+void page_set_last_page_dialplate(bool value)
+{
+ is_last_page_dialplate = value;
+}
+
+bool page_is_last_page_dialplate(void)
+{
+ return is_last_page_dialplate;
+}
+
+/**********************
+ * STATIC FUNCTIONS
+ **********************/
diff --git a/bandx/page/page.h b/bandx/page/page.h
new file mode 100644
index 0000000000000000000000000000000000000000..e73af20fc362909b1fec940436dd4ad59426e32e
--- /dev/null
+++ b/bandx/page/page.h
@@ -0,0 +1,139 @@
+/**
+ * @file page.h
+ *
+ */
+
+#ifndef PAGE_H
+#define PAGE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*********************
+ * INCLUDES
+ *********************/
+#include
+
+#ifdef CONFIG_LVX_USE_DEMO_BANDX
+
+#include "../resource/resource.h"
+#include "../utils/lv_auto_event.h"
+
+/*********************
+ * DEFINES
+ *********************/
+
+#define PAGE_CLASS_DEF(NAME) \
+ const lv_fragment_class_t _page_##NAME##_cls = { \
+ .constructor_cb = on_page_construct, \
+ .destructor_cb = on_page_destruct, \
+ .attached_cb = on_page_attached, \
+ .detached_cb = on_page_detached, \
+ .create_obj_cb = on_page_create, \
+ .obj_created_cb = on_page_created, \
+ .obj_will_delete_cb = on_page_will_delete, \
+ .obj_deleted_cb = on_page_deleted, \
+ .event_cb = on_page_event, \
+ .instance_size = sizeof(page_ctx_t) \
+ }
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+/**********************
+ * GLOBAL PROTOTYPES
+ **********************/
+
+/**
+ * Initialize the page manager.
+ */
+void page_init(void);
+
+/**
+ * Create a new page fragment.
+ * @param name The name of the page.
+ * @param arg Arguments to be passed for the page fragment create.
+ * @return A pointer to the newly created page fragment.
+ */
+lv_fragment_t* page_create(const char* name, void* arg);
+
+/**
+ * Push a new page onto the stack.
+ * @param self Pointer to the current page fragment.
+ * @param name The name of the page to push.
+ * @param arg Arguments for the new page.
+ * @return Return true if the push operation was successful.
+ */
+bool page_push(lv_fragment_t* self, const char* name, void* arg);
+
+/**
+ * Pop the current page from the stack.
+ * @param self Pointer to the current page fragment.
+ */
+void page_pop(lv_fragment_t* self);
+
+/**
+ * Get the auto-show enable status.
+ * @return Return true if auto-show is enabled, false otherwise.
+ */
+bool page_get_autoshow_enable(void);
+
+/**
+ * Set the auto-show enable status.
+ * @param en Enable or disable the auto-show.
+ */
+void page_set_autoshow_enable(bool en);
+
+/**
+ * Set the status of the last page dial plate.
+ * @param value Set to true to indicate that the last page is dial plate, false otherwise.
+ */
+void page_set_last_page_dialplate(bool value);
+
+/**
+ * Check if the last page is dial plate.
+ * @return Return true if the last page is dial plate, false otherwise.
+ */
+bool page_is_last_page_dialplate(void);
+
+/**********************
+ * MACROS
+ **********************/
+#define PAGE_VER_RES 368
+#define PAGE_HOR_RES 194
+
+#ifndef ARRAY_SIZE
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
+#endif
+
+#if !defined(BANDX_REGULAR_FONT) && !defined(BANDX_BOLD_FONT)
+#define BANDX_REGULAR_FONT "AlibabaPuHuiTi-3-55-Regular"
+#define BANDX_BOLD_FONT "AlibabaPuHuiTi-3-95-ExtraBold"
+#endif
+
+#define AUTO_EVENT_CREATE(ae, ae_data) \
+ do { \
+ if (page_get_autoshow_enable()) { \
+ ae = lv_auto_event_create(ae_data, ARRAY_SIZE(ae_data)); \
+ } else { \
+ ae = NULL; \
+ } \
+ } while (0)
+
+#define AUTO_EVENT_DELETE(ae) \
+ do { \
+ if (page_get_autoshow_enable()) { \
+ lv_auto_event_del(ae); \
+ ae = NULL; \
+ } \
+ } while (0)
+
+#endif /*CONFIG_LVX_USE_DEMO_BANDX*/
+
+#ifdef __cplusplus
+} /*extern "C"*/
+#endif
+
+#endif /*PAGE_H*/
diff --git a/bandx/page/page.inc b/bandx/page/page.inc
new file mode 100644
index 0000000000000000000000000000000000000000..2018b5503f58cf4f076c4f7a471d38d0ecb0e82c
--- /dev/null
+++ b/bandx/page/page.inc
@@ -0,0 +1,11 @@
+/* page list */
+PAGE_DEF(template)
+PAGE_DEF(dialplate)
+PAGE_DEF(launcher)
+PAGE_DEF(flashlight)
+PAGE_DEF(settings)
+PAGE_DEF(stop_watch)
+PAGE_DEF(sleep)
+PAGE_DEF(sport)
+PAGE_DEF(music)
+PAGE_DEF(heart_rate)
\ No newline at end of file
diff --git a/bandx/page/settings.c b/bandx/page/settings.c
new file mode 100644
index 0000000000000000000000000000000000000000..bb03f1d8a2ee8f5a7610de76c01de42d747149d0
--- /dev/null
+++ b/bandx/page/settings.c
@@ -0,0 +1,286 @@
+/**
+ * @file settings.c
+ *
+ */
+
+/*********************
+ * INCLUDES
+ *********************/
+#include "page.h"
+
+#include "../utils/lv_obj_ext_func.h"
+
+/*********************
+ * DEFINES
+ *********************/
+
+#define BAR_SETTING_CNT 2
+#define EVENT_CNT 9
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+typedef struct {
+ const char* text;
+ const char* img_src_left;
+ const char* img_src_right;
+ lv_coord_t y;
+
+ lv_obj_t* img_left;
+ lv_obj_t* img_right;
+ lv_obj_t* bar;
+} bar_setting_t;
+
+typedef struct {
+ lv_fragment_t base;
+ bar_setting_t bar_setting_grp[BAR_SETTING_CNT];
+ lv_auto_event_data_t event_data[EVENT_CNT];
+ lv_auto_event_t* auto_event;
+} page_ctx_t;
+
+/**********************
+ * STATIC PROTOTYPES
+ **********************/
+
+/**********************
+ * STATIC VARIABLES
+ **********************/
+
+/**********************
+ * MACROS
+ **********************/
+
+#define BX_NAME "BandX"
+#define BX_VERSION "v1.0"
+
+/**********************
+ * GLOBAL FUNCTIONS
+ **********************/
+
+/**********************
+ * STATIC FUNCTIONS
+ **********************/
+
+static void bar_setting_event_handler(lv_event_t* event)
+{
+ lv_obj_t* obj = lv_event_get_user_data(event);
+ lv_event_code_t code = lv_event_get_code(event);
+ if (code == LV_EVENT_CLICKED) {
+ bar_setting_t* bar_setting = (bar_setting_t*)lv_obj_get_user_data(obj);
+ const char* img_src = lv_img_get_src(obj);
+ int add_value = (lv_strcmp(img_src, resource_get_img(bar_setting->img_src_left)) == 0) ? -10 : +10;
+ int16_t old_value = lv_bar_get_value(bar_setting->bar);
+ lv_bar_set_value(bar_setting->bar, old_value + add_value, LV_ANIM_ON);
+ }
+}
+
+static void img_setting_set_style(lv_obj_t* img)
+{
+ lv_obj_add_event(img, bar_setting_event_handler, LV_EVENT_ALL, img);
+ lv_obj_add_flag(img, LV_OBJ_FLAG_CLICKABLE);
+
+ lv_obj_set_style_image_recolor_opa(img, LV_OPA_COVER, LV_PART_MAIN);
+ lv_obj_set_style_image_recolor(img, lv_color_make(0x66, 0x66, 0x66), LV_PART_MAIN);
+ lv_obj_set_style_image_recolor(img, lv_color_make(0x00, 0x89, 0xFF), LV_STATE_PRESSED);
+}
+
+static void bar_setting_create(lv_obj_t* par, bar_setting_t* bar_setting, int len)
+{
+ for (int i = 0; i < len; i++) {
+ lv_obj_t* obj_base = lv_obj_create(par);
+ lv_obj_remove_style_all(obj_base);
+ lv_obj_set_size(obj_base, PAGE_HOR_RES - 40, 50);
+ lv_obj_clear_flag(obj_base, LV_OBJ_FLAG_SCROLLABLE);
+ lv_obj_align(obj_base, LV_ALIGN_TOP_MID, 0, bar_setting[i].y);
+
+ lv_obj_t* label = lv_label_create(obj_base);
+ lv_obj_set_style_text_font(label, resource_get_font(BANDX_REGULAR_FONT "_20"), LV_PART_MAIN);
+ lv_obj_set_style_text_color(label, lv_color_make(0x66, 0x66, 0x66), LV_PART_MAIN);
+ lv_label_set_text(label, bar_setting[i].text);
+ lv_obj_align_to(label, obj_base, LV_ALIGN_TOP_LEFT, 0, 0);
+
+ lv_obj_t* bar = lv_bar_create(obj_base);
+ lv_obj_set_size(bar, 74, 6);
+ lv_obj_set_style_bg_color(bar, lv_color_make(0x00, 0x89, 0xFF), LV_PART_INDICATOR);
+ lv_obj_set_style_bg_color(bar, lv_color_make(0x33, 0x33, 0x33), LV_PART_MAIN);
+ lv_obj_align(bar, LV_ALIGN_BOTTOM_MID, 0, -10);
+ lv_bar_set_value(bar, 50, LV_ANIM_OFF);
+ bar_setting[i].bar = bar;
+
+ lv_obj_t* img1 = lv_img_create(obj_base);
+ lv_img_set_src(img1, resource_get_img(bar_setting[i].img_src_left));
+ lv_obj_set_user_data(img1, &(bar_setting[i]));
+ img_setting_set_style(img1);
+ lv_obj_align_to(img1, bar, LV_ALIGN_OUT_LEFT_MID, -10, 0);
+
+ lv_obj_t* img2 = lv_img_create(obj_base);
+ lv_img_set_src(img2, resource_get_img(bar_setting[i].img_src_right));
+ lv_obj_set_user_data(img2, &(bar_setting[i]));
+ img_setting_set_style(img2);
+ lv_obj_align_to(img2, bar, LV_ALIGN_OUT_RIGHT_MID, 10, 0);
+
+ bar_setting[i].img_left = img1;
+ bar_setting[i].img_right = img2;
+ }
+}
+
+static void sw_auto_show_event_handler(lv_event_t* e)
+{
+ lv_event_code_t code = lv_event_get_code(e);
+ page_ctx_t* ctx = lv_event_get_user_data(e);
+
+ if (code == LV_EVENT_VALUE_CHANGED) {
+ page_set_autoshow_enable(!page_get_autoshow_enable());
+ if (page_get_autoshow_enable()) {
+ LV_LOG_USER("Auto-show Begin ...");
+ lv_obj_send_event(ctx->base.obj, LV_EVENT_LEAVE, NULL);
+ } else {
+ lv_auto_event_del(ctx->auto_event);
+ ctx->auto_event = NULL;
+ LV_LOG_USER("Auto-show End ...");
+ }
+ }
+}
+
+static void sw_auto_show_create(page_ctx_t* ctx)
+{
+ lv_obj_t* par = ctx->base.obj;
+ lv_obj_t* label = lv_label_create(par);
+ lv_obj_set_style_text_font(label, resource_get_font(BANDX_REGULAR_FONT "_20"), LV_PART_MAIN);
+ lv_obj_set_style_text_color(label, lv_color_make(0x66, 0x66, 0x66), LV_PART_MAIN);
+ lv_label_set_text(label, "Auto-show");
+ lv_obj_align(label, LV_ALIGN_TOP_LEFT, 23, 187);
+
+ lv_obj_t* sw = lv_switch_create(par);
+ lv_obj_set_size(sw, 41, 23);
+ lv_obj_align_to(sw, label, LV_ALIGN_OUT_RIGHT_MID, 12, -5);
+ lv_obj_set_style_bg_color(sw, lv_color_make(0x33, 0x33, 0x33), LV_PART_MAIN);
+ lv_obj_set_style_bg_color(sw, lv_color_make(0x00, 0x89, 0xFF), LV_PART_INDICATOR);
+ lv_obj_set_style_bg_color(sw, lv_color_make(0xAA, 0xAA, 0xAA), LV_PART_KNOB);
+ lv_obj_set_style_outline_width(sw, 0, LV_PART_MAIN);
+ lv_obj_add_event(sw, sw_auto_show_event_handler, LV_EVENT_ALL, ctx);
+ page_get_autoshow_enable() ? lv_obj_add_state(sw, LV_STATE_CHECKED) : lv_obj_clear_state(sw, LV_STATE_CHECKED);
+}
+
+static void label_info_create(lv_obj_t* par)
+{
+ lv_obj_t* label = lv_label_create(par);
+ lv_obj_set_style_text_font(label, resource_get_font(BANDX_REGULAR_FONT "_20"), LV_PART_MAIN);
+ lv_obj_set_style_text_color(label, lv_color_white(), LV_PART_MAIN);
+ lv_label_set_text(label, BX_NAME " " BX_VERSION "\n"__DATE__
+ "\nBuild");
+ lv_obj_align(label, LV_ALIGN_TOP_MID, 0, 270);
+}
+
+static void auto_event_create(page_ctx_t* ctx)
+{
+ bar_setting_t* bar_setting_grp = ctx->bar_setting_grp;
+ lv_auto_event_data_t ae_grp[EVENT_CNT] = {
+ { &(bar_setting_grp[0].img_left), LV_EVENT_CLICKED, 500 },
+ { &(bar_setting_grp[0].img_left), LV_EVENT_CLICKED, 500 },
+ { &(bar_setting_grp[0].img_right), LV_EVENT_CLICKED, 500 },
+ { &(bar_setting_grp[0].img_right), LV_EVENT_CLICKED, 500 },
+ { &(bar_setting_grp[1].img_left), LV_EVENT_CLICKED, 500 },
+ { &(bar_setting_grp[1].img_left), LV_EVENT_CLICKED, 500 },
+ { &(bar_setting_grp[1].img_right), LV_EVENT_CLICKED, 500 },
+ { &(bar_setting_grp[1].img_right), LV_EVENT_CLICKED, 500 },
+ { &ctx->base.obj, LV_EVENT_LEAVE, 1000 },
+ };
+ for (int i = 0; i < EVENT_CNT; i++) {
+ ctx->event_data[i] = ae_grp[i];
+ }
+ AUTO_EVENT_CREATE(ctx->auto_event, ctx->event_data);
+}
+
+static void on_root_event(lv_event_t* e)
+{
+ lv_obj_t* root = lv_event_get_current_target_obj(e);
+ lv_event_code_t code = lv_event_get_code(e);
+ page_ctx_t* ctx = lv_obj_get_user_data(root);
+
+ if (code == LV_EVENT_GESTURE) {
+ lv_dir_t dir = lv_indev_get_gesture_dir(lv_indev_get_act());
+ if (dir == LV_DIR_RIGHT) {
+ lv_obj_send_event(root, LV_EVENT_LEAVE, NULL);
+ }
+ } else if (code == LV_EVENT_LEAVE) {
+ page_pop(&ctx->base);
+ }
+}
+
+static void on_page_construct(lv_fragment_t* self, void* args)
+{
+ LV_LOG_INFO("self: %p args: %p", self, args);
+
+ page_ctx_t* ctx = (page_ctx_t*)self;
+
+ bar_setting_t bar_setting_grp[BAR_SETTING_CNT] = {
+ { .text = "Volume", .img_src_left = "icon_volume_reduce", .img_src_right = "icon_volume_add", 24 },
+ { .text = "Backlight", .img_src_left = "icon_minus", .img_src_right = "icon_plus", 98 },
+ };
+
+ for (int i = 0; i < BAR_SETTING_CNT; i++) {
+ ctx->bar_setting_grp[i] = bar_setting_grp[i];
+ }
+}
+
+static void on_page_destruct(lv_fragment_t* self)
+{
+ LV_LOG_INFO("self: %p", self);
+}
+
+static void on_page_attached(lv_fragment_t* self)
+{
+ LV_LOG_INFO("self: %p", self);
+}
+
+static void on_page_detached(lv_fragment_t* self)
+{
+ LV_LOG_INFO("self: %p", self);
+}
+
+static lv_obj_t* on_page_create(lv_fragment_t* self, lv_obj_t* container)
+{
+ LV_LOG_INFO("self: %p container: %p", self, container);
+
+ lv_obj_t* root = lv_obj_create(container);
+ lv_obj_remove_style_all(root);
+ lv_obj_add_style(root, resource_get_style("root_def"), 0);
+ lv_obj_add_event(root, on_root_event, LV_EVENT_ALL, NULL);
+ lv_obj_clear_flag(root, LV_OBJ_FLAG_GESTURE_BUBBLE);
+ lv_obj_set_user_data(root, self);
+ return root;
+}
+
+static void on_page_created(lv_fragment_t* self, lv_obj_t* obj)
+{
+ LV_LOG_INFO("self: %p obj: %p", self, obj);
+
+ page_ctx_t* ctx = (page_ctx_t*)self;
+
+ bar_setting_create(obj, ctx->bar_setting_grp, BAR_SETTING_CNT);
+ sw_auto_show_create(ctx);
+ label_info_create(obj);
+ auto_event_create(ctx);
+}
+
+static void on_page_will_delete(lv_fragment_t* self, lv_obj_t* obj)
+{
+ LV_LOG_INFO("self: %p obj: %p", self, obj);
+ page_ctx_t* ctx = (page_ctx_t*)self;
+ lv_auto_event_del(ctx->auto_event);
+}
+
+static void on_page_deleted(lv_fragment_t* self, lv_obj_t* obj)
+{
+ LV_LOG_INFO("self: %p obj: %p", self, obj);
+}
+
+static bool on_page_event(lv_fragment_t* self, int code, void* user_data)
+{
+ LV_LOG_INFO("self: %p code: %d user_data: %p", self, code, user_data);
+ return false;
+}
+
+PAGE_CLASS_DEF(settings);
diff --git a/bandx/page/sleep.c b/bandx/page/sleep.c
new file mode 100644
index 0000000000000000000000000000000000000000..d20444f00d868c0d7dcc8b18c090fb0443757904
--- /dev/null
+++ b/bandx/page/sleep.c
@@ -0,0 +1,469 @@
+/**
+ * @file sleep.c
+ *
+ */
+
+/*********************
+ * INCLUDES
+ *********************/
+#include "page.h"
+
+#include "../utils/lv_obj_ext_func.h"
+
+/*********************
+ * DEFINES
+ *********************/
+
+#define SLEEP_TIME_CNT 6
+#define SLEEP_INFO_CNT 4
+#define EVENT_CNT 3
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+typedef enum {
+ SLEEP_TYPE_SHALLOW,
+ SLEEP_TYPE_DEEP,
+ SLEEP_TYPE_REM,
+ SLEEP_TYPE_AWAKE,
+ SLEEP_TYPE_MAX
+} sleep_type;
+
+typedef struct {
+ const char* text;
+ sleep_type type;
+} sleep_info_t;
+
+typedef struct {
+ sleep_type type;
+ uint32_t start_min;
+ uint32_t end_min;
+} sleep_time_t;
+
+typedef struct {
+ lv_fragment_t base;
+ lv_obj_t* img_down;
+ lv_obj_t* img_up;
+ lv_obj_t* cont_sleep;
+
+ uint32_t sleep_type_color[SLEEP_TYPE_MAX];
+ sleep_time_t sleep_time_grp[SLEEP_TIME_CNT];
+ sleep_info_t sleep_info_grp[SLEEP_INFO_CNT];
+ lv_auto_event_data_t event_data[EVENT_CNT];
+ lv_auto_event_t* auto_event;
+} page_ctx_t;
+
+/**********************
+ * STATIC PROTOTYPES
+ **********************/
+
+/**********************
+ * STATIC VARIABLES
+ **********************/
+
+/**********************
+ * MACROS
+ **********************/
+
+#define CONT_SLEEP_MOVE_DOWN(down, obj) \
+ LV_OBJ_ADD_ANIM( \
+ obj, \
+ y, \
+ (down) ? -PAGE_VER_RES : 0, LV_ANIM_TIME_DEFAULT)
+
+/**********************
+ * GLOBAL FUNCTIONS
+ **********************/
+
+/**********************
+ * STATIC FUNCTIONS
+ **********************/
+
+static void cont_sleep_create(lv_obj_t* par)
+{
+ page_ctx_t* ctx = (page_ctx_t*)lv_obj_get_user_data(par);
+ lv_obj_t* obj = lv_obj_create(par);
+ lv_obj_remove_style_all(obj);
+ lv_obj_set_size(obj, PAGE_HOR_RES, PAGE_VER_RES * 2);
+ lv_obj_align(obj, LV_ALIGN_TOP_MID, 0, 0);
+ lv_obj_set_style_bg_color(obj, lv_color_black(), LV_PART_MAIN);
+ lv_obj_set_user_data(obj, ctx);
+
+ ctx->cont_sleep = obj;
+}
+
+static void img_arrow_event_handler(lv_event_t* event)
+{
+ lv_event_code_t code = lv_event_get_code(event);
+ lv_obj_t* obj = lv_event_get_current_target_obj(event);
+ page_ctx_t* ctx = (page_ctx_t*)lv_event_get_user_data(event);
+
+ if (code == LV_EVENT_CLICKED) {
+ bool is_down = (lv_strcmp(lv_img_get_src(obj), resource_get_img("arrow_down")) == 0);
+ CONT_SLEEP_MOVE_DOWN(is_down, ctx->cont_sleep);
+ }
+}
+
+static void img_arrow_create(lv_obj_t* par)
+{
+ page_ctx_t* ctx = (page_ctx_t*)lv_obj_get_user_data(par);
+
+ lv_obj_t* img1 = lv_img_create(par);
+ lv_img_set_src(img1, resource_get_img("arrow_down"));
+ lv_obj_align(img1, LV_ALIGN_TOP_MID, 0, 339);
+ lv_obj_add_event(img1, img_arrow_event_handler, LV_EVENT_ALL, ctx);
+ lv_obj_add_flag(img1, LV_OBJ_FLAG_CLICKABLE);
+
+ lv_obj_t* img2 = lv_img_create(par);
+ lv_img_set_src(img2, resource_get_img("arrow_up"));
+ lv_obj_align(img2, LV_ALIGN_TOP_MID, 0, PAGE_VER_RES + 12);
+ lv_obj_add_event(img2, img_arrow_event_handler, LV_EVENT_ALL, ctx);
+ lv_obj_add_flag(img2, LV_OBJ_FLAG_CLICKABLE);
+
+ ctx->img_down = img1;
+ ctx->img_up = img2;
+}
+
+static void img_sleep_create(lv_obj_t* par)
+{
+ lv_obj_t* img = lv_img_create(par);
+ lv_img_set_src(img, resource_get_img("sleep"));
+ lv_obj_align(img, LV_ALIGN_TOP_MID, 0, 32);
+}
+
+static void label_score_create(lv_obj_t* par)
+{
+ lv_obj_t* label1 = lv_label_create(par);
+ lv_obj_set_style_text_font(
+ label1,
+ resource_get_font(BANDX_REGULAR_FONT "_20"),
+ LV_PART_MAIN);
+ lv_obj_set_style_text_color(label1, lv_color_hex(0x808080), LV_PART_MAIN);
+ lv_label_set_text(label1, "Score");
+ lv_obj_align(label1, LV_ALIGN_TOP_MID, 0, 123);
+
+ lv_obj_t* label2 = lv_label_create(par);
+ lv_obj_set_style_text_font(
+ label2,
+ resource_get_font(BANDX_REGULAR_FONT "_72"),
+ LV_PART_MAIN);
+ lv_obj_set_style_text_color(label2, lv_color_white(), LV_PART_MAIN);
+ lv_label_set_text(label2, "78");
+ lv_obj_align(label2, LV_ALIGN_TOP_MID, 0, 148);
+}
+
+static void label_total_time_create(lv_obj_t* par)
+{
+ lv_obj_t* label1 = lv_label_create(par);
+ lv_obj_set_style_text_font(label1, resource_get_font(BANDX_REGULAR_FONT "_20"), LV_PART_MAIN);
+ lv_obj_set_style_text_color(label1, lv_color_hex(0x808080), LV_PART_MAIN);
+ lv_label_set_text(label1, "Total time");
+ lv_obj_align(label1, LV_ALIGN_TOP_MID, 0, 224);
+
+ lv_obj_t* label2 = lv_label_create(par);
+ lv_obj_set_style_text_font(label2, resource_get_font(BANDX_REGULAR_FONT "_20"), LV_PART_MAIN);
+ lv_obj_set_style_text_color(label2, lv_color_hex(0x3FA9F5), LV_PART_MAIN);
+ lv_label_set_text(label2, "8 h 52 min");
+ lv_obj_set_style_text_align(label2, LV_ALIGN_CENTER, 0);
+ lv_obj_align(label2, LV_ALIGN_TOP_MID, 0, 249);
+
+ lv_obj_t* spangroup = lv_spangroup_create(par);
+ lv_obj_set_style_text_font(spangroup, resource_get_font(BANDX_REGULAR_FONT "_20"), LV_PART_MAIN);
+
+ lv_span_t* span = lv_spangroup_new_span(spangroup);
+ lv_span_set_text_static(span, "00:29");
+ lv_style_set_text_color(&span->style, lv_color_hex(0x3FA9F5));
+
+ lv_span_t* span1 = lv_spangroup_new_span(spangroup);
+ lv_span_set_text_static(span1, "-");
+ lv_style_set_text_color(&span1->style, lv_color_hex(0x808080));
+
+ lv_span_t* span2 = lv_spangroup_new_span(spangroup);
+ lv_span_set_text_static(span2, "09:48");
+ lv_style_set_text_color(&span2->style, lv_color_hex(0x3FA9F5));
+ lv_obj_align(spangroup, LV_ALIGN_TOP_MID, 0, 274);
+}
+
+static uint32_t sleep_time_get_sum(sleep_time_t* sleep_time, int len, sleep_type check_type)
+{
+ uint32_t sum = 0;
+ if (check_type == SLEEP_TYPE_SHALLOW) {
+ sum = sleep_time[0].end_min - sleep_time[0].start_min;
+ sum -= sleep_time_get_sum(sleep_time, len, SLEEP_TYPE_DEEP);
+ sum -= sleep_time_get_sum(sleep_time, len, SLEEP_TYPE_REM);
+ sum -= sleep_time_get_sum(sleep_time, len, SLEEP_TYPE_AWAKE);
+ } else {
+ for (int i = 0; i < len; i++) {
+ if (sleep_time[i].type == check_type) {
+ uint32_t time = sleep_time[i].end_min - sleep_time[i].start_min;
+ sum += time;
+ }
+ }
+ }
+ return sum;
+}
+
+static void obj_sleep_time_create(lv_obj_t* par, sleep_time_t* sleep_time, int len)
+{
+ page_ctx_t* ctx = (page_ctx_t*)lv_obj_get_user_data(par);
+
+ lv_obj_t* obj_base = lv_obj_create(par);
+ lv_obj_remove_style_all(obj_base);
+ lv_obj_set_size(obj_base, 165, 74);
+ lv_obj_set_style_radius(obj_base, 10, LV_PART_MAIN);
+ lv_obj_set_style_clip_corner(obj_base, 10, LV_PART_MAIN);
+
+ lv_obj_align(obj_base, LV_ALIGN_TOP_MID, 0, PAGE_VER_RES + 42);
+ lv_obj_clear_flag(obj_base, LV_OBJ_FLAG_SCROLLABLE);
+
+ uint32_t time_len = sleep_time[0].end_min - sleep_time[0].start_min;
+ uint32_t time_start = sleep_time[0].start_min;
+ lv_coord_t width_base = lv_obj_get_style_width(obj_base, 0);
+
+ for (int i = 0; i < len; i++) {
+ uint8_t type_index = sleep_time[i].type;
+ lv_color_t color = lv_color_hex(ctx->sleep_type_color[type_index]);
+
+ lv_obj_t* obj = lv_obj_create(obj_base);
+ lv_obj_set_style_radius(obj, 0, LV_PART_MAIN);
+ lv_obj_set_style_border_width(obj, 0, LV_PART_MAIN);
+ lv_obj_set_style_bg_color(obj, color, LV_PART_MAIN);
+
+ lv_coord_t width = width_base * (sleep_time[i].end_min - sleep_time[i].start_min) / time_len;
+ lv_coord_t x_ofs = width_base * (sleep_time[i].start_min - time_start) / time_len;
+
+ lv_obj_set_size(obj, width, lv_obj_get_style_height(obj_base, 0));
+ lv_obj_clear_flag(obj, LV_OBJ_FLAG_SCROLLABLE);
+ lv_obj_align(obj, LV_ALIGN_TOP_LEFT, x_ofs, 0);
+ }
+
+ lv_obj_t* label1 = lv_label_create(par);
+ lv_obj_set_style_text_font(
+ label1,
+ resource_get_font(BANDX_REGULAR_FONT "_15"),
+ LV_PART_MAIN);
+ lv_obj_set_style_text_color(
+ label1,
+ lv_color_make(0x80, 0x80, 0x80),
+ LV_PART_MAIN);
+
+ uint32_t start_min = sleep_time[0].start_min;
+ lv_label_set_text_fmt(label1, "%02" LV_PRIu32 ":%02" LV_PRIu32, start_min / 60, start_min % 60);
+ lv_obj_align_to(label1, obj_base, LV_ALIGN_OUT_BOTTOM_LEFT, 0, 5);
+
+ lv_obj_t* label2 = lv_label_create(par);
+ lv_obj_set_style_text_font(
+ label2,
+ resource_get_font(BANDX_REGULAR_FONT "_15"),
+ LV_PART_MAIN);
+ lv_obj_set_style_text_color(
+ label2,
+ lv_color_make(0x80, 0x80, 0x80),
+ LV_PART_MAIN);
+
+ uint32_t end_min = sleep_time[0].end_min;
+ lv_label_set_text_fmt(label2, "%02" LV_PRIu32 ":%02" LV_PRIu32, end_min / 60, end_min % 60);
+ lv_obj_align_to(label2, obj_base, LV_ALIGN_OUT_BOTTOM_RIGHT, 0, 5);
+}
+
+static void obj_sleep_info_create(lv_obj_t* par, sleep_info_t* sleep_info, int len)
+{
+ page_ctx_t* ctx = (page_ctx_t*)lv_obj_get_user_data(par);
+
+ for (int i = 0; i < len; i++) {
+ lv_obj_t* obj_base = lv_obj_create(par);
+ lv_obj_remove_style_all(obj_base);
+ lv_obj_set_style_bg_opa(obj_base, LV_OPA_0, LV_PART_MAIN);
+ lv_obj_set_size(obj_base, PAGE_HOR_RES - 40, 50);
+ lv_obj_clear_flag(obj_base, LV_OBJ_FLAG_SCROLLABLE);
+
+ lv_coord_t y_ofs = PAGE_VER_RES + 150 + (lv_obj_get_style_height(obj_base, 0) + 5) * i;
+ lv_obj_align(obj_base, LV_ALIGN_TOP_MID, 0, y_ofs);
+
+ lv_obj_t* label1 = lv_label_create(obj_base);
+ lv_obj_set_style_text_font(
+ label1,
+ resource_get_font(BANDX_REGULAR_FONT "_20"),
+ LV_PART_MAIN);
+ lv_obj_set_style_text_color(
+ label1,
+ lv_color_make(0x80, 0x80, 0x80),
+ LV_PART_MAIN);
+ lv_label_set_text(label1, sleep_info[i].text);
+ lv_obj_align_to(label1, obj_base, LV_ALIGN_TOP_LEFT, 0, 0);
+
+ lv_obj_t* label2 = lv_label_create(obj_base);
+ lv_obj_set_style_text_font(
+ label2,
+ resource_get_font(BANDX_REGULAR_FONT "_20"),
+ LV_PART_MAIN);
+ lv_obj_set_style_text_color(
+ label2,
+ lv_color_white(),
+ LV_PART_MAIN);
+ uint32_t time = sleep_time_get_sum(
+ ctx->sleep_time_grp,
+ SLEEP_TIME_CNT,
+ sleep_info[i].type);
+ lv_label_set_text_fmt(label2, "%" LV_PRIu32 "h %" LV_PRIu32 "min", time / 60, time % 60);
+ lv_obj_align_to(label2, obj_base, LV_ALIGN_BOTTOM_LEFT, 0, 0);
+
+ lv_obj_t* label3 = lv_label_create(obj_base);
+ lv_obj_set_style_text_font(
+ label3,
+ resource_get_font(BANDX_REGULAR_FONT "_20"),
+ LV_PART_MAIN);
+ lv_obj_set_style_text_color(
+ label3,
+ lv_color_hex(ctx->sleep_type_color[sleep_info[i].type]),
+ LV_PART_MAIN);
+ uint32_t time_len = ctx->sleep_time_grp[0].end_min - ctx->sleep_time_grp[0].start_min;
+ lv_label_set_text_fmt(label3, "%" LV_PRIu32 "%%", time * 100 / time_len);
+ lv_obj_align_to(label3, obj_base, LV_ALIGN_BOTTOM_RIGHT, 0, 0);
+ }
+}
+
+static void auto_event_create(page_ctx_t* ctx)
+{
+ lv_auto_event_data_t ae_grp[EVENT_CNT] = {
+ { &ctx->img_down, LV_EVENT_CLICKED, 2000 },
+ { &ctx->img_up, LV_EVENT_CLICKED, 2000 },
+ { &ctx->base.obj, LV_EVENT_LEAVE, 2000 },
+ };
+ for (int i = 0; i < EVENT_CNT; i++) {
+ ctx->event_data[i] = ae_grp[i];
+ }
+
+ AUTO_EVENT_CREATE(ctx->auto_event, ctx->event_data);
+}
+
+static void on_root_event(lv_event_t* e)
+{
+ lv_obj_t* root = lv_event_get_current_target_obj(e);
+ lv_event_code_t code = lv_event_get_code(e);
+ page_ctx_t* ctx = lv_obj_get_user_data(root);
+
+ if (code == LV_EVENT_GESTURE) {
+ lv_dir_t dir = lv_indev_get_gesture_dir(lv_indev_get_act());
+ if (dir == LV_DIR_RIGHT) {
+ lv_obj_send_event(root, LV_EVENT_LEAVE, NULL);
+ } else if (dir == LV_DIR_BOTTOM) {
+ CONT_SLEEP_MOVE_DOWN(false, ctx->cont_sleep);
+ } else if (dir == LV_DIR_TOP) {
+ CONT_SLEEP_MOVE_DOWN(true, ctx->cont_sleep);
+ }
+ } else if (code == LV_EVENT_LEAVE) {
+ page_pop(&ctx->base);
+ }
+}
+
+static void on_page_construct(lv_fragment_t* self, void* args)
+{
+ LV_LOG_INFO("self: %p args: %p", self, args);
+
+ page_ctx_t* ctx = (page_ctx_t*)self;
+
+ uint32_t sleep_type_color[SLEEP_TYPE_MAX] = {
+ 0x3f71f5,
+ 0x023192,
+ 0x7ac943,
+ 0xff931e,
+ };
+
+ for (int i = 0; i < SLEEP_TYPE_MAX; i++) {
+ ctx->sleep_type_color[i] = sleep_type_color[i];
+ }
+
+ sleep_time_t sleep_time_grp[SLEEP_TIME_CNT] = {
+ { SLEEP_TYPE_SHALLOW, 29, 588 },
+ { SLEEP_TYPE_DEEP, 61, 80 },
+ { SLEEP_TYPE_DEEP, 120, 200 },
+ { SLEEP_TYPE_REM, 80, 95 },
+ { SLEEP_TYPE_REM, 250, 310 },
+ { SLEEP_TYPE_AWAKE, 400, 410 },
+ };
+
+ for (int i = 0; i < SLEEP_TIME_CNT; i++) {
+ ctx->sleep_time_grp[i] = sleep_time_grp[i];
+ }
+
+ sleep_info_t sleep_info_grp[SLEEP_INFO_CNT] = {
+ { "Shallow sleep", SLEEP_TYPE_SHALLOW },
+ { "Deep sleep", SLEEP_TYPE_DEEP },
+ { "REM", SLEEP_TYPE_REM },
+ { "Awake", SLEEP_TYPE_AWAKE },
+ };
+
+ for (int i = 0; i < SLEEP_INFO_CNT; i++) {
+ ctx->sleep_info_grp[i] = sleep_info_grp[i];
+ }
+}
+
+static void on_page_destruct(lv_fragment_t* self)
+{
+ LV_LOG_INFO("self: %p", self);
+}
+
+static void on_page_attached(lv_fragment_t* self)
+{
+ LV_LOG_INFO("self: %p", self);
+}
+
+static void on_page_detached(lv_fragment_t* self)
+{
+ LV_LOG_INFO("self: %p", self);
+}
+
+static lv_obj_t* on_page_create(lv_fragment_t* self, lv_obj_t* container)
+{
+ LV_LOG_INFO("self: %p container: %p", self, container);
+
+ lv_obj_t* root = lv_obj_create(container);
+ lv_obj_remove_style_all(root);
+ lv_obj_add_style(root, resource_get_style("root_def"), 0);
+ lv_obj_add_event(root, on_root_event, LV_EVENT_ALL, NULL);
+ lv_obj_clear_flag(root, LV_OBJ_FLAG_GESTURE_BUBBLE);
+ lv_obj_clear_flag(root, LV_OBJ_FLAG_SCROLLABLE);
+ lv_obj_set_user_data(root, self);
+ return root;
+}
+
+static void on_page_created(lv_fragment_t* self, lv_obj_t* obj)
+{
+ LV_LOG_INFO("self: %p obj: %p", self, obj);
+
+ page_ctx_t* ctx = (page_ctx_t*)self;
+
+ cont_sleep_create(obj);
+
+ img_arrow_create(ctx->cont_sleep);
+ img_sleep_create(ctx->cont_sleep);
+ label_score_create(ctx->cont_sleep);
+ label_total_time_create(ctx->cont_sleep);
+
+ obj_sleep_time_create(ctx->cont_sleep, ctx->sleep_time_grp, SLEEP_TIME_CNT);
+ obj_sleep_info_create(ctx->cont_sleep, ctx->sleep_info_grp, SLEEP_INFO_CNT);
+ auto_event_create(ctx);
+}
+
+static void on_page_will_delete(lv_fragment_t* self, lv_obj_t* obj)
+{
+ LV_LOG_INFO("self: %p obj: %p", self, obj);
+
+ page_ctx_t* ctx = (page_ctx_t*)self;
+ AUTO_EVENT_DELETE(ctx->auto_event);
+}
+
+static void on_page_deleted(lv_fragment_t* self, lv_obj_t* obj)
+{
+ LV_LOG_INFO("self: %p obj: %p", self, obj);
+}
+
+static bool on_page_event(lv_fragment_t* self, int code, void* user_data)
+{
+ LV_LOG_INFO("self: %p code: %d user_data: %p", self, code, user_data);
+ return false;
+}
+
+PAGE_CLASS_DEF(sleep);
diff --git a/bandx/page/sport.c b/bandx/page/sport.c
new file mode 100644
index 0000000000000000000000000000000000000000..3cfd43ccd9e81e8fcfa150f8788b02274bf64f81
--- /dev/null
+++ b/bandx/page/sport.c
@@ -0,0 +1,233 @@
+/**
+ * @file sport.c
+ *
+ */
+
+/*********************
+ * INCLUDES
+ *********************/
+#include "page.h"
+
+/*********************
+ * DEFINES
+ *********************/
+
+#define BTN_WIDTH 170
+#define BTN_HEIGHT 50
+#define SPORT_INFO_CNT 9
+#define EVENT_CNT 2
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+typedef struct {
+ const char* img_src_name;
+ const char* sport_name;
+ uint32_t color;
+} sport_info_t;
+
+typedef struct {
+ lv_fragment_t base;
+ lv_obj_t* list;
+ sport_info_t sport_info_grp[SPORT_INFO_CNT];
+ lv_auto_event_data_t event_data[EVENT_CNT];
+ lv_auto_event_t* auto_event;
+} page_ctx_t;
+
+/**********************
+ * STATIC PROTOTYPES
+ **********************/
+
+/**********************
+ * STATIC VARIABLES
+ **********************/
+
+/**********************
+ * MACROS
+ **********************/
+
+/**********************
+ * GLOBAL FUNCTIONS
+ **********************/
+
+/**********************
+ * STATIC FUNCTIONS
+ **********************/
+
+static void on_sport_icon_event(lv_event_t* event)
+{
+ lv_event_code_t code = lv_event_get_code(event);
+ if (code == LV_EVENT_CLICKED) {
+ LV_LOG_WARN("NO match for this sport.");
+ }
+}
+static void on_sport_list_event(lv_event_t* event)
+{
+ lv_event_code_t code = lv_event_get_code(event);
+ lv_obj_t* last_obj = lv_event_get_user_data(event);
+ if (code == LV_EVENT_VALUE_CHANGED) {
+ lv_obj_scroll_to_view(last_obj, LV_ANIM_ON);
+ }
+}
+
+static void sport_title_create(lv_obj_t* par)
+{
+ lv_obj_t* obj = lv_obj_create(par);
+ lv_obj_remove_style_all(obj);
+ lv_obj_set_size(obj, PAGE_HOR_RES, 35);
+ lv_obj_align(obj, LV_ALIGN_TOP_MID, 0, 0);
+
+ lv_obj_t* label = lv_label_create(obj);
+ lv_label_set_text(label, "Sports");
+ lv_obj_set_style_text_color(label, lv_color_white(), LV_PART_MAIN);
+ lv_obj_set_style_text_font(label, resource_get_font(BANDX_REGULAR_FONT "_15"), LV_PART_MAIN);
+ lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
+}
+
+static void sport_list_create(page_ctx_t* ctx)
+{
+ ctx->list = lv_obj_create(ctx->base.obj);
+ lv_obj_remove_style_all(ctx->list);
+ lv_obj_set_size(ctx->list, PAGE_HOR_RES, PAGE_VER_RES - 40);
+ lv_obj_set_flex_flow(ctx->list, LV_FLEX_FLOW_COLUMN);
+ lv_obj_set_flex_align(ctx->list, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
+ lv_obj_set_style_pad_row(ctx->list, 10, 0);
+ lv_obj_align(ctx->list, LV_ALIGN_TOP_MID, 0, 40);
+
+ lv_obj_t* obj_base = NULL;
+
+ for (int i = 0; i < SPORT_INFO_CNT; i++) {
+
+ obj_base = lv_btn_create(ctx->list);
+ lv_obj_set_style_bg_color(obj_base, lv_color_hex(0x666666), LV_PART_MAIN);
+ lv_obj_set_style_border_width(obj_base, 0, LV_PART_MAIN);
+ lv_obj_set_size(obj_base, BTN_WIDTH, BTN_HEIGHT);
+ lv_obj_set_style_pad_left(obj_base, 10, LV_PART_MAIN);
+ lv_obj_add_event(obj_base, on_sport_icon_event, LV_EVENT_ALL, NULL);
+
+ lv_obj_t* img = lv_img_create(obj_base);
+ lv_img_set_src(img, resource_get_img(ctx->sport_info_grp[i].img_src_name));
+ lv_obj_set_style_image_recolor_opa(img, LV_OPA_COVER, LV_PART_MAIN);
+ lv_obj_set_style_image_recolor(img, lv_color_hex(ctx->sport_info_grp[i].color), LV_PART_MAIN);
+ lv_obj_align(img, LV_ALIGN_LEFT_MID, 0, 0);
+
+ lv_obj_t* label = lv_label_create(obj_base);
+ lv_label_set_text(label, ctx->sport_info_grp[i].sport_name);
+ lv_obj_set_style_text_color(label, lv_color_white(), LV_PART_MAIN);
+ lv_obj_set_style_text_font(label, resource_get_font(BANDX_REGULAR_FONT "_15"), LV_PART_MAIN);
+ lv_obj_align_to(label, img, LV_ALIGN_OUT_RIGHT_MID, 4, 0);
+ }
+ lv_obj_add_event(ctx->list, on_sport_list_event, LV_EVENT_ALL, obj_base);
+}
+
+static void auto_event_create(page_ctx_t* ctx)
+{
+ lv_auto_event_data_t ae_grp[EVENT_CNT] = {
+ { &ctx->list, LV_EVENT_VALUE_CHANGED, 1000 },
+ { &ctx->base.obj, LV_EVENT_LEAVE, 2000 },
+ };
+ for (int i = 0; i < EVENT_CNT; i++) {
+ ctx->event_data[i] = ae_grp[i];
+ }
+ AUTO_EVENT_CREATE(ctx->auto_event, ctx->event_data);
+}
+
+static void on_root_event(lv_event_t* e)
+{
+ lv_obj_t* root = lv_event_get_current_target_obj(e);
+ lv_event_code_t code = lv_event_get_code(e);
+ page_ctx_t* ctx = lv_obj_get_user_data(root);
+
+ if (code == LV_EVENT_GESTURE) {
+ lv_dir_t dir = lv_indev_get_gesture_dir(lv_indev_get_act());
+ if (dir == LV_DIR_RIGHT) {
+ lv_obj_send_event(root, LV_EVENT_LEAVE, NULL);
+ }
+ } else if (code == LV_EVENT_LEAVE) {
+ page_pop(&ctx->base);
+ }
+}
+
+static void on_page_construct(lv_fragment_t* self, void* args)
+{
+ LV_LOG_INFO("self: %p args: %p", self, args);
+
+ page_ctx_t* ctx = (page_ctx_t*)self;
+
+ sport_info_t sport_info_grp[SPORT_INFO_CNT] = {
+ { "icon_running", "Running", 0x9AFF9A },
+ { "icon_stretching", "Stretching", 0xFFF68F },
+ { "icon_riding", "Riding", 0xFFC1C1 },
+ { "icon_skiing", "Skiing", 0xAB82FF },
+ { "icon_yoga", "Yoga", 0x97FFFF },
+ { "icon_tabletennis", "Table tennis", 0xFF83FA },
+ { "icon_basketball", "Basketball", 0xFFA54F },
+ { "icon_volleyball", "Volleyball", 0x87CEFA },
+ { "icon_more_sports", "More sports", 0x4876FF },
+ };
+
+ for (int i = 0; i < SPORT_INFO_CNT; i++) {
+ ctx->sport_info_grp[i] = sport_info_grp[i];
+ }
+}
+
+static void on_page_destruct(lv_fragment_t* self)
+{
+ LV_LOG_INFO("self: %p", self);
+}
+
+static void on_page_attached(lv_fragment_t* self)
+{
+ LV_LOG_INFO("self: %p", self);
+}
+
+static void on_page_detached(lv_fragment_t* self)
+{
+ LV_LOG_INFO("self: %p", self);
+}
+
+static lv_obj_t* on_page_create(lv_fragment_t* self, lv_obj_t* container)
+{
+ LV_LOG_INFO("self: %p container: %p", self, container);
+
+ lv_obj_t* root = lv_obj_create(container);
+ lv_obj_remove_style_all(root);
+ lv_obj_add_style(root, resource_get_style("root_def"), 0);
+ lv_obj_add_event(root, on_root_event, LV_EVENT_ALL, NULL);
+ lv_obj_clear_flag(root, LV_OBJ_FLAG_GESTURE_BUBBLE);
+ lv_obj_clear_flag(root, LV_OBJ_FLAG_SCROLLABLE);
+ lv_obj_set_user_data(root, self);
+ return root;
+}
+
+static void on_page_created(lv_fragment_t* self, lv_obj_t* obj)
+{
+ LV_LOG_INFO("self: %p obj: %p", self, obj);
+
+ page_ctx_t* ctx = (page_ctx_t*)self;
+
+ sport_title_create(obj);
+ sport_list_create(ctx);
+ auto_event_create(ctx);
+}
+
+static void on_page_will_delete(lv_fragment_t* self, lv_obj_t* obj)
+{
+ LV_LOG_INFO("self: %p obj: %p", self, obj);
+ page_ctx_t* ctx = (page_ctx_t*)self;
+ AUTO_EVENT_DELETE(ctx->auto_event);
+}
+
+static void on_page_deleted(lv_fragment_t* self, lv_obj_t* obj)
+{
+ LV_LOG_INFO("self: %p obj: %p", self, obj);
+}
+
+static bool on_page_event(lv_fragment_t* self, int code, void* user_data)
+{
+ LV_LOG_INFO("self: %p code: %d user_data: %p", self, code, user_data);
+ return false;
+}
+
+PAGE_CLASS_DEF(sport);
diff --git a/bandx/page/stop_watch.c b/bandx/page/stop_watch.c
new file mode 100644
index 0000000000000000000000000000000000000000..5ba72497873ef56025ebbd6a20883a11f96a7d0c
--- /dev/null
+++ b/bandx/page/stop_watch.c
@@ -0,0 +1,417 @@
+/**
+ * @file stop_watch.c
+ *
+ */
+
+/*********************
+ * INCLUDES
+ *********************/
+#include "page.h"
+
+#include "../utils/lv_obj_ext_func.h"
+
+/*********************
+ * DEFINES
+ *********************/
+
+#define BTN_WIDTH 83
+#define BTN_HEIGHT 60
+#define BTN_CNT 2
+#define EVENT_CNT 10
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+typedef struct {
+ const char* img_src;
+ lv_obj_t* btn;
+ lv_obj_t* img;
+} btn_sw_t;
+
+typedef struct {
+ lv_obj_t* list_btn;
+ const char* text;
+ const uint16_t btn_val;
+} list_btn_t;
+
+enum BtnGrpEnum {
+ BTN_START_PAUSE,
+ BTN_FLAG_RESET,
+};
+
+typedef struct {
+ lv_fragment_t base;
+ lv_obj_t* label_time;
+ lv_obj_t* list_history;
+ lv_timer_t* timer_sw_update;
+ btn_sw_t btn_grp[BTN_CNT];
+
+ lv_coord_t label_time_record_y;
+ lv_coord_t label_time_ready_y;
+
+ bool sw_is_pause;
+ uint32_t sw_current_time;
+ uint32_t sw_interval_time;
+ uint32_t sw_last_time;
+ uint8_t sw_history_record_cnt;
+ lv_auto_event_data_t event_data[EVENT_CNT];
+ lv_auto_event_t* auto_event;
+} page_ctx_t;
+
+/**********************
+ * STATIC PROTOTYPES
+ **********************/
+
+/**********************
+ * STATIC VARIABLES
+ **********************/
+
+/**********************
+ * MACROS
+ **********************/
+
+/**********************
+ * GLOBAL FUNCTIONS
+ **********************/
+
+/**********************
+ * STATIC FUNCTIONS
+ **********************/
+
+static void line_sw_create(lv_obj_t* par)
+{
+ static const lv_point_precise_t line_points[] = { { 0, 0 }, { 170, 0 } };
+
+ lv_obj_t* line = lv_line_create(par);
+ lv_line_set_points(line, line_points, ARRAY_SIZE(line_points));
+
+ lv_obj_set_style_line_color(
+ line,
+ lv_color_make(0x80, 0x80, 0x80),
+ LV_STATE_DEFAULT);
+ lv_obj_set_style_line_width(line, 2, LV_STATE_DEFAULT);
+ lv_obj_align_to(line, par, LV_ALIGN_TOP_MID, 0, 286);
+}
+
+static void list_history_create(lv_obj_t* par)
+{
+ page_ctx_t* ctx = (page_ctx_t*)lv_obj_get_user_data(par);
+
+ lv_obj_t* list = lv_list_create(par);
+ lv_obj_set_style_text_font(list, resource_get_font(BANDX_REGULAR_FONT "_20"), LV_PART_MAIN);
+ lv_obj_set_style_bg_color(list, lv_color_black(), LV_PART_MAIN);
+ lv_obj_set_style_border_width(list, 0, LV_PART_MAIN);
+
+ lv_obj_set_size(list, 170, 170);
+ lv_obj_align(list, LV_ALIGN_CENTER, 0, 0);
+ ctx->list_history = list;
+}
+
+static void label_time_create(lv_obj_t* par)
+{
+ page_ctx_t* ctx = (page_ctx_t*)lv_obj_get_user_data(par);
+
+ lv_obj_t* label = lv_label_create(par);
+ lv_obj_set_style_text_font(label, resource_get_font(BANDX_REGULAR_FONT "_38"), LV_PART_MAIN);
+ lv_obj_set_style_text_color(label, lv_color_hex(0x87FFCE), LV_PART_MAIN);
+
+ lv_label_set_text(label, "00:00.000");
+ lv_obj_set_style_text_align(label, LV_TEXT_ALIGN_CENTER, 0);
+ lv_obj_align(label, LV_ALIGN_TOP_MID, 0, ctx->label_time_ready_y);
+ lv_obj_set_width(label, 180);
+ lv_obj_set_style_border_color(label, lv_color_make(0x80, 0x80, 0x80), LV_PART_MAIN);
+ lv_obj_set_style_border_width(label, 2, LV_PART_MAIN);
+ lv_obj_set_style_border_side(label, LV_BORDER_SIDE_BOTTOM, LV_PART_MAIN);
+ ctx->label_time = label;
+}
+
+static void sw_show_time(uint32_t ms, lv_obj_t* label_time)
+{
+ uint16_t min = 0, sec = 0, msec = 0;
+ min = ms / 60000;
+ sec = (ms - (min * 60000)) / 1000;
+ msec = ms - (sec * 1000) - (min * 60000);
+ lv_label_set_text_fmt(label_time, "%02d:%02d.%03d", min, sec, msec);
+}
+
+static void sw_pause(page_ctx_t* ctx)
+{
+ if (ctx->sw_is_pause) {
+ ctx->sw_interval_time = lv_tick_get() - ctx->sw_last_time;
+ lv_timer_resume(ctx->timer_sw_update);
+ ctx->sw_is_pause = false;
+ } else {
+ ctx->sw_last_time = ctx->sw_current_time;
+ lv_timer_pause(ctx->timer_sw_update);
+ ctx->sw_is_pause = true;
+ }
+}
+
+static void sw_reset(page_ctx_t* ctx)
+{
+ ctx->sw_last_time = 0;
+ ctx->sw_current_time = 0;
+ ctx->sw_interval_time = 0;
+ ctx->sw_is_pause = true;
+ ctx->sw_history_record_cnt = 0;
+ lv_timer_pause(ctx->timer_sw_update);
+ sw_show_time(ctx->sw_current_time, ctx->label_time);
+ lv_obj_clean(ctx->list_history);
+
+ if (lv_obj_get_y(ctx->label_time) == ctx->label_time_record_y) {
+ LV_OBJ_ADD_ANIM(ctx->label_time, y, ctx->label_time_ready_y, 500);
+ }
+}
+
+static void sw_update(lv_timer_t* timer)
+{
+ page_ctx_t* ctx = lv_timer_get_user_data(timer);
+
+ ctx->sw_current_time = lv_tick_get() - ctx->sw_interval_time;
+ sw_show_time(ctx->sw_current_time, ctx->label_time);
+}
+
+static void sw_record(page_ctx_t* ctx)
+{
+ lv_obj_t* list_btn;
+ lv_obj_t* spangroup;
+ uint16_t min = 0, sec = 0, msec = 0;
+
+ if (ctx->sw_history_record_cnt < 10 && !ctx->sw_is_pause) {
+ min = ctx->sw_current_time / 60000;
+ sec = (ctx->sw_current_time - (min * 60000)) / 1000;
+ msec = ctx->sw_current_time - (sec * 1000) - (min * 60000);
+
+ char cnt_buf[8];
+ char time_buf[64];
+
+ lv_snprintf(cnt_buf, sizeof(cnt_buf), "%d. ", ctx->sw_history_record_cnt + 1);
+ lv_snprintf(time_buf, sizeof(time_buf), "%02d:%02d.%03d", min, sec, msec);
+
+ list_btn = lv_list_add_btn(ctx->list_history, NULL, NULL);
+ lv_obj_clear_flag(list_btn, LV_OBJ_FLAG_CLICKABLE);
+ lv_obj_set_style_border_width(list_btn, 2, LV_STATE_DEFAULT);
+ lv_obj_set_style_pad_left(list_btn, 0, LV_PART_MAIN);
+
+ spangroup = lv_spangroup_create(list_btn);
+
+ lv_span_t* cnt_span = lv_spangroup_new_span(spangroup);
+ lv_span_set_text(cnt_span, cnt_buf);
+ lv_style_set_text_color(&cnt_span->style, lv_color_hex(0x87FFCE));
+
+ lv_span_t* time_span = lv_spangroup_new_span(spangroup);
+ lv_span_set_text(time_span, time_buf);
+ lv_style_set_text_color(&time_span->style, lv_color_hex(0xffffff));
+
+ lv_obj_add_state(list_btn, LV_STATE_FOCUSED);
+ lv_obj_set_style_bg_color(list_btn, lv_color_black(), 0);
+ lv_obj_set_style_text_decor(
+ list_btn,
+ LV_TEXT_DECOR_NONE,
+ LV_STATE_FOCUSED);
+ ctx->sw_history_record_cnt++;
+ }
+
+ if (lv_obj_get_y(ctx->label_time) == ctx->label_time_ready_y) {
+ LV_OBJ_ADD_ANIM(ctx->label_time, y, ctx->label_time_record_y, 500);
+ }
+}
+
+static void btn_grp_event_handler(lv_event_t* event)
+{
+ lv_event_code_t code = lv_event_get_code(event);
+ lv_obj_t* obj = lv_event_get_current_target_obj(event);
+ page_ctx_t* ctx = (page_ctx_t*)lv_event_get_user_data(event);
+
+ if (code == LV_EVENT_CLICKED) {
+ lv_obj_t* img = lv_obj_get_child(obj, 0);
+ const char* img_src = lv_img_get_src(img);
+
+ if (obj == ctx->btn_grp[BTN_START_PAUSE].btn) {
+ sw_pause(ctx);
+ if (ctx->sw_is_pause) {
+ lv_img_set_src(ctx->btn_grp[BTN_START_PAUSE].img, resource_get_img("icon_start"));
+ lv_img_set_src(ctx->btn_grp[BTN_FLAG_RESET].img, resource_get_img("icon_reset"));
+ } else {
+ lv_img_set_src(ctx->btn_grp[BTN_START_PAUSE].img, resource_get_img("icon_pause"));
+ lv_img_set_src(ctx->btn_grp[BTN_FLAG_RESET].img, resource_get_img("icon_flag"));
+ lv_obj_clear_state(ctx->btn_grp[BTN_FLAG_RESET].btn, LV_STATE_DISABLED);
+ }
+ } else if (obj == ctx->btn_grp[BTN_FLAG_RESET].btn) {
+ if (lv_strcmp(img_src, resource_get_img("icon_flag")) == 0) {
+ sw_record(ctx);
+ } else {
+ if (ctx->sw_is_pause) {
+ sw_reset(ctx);
+ lv_obj_add_state(ctx->btn_grp[BTN_FLAG_RESET].btn, LV_STATE_DISABLED);
+ ctx->sw_history_record_cnt = 0;
+ }
+ }
+ }
+ }
+}
+
+static void btn_grp_create(lv_obj_t* par, btn_sw_t* btn_sw, int len)
+{
+ page_ctx_t* ctx = (page_ctx_t*)lv_obj_get_user_data(par);
+
+ for (int i = 0; i < len; i++) {
+ lv_obj_t* btn = lv_btn_create(par);
+ lv_obj_set_size(btn, BTN_WIDTH, BTN_HEIGHT);
+
+ lv_coord_t x_ofs = BTN_WIDTH / 2 + 3;
+ lv_obj_align(btn, LV_ALIGN_TOP_MID, i == 0 ? -x_ofs : x_ofs, 300);
+ lv_obj_set_style_border_color(btn, lv_color_black(), LV_STATE_DEFAULT);
+ lv_obj_set_style_border_width(btn, 2, LV_STATE_DEFAULT);
+ lv_obj_set_style_bg_color(
+ btn,
+ lv_palette_main(LV_PALETTE_GREY),
+ LV_STATE_PRESSED || LV_STATE_DISABLED);
+ lv_obj_set_style_bg_color(
+ btn,
+ lv_color_make(0x87, 0xFF, 0xCE),
+ LV_STATE_DEFAULT);
+ lv_obj_set_style_radius(btn, 10, LV_PART_MAIN);
+ lv_obj_add_event(btn, btn_grp_event_handler, LV_EVENT_ALL, ctx);
+
+ lv_obj_t* img = lv_img_create(btn);
+ lv_img_set_src(img, resource_get_img(btn_sw[i].img_src));
+ lv_obj_align_to(img, btn, LV_ALIGN_CENTER, 0, 0);
+
+ btn_sw[i].btn = btn;
+ btn_sw[i].img = img;
+ }
+}
+
+static void auto_event_create(page_ctx_t* ctx)
+{
+ btn_sw_t* btn_sw = ctx->btn_grp;
+ lv_auto_event_data_t ae_grp[EVENT_CNT] = {
+ { &(btn_sw[BTN_START_PAUSE].btn), LV_EVENT_CLICKED, 2000 },
+ { &(btn_sw[BTN_FLAG_RESET].btn), LV_EVENT_CLICKED, 453 },
+ { &(btn_sw[BTN_FLAG_RESET].btn), LV_EVENT_CLICKED, 888 },
+ { &(btn_sw[BTN_FLAG_RESET].btn), LV_EVENT_CLICKED, 1605 },
+ { &(btn_sw[BTN_FLAG_RESET].btn), LV_EVENT_CLICKED, 785 },
+ { &(btn_sw[BTN_FLAG_RESET].btn), LV_EVENT_CLICKED, 1200 },
+ { &(btn_sw[BTN_FLAG_RESET].btn), LV_EVENT_CLICKED, 1456 },
+ { &(btn_sw[BTN_START_PAUSE].btn), LV_EVENT_CLICKED, 2000 },
+ { &(btn_sw[BTN_FLAG_RESET].btn), LV_EVENT_CLICKED, 1000 },
+ { &ctx->base.obj, LV_EVENT_LEAVE, 2000 },
+ };
+ for (int i = 0; i < EVENT_CNT; i++) {
+ ctx->event_data[i] = ae_grp[i];
+ }
+
+ AUTO_EVENT_CREATE(ctx->auto_event, ctx->event_data);
+}
+
+static void on_root_event(lv_event_t* e)
+{
+ lv_obj_t* root = lv_event_get_current_target_obj(e);
+ lv_event_code_t code = lv_event_get_code(e);
+ page_ctx_t* ctx = lv_obj_get_user_data(root);
+
+ if (code == LV_EVENT_GESTURE) {
+ lv_dir_t dir = lv_indev_get_gesture_dir(lv_indev_get_act());
+ if (dir == LV_DIR_RIGHT) {
+ lv_obj_send_event(root, LV_EVENT_LEAVE, NULL);
+ }
+ } else if (code == LV_EVENT_LEAVE) {
+ page_pop(&ctx->base);
+ }
+}
+
+static void on_page_construct(lv_fragment_t* self, void* args)
+{
+ LV_LOG_INFO("self: %p args: %p", self, args);
+
+ page_ctx_t* ctx = (page_ctx_t*)self;
+
+ btn_sw_t btn_grp[BTN_CNT] = {
+ { "icon_start" },
+ { "icon_reset" },
+ };
+
+ for (int i = 0; i < BTN_CNT; i++) {
+ ctx->btn_grp[i] = btn_grp[i];
+ }
+
+ ctx->label_time_record_y = 18;
+ ctx->label_time_ready_y = 64;
+
+ ctx->sw_is_pause = true;
+ ctx->sw_current_time = 0;
+ ctx->sw_interval_time = 0;
+ ctx->sw_last_time = 0;
+ ctx->sw_history_record_cnt = 0;
+}
+
+static void on_page_destruct(lv_fragment_t* self)
+{
+ LV_LOG_INFO("self: %p", self);
+}
+
+static void on_page_attached(lv_fragment_t* self)
+{
+ LV_LOG_INFO("self: %p", self);
+}
+
+static void on_page_detached(lv_fragment_t* self)
+{
+ LV_LOG_INFO("self: %p", self);
+}
+
+static lv_obj_t* on_page_create(lv_fragment_t* self, lv_obj_t* container)
+{
+ LV_LOG_INFO("self: %p container: %p", self, container);
+
+ lv_obj_t* root = lv_obj_create(container);
+ lv_obj_remove_style_all(root);
+ lv_obj_add_style(root, resource_get_style("root_def"), 0);
+ lv_obj_add_event(root, on_root_event, LV_EVENT_ALL, NULL);
+ lv_obj_clear_flag(root, LV_OBJ_FLAG_GESTURE_BUBBLE);
+ lv_obj_clear_flag(root, LV_OBJ_FLAG_SCROLLABLE);
+ lv_obj_set_user_data(root, self);
+ return root;
+}
+
+static void on_page_created(lv_fragment_t* self, lv_obj_t* obj)
+{
+ LV_LOG_INFO("self: %p obj: %p", self, obj);
+
+ page_ctx_t* ctx = (page_ctx_t*)self;
+
+ ctx->timer_sw_update = lv_timer_create(sw_update, 51, ctx);
+
+ list_history_create(obj);
+ label_time_create(obj);
+ btn_grp_create(obj, ctx->btn_grp, BTN_CNT);
+ line_sw_create(obj);
+ auto_event_create(ctx);
+
+ ctx->sw_is_pause ? lv_timer_pause(ctx->timer_sw_update) : lv_timer_resume(ctx->timer_sw_update);
+}
+
+static void on_page_will_delete(lv_fragment_t* self, lv_obj_t* obj)
+{
+ LV_LOG_INFO("self: %p obj: %p", self, obj);
+
+ page_ctx_t* ctx = (page_ctx_t*)self;
+
+ lv_timer_del(ctx->timer_sw_update);
+ AUTO_EVENT_DELETE(ctx->auto_event);
+}
+
+static void on_page_deleted(lv_fragment_t* self, lv_obj_t* obj)
+{
+ LV_LOG_INFO("self: %p obj: %p", self, obj);
+}
+
+static bool on_page_event(lv_fragment_t* self, int code, void* user_data)
+{
+ LV_LOG_INFO("self: %p code: %d user_data: %p", self, code, user_data);
+ return false;
+}
+
+PAGE_CLASS_DEF(stop_watch);
diff --git a/bandx/resource/font/assets/AlibabaPuHuiTi-3-55-Regular.ttf b/bandx/resource/font/assets/AlibabaPuHuiTi-3-55-Regular.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..a6eaf3613ee1160caf604ab3f11b303fdb5f8668
Binary files /dev/null and b/bandx/resource/font/assets/AlibabaPuHuiTi-3-55-Regular.ttf differ
diff --git a/bandx/resource/font/assets/AlibabaPuHuiTi-3-95-ExtraBold.ttf b/bandx/resource/font/assets/AlibabaPuHuiTi-3-95-ExtraBold.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..79f26640f72fa5b81f9ebf915cd29319eba487d7
Binary files /dev/null and b/bandx/resource/font/assets/AlibabaPuHuiTi-3-95-ExtraBold.ttf differ
diff --git a/bandx/resource/font/font.inc b/bandx/resource/font/font.inc
new file mode 100644
index 0000000000000000000000000000000000000000..da9bb9defa752d7fe61ce969a75a17ec62ada08c
--- /dev/null
+++ b/bandx/resource/font/font.inc
@@ -0,0 +1,8 @@
+/* font list */
+FONT_DEF(AlibabaPuHuiTi-3-55-Regular, 15)
+FONT_DEF(AlibabaPuHuiTi-3-55-Regular, 20)
+FONT_DEF(AlibabaPuHuiTi-3-55-Regular, 38)
+FONT_DEF(AlibabaPuHuiTi-3-55-Regular, 72)
+FONT_DEF(AlibabaPuHuiTi-3-95-ExtraBold, 23)
+FONT_DEF(AlibabaPuHuiTi-3-95-ExtraBold, 28)
+FONT_DEF(AlibabaPuHuiTi-3-95-ExtraBold, 110)
\ No newline at end of file
diff --git a/bandx/resource/image/assets/arrow_down.png b/bandx/resource/image/assets/arrow_down.png
new file mode 100644
index 0000000000000000000000000000000000000000..90e532f8b777e4bf45bfdb8cb2f6fdfaa11146de
Binary files /dev/null and b/bandx/resource/image/assets/arrow_down.png differ
diff --git a/bandx/resource/image/assets/arrow_up.png b/bandx/resource/image/assets/arrow_up.png
new file mode 100644
index 0000000000000000000000000000000000000000..b93a69d3f0235b06ffa069c7d00aff4c080f6e10
Binary files /dev/null and b/bandx/resource/image/assets/arrow_up.png differ
diff --git a/bandx/resource/image/assets/centigrade.png b/bandx/resource/image/assets/centigrade.png
new file mode 100644
index 0000000000000000000000000000000000000000..0eb787adf935d104370ce00d2b9ce282747b21fd
Binary files /dev/null and b/bandx/resource/image/assets/centigrade.png differ
diff --git a/bandx/resource/image/assets/flashlight.png b/bandx/resource/image/assets/flashlight.png
new file mode 100644
index 0000000000000000000000000000000000000000..45a6a78609a2e8504f46e7008f48ad2d416ef0ff
Binary files /dev/null and b/bandx/resource/image/assets/flashlight.png differ
diff --git a/bandx/resource/image/assets/heart.png b/bandx/resource/image/assets/heart.png
new file mode 100644
index 0000000000000000000000000000000000000000..59f72d3a58fd758e2929f7ff1885bc641604bd36
Binary files /dev/null and b/bandx/resource/image/assets/heart.png differ
diff --git a/bandx/resource/image/assets/heart_color.png b/bandx/resource/image/assets/heart_color.png
new file mode 100644
index 0000000000000000000000000000000000000000..c0117de706f65b467f72eeaa5617a5335875212a
Binary files /dev/null and b/bandx/resource/image/assets/heart_color.png differ
diff --git a/bandx/resource/image/assets/heart_rate.png b/bandx/resource/image/assets/heart_rate.png
new file mode 100644
index 0000000000000000000000000000000000000000..393544fa58b4ef0fce0ee83d6291a6e1f976a1a9
Binary files /dev/null and b/bandx/resource/image/assets/heart_rate.png differ
diff --git a/bandx/resource/image/assets/icon_basketball.png b/bandx/resource/image/assets/icon_basketball.png
new file mode 100644
index 0000000000000000000000000000000000000000..68973a0eb939e25a20e801cb6614bfccc8b5f562
Binary files /dev/null and b/bandx/resource/image/assets/icon_basketball.png differ
diff --git a/bandx/resource/image/assets/icon_battery.png b/bandx/resource/image/assets/icon_battery.png
new file mode 100644
index 0000000000000000000000000000000000000000..546096f08e82f61ff34d089db66044965f7fc3cb
Binary files /dev/null and b/bandx/resource/image/assets/icon_battery.png differ
diff --git a/bandx/resource/image/assets/icon_bluetooth.png b/bandx/resource/image/assets/icon_bluetooth.png
new file mode 100644
index 0000000000000000000000000000000000000000..9b5fe0c50fc334d484833df8b5a6bae23c4ed666
Binary files /dev/null and b/bandx/resource/image/assets/icon_bluetooth.png differ
diff --git a/bandx/resource/image/assets/icon_flag.png b/bandx/resource/image/assets/icon_flag.png
new file mode 100644
index 0000000000000000000000000000000000000000..3b4c8e94187417e93223ce0639ebfc86806ff503
Binary files /dev/null and b/bandx/resource/image/assets/icon_flag.png differ
diff --git a/bandx/resource/image/assets/icon_minus.png b/bandx/resource/image/assets/icon_minus.png
new file mode 100644
index 0000000000000000000000000000000000000000..db7467c5c7534e0ae8b1ff328a0203b86ee3bdcc
Binary files /dev/null and b/bandx/resource/image/assets/icon_minus.png differ
diff --git a/bandx/resource/image/assets/icon_more_sports.png b/bandx/resource/image/assets/icon_more_sports.png
new file mode 100644
index 0000000000000000000000000000000000000000..f98e4234800defb0e1362ad94e3a531814b59c09
Binary files /dev/null and b/bandx/resource/image/assets/icon_more_sports.png differ
diff --git a/bandx/resource/image/assets/icon_next.png b/bandx/resource/image/assets/icon_next.png
new file mode 100644
index 0000000000000000000000000000000000000000..a3545ce2489c2a387775c1f0da6ebe51f23530f5
Binary files /dev/null and b/bandx/resource/image/assets/icon_next.png differ
diff --git a/bandx/resource/image/assets/icon_pause.png b/bandx/resource/image/assets/icon_pause.png
new file mode 100644
index 0000000000000000000000000000000000000000..e7ac579c25e5e1feb8b2884091843efc96c832f5
Binary files /dev/null and b/bandx/resource/image/assets/icon_pause.png differ
diff --git a/bandx/resource/image/assets/icon_plus.png b/bandx/resource/image/assets/icon_plus.png
new file mode 100644
index 0000000000000000000000000000000000000000..27f41d65a1f7e44d4719809169380721a8890c7c
Binary files /dev/null and b/bandx/resource/image/assets/icon_plus.png differ
diff --git a/bandx/resource/image/assets/icon_prev.png b/bandx/resource/image/assets/icon_prev.png
new file mode 100644
index 0000000000000000000000000000000000000000..2f3d62a057aa7d82b604a06bd2a9f148ca89fd67
Binary files /dev/null and b/bandx/resource/image/assets/icon_prev.png differ
diff --git a/bandx/resource/image/assets/icon_reset.png b/bandx/resource/image/assets/icon_reset.png
new file mode 100644
index 0000000000000000000000000000000000000000..c61ea2ef94b5bc384b0e9ad8278e15a17ca109cc
Binary files /dev/null and b/bandx/resource/image/assets/icon_reset.png differ
diff --git a/bandx/resource/image/assets/icon_riding.png b/bandx/resource/image/assets/icon_riding.png
new file mode 100644
index 0000000000000000000000000000000000000000..70d607bbd459386ac9ad407a35f9c5ea9d10fb11
Binary files /dev/null and b/bandx/resource/image/assets/icon_riding.png differ
diff --git a/bandx/resource/image/assets/icon_running.png b/bandx/resource/image/assets/icon_running.png
new file mode 100644
index 0000000000000000000000000000000000000000..0623c81cbdf433eed2131920402f0eadc146b43e
Binary files /dev/null and b/bandx/resource/image/assets/icon_running.png differ
diff --git a/bandx/resource/image/assets/icon_shadow_down.png b/bandx/resource/image/assets/icon_shadow_down.png
new file mode 100644
index 0000000000000000000000000000000000000000..47c48985ec81752c293018bd3f85cfbb3c08d6a5
Binary files /dev/null and b/bandx/resource/image/assets/icon_shadow_down.png differ
diff --git a/bandx/resource/image/assets/icon_shadow_up.png b/bandx/resource/image/assets/icon_shadow_up.png
new file mode 100644
index 0000000000000000000000000000000000000000..82af8cf81e5a2643d875d68f3d64aa1fef49ebfe
Binary files /dev/null and b/bandx/resource/image/assets/icon_shadow_up.png differ
diff --git a/bandx/resource/image/assets/icon_skiing.png b/bandx/resource/image/assets/icon_skiing.png
new file mode 100644
index 0000000000000000000000000000000000000000..ef2b04ce2af243e70f1be5d51b9c212dbcaca515
Binary files /dev/null and b/bandx/resource/image/assets/icon_skiing.png differ
diff --git a/bandx/resource/image/assets/icon_start.png b/bandx/resource/image/assets/icon_start.png
new file mode 100644
index 0000000000000000000000000000000000000000..5006892b7dedefcfa2cc52560f322f3770cfa594
Binary files /dev/null and b/bandx/resource/image/assets/icon_start.png differ
diff --git a/bandx/resource/image/assets/icon_stretching.png b/bandx/resource/image/assets/icon_stretching.png
new file mode 100644
index 0000000000000000000000000000000000000000..ce8a6740c7826fb97d0f4ceb9e67f72694b7d3e4
Binary files /dev/null and b/bandx/resource/image/assets/icon_stretching.png differ
diff --git a/bandx/resource/image/assets/icon_tabletennis.png b/bandx/resource/image/assets/icon_tabletennis.png
new file mode 100644
index 0000000000000000000000000000000000000000..97de39a2b067a250e059b675ba738efde9a7a0dd
Binary files /dev/null and b/bandx/resource/image/assets/icon_tabletennis.png differ
diff --git a/bandx/resource/image/assets/icon_volleyball.png b/bandx/resource/image/assets/icon_volleyball.png
new file mode 100644
index 0000000000000000000000000000000000000000..fb1891fbdab1680a4ba8c8824d4bea6da27e7f4a
Binary files /dev/null and b/bandx/resource/image/assets/icon_volleyball.png differ
diff --git a/bandx/resource/image/assets/icon_volume_add.png b/bandx/resource/image/assets/icon_volume_add.png
new file mode 100644
index 0000000000000000000000000000000000000000..84c483a31d7234fff4769c859177ebd05a87c8f6
Binary files /dev/null and b/bandx/resource/image/assets/icon_volume_add.png differ
diff --git a/bandx/resource/image/assets/icon_volume_reduce.png b/bandx/resource/image/assets/icon_volume_reduce.png
new file mode 100644
index 0000000000000000000000000000000000000000..42633fc4606387fbb5842dd188f9a63344c5412e
Binary files /dev/null and b/bandx/resource/image/assets/icon_volume_reduce.png differ
diff --git a/bandx/resource/image/assets/icon_yoga.png b/bandx/resource/image/assets/icon_yoga.png
new file mode 100644
index 0000000000000000000000000000000000000000..376448d02fe15f0b9f23b1251aeb0915c19bf969
Binary files /dev/null and b/bandx/resource/image/assets/icon_yoga.png differ
diff --git a/bandx/resource/image/assets/music.png b/bandx/resource/image/assets/music.png
new file mode 100644
index 0000000000000000000000000000000000000000..641e09c1df4a6a034308b4f3fa564a4c41ab78d2
Binary files /dev/null and b/bandx/resource/image/assets/music.png differ
diff --git a/bandx/resource/image/assets/num_shadow.png b/bandx/resource/image/assets/num_shadow.png
new file mode 100644
index 0000000000000000000000000000000000000000..30174950769ed56a9e32c096f540061bfb045661
Binary files /dev/null and b/bandx/resource/image/assets/num_shadow.png differ
diff --git a/bandx/resource/image/assets/settings.png b/bandx/resource/image/assets/settings.png
new file mode 100644
index 0000000000000000000000000000000000000000..96b0721bb74a8f7f13bbe38f70784aa68b03db8a
Binary files /dev/null and b/bandx/resource/image/assets/settings.png differ
diff --git a/bandx/resource/image/assets/sleep.png b/bandx/resource/image/assets/sleep.png
new file mode 100644
index 0000000000000000000000000000000000000000..c34f3fe816a36207365275901ed99a79f9e1a4a6
Binary files /dev/null and b/bandx/resource/image/assets/sleep.png differ
diff --git a/bandx/resource/image/assets/sport.png b/bandx/resource/image/assets/sport.png
new file mode 100644
index 0000000000000000000000000000000000000000..8bf1f8ac29a8af7881b3b3cba3657072069929d0
Binary files /dev/null and b/bandx/resource/image/assets/sport.png differ
diff --git a/bandx/resource/image/assets/step.png b/bandx/resource/image/assets/step.png
new file mode 100644
index 0000000000000000000000000000000000000000..708614c1450adddcebefb68111c75812531d4563
Binary files /dev/null and b/bandx/resource/image/assets/step.png differ
diff --git a/bandx/resource/image/assets/stop_watch.png b/bandx/resource/image/assets/stop_watch.png
new file mode 100644
index 0000000000000000000000000000000000000000..acc2e9521065c3bc27f90393ac8a96c62c98cce6
Binary files /dev/null and b/bandx/resource/image/assets/stop_watch.png differ
diff --git a/bandx/resource/image/assets/weather.png b/bandx/resource/image/assets/weather.png
new file mode 100644
index 0000000000000000000000000000000000000000..77dc8dbc4443a0d23f3407bc46845531d97d103d
Binary files /dev/null and b/bandx/resource/image/assets/weather.png differ
diff --git a/bandx/resource/image/img_src.inc b/bandx/resource/image/img_src.inc
new file mode 100644
index 0000000000000000000000000000000000000000..c373bb160450b6fd27c5d8e432da74694815dbd8
--- /dev/null
+++ b/bandx/resource/image/img_src.inc
@@ -0,0 +1,39 @@
+/* img list */
+IMG_DEF(arrow_down)
+IMG_DEF(arrow_up)
+IMG_DEF(icon_battery)
+IMG_DEF(icon_bluetooth)
+IMG_DEF(centigrade)
+IMG_DEF(flashlight)
+IMG_DEF(heart_color)
+IMG_DEF(heart_rate)
+IMG_DEF(heart)
+IMG_DEF(icon_flag)
+IMG_DEF(icon_minus)
+IMG_DEF(icon_next)
+IMG_DEF(icon_pause)
+IMG_DEF(icon_plus)
+IMG_DEF(icon_prev)
+IMG_DEF(icon_reset)
+IMG_DEF(icon_shadow_down)
+IMG_DEF(icon_shadow_up)
+IMG_DEF(icon_start)
+IMG_DEF(icon_volume_add)
+IMG_DEF(icon_volume_reduce)
+IMG_DEF(music)
+IMG_DEF(num_shadow)
+IMG_DEF(settings)
+IMG_DEF(sleep)
+IMG_DEF(sport)
+IMG_DEF(step)
+IMG_DEF(stop_watch)
+IMG_DEF(weather)
+IMG_DEF(icon_more_sports)
+IMG_DEF(icon_running)
+IMG_DEF(icon_stretching)
+IMG_DEF(icon_riding)
+IMG_DEF(icon_skiing)
+IMG_DEF(icon_yoga)
+IMG_DEF(icon_tabletennis)
+IMG_DEF(icon_basketball)
+IMG_DEF(icon_volleyball)
diff --git a/bandx/resource/resource.c b/bandx/resource/resource.c
new file mode 100644
index 0000000000000000000000000000000000000000..4b7088052c24ae799c72d508c0ce8efaec0dac8e
--- /dev/null
+++ b/bandx/resource/resource.c
@@ -0,0 +1,154 @@
+/**
+ * @file resource.c
+ *
+ */
+
+/*********************
+ * INCLUDES
+ *********************/
+
+#include "resource.h"
+
+/*********************
+ * DEFINES
+ *********************/
+
+#define ARRAY_SIZE(ARRAY) (sizeof(ARRAY) / sizeof(ARRAY[0]))
+
+/* import images*/
+#define IMG_DEF(NAME) LV_IMG_DECLARE(img_src_##NAME);
+#include "image/img_src.inc"
+#undef IMG_DEF
+
+#define FONT_BASE_PATH "/font/"
+#define IMG_BASE_PATH "/image/"
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+typedef struct {
+ const char* key;
+ const char* img_src;
+} resource_img_t;
+
+typedef struct {
+ const char* key;
+ const lv_font_t* font;
+} resource_font_t;
+
+typedef struct {
+ const char* key;
+ lv_style_t style;
+} resource_style_t;
+
+/**********************
+ * STATIC PROTOTYPES
+ **********************/
+
+/**********************
+ * STATIC VARIABLES
+ **********************/
+
+#define IMG_DEF(NAME) { #NAME, CONFIG_BANDX_BASE_PATH IMG_BASE_PATH #NAME ".png" },
+static const resource_img_t g_img_resource_map[] = {
+#include "image/img_src.inc"
+};
+#undef IMG_DEF
+
+#define FONT_DEF(NAME, SIZE) { "default", LV_FONT_DEFAULT },
+static resource_font_t g_font_resource_map[] = {
+#include "font/font.inc"
+};
+#undef FONT_DEF
+
+#define STYLE_DEF(NAME) { "empty" },
+static resource_style_t g_style_resource_map[] = {
+#include "style/style.inc"
+};
+#undef STYLE_DEF
+
+/**********************
+ * MACROS
+ **********************/
+
+/**********************
+ * GLOBAL FUNCTIONS
+ **********************/
+
+void resource_init(void)
+{
+ /* create fonts */
+ int font_index = 0;
+#define FONT_DEF(NAME, SIZE) \
+ do { \
+ lv_font_t* font = lv_freetype_font_create(CONFIG_BANDX_BASE_PATH FONT_BASE_PATH #NAME ".ttf", \
+ LV_FREETYPE_FONT_RENDER_MODE_BITMAP, \
+ SIZE, \
+ LV_FREETYPE_FONT_STYLE_NORMAL); \
+ if (font) { \
+ g_font_resource_map[font_index].key = #NAME "_" #SIZE; \
+ g_font_resource_map[font_index].font = font; \
+ font_index++; \
+ } else { \
+ LV_LOG_WARN("font_" #NAME "_" #SIZE "not found"); \
+ } \
+ } while (0);
+#include "font/font.inc"
+#undef FONT_DEF
+ LV_LOG_USER("create %d fonts", font_index);
+
+ /* init styles */
+ int style_index = 0;
+#define STYLE_DEF(NAME) \
+ do { \
+ extern void style_##NAME##_init(lv_style_t* style); \
+ g_style_resource_map[style_index].key = #NAME; \
+ lv_style_init(&g_style_resource_map[style_index].style); \
+ style_##NAME##_init(&g_style_resource_map[style_index].style); \
+ style_index++; \
+ } while (0);
+#include "style/style.inc"
+#undef STYLE_DEF
+ LV_LOG_USER("create %d styles", style_index);
+}
+
+const lv_font_t* resource_get_font(const char* key)
+{
+ for (int i = 0; i < ARRAY_SIZE(g_font_resource_map); i++) {
+ if (lv_strcmp(key, g_font_resource_map[i].key) == 0) {
+ return g_font_resource_map[i].font;
+ }
+ }
+
+ LV_LOG_WARN("key %s not found", key);
+ return LV_FONT_DEFAULT;
+}
+
+const void* resource_get_img(const char* key)
+{
+ for (int i = 0; i < ARRAY_SIZE(g_img_resource_map); i++) {
+ if (lv_strcmp(key, g_img_resource_map[i].key) == 0) {
+ return g_img_resource_map[i].img_src;
+ }
+ }
+
+ LV_LOG_WARN("key %s not found", key);
+ return LV_SYMBOL_IMAGE;
+}
+
+lv_style_t* resource_get_style(const char* key)
+{
+ for (int i = 0; i < ARRAY_SIZE(g_style_resource_map); i++) {
+ if (lv_strcmp(key, g_style_resource_map[i].key) == 0) {
+ return &g_style_resource_map[i].style;
+ }
+ }
+
+ LV_LOG_WARN("key %s not found", key);
+ return &g_style_resource_map[0].style;
+}
+
+/**********************
+ * STATIC FUNCTIONS
+ **********************/
diff --git a/bandx/resource/resource.h b/bandx/resource/resource.h
new file mode 100644
index 0000000000000000000000000000000000000000..0fe0a7b5617d8dfd6696394f7a104def389bb047
--- /dev/null
+++ b/bandx/resource/resource.h
@@ -0,0 +1,65 @@
+/**
+ * @file resource.h
+ *
+ */
+
+#ifndef RESOURCE_H
+#define RESOURCE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*********************
+ * INCLUDES
+ *********************/
+
+#include
+
+/*********************
+ * DEFINES
+ *********************/
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+/**********************
+ * GLOBAL PROTOTYPES
+ **********************/
+
+/**
+ * Initialize the resource manager.
+ */
+void resource_init(void);
+
+/**
+ * Get a font resource by key.
+ * @param key The key associated with the desired font.
+ * @return A pointer to the font resource, or NULL if not found.
+ */
+const lv_font_t* resource_get_font(const char* key);
+
+/**
+ * Get an image resource by key.
+ * @param key The key associated with the desired image.
+ * @return A pointer to the image resource, or NULL if not found.
+ */
+const void* resource_get_img(const char* key);
+
+/**
+ * Get a style resource by key.
+ * @param key The key associated with the desired style.
+ * @return A pointer to the style resource, or NULL if not found.
+ */
+lv_style_t* resource_get_style(const char* key);
+
+/**********************
+ * MACROS
+ **********************/
+
+#ifdef __cplusplus
+} /*extern "C"*/
+#endif
+
+#endif /*RESOURCE_H*/
diff --git a/bandx/resource/style/style.c b/bandx/resource/style/style.c
new file mode 100644
index 0000000000000000000000000000000000000000..c3e02f2f3b9a3e3201fc7b2597167495600303fc
--- /dev/null
+++ b/bandx/resource/style/style.c
@@ -0,0 +1,62 @@
+/**
+ * @file style.c
+ *
+ */
+
+/*********************
+ * INCLUDES
+ *********************/
+
+#include "lvgl/lvgl.h"
+
+/*********************
+ * DEFINES
+ *********************/
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+/**********************
+ * STATIC PROTOTYPES
+ **********************/
+
+/**********************
+ * STATIC VARIABLES
+ **********************/
+
+/**********************
+ * MACROS
+ **********************/
+
+/**********************
+ * GLOBAL FUNCTIONS
+ **********************/
+
+void style_empty_init(lv_style_t* style)
+{
+}
+
+void style_root_def_init(lv_style_t* style)
+{
+ lv_style_set_bg_color(style, lv_color_black());
+ lv_style_set_bg_opa(style, LV_OPA_COVER);
+ lv_style_set_size(style, LV_PCT(100), LV_PCT(100));
+}
+
+void style_btn_trans_init(lv_style_t* style)
+{
+ static const lv_style_prop_t props[] = {
+ LV_STYLE_BG_COLOR,
+ LV_STYLE_TRANSFORM_WIDTH,
+ LV_STYLE_TRANSFORM_HEIGHT,
+ LV_STYLE_PROP_INV
+ };
+ static lv_style_transition_dsc_t dsc;
+ lv_style_transition_dsc_init(&dsc, props, lv_anim_path_overshoot, 200, 0, NULL);
+ lv_style_set_transition(style, &dsc);
+}
+
+/**********************
+ * STATIC FUNCTIONS
+ **********************/
diff --git a/bandx/resource/style/style.inc b/bandx/resource/style/style.inc
new file mode 100644
index 0000000000000000000000000000000000000000..5a7c1b120794fcf2d2d820cac7006fa4a0b904c7
--- /dev/null
+++ b/bandx/resource/style/style.inc
@@ -0,0 +1,4 @@
+/* style list */
+STYLE_DEF(empty)
+STYLE_DEF(root_def)
+STYLE_DEF(btn_trans)
diff --git a/bandx/utils/lv_anim_label.c b/bandx/utils/lv_anim_label.c
new file mode 100755
index 0000000000000000000000000000000000000000..61f42b8bb8adec174825341fcb3d7fafddfe0e11
--- /dev/null
+++ b/bandx/utils/lv_anim_label.c
@@ -0,0 +1,349 @@
+/*
+ * MIT License
+ * Copyright (c) 2021 _VIFEXTech
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/*********************
+ * INCLUDES
+ *********************/
+
+#include "lv_anim_label.h"
+
+/*********************
+ * DEFINES
+ *********************/
+#define MY_CLASS &lv_anim_label_class
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+/**********************
+ * STATIC PROTOTYPES
+ **********************/
+
+static void lv_anim_label_constructor(const lv_obj_class_t* class_p, lv_obj_t* obj);
+static void lv_anim_label_set_x(void* obj, int32_t x);
+static void lv_anim_label_set_y(void* obj, int32_t y);
+
+/**********************
+ * STATIC VARIABLES
+ **********************/
+
+const lv_obj_class_t lv_anim_label_class = {
+ .constructor_cb = lv_anim_label_constructor,
+ .instance_size = sizeof(lv_anim_label_t),
+ .base_class = &lv_obj_class
+};
+
+/**********************
+ * MACROS
+ **********************/
+
+/**********************
+ * GLOBAL FUNCTIONS
+ **********************/
+
+lv_obj_t* lv_anim_label_create(lv_obj_t* parent)
+{
+ lv_obj_t* obj = lv_obj_class_create_obj(MY_CLASS, parent);
+ lv_obj_class_init_obj(obj);
+ lv_obj_clear_flag(obj, LV_OBJ_FLAG_SCROLLABLE);
+ return obj;
+}
+
+void lv_anim_label_set_dir(lv_obj_t* obj, lv_dir_t dir)
+{
+ lv_anim_label_set_enter_dir(obj, dir);
+ lv_anim_label_set_exit_dir(obj, dir);
+}
+
+void lv_anim_label_set_enter_dir(lv_obj_t* obj, lv_dir_t dir)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_anim_label_t* alabel = (lv_anim_label_t*)obj;
+
+ alabel->enter_dir = dir;
+}
+
+void lv_anim_label_set_exit_dir(lv_obj_t* obj, lv_dir_t dir)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_anim_label_t* alabel = (lv_anim_label_t*)obj;
+
+ alabel->exit_dir = dir;
+}
+
+void lv_anim_label_set_time(lv_obj_t* obj, uint32_t duration)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_anim_label_t* alabel = (lv_anim_label_t*)obj;
+
+ alabel->duration = duration;
+}
+
+void lv_anim_label_set_path(lv_obj_t* obj, lv_anim_path_cb_t path_cb)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_anim_label_t* alabel = (lv_anim_label_t*)obj;
+
+ alabel->path_cb = path_cb;
+}
+
+void lv_anim_label_add_style(lv_obj_t* obj, lv_style_t* style)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_anim_label_t* alabel = (lv_anim_label_t*)obj;
+
+ lv_obj_add_style(alabel->label_1, style, LV_PART_MAIN);
+ lv_obj_add_style(alabel->label_2, style, LV_PART_MAIN);
+}
+
+void lv_anim_label_set_custom_enter_anim(lv_obj_t* obj, const lv_anim_t* a)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_anim_label_t* alabel = (lv_anim_label_t*)obj;
+
+ if (a != NULL) {
+ alabel->a_enter = *a;
+ }
+}
+
+void lv_anim_label_set_custom_exit_anim(lv_obj_t* obj, const lv_anim_t* a)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_anim_label_t* alabel = (lv_anim_label_t*)obj;
+
+ if (a != NULL) {
+ alabel->a_exit = *a;
+ }
+}
+
+static void lv_anim_label_start_anim(
+ lv_obj_t* label,
+ lv_dir_t dir,
+ int32_t start,
+ int32_t end,
+ uint32_t duration,
+ lv_anim_path_cb_t path_cb)
+{
+ lv_anim_exec_xcb_t exec_xcb;
+
+ if (dir & LV_DIR_HOR) {
+ exec_xcb = lv_anim_label_set_x;
+ } else if (dir & LV_DIR_VER) {
+ exec_xcb = lv_anim_label_set_y;
+ } else {
+ return;
+ }
+
+ lv_anim_t a;
+ lv_anim_init(&a);
+ lv_anim_set_var(&a, label);
+ lv_anim_set_values(&a, start, end);
+ lv_anim_set_time(&a, duration);
+ lv_anim_set_path_cb(&a, path_cb);
+ lv_anim_set_exec_cb(&a, exec_xcb);
+ lv_anim_start(&a);
+}
+
+void lv_anim_label_push_text(lv_obj_t* obj, const char* txt)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_anim_label_t* alabel = (lv_anim_label_t*)obj;
+
+ lv_obj_t* label_exit = alabel->label_act;
+ lv_obj_t* label_enter = alabel->label_act == alabel->label_1 ? alabel->label_2 : alabel->label_1;
+ lv_dir_t enter_dir = alabel->enter_dir;
+ lv_dir_t exit_dir = alabel->exit_dir;
+
+ lv_label_set_text(label_enter, txt);
+ lv_obj_update_layout(label_enter);
+
+ lv_coord_t obj_width = lv_obj_get_width(obj);
+ lv_coord_t obj_height = lv_obj_get_height(obj);
+
+ /* enter */
+ lv_coord_t label_enter_width = lv_obj_get_width(label_enter);
+ lv_coord_t label_enter_height = lv_obj_get_height(label_enter);
+ lv_coord_t label_enter_end_x = (obj_width - label_enter_width) / 2;
+ lv_coord_t label_enter_end_y = (obj_height - label_enter_height) / 2;
+
+ if (enter_dir & LV_DIR_HOR) {
+ lv_obj_set_y(label_enter, label_enter_end_y);
+ lv_coord_t start_x = label_enter_end_x;
+ start_x += ((enter_dir == LV_DIR_LEFT) ? -obj_width : obj_width);
+ lv_anim_label_start_anim(
+ label_enter,
+ enter_dir,
+ start_x,
+ label_enter_end_x,
+ alabel->duration,
+ alabel->path_cb);
+ } else if (enter_dir & LV_DIR_VER) {
+ lv_obj_set_x(label_enter, label_enter_end_x);
+ lv_coord_t start_y = label_enter_end_y;
+ start_y += ((enter_dir == LV_DIR_TOP) ? -obj_height : obj_height);
+ lv_anim_label_start_anim(
+ label_enter,
+ enter_dir,
+ start_y,
+ label_enter_end_y,
+ alabel->duration,
+ alabel->path_cb);
+ } else {
+ lv_obj_set_pos(label_enter, label_enter_end_x, label_enter_end_y);
+ }
+
+ if (alabel->a_enter.exec_cb != NULL) {
+ lv_anim_set_var(&alabel->a_enter, label_enter);
+ lv_anim_start(&alabel->a_enter);
+ }
+
+ /* exit */
+ lv_coord_t label_exit_width = lv_obj_get_width(label_exit);
+ lv_coord_t label_exit_height = lv_obj_get_height(label_exit);
+ lv_coord_t label_exit_start_x = (obj_width - label_exit_width) / 2;
+ lv_coord_t label_exit_start_y = (obj_height - label_exit_height) / 2;
+
+ if (exit_dir & LV_DIR_HOR) {
+ lv_obj_set_y(label_exit, label_exit_start_y);
+ lv_coord_t end_x = label_exit_start_x;
+ end_x += ((exit_dir == LV_DIR_LEFT) ? obj_width : -obj_width);
+ lv_anim_label_start_anim(
+ label_exit,
+ exit_dir,
+ label_exit_start_x,
+ end_x,
+ alabel->duration,
+ alabel->path_cb);
+ } else if (exit_dir & LV_DIR_VER) {
+ lv_obj_set_x(label_exit, label_exit_start_x);
+ lv_coord_t end_y = label_exit_start_y;
+ end_y += ((exit_dir == LV_DIR_TOP) ? obj_height : -obj_height);
+ lv_anim_label_start_anim(
+ label_exit,
+ exit_dir,
+ label_exit_start_y,
+ end_y,
+ alabel->duration,
+ alabel->path_cb);
+ } else {
+ lv_obj_set_pos(label_exit, label_exit_start_x, label_exit_start_y);
+ }
+
+ if (alabel->a_exit.exec_cb != NULL) {
+ lv_anim_set_var(&alabel->a_exit, label_exit);
+ lv_anim_start(&alabel->a_exit);
+ }
+
+ alabel->label_act = label_enter;
+}
+
+lv_dir_t lv_anim_label_get_enter_dir(lv_obj_t* obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_anim_label_t* alabel = (lv_anim_label_t*)obj;
+
+ return alabel->enter_dir;
+}
+
+lv_dir_t lv_anim_label_get_exit_dir(lv_obj_t* obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_anim_label_t* alabel = (lv_anim_label_t*)obj;
+
+ return alabel->exit_dir;
+}
+
+uint32_t lv_anim_label_get_time(lv_obj_t* obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_anim_label_t* alabel = (lv_anim_label_t*)obj;
+
+ return alabel->duration;
+}
+
+lv_anim_path_cb_t lv_anim_label_get_path(lv_obj_t* obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_anim_label_t* alabel = (lv_anim_label_t*)obj;
+
+ return alabel->path_cb;
+}
+
+const char* lv_anim_label_get_text(lv_obj_t* obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_anim_label_t* alabel = (lv_anim_label_t*)obj;
+
+ return lv_label_get_text(alabel->label_act);
+}
+
+/**********************
+ * STATIC FUNCTIONS
+ **********************/
+
+static void lv_anim_label_constructor(const lv_obj_class_t* class_p, lv_obj_t* obj)
+{
+ LV_TRACE_OBJ_CREATE("begin");
+
+ lv_anim_label_t* alabel = (lv_anim_label_t*)obj;
+
+ alabel->label_1 = lv_label_create(obj);
+ lv_label_set_text(alabel->label_1, "");
+
+ alabel->label_2 = lv_label_create(obj);
+ lv_label_set_text(alabel->label_2, "");
+
+ alabel->label_act = alabel->label_1;
+ // alabel->dir = LV_DIR_BOTTOM;
+ alabel->duration = 500;
+ alabel->path_cb = lv_anim_path_ease_out;
+
+ lv_anim_init(&alabel->a_enter);
+ lv_anim_init(&alabel->a_exit);
+
+ LV_TRACE_OBJ_CREATE("finished");
+}
+
+static void lv_anim_label_set_x(void* obj, int32_t x)
+{
+ lv_obj_set_x(obj, x);
+}
+
+static void lv_anim_label_set_y(void* obj, int32_t y)
+{
+ lv_obj_set_y(obj, y);
+}
\ No newline at end of file
diff --git a/bandx/utils/lv_anim_label.h b/bandx/utils/lv_anim_label.h
new file mode 100755
index 0000000000000000000000000000000000000000..32b8e85c31f05cc09f01f32a94f2c5f5d6611135
--- /dev/null
+++ b/bandx/utils/lv_anim_label.h
@@ -0,0 +1,158 @@
+/**
+ * @file lv_anim_label.h
+ *
+ */
+
+#ifndef LV_ANIM_LABEL_H
+#define LV_ANIM_LABEL_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*********************
+ * INCLUDES
+ *********************/
+#include "lvgl/lvgl.h"
+
+/*********************
+ * DEFINES
+ *********************/
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+typedef struct {
+ lv_obj_t obj;
+ lv_obj_t* label_1;
+ lv_obj_t* label_2;
+ lv_obj_t* label_act;
+ lv_dir_t enter_dir;
+ lv_dir_t exit_dir;
+ lv_anim_t a_enter;
+ lv_anim_t a_exit;
+ uint32_t duration;
+ lv_anim_path_cb_t path_cb;
+} lv_anim_label_t;
+
+extern const lv_obj_class_t lv_anim_label_class;
+
+/**********************
+ * GLOBAL PROTOTYPES
+ **********************/
+
+/**
+ * Create a new animation label object.
+ * @param parent Pointer to the parent object.
+ * @return A pointer to the newly created animation label object.
+ */
+lv_obj_t* lv_anim_label_create(lv_obj_t* parent);
+
+/**
+ * Set the direction of the animation label.
+ * @param obj Pointer to the animation label object.
+ * @param dir The direction to set for the animation.
+ */
+void lv_anim_label_set_dir(lv_obj_t* obj, lv_dir_t dir);
+
+/**
+ * Set the entry direction for the animation label.
+ * @param obj Pointer to the animation label object.
+ * @param dir The entry direction to set.
+ */
+void lv_anim_label_set_enter_dir(lv_obj_t* obj, lv_dir_t dir);
+
+/**
+ * Set the exit direction for the animation label.
+ * @param obj Pointer to the animation label object.
+ * @param dir The exit direction to set.
+ */
+void lv_anim_label_set_exit_dir(lv_obj_t* obj, lv_dir_t dir);
+
+/**
+ * Set the duration of the animation for the animation label.
+ * @param obj Pointer to the animation label object.
+ * @param duration The duration of the animation in milliseconds.
+ */
+void lv_anim_label_set_time(lv_obj_t* obj, uint32_t duration);
+
+/**
+ * Set the animation path callback for the animation label.
+ * @param obj Pointer to the animation label object.
+ * @param path_cb Callback function for the animation path.
+ */
+void lv_anim_label_set_path(lv_obj_t* obj, lv_anim_path_cb_t path_cb);
+
+/**
+ * Add a style to the animation label.
+ * @param obj Pointer to the animation label object.
+ * @param style Pointer to the style to add.
+ */
+void lv_anim_label_add_style(lv_obj_t* obj, lv_style_t* style);
+
+/**
+ * Set a custom enter animation for the animation label.
+ * @param obj Pointer to the animation label object.
+ * @param a Pointer to the custom animation.
+ */
+void lv_anim_label_set_custom_enter_anim(lv_obj_t* obj, const lv_anim_t* a);
+
+/**
+ * Set a custom exit animation for the animation label.
+ * @param obj Pointer to the animation label object.
+ * @param a Pointer to the custom animation.
+ */
+void lv_anim_label_set_custom_exit_anim(lv_obj_t* obj, const lv_anim_t* a);
+
+/**
+ * Push new text to the animation label.
+ * @param obj Pointer to the animation label object.
+ * @param txt The text to push to the animation label.
+ */
+void lv_anim_label_push_text(lv_obj_t* obj, const char* txt);
+
+/**
+ * Get the enter direction of the animation label.
+ * @param obj Pointer to the animation label object.
+ * @return The current enter direction of the animation label.
+ */
+lv_dir_t lv_anim_label_get_enter_dir(lv_obj_t* obj);
+
+/**
+ * Get the exit direction of the animation label.
+ * @param obj Pointer to the animation label object.
+ * @return The current exit direction of the animation label.
+ */
+lv_dir_t lv_anim_label_get_exit_dir(lv_obj_t* obj);
+
+/**
+ * Get the duration of the animation for the animation label.
+ * @param obj Pointer to the animation label object.
+ * @return The duration of the animation in milliseconds.
+ */
+uint32_t lv_anim_label_get_time(lv_obj_t* obj);
+
+/**
+ * Get the animation path callback for the animation label.
+ * @param obj Pointer to the animation label object.
+ * @return The callback function for the animation path.
+ */
+lv_anim_path_cb_t lv_anim_label_get_path(lv_obj_t* obj);
+
+/**
+ * Get the current text of the animation label.
+ * @param obj Pointer to the animation label object.
+ * @return The current text of the animation label.
+ */
+const char* lv_anim_label_get_text(lv_obj_t* obj);
+
+/**********************
+ * MACROS
+ **********************/
+
+#ifdef __cplusplus
+} /*extern "C"*/
+#endif
+
+#endif /*LV_ANIM_LABEL_H*/
\ No newline at end of file
diff --git a/bandx/utils/lv_auto_event.c b/bandx/utils/lv_auto_event.c
new file mode 100755
index 0000000000000000000000000000000000000000..c99f31eed9ca642e7aa7734059c572fbe1a0c48d
--- /dev/null
+++ b/bandx/utils/lv_auto_event.c
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2020 Xiaomi Corporation
+ *
+ * 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.
+ */
+
+/*********************
+ * INCLUDES
+ *********************/
+
+#include "lv_auto_event.h"
+
+/*********************
+ * DEFINES
+ *********************/
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+/**********************
+ * STATIC PROTOTYPES
+ **********************/
+
+static void lv_auto_event_timer_handler(lv_timer_t* timer);
+
+/**********************
+ * STATIC VARIABLES
+ **********************/
+
+/**********************
+ * MACROS
+ **********************/
+
+/**********************
+ * GLOBAL FUNCTIONS
+ **********************/
+
+lv_auto_event_t* lv_auto_event_create(lv_auto_event_data_t* auto_event_data, uint32_t len)
+{
+ lv_auto_event_t* ae = lv_malloc(sizeof(lv_auto_event_t));
+
+ if (ae == NULL) {
+ return NULL;
+ }
+
+ ae->auto_event_data = auto_event_data;
+ ae->len = len;
+ ae->run_index = 0;
+ ae->timer = lv_timer_create(lv_auto_event_timer_handler, auto_event_data[0].delay, ae);
+ return ae;
+}
+
+void lv_auto_event_del(lv_auto_event_t* ae)
+{
+ if (ae == NULL) {
+ return;
+ }
+
+ if (ae->timer != NULL) {
+ lv_timer_del(ae->timer);
+ ae->timer = NULL;
+ }
+
+ lv_free(ae);
+}
+
+/**********************
+ * STATIC FUNCTIONS
+ **********************/
+
+static void lv_auto_event_timer_handler(lv_timer_t* timer)
+{
+ lv_auto_event_t* ae = lv_timer_get_user_data(timer);
+
+ uint32_t cur_index = ae->run_index;
+ ae->run_index++;
+
+ if (ae->run_index < ae->len) {
+ lv_timer_set_period(timer, ae->auto_event_data[ae->run_index].delay);
+ } else {
+ lv_timer_del(timer);
+ ae->timer = NULL;
+ }
+
+ lv_obj_send_event(
+ *(ae->auto_event_data[cur_index].obj_p),
+ ae->auto_event_data[cur_index].event,
+ ae->auto_event_data[cur_index].user_data);
+}
\ No newline at end of file
diff --git a/bandx/utils/lv_auto_event.h b/bandx/utils/lv_auto_event.h
new file mode 100755
index 0000000000000000000000000000000000000000..a101854d1b3c6be8f00935d5cf77e2bed698a2ce
--- /dev/null
+++ b/bandx/utils/lv_auto_event.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2020 Xiaomi Corporation
+ *
+ * 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.
+ */
+#ifndef __LV_AUTO_EVENT_H
+#define __LV_AUTO_EVENT_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*********************
+ * INCLUDES
+ *********************/
+
+#include "lvgl/lvgl.h"
+
+/*********************
+ * DEFINES
+ *********************/
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+typedef struct {
+ lv_obj_t** obj_p;
+ lv_event_code_t event;
+ uint32_t delay;
+ void* user_data;
+} lv_auto_event_data_t;
+
+typedef struct {
+ lv_timer_t* timer;
+ lv_auto_event_data_t* auto_event_data;
+ uint32_t len;
+ uint32_t run_index;
+} lv_auto_event_t;
+
+/**********************
+ * GLOBAL PROTOTYPES
+ **********************/
+
+/**
+ * Create a new auto event.
+ * @param auto_event_data Pointer to the data structure containing auto event parameters.
+ * @param len Length of the auto event data array.
+ * @return A pointer to the newly created auto event, or NULL if creation failed.
+ */
+lv_auto_event_t* lv_auto_event_create(lv_auto_event_data_t* auto_event_data, uint32_t len);
+
+/**
+ * Delete an existing auto event.
+ * @param ae Pointer to the auto event to be deleted.
+ */
+void lv_auto_event_del(lv_auto_event_t* ae);
+
+/**********************
+ * MACROS
+ **********************/
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/bandx/utils/lv_obj_ext_func.c b/bandx/utils/lv_obj_ext_func.c
new file mode 100755
index 0000000000000000000000000000000000000000..c20dd33d73b99f60c13bc63705c716c97099b1cd
--- /dev/null
+++ b/bandx/utils/lv_obj_ext_func.c
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2020 Xiaomi Corporation
+ *
+ * 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.
+ */
+
+/*********************
+ * INCLUDES
+ *********************/
+
+#include "lv_obj_ext_func.h"
+
+/*********************
+ * DEFINES
+ *********************/
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+/**********************
+ * STATIC PROTOTYPES
+ **********************/
+
+/**********************
+ * STATIC VARIABLES
+ **********************/
+
+/**********************
+ * MACROS
+ **********************/
+
+/**********************
+ * GLOBAL FUNCTIONS
+ **********************/
+
+void lv_obj_add_anim(
+ lv_obj_t* obj, lv_anim_t* a,
+ lv_anim_exec_xcb_t exec_cb,
+ int32_t start, int32_t end,
+ uint16_t time,
+ uint32_t delay,
+ lv_anim_ready_cb_t ready_cb,
+ lv_anim_path_cb_t path_cb)
+{
+ lv_anim_t anim_temp;
+
+ if (a == NULL) {
+ a = &anim_temp;
+
+ /* INITIALIZE AN ANIMATION
+ *-----------------------*/
+ lv_anim_init(a);
+ }
+
+ /* MANDATORY SETTINGS
+ *------------------*/
+
+ /*Set the "animator" function*/
+ lv_anim_set_exec_cb(a, exec_cb);
+
+ /*Set the "animator" function*/
+ lv_anim_set_var(a, obj);
+
+ /*Length of the animation [ms]*/
+ lv_anim_set_time(a, time);
+
+ /*Set start and end values. E.g. 0, 150*/
+ lv_anim_set_values(a, start, end);
+
+ /* OPTIONAL SETTINGS
+ *------------------*/
+
+ /*Time to wait before starting the animation [ms]*/
+ lv_anim_set_delay(a, delay);
+
+ /*Set the path in an animation*/
+ lv_anim_set_path_cb(a, path_cb);
+
+ /*Set a callback to call when animation is ready.*/
+ lv_anim_set_ready_cb(a, ready_cb);
+
+ /*Set a callback to call when animation is started (after delay).*/
+ lv_anim_set_start_cb(a, ready_cb);
+
+ /* START THE ANIMATION
+ *------------------*/
+ lv_anim_start(a); /*Start the animation*/
+}
+
+/**********************
+ * STATIC FUNCTIONS
+ **********************/
diff --git a/bandx/utils/lv_obj_ext_func.h b/bandx/utils/lv_obj_ext_func.h
new file mode 100755
index 0000000000000000000000000000000000000000..a0f34d76bbb80002e4f801e6ca57135216e2613d
--- /dev/null
+++ b/bandx/utils/lv_obj_ext_func.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2020 Xiaomi Corporation
+ *
+ * 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.
+ */
+#ifndef __LV_OBJ_EXT_FUNC_H
+#define __LV_OBJ_EXT_FUNC_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*********************
+ * INCLUDES
+ *********************/
+
+#include "lvgl/lvgl.h"
+
+/*********************
+ * DEFINES
+ *********************/
+
+#define LV_ANIM_TIME_DEFAULT 300
+#define LV_ANIM_EXEC(attr) (lv_anim_exec_xcb_t) lv_obj_set_##attr
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+/**********************
+ * GLOBAL PROTOTYPES
+ **********************/
+
+/**
+ * Add animation to an object.
+ * @param obj pointer to the object.
+ * @param a pointer to the animation (can be NULL).
+ * @param exec_cb the callback to execute for the animation.
+ * @param start the starting value of the animation.
+ * @param end the ending value of the animation.
+ * @param time the duration of the animation in milliseconds.
+ * @param delay the delay before the animation starts in milliseconds.
+ * @param ready_cb the callback to call when the animation is ready (can be NULL).
+ * @param path_cb the callback for the animation path (can be NULL).
+ */
+void lv_obj_add_anim(
+ lv_obj_t* obj, lv_anim_t* a,
+ lv_anim_exec_xcb_t exec_cb,
+ int32_t start,
+ int32_t end,
+ uint16_t time,
+ uint32_t delay,
+ lv_anim_ready_cb_t ready_cb,
+ lv_anim_path_cb_t path_cb);
+
+/**
+ * Macro to add animation to an object with specified attributes.
+ * @param obj pointer to the object.
+ * @param attr the attribute to animate.
+ * @param target the target value for the animation.
+ * @param time the duration of the animation in milliseconds.
+ */
+#define LV_OBJ_ADD_ANIM(obj, attr, target, time) \
+ do { \
+ lv_obj_add_anim( \
+ (obj), NULL, \
+ (lv_anim_exec_xcb_t)lv_obj_set_##attr, \
+ lv_obj_get_##attr(obj), \
+ (target), \
+ (time), \
+ 0, \
+ NULL, \
+ lv_anim_path_ease_out); \
+ } while (0)
+
+/**********************
+ * MACROS
+ **********************/
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/breakout/.vscode/settings.json b/breakout/.vscode/settings.json
new file mode 100644
index 0000000000000000000000000000000000000000..cc695570c08595296c4adc63018dc248a7955059
--- /dev/null
+++ b/breakout/.vscode/settings.json
@@ -0,0 +1,48 @@
+{
+ "files.associations": {
+ "array": "cpp",
+ "atomic": "cpp",
+ "bit": "cpp",
+ "cctype": "cpp",
+ "clocale": "cpp",
+ "cmath": "cpp",
+ "compare": "cpp",
+ "concepts": "cpp",
+ "cstdarg": "cpp",
+ "cstddef": "cpp",
+ "cstdint": "cpp",
+ "cstdio": "cpp",
+ "cstdlib": "cpp",
+ "cwchar": "cpp",
+ "cwctype": "cpp",
+ "deque": "cpp",
+ "string": "cpp",
+ "unordered_map": "cpp",
+ "vector": "cpp",
+ "exception": "cpp",
+ "algorithm": "cpp",
+ "functional": "cpp",
+ "iterator": "cpp",
+ "memory": "cpp",
+ "memory_resource": "cpp",
+ "numeric": "cpp",
+ "optional": "cpp",
+ "random": "cpp",
+ "string_view": "cpp",
+ "system_error": "cpp",
+ "tuple": "cpp",
+ "type_traits": "cpp",
+ "utility": "cpp",
+ "initializer_list": "cpp",
+ "iosfwd": "cpp",
+ "limits": "cpp",
+ "new": "cpp",
+ "numbers": "cpp",
+ "ostream": "cpp",
+ "ranges": "cpp",
+ "stdexcept": "cpp",
+ "streambuf": "cpp",
+ "typeinfo": "cpp",
+ "map": "cpp"
+ }
+}
\ No newline at end of file
diff --git a/breakout/Kconfig b/breakout/Kconfig
new file mode 100644
index 0000000000000000000000000000000000000000..b34c07af636201ea95a17d884665a12be989a5c6
--- /dev/null
+++ b/breakout/Kconfig
@@ -0,0 +1,17 @@
+#
+# For a description of the syntax of this configuration file,
+# see the file kconfig-language.txt in the NuttX tools repository.
+#
+
+config LVX_USE_DEMO_BREAKOUT
+ bool "breakout"
+ default n
+
+if LVX_USE_DEMO_BREAKOUT
+ config LVX_BREAKOUT_DATA_ROOT
+ string "BREAKOUT Data Root"
+ default "/data"
+ config LVX_BREAKOUT_STACKSIZE
+ int "LVX_BREAKOUT stack size"
+ default 65536
+endif
diff --git a/breakout/Make.defs b/breakout/Make.defs
new file mode 100644
index 0000000000000000000000000000000000000000..e5f6c177df689ddb2c039a5e0b4db1a0082dbce2
--- /dev/null
+++ b/breakout/Make.defs
@@ -0,0 +1,18 @@
+#
+# Copyright (C) 2023 Xiaomi Corporation
+#
+# 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.
+#
+ifneq ($(CONFIG_LVX_USE_DEMO_BREAKOUT),)
+CONFIGURED_APPS += $(APPDIR)/packages/demos/breakout
+endif
diff --git a/breakout/Makefile b/breakout/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..f9dbb5020a7bd0dbd8063540a00407edd901c174
--- /dev/null
+++ b/breakout/Makefile
@@ -0,0 +1,32 @@
+#
+# Copyright (C) 2022 Xiaomi Corporation
+#
+# 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.
+#
+
+include $(APPDIR)/Make.defs
+
+ifeq ($(CONFIG_LVX_USE_DEMO_BREAKOUT), y)
+PROGNAME = breakout
+PRIORITY = 100
+STACKSIZE = $(CONFIG_LVX_BREAKOUT_STACKSIZE)
+MODULE = $(CONFIG_LVX_USE_DEMO_BREAKOUT)
+CSRCS += src/Audio/audio_ctl.c
+CXXFLAGS += -I$(APPDIR)/packages/demos/breakout/src
+CXXEXT := .cpp
+CXXSRCS += $(shell find -L ./ -name "*.cpp" | grep -v breakout_main.cpp)
+
+MAINSRC = breakout_main.cpp
+endif
+
+include $(APPDIR)/Application.mk
diff --git a/breakout/Readme.md b/breakout/Readme.md
new file mode 100644
index 0000000000000000000000000000000000000000..e83eb2998ad51d90391fb79a46fdcedcf2657b25
--- /dev/null
+++ b/breakout/Readme.md
@@ -0,0 +1,148 @@
+# 🎮 Breakout Game 《打砖块游戏》
+**“KUN生是旷野,不是轨道!”**
+拥有对篮球无限热爱的KUN,在家族中独树一帜,正挥洒着汗水,勤奋努力的击打篮球撞碎砖块,缔造属于自己的篮球哲学。
+
+🧱 基于OpenVela和LVGL开发的触屏Breakout打砖块游戏,实现了基本的游戏逻辑,增加了图片素材并实现了打击音效。
+
+
+在对应素材文件夹下,已通过`readme`和`LICENSE`文件详细标明了作者出处等信息,满足了相应的license协议要求,允许个人在作品及商业中使用,但不得对素材进行单独售卖。
+
+以下是第二版游戏界面:
+
+
+
+# ✨ Features 特点
+
+- **游戏区域**:使用 LVGL 创建全屏的游戏区域,并支持动态生成砖块。
+- **挡板与球的物理机制**:挡板通过触摸输入进行控制,球的运动具有物理效果,碰撞后反弹。
+- **砖块系统**:砖块具有血量,可被破坏,支持不同类型砖块的行为。
+- **音效反馈**:根据砖块的血量播放不同的音效。
+- **触摸输入**:支持触摸屏输入来控制挡板移动。
+- **游戏状态管理**:管理游戏状态(例如:PLAYING、GAME_OVER、PAUSE)以控制游戏流程。
+
+
+# 🖼️ Image Handling(图片处理)
+## 1. libpng decoder(libpng 解码器)
+好消息,OpenVela系统已经内置了该PNG解码器,仅需将`LIB_PNG`和`LV_USE_LIBPNG`配置为`yes`,即可轻松使用`lv_image_set_src`等函数读取使用`PNG`图片。使用方法可以参考:`https://lvgl.100ask.net/master/details/libs/libpng.html`
+此外,这是libpng的github仓库链接:`https://github.com/pnggroup/libpng`,自己手动引入还是有难度。
+
+## 2.Image Caching(图片缓存)
+LVGL 支持将图像缓存到内存中,以提升图像组件在运行时的加载速度,特别适用于反复绘制的图像场景,如游戏背景、按钮图标等。
+在从外部读入图片过多,会有明显卡顿,可以使用图片缓存的方法,将图片读入内存。具体可以参考:`https://lvgl.100ask.net/master/details/main-components/image.html#overview-image-caching`
+
+# 🔊 Audio Handling(音频处理)
+引用了packages_demos仓库下的music_player的音频控制文件`audio_ctl.c`和`audio_ctl.h`,通过`audio_ctl_init_nxaudio`加载音频文件,`audio_ctl_start`播放音频,`audio_ctl_stop`暂停音频,`audio_ctl_uninit_nxaudio`释放资源。目前实现了碰撞砖块发出音效,但还存在一些bug。
+
+
+
+# 🚀 Getting Started 快速开始
+
+首先进入模拟器配置
+## 1.配置模拟器(menuconfig)
+```bash
+./build.sh vendor/openvela/boards/vela/configs/goldfish-armeabi-v7a-ap menuconfig
+```
+
+使用`/`键进入搜索模式
+- 配置`LVX_USE_DEMO_BREAKOUT `为 `yes`
+- `LVX_BREAKOUT_DATA_ROOT`的路径设置为`/data ` (Kconfig文件默认预设为 `/data`)
+- `LVX_BREAKOUT_STACKSIZE`设置为65536
+- `LIB_PNG`和`LV_USE_LIBPNG`配置为`yes` (读取png图片资源)
+- `AUDIO`和`AUDIOUTILS_NXAUDIO_LIB`配置为`yes` (读取wav音频文件)
+
+## 2.构建和清除构建文件
+### 开始构建
+```bash
+./build.sh vendor/openvela/boards/vela/configs/goldfish-armeabi-v7a-ap -j$(nproc)
+```
+### 清理构建产物
+```bash
+./build.sh vendor/openvela/boards/vela/configs/goldfish-armeabi-v7a-ap distclean -j$(nproc)
+```
+## 3.ADB推送更新关卡等资源(在模拟器运行状况下)
+```bash
+adb push apps/packages/demos/breakout/res /data/
+```
+
+## 4.启动模拟器
+```bash
+./emulator.sh vela
+```
+
+## 5.启动游戏
+```bash
+breakout &
+```
+---
+# ▶️ 启动游戏
+
+在 main文件函数中初始化 LVGL后,调用:
+
+ballgame_start(); // 启动游戏
+
+
+# 🎮 Controls
+
+- 移动挡板:按住并拖动屏幕,挡板随手指水平移动。
+
+- 发射小球:游戏开始时,轻触屏幕即可发射小球。
+
+- 重新开始:掉球后点击 "Restart" 按钮重开一局。
+
+# 📄 Level File Format (.dat)
+
+放置关卡文件:
+将 .dat 文件放到资源路径下,例如:
+
+ `breakout/res/map/level_1.dat`
+
+关卡文件为文本格式,使用字符描述砖块:
+| 字符 | 说明 | 生命值 |
+|------|----------------|--------|
+| 空格 | 空(无生成) | 无 |
+| `#` | 墙(不可破坏) | -1 |
+| `A` | 绿色砖块 | 1 |
+| `B` | 橙色砖块 | 2 |
+| `C` | 紫色砖块 | 3 |
+| `D` | 黄色砖块 | 4 |
+| `E` | 蓝色砖块 | 5 |
+| 未知类型 | 绿色砖块 | 1 |
+
+此处读取文件时,会将换行符`\n`,制表符`\r`,文件结尾符号`\0`也识别出来,需要注意排除。
+
+关卡示例:
+```
+#######
+#A B A#
+# C #
+#######
+```
+
+
+# 素材使用声明
+- 图片素材来源于`https://icon.sucai999.com/`以及`https://craftpix.net`
+- 音频素材来源于`https://freesound.org/`
+
+**本项目中的图片资源可以在个人作品中使用,并且可以用于商业项目,前提是遵守相应的许可协议。**
+
+- **Microsoft 图片**:可在个人及商业项目中使用,需遵守 **MIT** 许可协议。
+- **Google 图片**:可在个人及商业项目中使用,需遵守 **Apache 2.0** 许可协议,并附带版权声明、许可证文本及修改声明。
+- **CraftPix.net 图片**:可用于个人及商业项目,但需遵守 **CraftPix 自定义许可协议**,保留版权信息并不得单独转售源文件。
+- **freesound音频**:本作品使用的音频文件皆为 **CC0(Creative Commons 0)** 授权的资源,表示该资源已被作者明确放弃所有版权及相关权利,可自由复制、修改、分发,亦可用于个人或商业用途,无需署名、无需许可、无任何限制。
+
+
+
+
+# 🛠️ TODO 未来的工作
+
+- UI和界面美化 √
+
+- 得分与生命系统
+
+- 多种道具(如掉落多球分裂,加长板等)
+
+- 更新多关卡支持与切换
+
+- 增加交互音效 √
+
+- 更智能的反弹算法(目前在考虑box2d)
diff --git a/breakout/breakout_main.cpp b/breakout/breakout_main.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..6b64e80c7d90d12a4535786598a24671089e944e
--- /dev/null
+++ b/breakout/breakout_main.cpp
@@ -0,0 +1,78 @@
+/****************************************************
+ * breakout_main.cpp
+ * Main program entry for the breakout game on NuttX OS.
+ ***************************************************/
+
+#include
+#include
+#include
+#include "src/breakout.h"
+#include
+
+/**
+ * A helper function to initialize and run the libuv event loop.
+ * The loop handles input events, timers, and screen refreshes.
+ */
+static void lv_nuttx_uv_loop(uv_loop_t* loop, lv_nuttx_result_t* result)
+{
+ // Create a struct to pass integration info between NuttX and LVGL
+ lv_nuttx_uv_t uv_info;
+ void* data;
+
+ // Initialize the libuv event loop
+ uv_loop_init(loop);
+
+ // Prepare config info to pass to the LVGL-UV integration layer
+ lv_memset(&uv_info, 0, sizeof(uv_info));
+ uv_info.loop = loop;
+ uv_info.disp = result->disp; // Pass in the initialized display device
+ uv_info.indev = result->indev; // Pass in the initialized input device (touchscreen)
+#ifdef CONFIG_UINPUT_TOUCH
+ uv_info.uindev = result->utouch_indev; // If configured, also pass virtual user input device
+#endif
+ data = lv_nuttx_uv_init(&uv_info);
+ uv_run(loop, UV_RUN_DEFAULT);
+ lv_nuttx_uv_deinit(&data);
+}
+
+/**************************************************
+ * Application main entry function.
+ ***************************************************/
+extern "C" int breakout_main(int argc, FAR char *argv[])
+{
+ lv_nuttx_dsc_t info;
+ lv_nuttx_result_t result;
+ uv_loop_t ui_loop;
+ lv_memset(&ui_loop, 0, sizeof(uv_loop_t));
+
+ // Safety check: ensure LVGL is not initialized multiple times
+ if (lv_is_initialized()) {
+ LV_LOG_ERROR("LVGL already initialized! aborting.");
+ return -1;
+ }
+
+ // Initialize LVGL core library
+ lv_init();
+
+ // Initialize and bind NuttX display and input drivers
+ lv_nuttx_dsc_init(&info);
+ lv_nuttx_init(&info, &result);
+
+ // Check if the display device was successfully initialized
+ if (result.disp == NULL) {
+ LV_LOG_ERROR("lv_demos initialization failure!");
+ return 1;
+ }
+
+ // Call the game start function to create the game UI and all objects
+ ballgame_start();
+
+ // Start the main event loop driving the entire UI and application
+ lv_nuttx_uv_loop(&ui_loop, &result);
+
+ // After the application exits the loop, deinitialize all resources
+ lv_nuttx_deinit(&result);
+ lv_deinit();
+
+ return 0;
+}
diff --git a/breakout/res/audio/A4.wav b/breakout/res/audio/A4.wav
new file mode 100644
index 0000000000000000000000000000000000000000..7137b6c75a10175d9f49910b797be1bbc8bc1632
Binary files /dev/null and b/breakout/res/audio/A4.wav differ
diff --git a/breakout/res/audio/B4.wav b/breakout/res/audio/B4.wav
new file mode 100644
index 0000000000000000000000000000000000000000..8d2e1e71a6c958ea360daa2645e03ce9b58b52e1
Binary files /dev/null and b/breakout/res/audio/B4.wav differ
diff --git a/breakout/res/audio/C4.wav b/breakout/res/audio/C4.wav
new file mode 100644
index 0000000000000000000000000000000000000000..2ba65afad1bfd257879b8ed31f5e00e5115613b1
Binary files /dev/null and b/breakout/res/audio/C4.wav differ
diff --git a/breakout/res/audio/C5.wav b/breakout/res/audio/C5.wav
new file mode 100644
index 0000000000000000000000000000000000000000..1269b363a7fa38d3006d682b5f0b378fc0921dad
Binary files /dev/null and b/breakout/res/audio/C5.wav differ
diff --git a/breakout/res/audio/D4.wav b/breakout/res/audio/D4.wav
new file mode 100644
index 0000000000000000000000000000000000000000..6b18f52995739d98306249d267f957b79d50a1fc
Binary files /dev/null and b/breakout/res/audio/D4.wav differ
diff --git a/breakout/res/audio/E4.wav b/breakout/res/audio/E4.wav
new file mode 100644
index 0000000000000000000000000000000000000000..b9dbeb5d1ce1b1f4b52a8b54e11c5a796c555994
Binary files /dev/null and b/breakout/res/audio/E4.wav differ
diff --git a/breakout/res/audio/F4.wav b/breakout/res/audio/F4.wav
new file mode 100644
index 0000000000000000000000000000000000000000..fcbd66beb0d0b5e4e4b0c605281d1d781dd2ca1a
Binary files /dev/null and b/breakout/res/audio/F4.wav differ
diff --git a/breakout/res/audio/G4.wav b/breakout/res/audio/G4.wav
new file mode 100644
index 0000000000000000000000000000000000000000..4ffb2b850acb04241d88e42d55dd77816003e253
Binary files /dev/null and b/breakout/res/audio/G4.wav differ
diff --git a/breakout/res/audio/LICENSE b/breakout/res/audio/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..a1974cbb9cb88a97653494b3d3f899ee22873653
--- /dev/null
+++ b/breakout/res/audio/LICENSE
@@ -0,0 +1,36 @@
+CC0 1.0 Universal
+
+CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER.
+Statement of Purpose
+
+The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an "owner") of an original work of authorship and/or a database (each, a "Work").
+
+Certain owners wish to permanently relinquish those rights to a Work for the purpose of contributing to a commons of creative, cultural and scientific works ("Commons") that the public can reliably and without fear of later claims of infringement build upon, modify, incorporate in other works, reuse and redistribute as freely as possible in any form whatsoever and for any purposes, including without limitation commercial purposes. These owners may contribute to the Commons to promote the ideal of a free culture and the further production of creative, cultural and scientific works, or to gain reputation or greater distribution for their Work in part through the use and efforts of others.
+
+For these and/or other purposes and motivations, and without any expectation of additional consideration or compensation, the person associating CC0 with a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on those rights.
+1. Copyright and Related Rights.
+
+A Work made available under CC0 may be protected by copyright and related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights include, but are not limited to, the following:
+
+ the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work;
+ moral rights retained by the original author(s) and/or performer(s);
+ publicity and privacy rights pertaining to a person's image or likeness depicted in a Work;
+ rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(a), below;
+ rights protecting the extraction, dissemination, use and reuse of data in a Work;
+ database rights (such as those arising under Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, and under any national implementation thereof, including any amended or successor version of such directive); and
+ other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof.
+
+2. Waiver.
+
+To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons, and surrenders all of Affirmer's Copyright and Related Rights and associated claims and causes of action, whether now known or unknown (including existing as well as future claims and causes of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each member of the public at large and to the detriment of Affirmer's heirs and successors, fully intending that such Waiver shall not be subject to revocation, rescission, cancellation, termination, or any other legal or equitable action to disrupt the quiet enjoyment of the Work by the public as contemplated by Affirmer's express Statement of Purpose.
+3. Public License Fallback.
+
+Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum extent permitted taking into account Affirmer's express Statement of Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free, non transferable, non sublicensable, non exclusive, irrevocable and unconditional license to exercise Affirmer's Copyright and Related Rights in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "License"). The License shall be deemed effective as of the date CC0 was applied by Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate the remainder of the License, and in such case Affirmer hereby affirms that he or she will not (i) exercise any of his or her remaining Copyright and Related Rights in the Work or (ii) assert any associated claims and causes of action with respect to the Work, in either case contrary to Affirmer's express Statement of Purpose.
+4. Limitations and Disclaimers.
+
+ No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document.
+ Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning the Work, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non infringement, or the absence of latent or other defects, accuracy, or the present or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law.
+ Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work or any use thereof, including without limitation any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions or other rights required for any use of the Work.
+ Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work.
+
+
diff --git a/breakout/res/audio/readme.md b/breakout/res/audio/readme.md
new file mode 100644
index 0000000000000000000000000000000000000000..f53e8e758fe1f8bb096b3e037ddc1f92c7b089be
--- /dev/null
+++ b/breakout/res/audio/readme.md
@@ -0,0 +1,24 @@
+
+All audio files are from: https://freesound.org/people/Teddy_Frost/
+
+All are licensed under the Creative Commons 0 (CC0) license.
+
+You can copy, modify, distribute, and perform the sound, even for commercial purposes, all without the need of asking permission from the author.
+
+
+
+ E4.wav by Teddy_Frost — https://freesound.org/s/334542/ — License: Creative Commons 0
+
+ F4.wav by Teddy_Frost — https://freesound.org/s/334541/ — License: Creative Commons 0
+
+ G4.wav by Teddy_Frost — https://freesound.org/s/334540/ — License: Creative Commons 0
+
+ B4.wav by Teddy_Frost — https://freesound.org/s/334539/ — License: Creative Commons 0
+
+ C4.wav by Teddy_Frost — https://freesound.org/s/334538/ — License: Creative Commons 0
+
+ C5.wav by Teddy_Frost — https://freesound.org/s/334537/ — License: Creative Commons 0
+
+ D4.wav by Teddy_Frost — https://freesound.org/s/334536/ — License: Creative Commons 0
+
+ A4.wav by Teddy_Frost — https://freesound.org/s/334534/ — License: Creative Commons 0
diff --git a/breakout/res/fonts/MiSans-Normal.ttf b/breakout/res/fonts/MiSans-Normal.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..3199d464141d0599dd1269a3fef4264a829c6da0
Binary files /dev/null and b/breakout/res/fonts/MiSans-Normal.ttf differ
diff --git a/breakout/res/fonts/MiSans-Semibold.ttf b/breakout/res/fonts/MiSans-Semibold.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..83c1a532632f582550d48bae117d758191ad1800
Binary files /dev/null and b/breakout/res/fonts/MiSans-Semibold.ttf differ
diff --git a/breakout/res/icons/KUN.png b/breakout/res/icons/KUN.png
new file mode 100644
index 0000000000000000000000000000000000000000..2efcde2c9a73633e2fefab9d5ab647e03add6b4d
Binary files /dev/null and b/breakout/res/icons/KUN.png differ
diff --git a/breakout/res/icons/LICENSE b/breakout/res/icons/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..fa127e844fff8bb29492eaf221c04d33c33b660f
--- /dev/null
+++ b/breakout/res/icons/LICENSE
@@ -0,0 +1,53 @@
+# LICENSE
+
+The copyright and license statements for the image resources used in this project are as follows:
+
+---
+
+## Microsoft Corporation Image Resources (MIT License)
+
+Images: ball.png, brick_#.png, brick_A.png, brick_B.png, brick_C.png, brick_D.png, brick_E.png, brick_F.png
+
+Copyright Notice:
+Copyright (c) Microsoft Corporation
+
+License:
+MIT License
+
+Full license text: https://opensource.org/licenses/MIT
+
+---
+
+## Google Inc Image Resources (Apache License 2.0)
+
+Image: KUN.png
+
+Copyright Notice:
+Copyright 2023 Google Inc
+
+License:
+Apache License, Version 2.0
+
+Full license text: https://www.apache.org/licenses/LICENSE-2.0
+
+---
+
+## CraftPix.net Image Resources (CraftPix.net License)
+
+Image: background.png
+
+Copyright Notice:
+© CraftPix.net - 2D & 3D Game Assets
+
+License details:
+Please refer to https://craftpix.net/file-licenses/
+
+---
+
+## Usage Notes
+
+- The image resources included in this project are subject to the above different licenses; please comply with the corresponding license terms.
+- Both MIT and Apache 2.0 licenses allow commercial use but require preserving copyright and license notices.
+- Use of CraftPix assets must strictly follow their official license terms; direct resale of source files is prohibited.
+
+
diff --git a/breakout/res/icons/background_1.png b/breakout/res/icons/background_1.png
new file mode 100644
index 0000000000000000000000000000000000000000..aa7e66e7c84a48b36da34d9623c852357f4d0a28
Binary files /dev/null and b/breakout/res/icons/background_1.png differ
diff --git a/breakout/res/icons/background_2.png b/breakout/res/icons/background_2.png
new file mode 100644
index 0000000000000000000000000000000000000000..248c3846b0c644e82e6bc18b47055b3906114602
Binary files /dev/null and b/breakout/res/icons/background_2.png differ
diff --git a/breakout/res/icons/background_3.png b/breakout/res/icons/background_3.png
new file mode 100644
index 0000000000000000000000000000000000000000..f6bfe203f58483abcc07c249b061b624b93c2c02
Binary files /dev/null and b/breakout/res/icons/background_3.png differ
diff --git a/breakout/res/icons/ball.png b/breakout/res/icons/ball.png
new file mode 100644
index 0000000000000000000000000000000000000000..4c43f9ea92b4616d9ff331f50d02c4f436384484
Binary files /dev/null and b/breakout/res/icons/ball.png differ
diff --git a/breakout/res/icons/brick_#.png b/breakout/res/icons/brick_#.png
new file mode 100644
index 0000000000000000000000000000000000000000..280a99c575379949c483db66309a87c86518f0c7
Binary files /dev/null and b/breakout/res/icons/brick_#.png differ
diff --git a/breakout/res/icons/brick_A.png b/breakout/res/icons/brick_A.png
new file mode 100644
index 0000000000000000000000000000000000000000..db7a95b4af9029dfae6a515545ccd89a38296ceb
Binary files /dev/null and b/breakout/res/icons/brick_A.png differ
diff --git a/breakout/res/icons/brick_B.png b/breakout/res/icons/brick_B.png
new file mode 100644
index 0000000000000000000000000000000000000000..4c6609435fba41c40ed738f060c315d6b9839a9d
Binary files /dev/null and b/breakout/res/icons/brick_B.png differ
diff --git a/breakout/res/icons/brick_C.png b/breakout/res/icons/brick_C.png
new file mode 100644
index 0000000000000000000000000000000000000000..ed398d6f4f84a1731b5a27d843a2da00757b11be
Binary files /dev/null and b/breakout/res/icons/brick_C.png differ
diff --git a/breakout/res/icons/brick_D.png b/breakout/res/icons/brick_D.png
new file mode 100644
index 0000000000000000000000000000000000000000..a77025c84dd17b8e6387d554e89c7518e17e24f8
Binary files /dev/null and b/breakout/res/icons/brick_D.png differ
diff --git a/breakout/res/icons/brick_E.png b/breakout/res/icons/brick_E.png
new file mode 100644
index 0000000000000000000000000000000000000000..247696da70a88849564a3e5601ed9e64737a1747
Binary files /dev/null and b/breakout/res/icons/brick_E.png differ
diff --git a/breakout/res/icons/brick_F.png b/breakout/res/icons/brick_F.png
new file mode 100644
index 0000000000000000000000000000000000000000..91f011f2f8ee20c3d0c8133e620761b0319ae326
Binary files /dev/null and b/breakout/res/icons/brick_F.png differ
diff --git a/breakout/res/icons/readme.md b/breakout/res/icons/readme.md
new file mode 100644
index 0000000000000000000000000000000000000000..5942cc36c39794aefb1d55709b92dbefa1dc3a5b
--- /dev/null
+++ b/breakout/res/icons/readme.md
@@ -0,0 +1,29 @@
+1. Microsoft Images (MIT License)
+
+ Images: ball.png, brick_#.png, brick_A.png, brick_B.png, brick_C.png, brick_D.png, brick_E.png, brick_F.png
+
+ Author: Microsoft Corporation
+
+ License: MIT License
+
+2. Google Images (Apache 2.0 License)
+
+ Image: KUN.png
+
+ Author: Google Inc
+
+ License: https://www.apache.org/licenses/LICENSE-2.0
+
+The Apache 2.0 License requires including copyright notices, the license text, and the NOTICE file (if any), stating any modifications, and allows commercial use.
+
+3. CraftPix.net Images (CraftPix Custom License)
+
+ Image: background_1.png, bcakground_2.png, bcakground_3.png
+
+ Author: CraftPix.net
+
+ License: https://craftpix.net/file-licenses/
+
+The CraftPix license generally requires retaining copyright information, prohibits separate resale of source files, and allows use in commercial projects; please refer to their official license details for specifics.
+
+
diff --git a/breakout/res/map/level_1.dat b/breakout/res/map/level_1.dat
new file mode 100644
index 0000000000000000000000000000000000000000..aa63efc2b89f33ddaac25d5c9f1d57568bd44d72
--- /dev/null
+++ b/breakout/res/map/level_1.dat
@@ -0,0 +1,30 @@
+
+
+
+ EEE AEE EEA A
+ A A EE EE EEE
+ E E EE EE E
+ E E EE EE E
+ E E AE EE EEEE AE EAE E E EEEE E EEEE
+ E E EE E A A EE E EA AE A A E A EE
+ E E E E E E E E E E E E E EEE
+ E E E E EEEEEE E E E E EEEEEE E EEE EE
+ E E E E E E E EEE E E EE EE
+ A A EE E E A E E E E A E EE EEE
+ EEE E EE EEEE EEE EEE A EEEE EEEEE EE EEA
+ E
+ EEE
+
+ C C C D C
+ DDDDDDDDDDD D DDDDDDDD D D DDDDDDDDDD
+ D D D D D DDDDD D D
+ D D D D DDDDD D D C D DD D
+ D D C D D D D D DDDDDDD D D D
+ DDDDDDDDDDDD D D DDDDD D D D D
+ D D C D D D DD D D D D
+ D D D D DDDDD D D D D D D D
+ D D D D DD D D D DD D
+ D D D D D D D D D C D C
+ D D D D D D D D DDCDDDCD DDDDDDDDDDD
+ DD D DD DD C D C
+
diff --git a/breakout/res/map/level_2.dat b/breakout/res/map/level_2.dat
new file mode 100644
index 0000000000000000000000000000000000000000..ea35758910bdc726383eb7a259fc9681329c7fab
--- /dev/null
+++ b/breakout/res/map/level_2.dat
@@ -0,0 +1,25 @@
+
+
+ AABBBBCCDDAABB DDAABBBBCCDDAA
+ AABBBBCCDDAABB DDAABBBBCCDDAA
+ AABBBBCCDDAABB DDAABBBBCCDDAA
+
+
+ AABBBBCCDDAABB DDAABBBBCCDDAA
+ AABBBBCCDDAABB DDAABBBBCCDDAA
+ AABBBBCCDDAABB DDAABBBBCCDDAA
+
+
+ AABBBBCCDDAABB DDAABBBBCCDDAA
+ AABBBBCCDDAABB DDAABBBBCCDDAA
+ AABBBBCCDDAABB DDAABBBBCCDDAA
+
+
+ AABBBBE ECCDDAA
+ AABBBBE # # ECCDDAA
+ AABBBBE # # ECCDDAA
+ AABBBBE # # ECCDDAA
+ # #
+ # #
+############### ##############
+############### ##############
diff --git a/breakout/screenshot/screenshot_1.png b/breakout/screenshot/screenshot_1.png
new file mode 100644
index 0000000000000000000000000000000000000000..84845540a78f8984e881e2eb83c0c977e7fab5d0
Binary files /dev/null and b/breakout/screenshot/screenshot_1.png differ
diff --git a/breakout/screenshot/screenshot_2.png b/breakout/screenshot/screenshot_2.png
new file mode 100644
index 0000000000000000000000000000000000000000..ccea37c533510142d1a8483d72bac88d31d65727
Binary files /dev/null and b/breakout/screenshot/screenshot_2.png differ
diff --git a/breakout/src/Audio/audio_ctl.c b/breakout/src/Audio/audio_ctl.c
new file mode 100644
index 0000000000000000000000000000000000000000..765c6f437451bc80ac089d6757a758aa82a3edd6
--- /dev/null
+++ b/breakout/src/Audio/audio_ctl.c
@@ -0,0 +1,287 @@
+/*********************
+ * INCLUDES
+ *********************/
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "audio_ctl.h"
+
+#include
+
+/**********************
+ * STATIC PROTOTYPES
+ **********************/
+
+static void app_dequeue_cb(unsigned long arg,
+ FAR struct ap_buffer_s *apb);
+static void app_complete_cb(unsigned long arg);
+static void app_user_cb(unsigned long arg,
+ FAR struct audio_msg_s *msg, FAR bool *running);
+
+/**********************
+ * STATIC VARIABLES
+ **********************/
+
+static struct nxaudio_callbacks_s cbs =
+{
+ app_dequeue_cb,
+ app_complete_cb,
+ app_user_cb
+};
+
+/**********************
+ * STATIC FUNCTIONS
+ **********************/
+
+static void app_dequeue_cb(unsigned long arg, FAR struct ap_buffer_s *apb)
+{
+ FAR audioctl_s *ctl = (FAR audioctl_s *)(uintptr_t)arg;
+
+ if (!apb)
+ {
+ return;
+ }
+
+ if (ctl->seek) {
+ lseek(ctl->fd, ctl->seek_position, SEEK_SET);
+ ctl->file_position = ctl->seek_position;
+ ctl->seek = false;
+ }
+
+ apb->nbytes = read(ctl->fd, apb->samp, apb->nmaxbytes);
+ apb->curbyte = 0;
+ apb->flags = 0;
+
+ while (0 < apb->nbytes && apb->nbytes < apb->nmaxbytes)
+ {
+ int n = apb->nmaxbytes - apb->nbytes;
+ int ret = read(ctl->fd, &apb->samp[apb->nbytes], n);
+
+ if (0 >= ret)
+ {
+ break;
+ }
+ apb->nbytes += ret;
+ }
+
+ if (apb->nbytes < apb->nmaxbytes)
+ {
+ close(ctl->fd);
+ ctl->fd = -1;
+
+ return ;
+ }
+
+ ctl->file_position += apb->nbytes;
+
+ nxaudio_enqbuffer(&ctl->nxaudio, apb);
+}
+
+static void app_complete_cb(unsigned long arg)
+{
+ /* Do nothing.. */
+
+ printf("Audio loop is Done\n");
+}
+
+static void app_user_cb(unsigned long arg,
+ FAR struct audio_msg_s *msg, FAR bool *running)
+{
+ /* Do nothing.. */
+}
+
+static FAR void *audio_loop_thread(pthread_addr_t arg)
+{
+ FAR audioctl_s *ctl = (FAR audioctl_s *)arg;
+
+ nxaudio_start(&ctl->nxaudio);
+ nxaudio_msgloop(&ctl->nxaudio, &cbs,
+ (unsigned long)(uintptr_t)ctl);
+
+ return NULL;
+}
+
+/**********************
+ * GLOBAL FUNCTIONS
+ **********************/
+
+FAR audioctl_s *audio_ctl_init_nxaudio(FAR const char *arg)
+{
+ FAR audioctl_s *ctl;
+ int ret;
+ int i;
+
+ ctl = (FAR audioctl_s *)malloc(sizeof(audioctl_s));
+ if(ctl == NULL)
+ {
+ return NULL;
+ }
+
+ ctl->seek = false;
+ ctl->seek_position = 0;
+ ctl->file_position = 0;
+
+ ctl->fd = open(arg, O_RDONLY);
+ if (ctl->fd < 0) {
+ printf("can't open audio file\n");
+ return NULL;
+ }
+
+ read(ctl->fd, &ctl->wav, sizeof(ctl->wav));
+
+ ret = init_nxaudio(&ctl->nxaudio, ctl->wav.fmt.samplerate,
+ ctl->wav.fmt.bitspersample,
+ ctl->wav.fmt.numchannels);
+ if (ret < 0)
+ {
+ printf("init_nxaudio() return with error!!\n");
+ return NULL;
+ }
+
+ for (i = 0; i < ctl->nxaudio.abufnum; i++)
+ {
+ app_dequeue_cb((unsigned long)ctl, ctl->nxaudio.abufs[i]);
+ }
+
+ ctl->state = AUDIO_CTL_STATE_INIT;
+
+ return ctl;
+}
+
+int audio_ctl_start(FAR audioctl_s *ctl)
+{
+ if (ctl == NULL)
+ return -EINVAL;
+
+ if (ctl->state != AUDIO_CTL_STATE_INIT && ctl->state != AUDIO_CTL_STATE_PAUSE)
+ {
+ return -1;
+ }
+
+ ctl->state = AUDIO_CTL_STATE_START;
+
+ pthread_attr_t tattr;
+ struct sched_param sparam;
+
+ pthread_attr_init(&tattr);
+ sparam.sched_priority = sched_get_priority_max(SCHED_FIFO) - 9;
+ pthread_attr_setschedparam(&tattr, &sparam);
+ pthread_attr_setstacksize(&tattr, 4096);
+
+ pthread_create(&ctl->pid, &tattr, audio_loop_thread,
+ (pthread_addr_t)ctl);
+
+ pthread_attr_destroy(&tattr);
+ pthread_setname_np(ctl->pid, "audioctl_thread");
+
+ return 0;
+}
+
+int audio_ctl_pause(FAR audioctl_s *ctl)
+{
+ if (ctl == NULL)
+ return -EINVAL;
+
+ if (ctl->state != AUDIO_CTL_STATE_START)
+ {
+ return -1;
+ }
+
+ ctl->state = AUDIO_CTL_STATE_PAUSE;
+
+ return nxaudio_pause(&ctl->nxaudio);
+}
+
+int audio_ctl_resume(FAR audioctl_s *ctl)
+{
+ if (ctl == NULL)
+ return -EINVAL;
+
+ if (ctl->state != AUDIO_CTL_STATE_PAUSE)
+ {
+ return -1;
+ }
+
+ ctl->state = AUDIO_CTL_STATE_START;
+
+ return nxaudio_resume(&ctl->nxaudio);
+}
+
+int audio_ctl_seek(FAR audioctl_s *ctl, unsigned ms)
+{
+ if (ctl == NULL)
+ return -EINVAL;
+
+ ctl->seek_position = ms * ctl->wav.fmt.samplerate * ctl->wav.fmt.bitspersample * ctl->wav.fmt.numchannels / 8;
+ ctl->seek = true;
+
+ return 0;
+}
+
+int audio_ctl_stop(FAR audioctl_s *ctl)
+{
+ if (ctl == NULL)
+ return -EINVAL;
+
+ if (ctl->state != AUDIO_CTL_STATE_PAUSE && ctl->state != AUDIO_CTL_STATE_START)
+ {
+ return -1;
+ }
+
+ ctl->state = AUDIO_CTL_STATE_STOP;
+
+ nxaudio_stop(&ctl->nxaudio);
+
+ if (ctl->pid > 0)
+ {
+ pthread_join(ctl->pid, NULL);
+ }
+
+ return 0;
+}
+
+int audio_ctl_set_volume(FAR audioctl_s *ctl, uint16_t vol)
+{
+ if (ctl == NULL)
+ return -EINVAL;
+
+ return nxaudio_setvolume(&ctl->nxaudio, vol);
+}
+
+int audio_ctl_get_position(FAR audioctl_s *ctl)
+{
+ if (ctl == NULL)
+ return -EINVAL;
+
+ return ctl->file_position / (ctl->wav.fmt.bitspersample * ctl->wav.fmt.numchannels * ctl->wav.fmt.samplerate / 8);
+}
+
+int audio_ctl_uninit_nxaudio(FAR audioctl_s *ctl)
+{
+ if (ctl == NULL)
+ return -EINVAL;
+
+ if (ctl->state == AUDIO_CTL_STATE_NOP)
+ {
+ return 0;
+ }
+
+ if (ctl->fd > 0)
+ {
+ close(ctl->fd);
+ ctl->fd = -1;
+ }
+
+ fin_nxaudio(&ctl->nxaudio);
+
+ free(ctl);
+
+ return 0;
+}
diff --git a/breakout/src/Audio/audio_ctl.h b/breakout/src/Audio/audio_ctl.h
new file mode 100644
index 0000000000000000000000000000000000000000..e7e60993b3da128a7917731c5f59195a718653f2
--- /dev/null
+++ b/breakout/src/Audio/audio_ctl.h
@@ -0,0 +1,85 @@
+#ifndef AUDIO_CTL_H
+#define AUDIO_CTL_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*********************
+ * INCLUDES
+ *********************/
+#include
+#include
+
+enum {
+ AUDIO_CTL_STATE_NOP,
+ AUDIO_CTL_STATE_INIT,
+ AUDIO_CTL_STATE_START,
+ AUDIO_CTL_STATE_PAUSE,
+ AUDIO_CTL_STATE_STOP,
+};
+
+typedef struct wav_riff {
+ /* chunk "riff" */
+ char chunkID[4]; /* "RIFF" */
+ /* sub-chunk-size */
+ uint32_t chunksize; /* 36 + subchunk2size */
+ /* sub-chunk-data */
+ char format[4]; /* "WAVE" */
+} riff_s;
+
+typedef struct wav_fmt {
+ /* sub-chunk "fmt" */
+ char subchunk1ID[4]; /* "fmt " */
+ /* sub-chunk-size */
+ uint32_t subchunk1size; /* 16 for PCM */
+ /* sub-chunk-data */
+ uint16_t audioformat; /* PCM = 1*/
+ uint16_t numchannels; /* Mono = 1, Stereo = 2, etc. */
+ uint32_t samplerate; /* 8000, 44100, etc. */
+ uint32_t byterate; /* = samplerate * numchannels * bitspersample/8 */
+ uint16_t blockalign; /* = numchannels * bitspersample/8 */
+ uint16_t bitspersample; /* 8bits, 16bits, etc. */
+} fmt_s;
+
+typedef struct wav_data {
+ /* sub-chunk "data" */
+ char subchunk2ID[4]; /* "data" */
+ /* sub-chunk-size */
+ uint32_t subchunk2size; /* data size */
+ /* sub-chunk-data */
+ //Data_block_t block;
+} data_s;
+
+typedef struct wav_fotmat {
+ riff_s riff;
+ fmt_s fmt;
+ data_s data;
+} wav_s;
+
+typedef struct audioctl {
+ struct nxaudio_s nxaudio;
+ wav_s wav;
+ int fd;
+ int state;
+ pthread_t pid;
+ int seek;
+ uint32_t seek_position;
+ uint32_t file_position;
+} audioctl_s;
+
+FAR audioctl_s *audio_ctl_init_nxaudio(FAR const char *arg);
+int audio_ctl_start(FAR audioctl_s *ctl);
+int audio_ctl_pause(FAR audioctl_s *ctl);
+int audio_ctl_resume(FAR audioctl_s *ctl);
+int audio_ctl_seek(FAR audioctl_s *ctl, unsigned ms);
+int audio_ctl_stop(FAR audioctl_s *ctl);
+int audio_ctl_set_volume(FAR audioctl_s *ctl, uint16_t vol);
+int audio_ctl_get_position(FAR audioctl_s *ctl);
+int audio_ctl_uninit_nxaudio(FAR audioctl_s *ctl);
+
+#ifdef __cplusplus
+} /*extern "C"*/
+#endif
+
+#endif /* AUDIO_CTL_H */
diff --git a/breakout/src/Ball/Ball.cpp b/breakout/src/Ball/Ball.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3f8d393b6b8e8d0068d019956e39927d346a9a05
--- /dev/null
+++ b/breakout/src/Ball/Ball.cpp
@@ -0,0 +1,115 @@
+#include "Ball.h"
+#include "GameResourceManager/GameResourceManager.h"
+#include
+
+// Define gravity
+static const float GRAVITY = 350.0f;
+// Define the maximum ball speed
+static const float MAX_BALL_SPEED = 750.0f;
+
+Ball::Ball(lv_obj_t* parent, float radius) : m_state(State::HELD), m_radius(radius), m_gui_object(nullptr) {
+ GameResourceManager resourceManager;
+ auto ball_src = resourceManager.getIconSource("ball.png");
+
+ printf("DEBUG: ball image loaded and decoded to RGB565 for the first time.\n");
+
+ m_gui_object = lv_img_create(parent);
+ if (!m_gui_object) {
+ printf("Failed to create lv_img object\n");
+ return;
+ }
+ if(ball_src) {
+ lv_img_set_src(m_gui_object, ball_src);
+ }
+ else{
+ printf("Failed to load ball.png\n");
+ }
+ lv_obj_set_size(m_gui_object, m_radius * 2, m_radius * 2);
+ lv_style_init(&m_style);
+ lv_style_set_bg_color(&m_style, lv_color_white());
+ lv_style_set_radius(&m_style, LV_RADIUS_CIRCLE);
+ lv_style_set_bg_color(&m_style, lv_palette_main(LV_PALETTE_RED));
+ lv_style_set_border_width(&m_style, 0);
+ lv_obj_add_style(m_gui_object, &m_style, 0);
+}
+
+void Ball::update(float deltaTime) {
+ if (m_state != State::MOVING) {
+ return;
+ }
+
+ // Apply gravity
+ m_vel.y += GRAVITY * deltaTime;
+
+ // Calculate current speed squared
+ float speed_sq = m_vel.x * m_vel.x + m_vel.y * m_vel.y;
+ float max_speed_sq = MAX_BALL_SPEED * MAX_BALL_SPEED;
+
+ // If current speed squared exceeds max speed squared
+ if (speed_sq > max_speed_sq) {
+ // Calculate actual current speed
+ float current_speed = sqrtf(speed_sq);
+ // Scale velocity vector proportionally to max speed
+ m_vel.x = (m_vel.x / current_speed) * MAX_BALL_SPEED;
+ m_vel.y = (m_vel.y / current_speed) * MAX_BALL_SPEED;
+ }
+
+ // Update position based on the finalized velocity
+ m_pos.x += m_vel.x * deltaTime;
+ m_pos.y += m_vel.y * deltaTime;
+
+ // Update the LVGL object's position on screen
+ lv_obj_set_pos(m_gui_object, (lv_coord_t)(m_pos.x - m_radius), (lv_coord_t)(m_pos.y - m_radius));
+}
+
+void Ball::stickToPaddle(const Rect& paddleRect) {
+ if (m_state != State::HELD) return;
+ m_pos.x = paddleRect.x + paddleRect.width / 2.0f;
+ m_pos.y = paddleRect.y - m_radius;
+ lv_obj_set_pos(m_gui_object, (lv_coord_t)(m_pos.x - m_radius), (lv_coord_t)(m_pos.y - m_radius));
+}
+
+void Ball::launch() {
+ if (m_state == State::HELD) {
+ m_state = State::MOVING;
+ m_vel = { 150.0f, -750.0f };
+ }
+}
+
+void Ball::bounceX() {
+ m_vel.x = -m_vel.x;
+}
+
+void Ball::bounceY() {
+ m_vel.y = -m_vel.y;
+}
+
+void Ball::boostSpeed(float factor) {
+ m_vel.x *= factor;
+ m_vel.y *= factor;
+}
+
+void Ball::setPosition(const Vec2& pos) {
+ m_pos = pos;
+ lv_obj_set_pos(m_gui_object, (lv_coord_t)(m_pos.x - m_radius), (lv_coord_t)(m_pos.y - m_radius));
+}
+
+void Ball::setVelocity(const Vec2& vel) {
+ m_vel = vel;
+}
+
+Ball::State Ball::getState() const {
+ return m_state;
+}
+
+Vec2 Ball::getPosition() const {
+ return m_pos;
+}
+
+Vec2 Ball::getVelocity() const {
+ return m_vel;
+}
+
+float Ball::getRadius() const {
+ return m_radius;
+}
\ No newline at end of file
diff --git a/breakout/src/Ball/Ball.h b/breakout/src/Ball/Ball.h
new file mode 100644
index 0000000000000000000000000000000000000000..fdd6557722e30de69de4d5afb546cac9b67332d8
--- /dev/null
+++ b/breakout/src/Ball/Ball.h
@@ -0,0 +1,34 @@
+#ifndef BALL_H
+#define BALL_H
+
+#pragma once
+
+#include "breakout_types.h"
+
+
+
+class Ball {
+public:
+ enum class State { HELD, MOVING };
+ Ball(lv_obj_t* parent, float radius);
+ void update(float deltaTime);
+ void stickToPaddle(const Rect& paddleRect);
+ void launch();
+ void bounceX();
+ void bounceY();
+ void setPosition(const Vec2& pos);
+ void setVelocity(const Vec2& vel);
+ void boostSpeed(float factor);
+ Vec2 getVelocity() const;
+ State getState() const;
+ Vec2 getPosition() const;
+ float getRadius() const;
+private:
+ State m_state;
+ Vec2 m_pos, m_vel;
+ float m_radius;
+ lv_obj_t* m_gui_object;
+ lv_style_t m_style;
+};
+
+#endif // BALL_H
\ No newline at end of file
diff --git a/breakout/src/Brick/Brick.cpp b/breakout/src/Brick/Brick.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..43a4311181ed12c4960786067e2783abc1f4acda
--- /dev/null
+++ b/breakout/src/Brick/Brick.cpp
@@ -0,0 +1,128 @@
+/********************************************
+ * Brick.cpp
+ * Implementation file for the Brick class
+ * Responsible for creating individual bricks, managing hit points, updating colors, and handling collisions.
+ *******************************************/
+#include "GameResourceManager/GameResourceManager.h"
+#include "Brick.h"
+
+/*****************************************
+ * x Brick's X coordinate
+ * y Brick's Y coordinate
+ * width Brick's width (also used for height as bricks are square)
+ * type Character representing the brick type and its initial hit points
+ ****************************************/
+Brick::Brick(lv_obj_t* parent, float x, float y, float width, char type)
+ : m_is_active(true),
+ m_rect{x, y, width, width},
+ m_gui_object(nullptr)
+{
+ // Set hit points based on brick type character
+ switch(type) {
+ case '#': m_hp = -1; break; // Indestructible wall
+ case 'A': m_hp = 1; break;
+ case 'B': m_hp = 2; break;
+ case 'C': m_hp = 3; break;
+ case 'D': m_hp = 4; break;
+ case 'E': m_hp = 5; break;
+ default: m_hp = 1; break;
+ }
+
+ m_gui_object = lv_img_create(parent);
+ if (!m_gui_object) {
+ printf("Failed to create lv_img object\n");
+ return;
+ }
+ updateImage();
+ // Set position and size
+ lv_obj_set_pos(m_gui_object, x, y);
+ lv_obj_set_size(m_gui_object, width, width);
+
+ // Add black border style
+ static lv_style_t border_style;
+
+ lv_style_init(&border_style);
+ lv_style_set_bg_color(&border_style, lv_color_white());
+ lv_style_set_border_width(&border_style, 1);
+ lv_style_set_border_color(&border_style, lv_color_black());
+ lv_style_set_pad_all(&border_style, 0);
+ lv_obj_add_style(m_gui_object, &border_style, 0);
+}
+
+/***************************************
+ * Destructor automatically called when Brick object is deleted
+ * Ensures corresponding UI object is also removed from screen to prevent memory leaks.
+ ***************************************/
+Brick::~Brick() {
+ if (m_gui_object) {
+ lv_obj_del(m_gui_object);
+ m_gui_object = nullptr;
+ }
+}
+
+/**
+ * Called when the ball hits the brick
+ */
+void Brick::onHit() {
+ // Only apply damage if hit points > 0 (i.e., not an indestructible wall)
+ if (m_hp > 0) {
+ m_hp--; // Decrease hit points by 1
+
+ if (m_hp == 0) {
+ // If hit points are depleted
+ m_is_active = false; // Mark brick as inactive, collision checks will ignore it
+ lv_obj_add_flag(m_gui_object, LV_OBJ_FLAG_HIDDEN); // Hide brick's UI object on screen
+ } else {
+ // If still has hit points, update color for visual feedback
+ updateImage();
+ }
+ }
+}
+
+/**
+ * Checks if the brick is currently active (not destroyed)
+ * Returns true if active, false if destroyed
+ */
+bool Brick::isActive() const {
+ return m_is_active;
+}
+
+/**
+ * Gets the bounding box of the brick (collision box)
+ * Returns a Rect containing position and size
+ */
+Rect Brick::getBoundingBox() const {
+ return m_rect;
+}
+
+int Brick::getHP() const {
+ return m_hp;
+}
+
+/**
+ * Updates the brick's background image based on current hit points
+ */
+void Brick::updateImage() {
+ if (!m_gui_object) return;
+
+ std::string image_filename;
+ switch (m_hp) {
+ case -1: image_filename = "brick_#.png"; break; // Wall: black
+ case 1: image_filename = "brick_A.png"; break; // HP=1: green
+ case 2: image_filename = "brick_B.png"; break; // HP=2: orange
+ case 3: image_filename = "brick_C.png"; break; // HP=3: purple
+ case 4: image_filename = "brick_D.png"; break; // HP=6: yellow
+ case 5: image_filename = "brick_E.png"; break; // HP=5: blue
+ default: image_filename = "brick_F.png"; break; // Damaged (blue-green)
+ }
+
+ GameResourceManager resourceManager;
+ auto img_src = resourceManager.getIconSource(image_filename);
+ if(img_src) {
+ lv_img_set_src(m_gui_object, img_src);
+ }
+ else{
+ printf("Failed to load brick.png\n");
+ }
+
+}
diff --git a/breakout/src/Brick/Brick.h b/breakout/src/Brick/Brick.h
new file mode 100644
index 0000000000000000000000000000000000000000..bd9df2b1072d3b8f899032f5e4980dc8e17e2e3c
--- /dev/null
+++ b/breakout/src/Brick/Brick.h
@@ -0,0 +1,23 @@
+#ifndef BRICK_H
+#define BRICK_H
+
+#pragma once
+#include "breakout_types.h"
+
+class Brick {
+public:
+ Brick(lv_obj_t* parent, float x, float y, float width, char type);
+ ~Brick();
+ void onHit();
+ bool isActive() const;
+ Rect getBoundingBox() const;
+ int getHP() const;
+private:
+ void updateImage();
+ int m_hp;
+ bool m_is_active;
+ Rect m_rect;
+ lv_obj_t* m_gui_object;
+ lv_style_t m_style;
+};
+#endif // BRICK_H
\ No newline at end of file
diff --git a/breakout/src/GameResourceManager/GameResourceManager.cpp b/breakout/src/GameResourceManager/GameResourceManager.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5227174c7e777288c5521e6a3b4df9092ae0b04a
--- /dev/null
+++ b/breakout/src/GameResourceManager/GameResourceManager.cpp
@@ -0,0 +1,221 @@
+/**
+ * GameResourceManager.cpp
+ * Implementation file for the game resource manager.
+ * Responsible for constructing resource paths, loading level maps,
+ * and managing the image resource cache.
+ * (Version: uses lv_draw_buf_dup and lv_draw_buf_destroy)
+ */
+
+#include "GameResourceManager.h"
+#include
+#include "Brick/Brick.h"
+#include "breakout.h"
+
+// Define the static member variable for image caching, now storing lv_draw_buf_t*
+std::map GameResourceManager::s_imageCache;
+
+GameResourceManager::GameResourceManager() : m_audioCtl(nullptr) {}
+GameResourceManager::~GameResourceManager() {
+ stopAudio();
+}
+
+/**
+ * @brief Frees all cached image resources.
+ * Equivalent to the `cleanup_resources` function from your example.
+ */
+void GameResourceManager::cleanupCache() {
+ printf("[ResourceManager] Cleaning up image cache, %zu items to delete.\n", s_imageCache.size());
+ for (auto const& [key, buf] : s_imageCache) {
+ if (buf != nullptr) {
+ lv_draw_buf_destroy(buf); // Use LVGL function to destroy draw_buf
+ }
+ }
+ s_imageCache.clear();
+ printf("[ResourceManager] Cache cleanup complete.\n");
+}
+
+/**
+ * @brief Retrieves an icon resource. Loads and caches it if not already cached.
+ * Core logic corresponding to your `app_create_main_page` example.
+ */
+const lv_draw_buf_t* GameResourceManager::getIconSource(const std::string& iconName) {
+ // 1. Check cache
+ auto it = s_imageCache.find(iconName);
+ if (it != s_imageCache.end()) {
+ return it->second;
+ }
+
+ // 2. Load and decode
+ std::string fullPath = getIconsPath() + "/" + iconName;
+
+ lv_image_decoder_dsc_t decoder_dsc;
+ lv_result_t res = lv_image_decoder_open(&decoder_dsc, fullPath.c_str(), NULL);
+
+ if (res != LV_RESULT_OK) {
+ printf("[ERROR] Failed to open image decoder: %s\n", fullPath.c_str());
+ return nullptr;
+ }
+
+ // 3. Copy the decoded image data to a permanent draw_buf
+ lv_draw_buf_t* permanent_buf = lv_draw_buf_dup(decoder_dsc.decoded);
+
+ // 4. Close decoder and free temporary resources
+ lv_image_decoder_close(&decoder_dsc);
+
+ if (permanent_buf == nullptr) {
+ printf("[ERROR] Failed to duplicate draw buffer.\n");
+ return nullptr;
+ }
+
+ // 5. Cache and return
+ s_imageCache[iconName] = permanent_buf;
+ printf("[ResourceManager] New image cached: %s\n", iconName.c_str());
+
+ return permanent_buf;
+}
+
+// --- Resource path accessors ---
+
+std::string GameResourceManager::getResPath() {
+ return std::string(RES_ROOT) + "/res";
+}
+
+std::string GameResourceManager::getMapPath() {
+ return getResPath() + "/map";
+}
+
+std::string GameResourceManager::getFontsPath() {
+ return getResPath() + "/fonts";
+}
+
+std::string GameResourceManager::getIconsPath() {
+ return getResPath() + "/icons";
+}
+
+std::string GameResourceManager::getAudioPath() {
+ return getResPath() + "/audio";
+}
+
+/**
+ * @brief Loads a map from file and populates the game brick list.
+ */
+bool GameResourceManager::loadMap(const std::string& mapName, Game* game) {
+ std::string fullPath = getMapPath() + "/" + mapName;
+ const int BRICK_COLS = 80;
+ const int BRICK_ROWS = 35;
+ const float BRICK_WIDTH = (float)GAME_AREA_WIDTH / BRICK_COLS;
+ FILE* file = fopen(fullPath.c_str(), "r");
+ if (!file) {
+ return false;
+ }
+ char line[BRICK_COLS + 2];
+ for (int row = 0; row < BRICK_ROWS; ++row) {
+ if (!fgets(line, sizeof(line), file)) {
+ break;
+ }
+ for (int col = 0; col < BRICK_COLS && line[col] != '\0'; ++col) {
+ char brickType = line[col];
+ if (brickType != ' ' && brickType != '\n' && brickType != '\r') {
+ float x = col * BRICK_WIDTH;
+ float y = row * BRICK_WIDTH;
+ Brick* newBrick = new Brick(game->m_game_area, x, y, BRICK_WIDTH, brickType);
+ game->m_bricks.push_back(newBrick);
+ }
+ }
+ }
+ fclose(file);
+ return true;
+}
+
+/**
+ * @brief Loads and displays a background image onto the parent object.
+ */
+void GameResourceManager::loadBackground(lv_obj_t* parent, const std::string& iconName) {
+ const lv_draw_buf_t* bg_src = getIconSource(iconName);
+ if (!bg_src) return;
+
+ lv_obj_t* bg_img = lv_img_create(parent);
+ lv_img_set_src(bg_img, bg_src); // Use draw buffer directly
+ lv_obj_set_size(bg_img, GAME_AREA_WIDTH, GAME_AREA_HEIGHT);
+ lv_obj_align(bg_img, LV_ALIGN_TOP_LEFT, 0, 0);
+ lv_obj_move_background(bg_img);
+}
+
+/**
+ * @brief Plays the corresponding audio based on the brick HP.
+ *
+ * This function selects the appropriate `.wav` file based on the brick's HP,
+ * initializes or reinitializes the audio controller, and starts audio playback.
+ * If the current audio is already playing for the given brick HP, it avoids reinitialization.
+ *
+ * @param brickHp The HP of the brick, used to select the correct audio file.
+ * @return true if the audio started successfully, false if an error occurred.
+ */
+bool GameResourceManager::playAudio(int brickHp) {
+ const char* wavFile = nullptr;
+ // Select appropriate audio file based on brick's HP
+ switch (brickHp) {
+ case -1: wavFile = "C4.wav"; break; // For brick HP -1, play C4.wav
+ case 1: wavFile = "E4.wav"; break; // For brick HP 1, play E4.wav
+ case 2: wavFile = "F4.wav"; break; // For brick HP 2, play F4.wav
+ case 3: wavFile = "G4.wav"; break; // For brick HP 3, play G4.wav
+ case 4: wavFile = "A4.wav"; break; // For brick HP 4, play A4.wav
+ case 5: wavFile = "B4.wav"; break; // For brick HP 5, play B4.wav
+ case 6: wavFile = "C5.wav"; break; // For brick HP 6, play C5.wav
+ default: wavFile = "C4.wav"; break; // Default case, play C4.wav
+ }
+
+ // Build full audio file path
+ std::string fullPath = getAudioPath() + "/" + wavFile;
+
+ if (!m_audioCtl) {
+ // If no audio control is initialized, initialize the audio controller
+ m_audioCtl = audio_ctl_init_nxaudio(fullPath.c_str());
+ if (!m_audioCtl) {
+ //printf("[Audio] Failed to initialize audio: %s\n", fullPath.c_str());
+ return false;
+ }
+ } else {
+ //printf("[Audio] Stopping and reinitializing audio controller...\n");
+ audio_ctl_stop(m_audioCtl); // Stop the current audio
+ audio_ctl_uninit_nxaudio(m_audioCtl); // Uninitialize the current audio controller
+
+ // Reinitialize the audio controller with the new file
+ m_audioCtl = audio_ctl_init_nxaudio(fullPath.c_str());
+ if (!m_audioCtl) {
+ //printf("[Audio] Failed to reinitialize audio: %s\n", fullPath.c_str());
+ return false;
+ }
+ }
+
+ if (m_audioCtl) {
+ audio_ctl_start(m_audioCtl); // Start the audio playback
+ //printf("[Audio] Playing: %s\n", fullPath.c_str());
+ return true;
+ }
+
+ return false;
+}
+/**
+ * @brief Stops the currently playing audio and releases resources.
+ *
+ * This function stops the audio if it is currently playing, uninitializes the
+ * audio controller, and frees associated resources.
+ */
+void GameResourceManager::stopAudio() {
+ // Check if the audio controller is initialized
+ if (m_audioCtl != nullptr) {
+ //printf("[Audio] Stopping and uninitializing audio controller...\n");
+
+ // If audio is playing, stop it
+ audio_ctl_stop(m_audioCtl);
+
+ // Uninitialize the audio controller and clean up
+ audio_ctl_uninit_nxaudio(m_audioCtl);
+ m_audioCtl = nullptr;
+
+ //printf("[Audio] Audio stopped and cleaned up.\n");
+ } else {
+ //printf("[Audio] No audio to stop.\n");
+ }
+}
diff --git a/breakout/src/GameResourceManager/GameResourceManager.h b/breakout/src/GameResourceManager/GameResourceManager.h
new file mode 100644
index 0000000000000000000000000000000000000000..07a6ff5b3194a6ea0c9c0f929c99e56779832184
--- /dev/null
+++ b/breakout/src/GameResourceManager/GameResourceManager.h
@@ -0,0 +1,39 @@
+#ifndef GAME_RESOURCE_MANAGER_H
+#define GAME_RESOURCE_MANAGER_H
+
+#include
+#include