move into firmware subfolder

Signed-off-by: Peter Siegmund <developer@mars3142.org>
This commit is contained in:
2025-08-20 10:27:03 +02:00
parent d316bb9f2c
commit 5a08c2e09d
117 changed files with 0 additions and 0 deletions

2
firmware/.clang-format Normal file
View File

@@ -0,0 +1,2 @@
---
BasedOnStyle: Microsoft

View File

@@ -0,0 +1,13 @@
ARG DOCKER_TAG=latest
FROM espressif/idf:${DOCKER_TAG}
ENV LC_ALL=C.UTF-8
ENV LANG=C.UTF-8
RUN apt-get update -y && apt-get install udev -y
RUN echo "source /opt/esp/idf/export.sh > /dev/null 2>&1" >> ~/.bashrc
ENTRYPOINT [ "/opt/esp/entrypoint.sh" ]
CMD ["/bin/bash", "-c"]

View File

@@ -0,0 +1,21 @@
{
"name": "ESP-IDF QEMU",
"build": {
"dockerfile": "Dockerfile"
},
"customizations": {
"vscode": {
"settings": {
"terminal.integrated.defaultProfile.linux": "bash",
"idf.espIdfPath": "/opt/esp/idf",
"idf.toolsPath": "/opt/esp",
"idf.gitPath": "/usr/bin/git"
},
"extensions": [
"espressif.esp-idf-extension",
"espressif.esp-idf-web"
]
}
},
"runArgs": ["--privileged"]
}

40
firmware/.gitignore vendored Normal file
View File

@@ -0,0 +1,40 @@
# esp-idf
build/
managed_components/
sdkconfig
sdkconfig.old
dependencies.lock
config.env
kconfigs_projbuild.in
kconfigs.in
config/
# cmake
cmake-build-debug
CMakeCache.txt
cmake_install.cmake
install_manifest.txt
CMakeFiles/
CTestTestfile.cmake
Makefile
CMakeScripts
compile_commands.json
# They ignored Idea (Webstorm) completely
.idea/
.cache/
# However, they kept the following 4 files version controlled
.vscode/*
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
__pycache__
_deps/
.cmake/
.ninja_*
bin/
components/**/*.a
*.ninja
Testing/*

15
firmware/.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,15 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "gdbtarget",
"request": "attach",
"name": "Eclipse CDT GDB Adapter"
},
{
"type": "espidf",
"name": "Launch",
"request": "launch"
}
]
}

106
firmware/CMakeLists.txt Executable file
View File

@@ -0,0 +1,106 @@
cmake_minimum_required(VERSION 3.30)
if (DEFINED ENV{IDF_PATH})
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(system_control)
return()
else ()
set(MAJOR_VERSION 0)
set(MINOR_VERSION 0)
set(MICRO_VERSION 1)
project(
SystemControl
LANGUAGES CXX C
VERSION "${MAJOR_VERSION}.${MINOR_VERSION}.${MICRO_VERSION}"
)
set(CMAKE_C_STANDARD 23)
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED YES)
set(CMAKE_CXX_EXTENSIONS YES)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_INCLUDE_PATH include)
set(CMAKE_SOURCE_DIR src)
set(CMAKE_MODULE_PATH cmake)
include(FetchContent)
function(include_dependency libName gitURL gitTag)
FetchContent_Declare(${libName}
GIT_REPOSITORY ${gitURL}
GIT_TAG ${gitTag}
GIT_SHALLOW TRUE
GIT_PROGRESS FALSE
)
FetchContent_MakeAvailable(${libName})
endfunction()
include_dependency(SDL3 https://github.com/libsdl-org/SDL release-3.2.x)
include_dependency(SDL_image https://github.com/libsdl-org/SDL_image release-3.2.x)
include_dependency(SDL_ttf https://github.com/libsdl-org/SDL_ttf release-3.2.x)
include_dependency(u8g2 https://github.com/olikraus/u8g2 master)
add_subdirectory(components)
configure_file(
"${CMAKE_SOURCE_DIR}/Version.h.in"
"${CMAKE_SOURCE_DIR}/Version.h"
)
include_directories(
${CMAKE_INCLUDE_PATH}
${CMAKE_SOURCE_DIR}
${PROJECT_BINARY_DIR}/src
)
add_executable(${PROJECT_NAME}
WIN32 MACOSX_BUNDLE
${CMAKE_SOURCE_DIR}/main.cpp
${CMAKE_SOURCE_DIR}/Common.cpp
${CMAKE_SOURCE_DIR}/debug/debug_overlay.cpp
${CMAKE_SOURCE_DIR}/hal/u8x8_hal_sdl.cpp
${CMAKE_SOURCE_DIR}/manager/ResourceManager.cpp
${CMAKE_SOURCE_DIR}/model/AppContext.cpp
${CMAKE_SOURCE_DIR}/model/Window.cpp
${CMAKE_SOURCE_DIR}/ui/Device.cpp
${CMAKE_SOURCE_DIR}/ui/UIWidget.cpp
${CMAKE_SOURCE_DIR}/ui/widgets/Button.cpp
${CMAKE_SOURCE_DIR}/ui/widgets/D_Pad.cpp
)
if (APPLE)
# copy assets to bundle directory
set(ASSETS_SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/assets)
set(ASSETS_DEST_DIR $<TARGET_BUNDLE_DIR:${PROJECT_NAME}>/Contents/Resources)
if (EXISTS ${ASSETS_SRC_DIR})
add_custom_command(
TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory ${ASSETS_SRC_DIR} ${ASSETS_DEST_DIR}
COMMENT "Copying assets to bundle via custom command"
VERBATIM
)
else ()
message(WARNING "Assets source directory ${ASSETS_SRC_DIR} does not exist. Skipping custom command.")
endif ()
elseif (WINDOWS)
add_custom_command(
TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${PROJECT_SOURCE_DIR}/assets ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/assets
)
endif ()
target_compile_definitions(${PROJECT_NAME} PRIVATE SDL_MAIN_USE_CALLBACKS)
target_link_libraries(${PROJECT_NAME} PRIVATE
ImGui
insa
led-manager
persistence-manager
SDL3::SDL3
SDL3_image::SDL3_image
SDL3_ttf::SDL3_ttf
u8g2
)
endif ()

661
firmware/LICENSE Normal file
View File

@@ -0,0 +1,661 @@
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU Affero General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
<http://www.gnu.org/licenses/>.

15
firmware/README.md Executable file
View File

@@ -0,0 +1,15 @@
## Systen Control
### ESP32-S3 (folder: main)
This is an implementation of my custom system control project (custom pcb with Lolin ESP32-S3 Mini) and LED strip.
The build process is straight forward with ESP-IDF. We used version 5.4 while development and the github actions tried to compile for multiple ESP-IDF versions, so we are safe.
### Desktop (folder: src)
It's included also a desktop application (with SDL3), so you can test the project without any MCU.
### Global Information
The projects can be generated from the root, because here is the starting CMakeLists.txt file.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 511 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 815 B

Binary file not shown.

View File

@@ -0,0 +1,12 @@
idf_component_register(SRCS "bootloader.c"
REQUIRES bootloader bootloader_support)
idf_build_get_property(target IDF_TARGET)
set(target_folder "${target}")
# Use the linker script files from the actual bootloader
set(scripts "${IDF_PATH}/components/bootloader/subproject/main/ld/${target_folder}/bootloader.ld"
"${IDF_PATH}/components/bootloader/subproject/main/ld/${target_folder}/bootloader.rom.ld")
target_linker_script(${COMPONENT_LIB} INTERFACE "${scripts}")

View File

@@ -0,0 +1,65 @@
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdbool.h>
#include "sdkconfig.h"
#include "esp_log.h"
#include "bootloader_init.h"
#include "bootloader_utility.h"
#include "bootloader_common.h"
static const char* TAG = "boot";
static int select_partition_number(bootloader_state_t* bs);
/*
* We arrive here after the ROM bootloader finished loading this second stage bootloader from flash.
* The hardware is mostly uninitialized, flash cache is down and the app CPU is in reset.
* We do have a stack, so we can do the initialization in C.
*/
void __attribute__((noreturn)) call_start_cpu0(void) {
// 1. Hardware initialization
if(bootloader_init() != ESP_OK) {
bootloader_reset();
}
#ifdef CONFIG_BOOTLOADER_SKIP_VALIDATE_IN_DEEP_SLEEP
// If this boot is a wake up from the deep sleep then go to the short way,
// try to load the application which worked before deep sleep.
// It skips a lot of checks due to it was done before (while first boot).
bootloader_utility_load_boot_image_from_deep_sleep();
// If it is not successful try to load an application as usual.
#endif
// 2. Select the number of boot partition
bootloader_state_t bs = {0};
int boot_index = select_partition_number(&bs);
if(boot_index == INVALID_INDEX) {
bootloader_reset();
}
// 2.1 Print a custom message!
ESP_LOGI(TAG, "Custom bootloader completed");
// 3. Load the app image for booting
bootloader_utility_load_boot_image(&bs, boot_index);
}
// Select the number of boot partition
static int select_partition_number(bootloader_state_t* bs) {
// 1. Load partition table
if(!bootloader_utility_load_partition_table(bs)) {
ESP_LOGE(TAG, "load partition table error!");
return INVALID_INDEX;
}
// 2. Select the number of boot partition
return bootloader_utility_get_selected_boot_partition(bs);
}
// Return global reent struct if any newlib functions are linked to bootloader
struct _reent* __getreent(void) {
return _GLOBAL_REENT;
}

View File

@@ -0,0 +1,10 @@
if (NOT DEFINED ENV{IDF_PATH})
add_library(components INTERFACE)
add_subdirectory(imgui)
add_subdirectory(insa)
add_subdirectory(led-manager)
add_subdirectory(persistence-manager)
target_link_libraries(components INTERFACE ImGui)
endif ()

View File

@@ -0,0 +1,23 @@
if (DEFINED ENV{IDF_PATH})
return()
endif ()
cmake_minimum_required(VERSION 3.30)
project(ImGui)
add_library(${PROJECT_NAME} STATIC
imgui.cpp
imgui_demo.cpp
imgui_draw.cpp
imgui_widgets.cpp
imgui_tables.cpp
imgui_impl_sdl3.cpp
imgui_impl_sdlrenderer3.cpp
)
include_directories(include)
target_include_directories(${PROJECT_NAME} PUBLIC include)
target_link_libraries(${PROJECT_NAME} PRIVATE SDL3::SDL3)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,826 @@
// dear imgui: Platform Backend for SDL3
// This needs to be used along with a Renderer (e.g. SDL_GPU, DirectX11, OpenGL3, Vulkan..)
// (Info: SDL3 is a cross-platform general purpose library for handling windows, inputs, graphics context creation, etc.)
// Implemented features:
// [X] Platform: Clipboard support.
// [X] Platform: Mouse support. Can discriminate Mouse/TouchScreen.
// [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy SDL_SCANCODE_* values are obsolete since 1.87 and not supported since 1.91.5]
// [X] Platform: Gamepad support.
// [X] Platform: Mouse cursor shape and visibility (ImGuiBackendFlags_HasMouseCursors). Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'.
// [X] Platform: IME support.
// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
// Learn about Dear ImGui:
// - FAQ https://dearimgui.com/faq
// - Getting Started https://dearimgui.com/getting-started
// - Documentation https://dearimgui.com/docs (same as your local docs/ folder).
// - Introduction, links and more at the top of imgui.cpp
// CHANGELOG
// (minor and older changes stripped away, please see git history for details)
// 2025-04-22: IME: honor ImGuiPlatformImeData->WantTextInput as an alternative way to call SDL_StartTextInput(), without IME being necessarily visible.
// 2025-04-09: Don't attempt to call SDL_CaptureMouse() on drivers where we don't call SDL_GetGlobalMouseState(). (#8561)
// 2025-03-30: Update for SDL3 api changes: Revert SDL_GetClipboardText() memory ownership change. (#8530, #7801)
// 2025-03-21: Fill gamepad inputs and set ImGuiBackendFlags_HasGamepad regardless of ImGuiConfigFlags_NavEnableGamepad being set.
// 2025-03-10: When dealing with OEM keys, use scancodes instead of translated keycodes to choose ImGuiKey values. (#7136, #7201, #7206, #7306, #7670, #7672, #8468)
// 2025-02-26: Only start SDL_CaptureMouse() when mouse is being dragged, to mitigate issues with e.g.Linux debuggers not claiming capture back. (#6410, #3650)
// 2025-02-24: Avoid calling SDL_GetGlobalMouseState() when mouse is in relative mode.
// 2025-02-18: Added ImGuiMouseCursor_Wait and ImGuiMouseCursor_Progress mouse cursor support.
// 2025-02-10: Using SDL_OpenURL() in platform_io.Platform_OpenInShellFn handler.
// 2025-01-20: Made ImGui_ImplSDL3_SetGamepadMode(ImGui_ImplSDL3_GamepadMode_Manual) accept an empty array.
// 2024-10-24: Emscripten: SDL_EVENT_MOUSE_WHEEL event doesn't require dividing by 100.0f on Emscripten.
// 2024-09-03: Update for SDL3 api changes: SDL_GetGamepads() memory ownership revert. (#7918, #7898, #7807)
// 2024-08-22: moved some OS/backend related function pointers from ImGuiIO to ImGuiPlatformIO:
// - io.GetClipboardTextFn -> platform_io.Platform_GetClipboardTextFn
// - io.SetClipboardTextFn -> platform_io.Platform_SetClipboardTextFn
// - io.PlatformSetImeDataFn -> platform_io.Platform_SetImeDataFn
// 2024-08-19: Storing SDL_WindowID inside ImGuiViewport::PlatformHandle instead of SDL_Window*.
// 2024-08-19: ImGui_ImplSDL3_ProcessEvent() now ignores events intended for other SDL windows. (#7853)
// 2024-07-22: Update for SDL3 api changes: SDL_GetGamepads() memory ownership change. (#7807)
// 2024-07-18: Update for SDL3 api changes: SDL_GetClipboardText() memory ownership change. (#7801)
// 2024-07-15: Update for SDL3 api changes: SDL_GetProperty() change to SDL_GetPointerProperty(). (#7794)
// 2024-07-02: Update for SDL3 api changes: SDLK_x renames and SDLK_KP_x removals (#7761, #7762).
// 2024-07-01: Update for SDL3 api changes: SDL_SetTextInputRect() changed to SDL_SetTextInputArea().
// 2024-06-26: Update for SDL3 api changes: SDL_StartTextInput()/SDL_StopTextInput()/SDL_SetTextInputRect() functions signatures.
// 2024-06-24: Update for SDL3 api changes: SDL_EVENT_KEY_DOWN/SDL_EVENT_KEY_UP contents.
// 2024-06-03; Update for SDL3 api changes: SDL_SYSTEM_CURSOR_ renames.
// 2024-05-15: Update for SDL3 api changes: SDLK_ renames.
// 2024-04-15: Inputs: Re-enable calling SDL_StartTextInput()/SDL_StopTextInput() as SDL3 no longer enables it by default and should play nicer with IME.
// 2024-02-13: Inputs: Fixed gamepad support. Handle gamepad disconnection. Added ImGui_ImplSDL3_SetGamepadMode().
// 2023-11-13: Updated for recent SDL3 API changes.
// 2023-10-05: Inputs: Added support for extra ImGuiKey values: F13 to F24 function keys, app back/forward keys.
// 2023-05-04: Fixed build on Emscripten/iOS/Android. (#6391)
// 2023-04-06: Inputs: Avoid calling SDL_StartTextInput()/SDL_StopTextInput() as they don't only pertain to IME. It's unclear exactly what their relation is to IME. (#6306)
// 2023-04-04: Inputs: Added support for io.AddMouseSourceEvent() to discriminate ImGuiMouseSource_Mouse/ImGuiMouseSource_TouchScreen. (#2702)
// 2023-02-23: Accept SDL_GetPerformanceCounter() not returning a monotonically increasing value. (#6189, #6114, #3644)
// 2023-02-07: Forked "imgui_impl_sdl2" into "imgui_impl_sdl3". Removed version checks for old feature. Refer to imgui_impl_sdl2.cpp for older changelog.
#include "imgui.h"
#ifndef IMGUI_DISABLE
#include "imgui_impl_sdl3.h"
// Clang warnings with -Weverything
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision
#endif
// SDL
#include <SDL3/SDL.h>
#include <stdio.h> // for snprintf()
#if defined(__APPLE__)
#include <TargetConditionals.h>
#endif
#ifdef _WIN32
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#endif
#if !defined(__EMSCRIPTEN__) && !defined(__ANDROID__) && !(defined(__APPLE__) && TARGET_OS_IOS) && !defined(__amigaos4__)
#define SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE 1
#else
#define SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE 0
#endif
// FIXME-LEGACY: remove when SDL 3.1.3 preview is released.
#ifndef SDLK_APOSTROPHE
#define SDLK_APOSTROPHE SDLK_QUOTE
#endif
#ifndef SDLK_GRAVE
#define SDLK_GRAVE SDLK_BACKQUOTE
#endif
// SDL Data
struct ImGui_ImplSDL3_Data
{
SDL_Window* Window;
SDL_WindowID WindowID;
SDL_Renderer* Renderer;
Uint64 Time;
char* ClipboardTextData;
char BackendPlatformName[48];
// IME handling
SDL_Window* ImeWindow;
// Mouse handling
Uint32 MouseWindowID;
int MouseButtonsDown;
SDL_Cursor* MouseCursors[ImGuiMouseCursor_COUNT];
SDL_Cursor* MouseLastCursor;
int MousePendingLeaveFrame;
bool MouseCanUseGlobalState;
bool MouseCanUseCapture;
// Gamepad handling
ImVector<SDL_Gamepad*> Gamepads;
ImGui_ImplSDL3_GamepadMode GamepadMode;
bool WantUpdateGamepadsList;
ImGui_ImplSDL3_Data() { memset((void*)this, 0, sizeof(*this)); }
};
// Backend data stored in io.BackendPlatformUserData to allow support for multiple Dear ImGui contexts
// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts.
// FIXME: multi-context support is not well tested and probably dysfunctional in this backend.
// FIXME: some shared resources (mouse cursor shape, gamepad) are mishandled when using multi-context.
static ImGui_ImplSDL3_Data* ImGui_ImplSDL3_GetBackendData()
{
return ImGui::GetCurrentContext() ? (ImGui_ImplSDL3_Data*)ImGui::GetIO().BackendPlatformUserData : nullptr;
}
// Functions
static const char* ImGui_ImplSDL3_GetClipboardText(ImGuiContext*)
{
ImGui_ImplSDL3_Data* bd = ImGui_ImplSDL3_GetBackendData();
if (bd->ClipboardTextData)
SDL_free(bd->ClipboardTextData);
bd->ClipboardTextData = SDL_GetClipboardText();
return bd->ClipboardTextData;
}
static void ImGui_ImplSDL3_SetClipboardText(ImGuiContext*, const char* text)
{
SDL_SetClipboardText(text);
}
static void ImGui_ImplSDL3_PlatformSetImeData(ImGuiContext*, ImGuiViewport* viewport, ImGuiPlatformImeData* data)
{
ImGui_ImplSDL3_Data* bd = ImGui_ImplSDL3_GetBackendData();
SDL_WindowID window_id = (SDL_WindowID)(intptr_t)viewport->PlatformHandle;
SDL_Window* window = SDL_GetWindowFromID(window_id);
if ((!(data->WantVisible || data->WantTextInput) || bd->ImeWindow != window) && bd->ImeWindow != nullptr)
{
SDL_StopTextInput(bd->ImeWindow);
bd->ImeWindow = nullptr;
}
if (data->WantVisible)
{
SDL_Rect r;
r.x = (int)data->InputPos.x;
r.y = (int)data->InputPos.y;
r.w = 1;
r.h = (int)data->InputLineHeight;
SDL_SetTextInputArea(window, &r, 0);
bd->ImeWindow = window;
}
if (data->WantVisible || data->WantTextInput)
SDL_StartTextInput(window);
}
// Not static to allow third-party code to use that if they want to (but undocumented)
ImGuiKey ImGui_ImplSDL3_KeyEventToImGuiKey(SDL_Keycode keycode, SDL_Scancode scancode);
ImGuiKey ImGui_ImplSDL3_KeyEventToImGuiKey(SDL_Keycode keycode, SDL_Scancode scancode)
{
// Keypad doesn't have individual key values in SDL3
switch (scancode)
{
case SDL_SCANCODE_KP_0: return ImGuiKey_Keypad0;
case SDL_SCANCODE_KP_1: return ImGuiKey_Keypad1;
case SDL_SCANCODE_KP_2: return ImGuiKey_Keypad2;
case SDL_SCANCODE_KP_3: return ImGuiKey_Keypad3;
case SDL_SCANCODE_KP_4: return ImGuiKey_Keypad4;
case SDL_SCANCODE_KP_5: return ImGuiKey_Keypad5;
case SDL_SCANCODE_KP_6: return ImGuiKey_Keypad6;
case SDL_SCANCODE_KP_7: return ImGuiKey_Keypad7;
case SDL_SCANCODE_KP_8: return ImGuiKey_Keypad8;
case SDL_SCANCODE_KP_9: return ImGuiKey_Keypad9;
case SDL_SCANCODE_KP_PERIOD: return ImGuiKey_KeypadDecimal;
case SDL_SCANCODE_KP_DIVIDE: return ImGuiKey_KeypadDivide;
case SDL_SCANCODE_KP_MULTIPLY: return ImGuiKey_KeypadMultiply;
case SDL_SCANCODE_KP_MINUS: return ImGuiKey_KeypadSubtract;
case SDL_SCANCODE_KP_PLUS: return ImGuiKey_KeypadAdd;
case SDL_SCANCODE_KP_ENTER: return ImGuiKey_KeypadEnter;
case SDL_SCANCODE_KP_EQUALS: return ImGuiKey_KeypadEqual;
default: break;
}
switch (keycode)
{
case SDLK_TAB: return ImGuiKey_Tab;
case SDLK_LEFT: return ImGuiKey_LeftArrow;
case SDLK_RIGHT: return ImGuiKey_RightArrow;
case SDLK_UP: return ImGuiKey_UpArrow;
case SDLK_DOWN: return ImGuiKey_DownArrow;
case SDLK_PAGEUP: return ImGuiKey_PageUp;
case SDLK_PAGEDOWN: return ImGuiKey_PageDown;
case SDLK_HOME: return ImGuiKey_Home;
case SDLK_END: return ImGuiKey_End;
case SDLK_INSERT: return ImGuiKey_Insert;
case SDLK_DELETE: return ImGuiKey_Delete;
case SDLK_BACKSPACE: return ImGuiKey_Backspace;
case SDLK_SPACE: return ImGuiKey_Space;
case SDLK_RETURN: return ImGuiKey_Enter;
case SDLK_ESCAPE: return ImGuiKey_Escape;
//case SDLK_APOSTROPHE: return ImGuiKey_Apostrophe;
case SDLK_COMMA: return ImGuiKey_Comma;
//case SDLK_MINUS: return ImGuiKey_Minus;
case SDLK_PERIOD: return ImGuiKey_Period;
//case SDLK_SLASH: return ImGuiKey_Slash;
case SDLK_SEMICOLON: return ImGuiKey_Semicolon;
//case SDLK_EQUALS: return ImGuiKey_Equal;
//case SDLK_LEFTBRACKET: return ImGuiKey_LeftBracket;
//case SDLK_BACKSLASH: return ImGuiKey_Backslash;
//case SDLK_RIGHTBRACKET: return ImGuiKey_RightBracket;
//case SDLK_GRAVE: return ImGuiKey_GraveAccent;
case SDLK_CAPSLOCK: return ImGuiKey_CapsLock;
case SDLK_SCROLLLOCK: return ImGuiKey_ScrollLock;
case SDLK_NUMLOCKCLEAR: return ImGuiKey_NumLock;
case SDLK_PRINTSCREEN: return ImGuiKey_PrintScreen;
case SDLK_PAUSE: return ImGuiKey_Pause;
case SDLK_LCTRL: return ImGuiKey_LeftCtrl;
case SDLK_LSHIFT: return ImGuiKey_LeftShift;
case SDLK_LALT: return ImGuiKey_LeftAlt;
case SDLK_LGUI: return ImGuiKey_LeftSuper;
case SDLK_RCTRL: return ImGuiKey_RightCtrl;
case SDLK_RSHIFT: return ImGuiKey_RightShift;
case SDLK_RALT: return ImGuiKey_RightAlt;
case SDLK_RGUI: return ImGuiKey_RightSuper;
case SDLK_APPLICATION: return ImGuiKey_Menu;
case SDLK_0: return ImGuiKey_0;
case SDLK_1: return ImGuiKey_1;
case SDLK_2: return ImGuiKey_2;
case SDLK_3: return ImGuiKey_3;
case SDLK_4: return ImGuiKey_4;
case SDLK_5: return ImGuiKey_5;
case SDLK_6: return ImGuiKey_6;
case SDLK_7: return ImGuiKey_7;
case SDLK_8: return ImGuiKey_8;
case SDLK_9: return ImGuiKey_9;
case SDLK_A: return ImGuiKey_A;
case SDLK_B: return ImGuiKey_B;
case SDLK_C: return ImGuiKey_C;
case SDLK_D: return ImGuiKey_D;
case SDLK_E: return ImGuiKey_E;
case SDLK_F: return ImGuiKey_F;
case SDLK_G: return ImGuiKey_G;
case SDLK_H: return ImGuiKey_H;
case SDLK_I: return ImGuiKey_I;
case SDLK_J: return ImGuiKey_J;
case SDLK_K: return ImGuiKey_K;
case SDLK_L: return ImGuiKey_L;
case SDLK_M: return ImGuiKey_M;
case SDLK_N: return ImGuiKey_N;
case SDLK_O: return ImGuiKey_O;
case SDLK_P: return ImGuiKey_P;
case SDLK_Q: return ImGuiKey_Q;
case SDLK_R: return ImGuiKey_R;
case SDLK_S: return ImGuiKey_S;
case SDLK_T: return ImGuiKey_T;
case SDLK_U: return ImGuiKey_U;
case SDLK_V: return ImGuiKey_V;
case SDLK_W: return ImGuiKey_W;
case SDLK_X: return ImGuiKey_X;
case SDLK_Y: return ImGuiKey_Y;
case SDLK_Z: return ImGuiKey_Z;
case SDLK_F1: return ImGuiKey_F1;
case SDLK_F2: return ImGuiKey_F2;
case SDLK_F3: return ImGuiKey_F3;
case SDLK_F4: return ImGuiKey_F4;
case SDLK_F5: return ImGuiKey_F5;
case SDLK_F6: return ImGuiKey_F6;
case SDLK_F7: return ImGuiKey_F7;
case SDLK_F8: return ImGuiKey_F8;
case SDLK_F9: return ImGuiKey_F9;
case SDLK_F10: return ImGuiKey_F10;
case SDLK_F11: return ImGuiKey_F11;
case SDLK_F12: return ImGuiKey_F12;
case SDLK_F13: return ImGuiKey_F13;
case SDLK_F14: return ImGuiKey_F14;
case SDLK_F15: return ImGuiKey_F15;
case SDLK_F16: return ImGuiKey_F16;
case SDLK_F17: return ImGuiKey_F17;
case SDLK_F18: return ImGuiKey_F18;
case SDLK_F19: return ImGuiKey_F19;
case SDLK_F20: return ImGuiKey_F20;
case SDLK_F21: return ImGuiKey_F21;
case SDLK_F22: return ImGuiKey_F22;
case SDLK_F23: return ImGuiKey_F23;
case SDLK_F24: return ImGuiKey_F24;
case SDLK_AC_BACK: return ImGuiKey_AppBack;
case SDLK_AC_FORWARD: return ImGuiKey_AppForward;
default: break;
}
// Fallback to scancode
switch (scancode)
{
case SDL_SCANCODE_GRAVE: return ImGuiKey_GraveAccent;
case SDL_SCANCODE_MINUS: return ImGuiKey_Minus;
case SDL_SCANCODE_EQUALS: return ImGuiKey_Equal;
case SDL_SCANCODE_LEFTBRACKET: return ImGuiKey_LeftBracket;
case SDL_SCANCODE_RIGHTBRACKET: return ImGuiKey_RightBracket;
case SDL_SCANCODE_NONUSBACKSLASH: return ImGuiKey_Oem102;
case SDL_SCANCODE_BACKSLASH: return ImGuiKey_Backslash;
case SDL_SCANCODE_SEMICOLON: return ImGuiKey_Semicolon;
case SDL_SCANCODE_APOSTROPHE: return ImGuiKey_Apostrophe;
case SDL_SCANCODE_COMMA: return ImGuiKey_Comma;
case SDL_SCANCODE_PERIOD: return ImGuiKey_Period;
case SDL_SCANCODE_SLASH: return ImGuiKey_Slash;
default: break;
}
return ImGuiKey_None;
}
static void ImGui_ImplSDL3_UpdateKeyModifiers(SDL_Keymod sdl_key_mods)
{
ImGuiIO& io = ImGui::GetIO();
io.AddKeyEvent(ImGuiMod_Ctrl, (sdl_key_mods & SDL_KMOD_CTRL) != 0);
io.AddKeyEvent(ImGuiMod_Shift, (sdl_key_mods & SDL_KMOD_SHIFT) != 0);
io.AddKeyEvent(ImGuiMod_Alt, (sdl_key_mods & SDL_KMOD_ALT) != 0);
io.AddKeyEvent(ImGuiMod_Super, (sdl_key_mods & SDL_KMOD_GUI) != 0);
}
static ImGuiViewport* ImGui_ImplSDL3_GetViewportForWindowID(SDL_WindowID window_id)
{
ImGui_ImplSDL3_Data* bd = ImGui_ImplSDL3_GetBackendData();
return (window_id == bd->WindowID) ? ImGui::GetMainViewport() : nullptr;
}
// You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs.
// - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or clear/overwrite your copy of the mouse data.
// - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application, or clear/overwrite your copy of the keyboard data.
// Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags.
// If you have multiple SDL events and some of them are not meant to be used by dear imgui, you may need to filter events based on their windowID field.
bool ImGui_ImplSDL3_ProcessEvent(const SDL_Event* event)
{
ImGui_ImplSDL3_Data* bd = ImGui_ImplSDL3_GetBackendData();
IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplSDL3_Init()?");
ImGuiIO& io = ImGui::GetIO();
switch (event->type)
{
case SDL_EVENT_MOUSE_MOTION:
{
if (ImGui_ImplSDL3_GetViewportForWindowID(event->motion.windowID) == nullptr)
return false;
ImVec2 mouse_pos((float)event->motion.x, (float)event->motion.y);
io.AddMouseSourceEvent(event->motion.which == SDL_TOUCH_MOUSEID ? ImGuiMouseSource_TouchScreen : ImGuiMouseSource_Mouse);
io.AddMousePosEvent(mouse_pos.x, mouse_pos.y);
return true;
}
case SDL_EVENT_MOUSE_WHEEL:
{
if (ImGui_ImplSDL3_GetViewportForWindowID(event->wheel.windowID) == nullptr)
return false;
//IMGUI_DEBUG_LOG("wheel %.2f %.2f, precise %.2f %.2f\n", (float)event->wheel.x, (float)event->wheel.y, event->wheel.preciseX, event->wheel.preciseY);
float wheel_x = -event->wheel.x;
float wheel_y = event->wheel.y;
io.AddMouseSourceEvent(event->wheel.which == SDL_TOUCH_MOUSEID ? ImGuiMouseSource_TouchScreen : ImGuiMouseSource_Mouse);
io.AddMouseWheelEvent(wheel_x, wheel_y);
return true;
}
case SDL_EVENT_MOUSE_BUTTON_DOWN:
case SDL_EVENT_MOUSE_BUTTON_UP:
{
if (ImGui_ImplSDL3_GetViewportForWindowID(event->button.windowID) == nullptr)
return false;
int mouse_button = -1;
if (event->button.button == SDL_BUTTON_LEFT) { mouse_button = 0; }
if (event->button.button == SDL_BUTTON_RIGHT) { mouse_button = 1; }
if (event->button.button == SDL_BUTTON_MIDDLE) { mouse_button = 2; }
if (event->button.button == SDL_BUTTON_X1) { mouse_button = 3; }
if (event->button.button == SDL_BUTTON_X2) { mouse_button = 4; }
if (mouse_button == -1)
break;
io.AddMouseSourceEvent(event->button.which == SDL_TOUCH_MOUSEID ? ImGuiMouseSource_TouchScreen : ImGuiMouseSource_Mouse);
io.AddMouseButtonEvent(mouse_button, (event->type == SDL_EVENT_MOUSE_BUTTON_DOWN));
bd->MouseButtonsDown = (event->type == SDL_EVENT_MOUSE_BUTTON_DOWN) ? (bd->MouseButtonsDown | (1 << mouse_button)) : (bd->MouseButtonsDown & ~(1 << mouse_button));
return true;
}
case SDL_EVENT_TEXT_INPUT:
{
if (ImGui_ImplSDL3_GetViewportForWindowID(event->text.windowID) == nullptr)
return false;
io.AddInputCharactersUTF8(event->text.text);
return true;
}
case SDL_EVENT_KEY_DOWN:
case SDL_EVENT_KEY_UP:
{
if (ImGui_ImplSDL3_GetViewportForWindowID(event->key.windowID) == nullptr)
return false;
ImGui_ImplSDL3_UpdateKeyModifiers((SDL_Keymod)event->key.mod);
//IMGUI_DEBUG_LOG("SDL_EVENT_KEY_%s : key=%d ('%s'), scancode=%d ('%s'), mod=%X\n",
// (event->type == SDL_EVENT_KEY_DOWN) ? "DOWN" : "UP ", event->key.key, SDL_GetKeyName(event->key.key), event->key.scancode, SDL_GetScancodeName(event->key.scancode), event->key.mod);
ImGuiKey key = ImGui_ImplSDL3_KeyEventToImGuiKey(event->key.key, event->key.scancode);
io.AddKeyEvent(key, (event->type == SDL_EVENT_KEY_DOWN));
io.SetKeyEventNativeData(key, event->key.key, event->key.scancode, event->key.scancode); // To support legacy indexing (<1.87 user code). Legacy backend uses SDLK_*** as indices to IsKeyXXX() functions.
return true;
}
case SDL_EVENT_WINDOW_MOUSE_ENTER:
{
if (ImGui_ImplSDL3_GetViewportForWindowID(event->window.windowID) == nullptr)
return false;
bd->MouseWindowID = event->window.windowID;
bd->MousePendingLeaveFrame = 0;
return true;
}
// - In some cases, when detaching a window from main viewport SDL may send SDL_WINDOWEVENT_ENTER one frame too late,
// causing SDL_WINDOWEVENT_LEAVE on previous frame to interrupt drag operation by clear mouse position. This is why
// we delay process the SDL_WINDOWEVENT_LEAVE events by one frame. See issue #5012 for details.
// FIXME: Unconfirmed whether this is still needed with SDL3.
case SDL_EVENT_WINDOW_MOUSE_LEAVE:
{
if (ImGui_ImplSDL3_GetViewportForWindowID(event->window.windowID) == nullptr)
return false;
bd->MousePendingLeaveFrame = ImGui::GetFrameCount() + 1;
return true;
}
case SDL_EVENT_WINDOW_FOCUS_GAINED:
case SDL_EVENT_WINDOW_FOCUS_LOST:
{
if (ImGui_ImplSDL3_GetViewportForWindowID(event->window.windowID) == nullptr)
return false;
io.AddFocusEvent(event->type == SDL_EVENT_WINDOW_FOCUS_GAINED);
return true;
}
case SDL_EVENT_GAMEPAD_ADDED:
case SDL_EVENT_GAMEPAD_REMOVED:
{
bd->WantUpdateGamepadsList = true;
return true;
}
}
return false;
}
static void ImGui_ImplSDL3_SetupPlatformHandles(ImGuiViewport* viewport, SDL_Window* window)
{
viewport->PlatformHandle = (void*)(intptr_t)SDL_GetWindowID(window);
viewport->PlatformHandleRaw = nullptr;
#if defined(_WIN32) && !defined(__WINRT__)
viewport->PlatformHandleRaw = (HWND)SDL_GetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_WIN32_HWND_POINTER, nullptr);
#elif defined(__APPLE__) && defined(SDL_VIDEO_DRIVER_COCOA)
viewport->PlatformHandleRaw = SDL_GetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_COCOA_WINDOW_POINTER, nullptr);
#endif
}
static bool ImGui_ImplSDL3_Init(SDL_Window* window, SDL_Renderer* renderer, void* sdl_gl_context)
{
ImGuiIO& io = ImGui::GetIO();
IMGUI_CHECKVERSION();
IM_ASSERT(io.BackendPlatformUserData == nullptr && "Already initialized a platform backend!");
IM_UNUSED(sdl_gl_context); // Unused in this branch
const int ver_linked = SDL_GetVersion();
// Setup backend capabilities flags
ImGui_ImplSDL3_Data* bd = IM_NEW(ImGui_ImplSDL3_Data)();
snprintf(bd->BackendPlatformName, sizeof(bd->BackendPlatformName), "imgui_impl_sdl3 (%u.%u.%u; %u.%u.%u)",
SDL_MAJOR_VERSION, SDL_MINOR_VERSION, SDL_MICRO_VERSION, SDL_VERSIONNUM_MAJOR(ver_linked), SDL_VERSIONNUM_MINOR(ver_linked), SDL_VERSIONNUM_MICRO(ver_linked));
io.BackendPlatformUserData = (void*)bd;
io.BackendPlatformName = bd->BackendPlatformName;
io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; // We can honor GetMouseCursor() values (optional)
io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos; // We can honor io.WantSetMousePos requests (optional, rarely used)
bd->Window = window;
bd->WindowID = SDL_GetWindowID(window);
bd->Renderer = renderer;
// Check and store if we are on a SDL backend that supports SDL_GetGlobalMouseState() and SDL_CaptureMouse()
// ("wayland" and "rpi" don't support it, but we chose to use a white-list instead of a black-list)
bd->MouseCanUseGlobalState = false;
bd->MouseCanUseCapture = false;
#if SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE
const char* sdl_backend = SDL_GetCurrentVideoDriver();
const char* capture_and_global_state_whitelist[] = { "windows", "cocoa", "x11", "DIVE", "VMAN" };
for (const char* item : capture_and_global_state_whitelist)
if (strncmp(sdl_backend, item, strlen(item)) == 0)
bd->MouseCanUseGlobalState = bd->MouseCanUseCapture = true;
#endif
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
platform_io.Platform_SetClipboardTextFn = ImGui_ImplSDL3_SetClipboardText;
platform_io.Platform_GetClipboardTextFn = ImGui_ImplSDL3_GetClipboardText;
platform_io.Platform_SetImeDataFn = ImGui_ImplSDL3_PlatformSetImeData;
platform_io.Platform_OpenInShellFn = [](ImGuiContext*, const char* url) { return SDL_OpenURL(url) == 0; };
// Gamepad handling
bd->GamepadMode = ImGui_ImplSDL3_GamepadMode_AutoFirst;
bd->WantUpdateGamepadsList = true;
// Load mouse cursors
bd->MouseCursors[ImGuiMouseCursor_Arrow] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_DEFAULT);
bd->MouseCursors[ImGuiMouseCursor_TextInput] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_TEXT);
bd->MouseCursors[ImGuiMouseCursor_ResizeAll] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_MOVE);
bd->MouseCursors[ImGuiMouseCursor_ResizeNS] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NS_RESIZE);
bd->MouseCursors[ImGuiMouseCursor_ResizeEW] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_EW_RESIZE);
bd->MouseCursors[ImGuiMouseCursor_ResizeNESW] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NESW_RESIZE);
bd->MouseCursors[ImGuiMouseCursor_ResizeNWSE] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NWSE_RESIZE);
bd->MouseCursors[ImGuiMouseCursor_Hand] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_POINTER);
bd->MouseCursors[ImGuiMouseCursor_Wait] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_WAIT);
bd->MouseCursors[ImGuiMouseCursor_Progress] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_PROGRESS);
bd->MouseCursors[ImGuiMouseCursor_NotAllowed] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NOT_ALLOWED);
// Set platform dependent data in viewport
// Our mouse update function expect PlatformHandle to be filled for the main viewport
ImGuiViewport* main_viewport = ImGui::GetMainViewport();
ImGui_ImplSDL3_SetupPlatformHandles(main_viewport, window);
// From 2.0.5: Set SDL hint to receive mouse click events on window focus, otherwise SDL doesn't emit the event.
// Without this, when clicking to gain focus, our widgets wouldn't activate even though they showed as hovered.
// (This is unfortunately a global SDL setting, so enabling it might have a side-effect on your application.
// It is unlikely to make a difference, but if your app absolutely needs to ignore the initial on-focus click:
// you can ignore SDL_EVENT_MOUSE_BUTTON_DOWN events coming right after a SDL_WINDOWEVENT_FOCUS_GAINED)
#ifdef SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH
SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1");
#endif
// From 2.0.22: Disable auto-capture, this is preventing drag and drop across multiple windows (see #5710)
#ifdef SDL_HINT_MOUSE_AUTO_CAPTURE
SDL_SetHint(SDL_HINT_MOUSE_AUTO_CAPTURE, "0");
#endif
return true;
}
bool ImGui_ImplSDL3_InitForOpenGL(SDL_Window* window, void* sdl_gl_context)
{
IM_UNUSED(sdl_gl_context); // Viewport branch will need this.
return ImGui_ImplSDL3_Init(window, nullptr, sdl_gl_context);
}
bool ImGui_ImplSDL3_InitForVulkan(SDL_Window* window)
{
return ImGui_ImplSDL3_Init(window, nullptr, nullptr);
}
bool ImGui_ImplSDL3_InitForD3D(SDL_Window* window)
{
#if !defined(_WIN32)
IM_ASSERT(0 && "Unsupported");
#endif
return ImGui_ImplSDL3_Init(window, nullptr, nullptr);
}
bool ImGui_ImplSDL3_InitForMetal(SDL_Window* window)
{
return ImGui_ImplSDL3_Init(window, nullptr, nullptr);
}
bool ImGui_ImplSDL3_InitForSDLRenderer(SDL_Window* window, SDL_Renderer* renderer)
{
return ImGui_ImplSDL3_Init(window, renderer, nullptr);
}
bool ImGui_ImplSDL3_InitForSDLGPU(SDL_Window* window)
{
return ImGui_ImplSDL3_Init(window, nullptr, nullptr);
}
bool ImGui_ImplSDL3_InitForOther(SDL_Window* window)
{
return ImGui_ImplSDL3_Init(window, nullptr, nullptr);
}
static void ImGui_ImplSDL3_CloseGamepads();
void ImGui_ImplSDL3_Shutdown()
{
ImGui_ImplSDL3_Data* bd = ImGui_ImplSDL3_GetBackendData();
IM_ASSERT(bd != nullptr && "No platform backend to shutdown, or already shutdown?");
ImGuiIO& io = ImGui::GetIO();
if (bd->ClipboardTextData)
SDL_free(bd->ClipboardTextData);
for (ImGuiMouseCursor cursor_n = 0; cursor_n < ImGuiMouseCursor_COUNT; cursor_n++)
SDL_DestroyCursor(bd->MouseCursors[cursor_n]);
ImGui_ImplSDL3_CloseGamepads();
io.BackendPlatformName = nullptr;
io.BackendPlatformUserData = nullptr;
io.BackendFlags &= ~(ImGuiBackendFlags_HasMouseCursors | ImGuiBackendFlags_HasSetMousePos | ImGuiBackendFlags_HasGamepad);
IM_DELETE(bd);
}
static void ImGui_ImplSDL3_UpdateMouseData()
{
ImGui_ImplSDL3_Data* bd = ImGui_ImplSDL3_GetBackendData();
ImGuiIO& io = ImGui::GetIO();
// We forward mouse input when hovered or captured (via SDL_EVENT_MOUSE_MOTION) or when focused (below)
#if SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE
// - SDL_CaptureMouse() let the OS know e.g. that our drags can extend outside of parent boundaries (we want updated position) and shouldn't trigger other operations outside.
// - Debuggers under Linux tends to leave captured mouse on break, which may be very inconvenient, so to mitigate the issue we wait until mouse has moved to begin capture.
if (bd->MouseCanUseCapture)
{
bool want_capture = false;
for (int button_n = 0; button_n < ImGuiMouseButton_COUNT && !want_capture; button_n++)
if (ImGui::IsMouseDragging(button_n, 1.0f))
want_capture = true;
SDL_CaptureMouse(want_capture);
}
SDL_Window* focused_window = SDL_GetKeyboardFocus();
const bool is_app_focused = (bd->Window == focused_window);
#else
SDL_Window* focused_window = bd->Window;
const bool is_app_focused = (SDL_GetWindowFlags(bd->Window) & SDL_WINDOW_INPUT_FOCUS) != 0; // SDL 2.0.3 and non-windowed systems: single-viewport only
#endif
if (is_app_focused)
{
// (Optional) Set OS mouse position from Dear ImGui if requested (rarely used, only when io.ConfigNavMoveSetMousePos is enabled by user)
if (io.WantSetMousePos)
SDL_WarpMouseInWindow(bd->Window, io.MousePos.x, io.MousePos.y);
// (Optional) Fallback to provide mouse position when focused (SDL_EVENT_MOUSE_MOTION already provides this when hovered or captured)
const bool is_relative_mouse_mode = SDL_GetWindowRelativeMouseMode(bd->Window);
if (bd->MouseCanUseGlobalState && bd->MouseButtonsDown == 0 && !is_relative_mouse_mode)
{
// Single-viewport mode: mouse position in client window coordinates (io.MousePos is (0,0) when the mouse is on the upper-left corner of the app window)
float mouse_x_global, mouse_y_global;
int window_x, window_y;
SDL_GetGlobalMouseState(&mouse_x_global, &mouse_y_global);
SDL_GetWindowPosition(focused_window, &window_x, &window_y);
io.AddMousePosEvent(mouse_x_global - window_x, mouse_y_global - window_y);
}
}
}
static void ImGui_ImplSDL3_UpdateMouseCursor()
{
ImGuiIO& io = ImGui::GetIO();
if (io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange)
return;
ImGui_ImplSDL3_Data* bd = ImGui_ImplSDL3_GetBackendData();
ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor();
if (io.MouseDrawCursor || imgui_cursor == ImGuiMouseCursor_None)
{
// Hide OS mouse cursor if imgui is drawing it or if it wants no cursor
SDL_HideCursor();
}
else
{
// Show OS mouse cursor
SDL_Cursor* expected_cursor = bd->MouseCursors[imgui_cursor] ? bd->MouseCursors[imgui_cursor] : bd->MouseCursors[ImGuiMouseCursor_Arrow];
if (bd->MouseLastCursor != expected_cursor)
{
SDL_SetCursor(expected_cursor); // SDL function doesn't have an early out (see #6113)
bd->MouseLastCursor = expected_cursor;
}
SDL_ShowCursor();
}
}
static void ImGui_ImplSDL3_CloseGamepads()
{
ImGui_ImplSDL3_Data* bd = ImGui_ImplSDL3_GetBackendData();
if (bd->GamepadMode != ImGui_ImplSDL3_GamepadMode_Manual)
for (SDL_Gamepad* gamepad : bd->Gamepads)
SDL_CloseGamepad(gamepad);
bd->Gamepads.resize(0);
}
void ImGui_ImplSDL3_SetGamepadMode(ImGui_ImplSDL3_GamepadMode mode, SDL_Gamepad** manual_gamepads_array, int manual_gamepads_count)
{
ImGui_ImplSDL3_Data* bd = ImGui_ImplSDL3_GetBackendData();
ImGui_ImplSDL3_CloseGamepads();
if (mode == ImGui_ImplSDL3_GamepadMode_Manual)
{
IM_ASSERT(manual_gamepads_array != nullptr || manual_gamepads_count <= 0);
for (int n = 0; n < manual_gamepads_count; n++)
bd->Gamepads.push_back(manual_gamepads_array[n]);
}
else
{
IM_ASSERT(manual_gamepads_array == nullptr && manual_gamepads_count <= 0);
bd->WantUpdateGamepadsList = true;
}
bd->GamepadMode = mode;
}
static void ImGui_ImplSDL3_UpdateGamepadButton(ImGui_ImplSDL3_Data* bd, ImGuiIO& io, ImGuiKey key, SDL_GamepadButton button_no)
{
bool merged_value = false;
for (SDL_Gamepad* gamepad : bd->Gamepads)
merged_value |= SDL_GetGamepadButton(gamepad, button_no) != 0;
io.AddKeyEvent(key, merged_value);
}
static inline float Saturate(float v) { return v < 0.0f ? 0.0f : v > 1.0f ? 1.0f : v; }
static void ImGui_ImplSDL3_UpdateGamepadAnalog(ImGui_ImplSDL3_Data* bd, ImGuiIO& io, ImGuiKey key, SDL_GamepadAxis axis_no, float v0, float v1)
{
float merged_value = 0.0f;
for (SDL_Gamepad* gamepad : bd->Gamepads)
{
float vn = Saturate((float)(SDL_GetGamepadAxis(gamepad, axis_no) - v0) / (float)(v1 - v0));
if (merged_value < vn)
merged_value = vn;
}
io.AddKeyAnalogEvent(key, merged_value > 0.1f, merged_value);
}
static void ImGui_ImplSDL3_UpdateGamepads()
{
ImGuiIO& io = ImGui::GetIO();
ImGui_ImplSDL3_Data* bd = ImGui_ImplSDL3_GetBackendData();
// Update list of gamepads to use
if (bd->WantUpdateGamepadsList && bd->GamepadMode != ImGui_ImplSDL3_GamepadMode_Manual)
{
ImGui_ImplSDL3_CloseGamepads();
int sdl_gamepads_count = 0;
SDL_JoystickID* sdl_gamepads = SDL_GetGamepads(&sdl_gamepads_count);
for (int n = 0; n < sdl_gamepads_count; n++)
if (SDL_Gamepad* gamepad = SDL_OpenGamepad(sdl_gamepads[n]))
{
bd->Gamepads.push_back(gamepad);
if (bd->GamepadMode == ImGui_ImplSDL3_GamepadMode_AutoFirst)
break;
}
bd->WantUpdateGamepadsList = false;
SDL_free(sdl_gamepads);
}
io.BackendFlags &= ~ImGuiBackendFlags_HasGamepad;
if (bd->Gamepads.Size == 0)
return;
io.BackendFlags |= ImGuiBackendFlags_HasGamepad;
// Update gamepad inputs
const int thumb_dead_zone = 8000; // SDL_gamepad.h suggests using this value.
ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGuiKey_GamepadStart, SDL_GAMEPAD_BUTTON_START);
ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGuiKey_GamepadBack, SDL_GAMEPAD_BUTTON_BACK);
ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGuiKey_GamepadFaceLeft, SDL_GAMEPAD_BUTTON_WEST); // Xbox X, PS Square
ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGuiKey_GamepadFaceRight, SDL_GAMEPAD_BUTTON_EAST); // Xbox B, PS Circle
ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGuiKey_GamepadFaceUp, SDL_GAMEPAD_BUTTON_NORTH); // Xbox Y, PS Triangle
ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGuiKey_GamepadFaceDown, SDL_GAMEPAD_BUTTON_SOUTH); // Xbox A, PS Cross
ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGuiKey_GamepadDpadLeft, SDL_GAMEPAD_BUTTON_DPAD_LEFT);
ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGuiKey_GamepadDpadRight, SDL_GAMEPAD_BUTTON_DPAD_RIGHT);
ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGuiKey_GamepadDpadUp, SDL_GAMEPAD_BUTTON_DPAD_UP);
ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGuiKey_GamepadDpadDown, SDL_GAMEPAD_BUTTON_DPAD_DOWN);
ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGuiKey_GamepadL1, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER);
ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGuiKey_GamepadR1, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER);
ImGui_ImplSDL3_UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadL2, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, 0.0f, 32767);
ImGui_ImplSDL3_UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadR2, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, 0.0f, 32767);
ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGuiKey_GamepadL3, SDL_GAMEPAD_BUTTON_LEFT_STICK);
ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGuiKey_GamepadR3, SDL_GAMEPAD_BUTTON_RIGHT_STICK);
ImGui_ImplSDL3_UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadLStickLeft, SDL_GAMEPAD_AXIS_LEFTX, -thumb_dead_zone, -32768);
ImGui_ImplSDL3_UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadLStickRight, SDL_GAMEPAD_AXIS_LEFTX, +thumb_dead_zone, +32767);
ImGui_ImplSDL3_UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadLStickUp, SDL_GAMEPAD_AXIS_LEFTY, -thumb_dead_zone, -32768);
ImGui_ImplSDL3_UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadLStickDown, SDL_GAMEPAD_AXIS_LEFTY, +thumb_dead_zone, +32767);
ImGui_ImplSDL3_UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadRStickLeft, SDL_GAMEPAD_AXIS_RIGHTX, -thumb_dead_zone, -32768);
ImGui_ImplSDL3_UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadRStickRight, SDL_GAMEPAD_AXIS_RIGHTX, +thumb_dead_zone, +32767);
ImGui_ImplSDL3_UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadRStickUp, SDL_GAMEPAD_AXIS_RIGHTY, -thumb_dead_zone, -32768);
ImGui_ImplSDL3_UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadRStickDown, SDL_GAMEPAD_AXIS_RIGHTY, +thumb_dead_zone, +32767);
}
static void ImGui_ImplSDL3_GetWindowSizeAndFramebufferScale(SDL_Window* window, ImVec2* out_size, ImVec2* out_framebuffer_scale)
{
int w, h;
int display_w, display_h;
SDL_GetWindowSize(window, &w, &h);
if (SDL_GetWindowFlags(window) & SDL_WINDOW_MINIMIZED)
w = h = 0;
SDL_GetWindowSizeInPixels(window, &display_w, &display_h);
if (out_size != nullptr)
*out_size = ImVec2((float)w, (float)h);
if (out_framebuffer_scale != nullptr)
*out_framebuffer_scale = (w > 0 && h > 0) ? ImVec2((float)display_w / w, (float)display_h / h) : ImVec2(1.0f, 1.0f);
}
void ImGui_ImplSDL3_NewFrame()
{
ImGui_ImplSDL3_Data* bd = ImGui_ImplSDL3_GetBackendData();
IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplSDL3_Init()?");
ImGuiIO& io = ImGui::GetIO();
// Setup main viewport size (every frame to accommodate for window resizing)
ImGui_ImplSDL3_GetWindowSizeAndFramebufferScale(bd->Window, &io.DisplaySize, &io.DisplayFramebufferScale);
// Setup time step (we could also use SDL_GetTicksNS() available since SDL3)
// (Accept SDL_GetPerformanceCounter() not returning a monotonically increasing value. Happens in VMs and Emscripten, see #6189, #6114, #3644)
static Uint64 frequency = SDL_GetPerformanceFrequency();
Uint64 current_time = SDL_GetPerformanceCounter();
if (current_time <= bd->Time)
current_time = bd->Time + 1;
io.DeltaTime = bd->Time > 0 ? (float)((double)(current_time - bd->Time) / frequency) : (float)(1.0f / 60.0f);
bd->Time = current_time;
if (bd->MousePendingLeaveFrame && bd->MousePendingLeaveFrame >= ImGui::GetFrameCount() && bd->MouseButtonsDown == 0)
{
bd->MouseWindowID = 0;
bd->MousePendingLeaveFrame = 0;
io.AddMousePosEvent(-FLT_MAX, -FLT_MAX);
}
ImGui_ImplSDL3_UpdateMouseData();
ImGui_ImplSDL3_UpdateMouseCursor();
// Update game controllers (if enabled and available)
ImGui_ImplSDL3_UpdateGamepads();
}
//-----------------------------------------------------------------------------
#if defined(__clang__)
#pragma clang diagnostic pop
#endif
#endif // #ifndef IMGUI_DISABLE

View File

@@ -0,0 +1,314 @@
// dear imgui: Renderer Backend for SDL_Renderer for SDL3
// (Requires: SDL 3.1.8+)
// Note that SDL_Renderer is an _optional_ component of SDL3, which IMHO is now largely obsolete.
// For a multi-platform app consider using other technologies:
// - SDL3+SDL_GPU: SDL_GPU is SDL3 new graphics abstraction API.
// - SDL3+DirectX, SDL3+OpenGL, SDL3+Vulkan: combine SDL with dedicated renderers.
// If your application wants to render any non trivial amount of graphics other than UI,
// please be aware that SDL_Renderer currently offers a limited graphic API to the end-user
// and it might be difficult to step out of those boundaries.
// Implemented features:
// [X] Renderer: User texture binding. Use 'SDL_Texture*' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef!
// [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset).
// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures).
// [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'.
// You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
// Learn about Dear ImGui:
// - FAQ https://dearimgui.com/faq
// - Getting Started https://dearimgui.com/getting-started
// - Documentation https://dearimgui.com/docs (same as your local docs/ folder).
// - Introduction, links and more at the top of imgui.cpp
// CHANGELOG
// 2025-06-11: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. Removed ImGui_ImplSDLRenderer3_CreateFontsTexture() and ImGui_ImplSDLRenderer3_DestroyFontsTexture().
// 2025-01-18: Use endian-dependent RGBA32 texture format, to match SDL_Color.
// 2024-10-09: Expose selected render state in ImGui_ImplSDLRenderer3_RenderState, which you can access in 'void* platform_io.Renderer_RenderState' during draw callbacks.
// 2024-07-01: Update for SDL3 api changes: SDL_RenderGeometryRaw() uint32 version was removed (SDL#9009).
// 2024-05-14: *BREAKING CHANGE* ImGui_ImplSDLRenderer3_RenderDrawData() requires SDL_Renderer* passed as parameter.
// 2024-02-12: Amend to query SDL_RenderViewportSet() and restore viewport accordingly.
// 2023-05-30: Initial version.
#include "imgui.h"
#ifndef IMGUI_DISABLE
#include "imgui_impl_sdlrenderer3.h"
#include <stdint.h> // intptr_t
// Clang warnings with -Weverything
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness
#endif
// SDL
#include <SDL3/SDL.h>
#if !SDL_VERSION_ATLEAST(3,0,0)
#error This backend requires SDL 3.0.0+
#endif
// SDL_Renderer data
struct ImGui_ImplSDLRenderer3_Data
{
SDL_Renderer* Renderer; // Main viewport's renderer
ImVector<SDL_FColor> ColorBuffer;
ImGui_ImplSDLRenderer3_Data() { memset((void*)this, 0, sizeof(*this)); }
};
// Backend data stored in io.BackendRendererUserData to allow support for multiple Dear ImGui contexts
// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts.
static ImGui_ImplSDLRenderer3_Data* ImGui_ImplSDLRenderer3_GetBackendData()
{
return ImGui::GetCurrentContext() ? (ImGui_ImplSDLRenderer3_Data*)ImGui::GetIO().BackendRendererUserData : nullptr;
}
// Functions
bool ImGui_ImplSDLRenderer3_Init(SDL_Renderer* renderer)
{
ImGuiIO& io = ImGui::GetIO();
IMGUI_CHECKVERSION();
IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!");
IM_ASSERT(renderer != nullptr && "SDL_Renderer not initialized!");
// Setup backend capabilities flags
ImGui_ImplSDLRenderer3_Data* bd = IM_NEW(ImGui_ImplSDLRenderer3_Data)();
io.BackendRendererUserData = (void*)bd;
io.BackendRendererName = "imgui_impl_sdlrenderer3";
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render.
bd->Renderer = renderer;
return true;
}
void ImGui_ImplSDLRenderer3_Shutdown()
{
ImGui_ImplSDLRenderer3_Data* bd = ImGui_ImplSDLRenderer3_GetBackendData();
IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?");
ImGuiIO& io = ImGui::GetIO();
ImGui_ImplSDLRenderer3_DestroyDeviceObjects();
io.BackendRendererName = nullptr;
io.BackendRendererUserData = nullptr;
io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasTextures);
IM_DELETE(bd);
}
static void ImGui_ImplSDLRenderer3_SetupRenderState(SDL_Renderer* renderer)
{
// Clear out any viewports and cliprect set by the user
// FIXME: Technically speaking there are lots of other things we could backup/setup/restore during our render process.
SDL_SetRenderViewport(renderer, nullptr);
SDL_SetRenderClipRect(renderer, nullptr);
}
void ImGui_ImplSDLRenderer3_NewFrame()
{
ImGui_ImplSDLRenderer3_Data* bd = ImGui_ImplSDLRenderer3_GetBackendData();
IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplSDLRenderer3_Init()?");
IM_UNUSED(bd);
}
// https://github.com/libsdl-org/SDL/issues/9009
static int SDL_RenderGeometryRaw8BitColor(SDL_Renderer* renderer, ImVector<SDL_FColor>& colors_out, SDL_Texture* texture, const float* xy, int xy_stride, const SDL_Color* color, int color_stride, const float* uv, int uv_stride, int num_vertices, const void* indices, int num_indices, int size_indices)
{
const Uint8* color2 = (const Uint8*)color;
colors_out.resize(num_vertices);
SDL_FColor* color3 = colors_out.Data;
for (int i = 0; i < num_vertices; i++)
{
color3[i].r = color->r / 255.0f;
color3[i].g = color->g / 255.0f;
color3[i].b = color->b / 255.0f;
color3[i].a = color->a / 255.0f;
color2 += color_stride;
color = (const SDL_Color*)color2;
}
return SDL_RenderGeometryRaw(renderer, texture, xy, xy_stride, color3, sizeof(*color3), uv, uv_stride, num_vertices, indices, num_indices, size_indices);
}
void ImGui_ImplSDLRenderer3_RenderDrawData(ImDrawData* draw_data, SDL_Renderer* renderer)
{
ImGui_ImplSDLRenderer3_Data* bd = ImGui_ImplSDLRenderer3_GetBackendData();
// If there's a scale factor set by the user, use that instead
// If the user has specified a scale factor to SDL_Renderer already via SDL_RenderSetScale(), SDL will scale whatever we pass
// to SDL_RenderGeometryRaw() by that scale factor. In that case we don't want to be also scaling it ourselves here.
float rsx = 1.0f;
float rsy = 1.0f;
SDL_GetRenderScale(renderer, &rsx, &rsy);
ImVec2 render_scale;
render_scale.x = (rsx == 1.0f) ? draw_data->FramebufferScale.x : 1.0f;
render_scale.y = (rsy == 1.0f) ? draw_data->FramebufferScale.y : 1.0f;
// Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates)
int fb_width = (int)(draw_data->DisplaySize.x * render_scale.x);
int fb_height = (int)(draw_data->DisplaySize.y * render_scale.y);
if (fb_width == 0 || fb_height == 0)
return;
// Catch up with texture updates. Most of the times, the list will have 1 element with an OK status, aka nothing to do.
// (This almost always points to ImGui::GetPlatformIO().Textures[] but is part of ImDrawData to allow overriding or disabling texture updates).
if (draw_data->Textures != nullptr)
for (ImTextureData* tex : *draw_data->Textures)
if (tex->Status != ImTextureStatus_OK)
ImGui_ImplSDLRenderer3_UpdateTexture(tex);
// Backup SDL_Renderer state that will be modified to restore it afterwards
struct BackupSDLRendererState
{
SDL_Rect Viewport;
bool ViewportEnabled;
bool ClipEnabled;
SDL_Rect ClipRect;
};
BackupSDLRendererState old = {};
old.ViewportEnabled = SDL_RenderViewportSet(renderer);
old.ClipEnabled = SDL_RenderClipEnabled(renderer);
SDL_GetRenderViewport(renderer, &old.Viewport);
SDL_GetRenderClipRect(renderer, &old.ClipRect);
// Setup desired state
ImGui_ImplSDLRenderer3_SetupRenderState(renderer);
// Setup render state structure (for callbacks and custom texture bindings)
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
ImGui_ImplSDLRenderer3_RenderState render_state;
render_state.Renderer = renderer;
platform_io.Renderer_RenderState = &render_state;
// Will project scissor/clipping rectangles into framebuffer space
ImVec2 clip_off = draw_data->DisplayPos; // (0,0) unless using multi-viewports
ImVec2 clip_scale = render_scale;
// Render command lists
for (int n = 0; n < draw_data->CmdListsCount; n++)
{
const ImDrawList* draw_list = draw_data->CmdLists[n];
const ImDrawVert* vtx_buffer = draw_list->VtxBuffer.Data;
const ImDrawIdx* idx_buffer = draw_list->IdxBuffer.Data;
for (int cmd_i = 0; cmd_i < draw_list->CmdBuffer.Size; cmd_i++)
{
const ImDrawCmd* pcmd = &draw_list->CmdBuffer[cmd_i];
if (pcmd->UserCallback)
{
// User callback, registered via ImDrawList::AddCallback()
// (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.)
if (pcmd->UserCallback == ImDrawCallback_ResetRenderState)
ImGui_ImplSDLRenderer3_SetupRenderState(renderer);
else
pcmd->UserCallback(draw_list, pcmd);
}
else
{
// Project scissor/clipping rectangles into framebuffer space
ImVec2 clip_min((pcmd->ClipRect.x - clip_off.x) * clip_scale.x, (pcmd->ClipRect.y - clip_off.y) * clip_scale.y);
ImVec2 clip_max((pcmd->ClipRect.z - clip_off.x) * clip_scale.x, (pcmd->ClipRect.w - clip_off.y) * clip_scale.y);
if (clip_min.x < 0.0f) { clip_min.x = 0.0f; }
if (clip_min.y < 0.0f) { clip_min.y = 0.0f; }
if (clip_max.x > (float)fb_width) { clip_max.x = (float)fb_width; }
if (clip_max.y > (float)fb_height) { clip_max.y = (float)fb_height; }
if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y)
continue;
SDL_Rect r = { (int)(clip_min.x), (int)(clip_min.y), (int)(clip_max.x - clip_min.x), (int)(clip_max.y - clip_min.y) };
SDL_SetRenderClipRect(renderer, &r);
const float* xy = (const float*)(const void*)((const char*)(vtx_buffer + pcmd->VtxOffset) + offsetof(ImDrawVert, pos));
const float* uv = (const float*)(const void*)((const char*)(vtx_buffer + pcmd->VtxOffset) + offsetof(ImDrawVert, uv));
const SDL_Color* color = (const SDL_Color*)(const void*)((const char*)(vtx_buffer + pcmd->VtxOffset) + offsetof(ImDrawVert, col)); // SDL 2.0.19+
// Bind texture, Draw
SDL_Texture* tex = (SDL_Texture*)pcmd->GetTexID();
SDL_RenderGeometryRaw8BitColor(renderer, bd->ColorBuffer, tex,
xy, (int)sizeof(ImDrawVert),
color, (int)sizeof(ImDrawVert),
uv, (int)sizeof(ImDrawVert),
draw_list->VtxBuffer.Size - pcmd->VtxOffset,
idx_buffer + pcmd->IdxOffset, pcmd->ElemCount, sizeof(ImDrawIdx));
}
}
}
platform_io.Renderer_RenderState = nullptr;
// Restore modified SDL_Renderer state
SDL_SetRenderViewport(renderer, old.ViewportEnabled ? &old.Viewport : nullptr);
SDL_SetRenderClipRect(renderer, old.ClipEnabled ? &old.ClipRect : nullptr);
}
void ImGui_ImplSDLRenderer3_UpdateTexture(ImTextureData* tex)
{
ImGui_ImplSDLRenderer3_Data* bd = ImGui_ImplSDLRenderer3_GetBackendData();
if (tex->Status == ImTextureStatus_WantCreate)
{
// Create and upload new texture to graphics system
//IMGUI_DEBUG_LOG("UpdateTexture #%03d: WantCreate %dx%d\n", tex->UniqueID, tex->Width, tex->Height);
IM_ASSERT(tex->TexID == 0 && tex->BackendUserData == nullptr);
IM_ASSERT(tex->Format == ImTextureFormat_RGBA32);
// Create texture
// (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling)
SDL_Texture* sdl_texture = SDL_CreateTexture(bd->Renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STATIC, tex->Width, tex->Height);
IM_ASSERT(sdl_texture != nullptr && "Backend failed to create texture!");
SDL_UpdateTexture(sdl_texture, nullptr, tex->GetPixels(), tex->GetPitch());
SDL_SetTextureBlendMode(sdl_texture, SDL_BLENDMODE_BLEND);
SDL_SetTextureScaleMode(sdl_texture, SDL_SCALEMODE_LINEAR);
// Store identifiers
tex->SetTexID((ImTextureID)(intptr_t)sdl_texture);
tex->SetStatus(ImTextureStatus_OK);
}
else if (tex->Status == ImTextureStatus_WantUpdates)
{
// Update selected blocks. We only ever write to textures regions which have never been used before!
// This backend choose to use tex->Updates[] but you can use tex->UpdateRect to upload a single region.
SDL_Texture* sdl_texture = (SDL_Texture*)(intptr_t)tex->TexID;
for (ImTextureRect& r : tex->Updates)
{
SDL_Rect sdl_r = { r.x, r.y, r.w, r.h };
SDL_UpdateTexture(sdl_texture, &sdl_r, tex->GetPixelsAt(r.x, r.y), tex->GetPitch());
}
tex->SetStatus(ImTextureStatus_OK);
}
else if (tex->Status == ImTextureStatus_WantDestroy)
{
SDL_Texture* sdl_texture = (SDL_Texture*)(intptr_t)tex->TexID;
if (sdl_texture == nullptr)
return;
SDL_DestroyTexture(sdl_texture);
// Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running)
tex->SetTexID(ImTextureID_Invalid);
tex->SetStatus(ImTextureStatus_Destroyed);
}
}
void ImGui_ImplSDLRenderer3_CreateDeviceObjects()
{
}
void ImGui_ImplSDLRenderer3_DestroyDeviceObjects()
{
// Destroy all textures
for (ImTextureData* tex : ImGui::GetPlatformIO().Textures)
if (tex->RefCount == 1)
{
tex->SetStatus(ImTextureStatus_WantDestroy);
ImGui_ImplSDLRenderer3_UpdateTexture(tex);
}
}
//-----------------------------------------------------------------------------
#if defined(__clang__)
#pragma clang diagnostic pop
#endif
#endif // #ifndef IMGUI_DISABLE

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,141 @@
//-----------------------------------------------------------------------------
// DEAR IMGUI COMPILE-TIME OPTIONS
// Runtime options (clipboard callbacks, enabling various features, etc.) can generally be set via the ImGuiIO structure.
// You can use ImGui::SetAllocatorFunctions() before calling ImGui::CreateContext() to rewire memory allocation functions.
//-----------------------------------------------------------------------------
// A) You may edit imconfig.h (and not overwrite it when updating Dear ImGui, or maintain a patch/rebased branch with your modifications to it)
// B) or '#define IMGUI_USER_CONFIG "my_imgui_config.h"' in your project and then add directives in your own file without touching this template.
//-----------------------------------------------------------------------------
// You need to make sure that configuration settings are defined consistently _everywhere_ Dear ImGui is used, which include the imgui*.cpp
// files but also _any_ of your code that uses Dear ImGui. This is because some compile-time options have an affect on data structures.
// Defining those options in imconfig.h will ensure every compilation unit gets to see the same data structure layouts.
// Call IMGUI_CHECKVERSION() from your .cpp file to verify that the data structures your files are using are matching the ones imgui.cpp is using.
//-----------------------------------------------------------------------------
#pragma once
//---- Define assertion handler. Defaults to calling assert().
// If your macro uses multiple statements, make sure is enclosed in a 'do { .. } while (0)' block so it can be used as a single statement.
//#define IM_ASSERT(_EXPR) MyAssert(_EXPR)
//#define IM_ASSERT(_EXPR) ((void)(_EXPR)) // Disable asserts
//---- Define attributes of all API symbols declarations, e.g. for DLL under Windows
// Using Dear ImGui via a shared library is not recommended, because of function call overhead and because we don't guarantee backward nor forward ABI compatibility.
// - Windows DLL users: heaps and globals are not shared across DLL boundaries! You will need to call SetCurrentContext() + SetAllocatorFunctions()
// for each static/DLL boundary you are calling from. Read "Context and Memory Allocators" section of imgui.cpp for more details.
//#define IMGUI_API __declspec(dllexport) // MSVC Windows: DLL export
//#define IMGUI_API __declspec(dllimport) // MSVC Windows: DLL import
//#define IMGUI_API __attribute__((visibility("default"))) // GCC/Clang: override visibility when set is hidden
//---- Don't define obsolete functions/enums/behaviors. Consider enabling from time to time after updating to clean your code of obsolete function/names.
//#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS
//---- Disable all of Dear ImGui or don't implement standard windows/tools.
// It is very strongly recommended to NOT disable the demo windows and debug tool during development. They are extremely useful in day to day work. Please read comments in imgui_demo.cpp.
//#define IMGUI_DISABLE // Disable everything: all headers and source files will be empty.
//#define IMGUI_DISABLE_DEMO_WINDOWS // Disable demo windows: ShowDemoWindow()/ShowStyleEditor() will be empty.
//#define IMGUI_DISABLE_DEBUG_TOOLS // Disable metrics/debugger and other debug tools: ShowMetricsWindow(), ShowDebugLogWindow() and ShowIDStackToolWindow() will be empty.
//---- Don't implement some functions to reduce linkage requirements.
//#define IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS // [Win32] Don't implement default clipboard handler. Won't use and link with OpenClipboard/GetClipboardData/CloseClipboard etc. (user32.lib/.a, kernel32.lib/.a)
//#define IMGUI_ENABLE_WIN32_DEFAULT_IME_FUNCTIONS // [Win32] [Default with Visual Studio] Implement default IME handler (require imm32.lib/.a, auto-link for Visual Studio, -limm32 on command-line for MinGW)
//#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS // [Win32] [Default with non-Visual Studio compilers] Don't implement default IME handler (won't require imm32.lib/.a)
//#define IMGUI_DISABLE_WIN32_FUNCTIONS // [Win32] Won't use and link with any Win32 function (clipboard, IME).
//#define IMGUI_ENABLE_OSX_DEFAULT_CLIPBOARD_FUNCTIONS // [OSX] Implement default OSX clipboard handler (need to link with '-framework ApplicationServices', this is why this is not the default).
//#define IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS // Don't implement default platform_io.Platform_OpenInShellFn() handler (Win32: ShellExecute(), require shell32.lib/.a, Mac/Linux: use system("")).
//#define IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS // Don't implement ImFormatString/ImFormatStringV so you can implement them yourself (e.g. if you don't want to link with vsnprintf)
//#define IMGUI_DISABLE_DEFAULT_MATH_FUNCTIONS // Don't implement ImFabs/ImSqrt/ImPow/ImFmod/ImCos/ImSin/ImAcos/ImAtan2 so you can implement them yourself.
//#define IMGUI_DISABLE_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle at all (replace them with dummies)
//#define IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle so you can implement them yourself if you don't want to link with fopen/fclose/fread/fwrite. This will also disable the LogToTTY() function.
//#define IMGUI_DISABLE_DEFAULT_ALLOCATORS // Don't implement default allocators calling malloc()/free() to avoid linking with them. You will need to call ImGui::SetAllocatorFunctions().
//#define IMGUI_DISABLE_DEFAULT_FONT // Disable default embedded font (ProggyClean.ttf), remove ~9.5 KB from output binary. AddFontDefault() will assert.
//#define IMGUI_DISABLE_SSE // Disable use of SSE intrinsics even if available
//---- Enable Test Engine / Automation features.
//#define IMGUI_ENABLE_TEST_ENGINE // Enable imgui_test_engine hooks. Generally set automatically by include "imgui_te_config.h", see Test Engine for details.
//---- Include imgui_user.h at the end of imgui.h as a convenience
// May be convenient for some users to only explicitly include vanilla imgui.h and have extra stuff included.
//#define IMGUI_INCLUDE_IMGUI_USER_H
//#define IMGUI_USER_H_FILENAME "my_folder/my_imgui_user.h"
//---- Pack vertex colors as BGRA8 instead of RGBA8 (to avoid converting from one to another). Need dedicated backend support.
//#define IMGUI_USE_BGRA_PACKED_COLOR
//---- Use legacy CRC32-adler tables (used before 1.91.6), in order to preserve old .ini data that you cannot afford to invalidate.
//#define IMGUI_USE_LEGACY_CRC32_ADLER
//---- Use 32-bit for ImWchar (default is 16-bit) to support Unicode planes 1-16. (e.g. point beyond 0xFFFF like emoticons, dingbats, symbols, shapes, ancient languages, etc...)
//#define IMGUI_USE_WCHAR32
//---- Avoid multiple STB libraries implementations, or redefine path/filenames to prioritize another version
// By default the embedded implementations are declared static and not available outside of Dear ImGui sources files.
//#define IMGUI_STB_TRUETYPE_FILENAME "my_folder/stb_truetype.h"
//#define IMGUI_STB_RECT_PACK_FILENAME "my_folder/stb_rect_pack.h"
//#define IMGUI_STB_SPRINTF_FILENAME "my_folder/stb_sprintf.h" // only used if IMGUI_USE_STB_SPRINTF is defined.
//#define IMGUI_DISABLE_STB_TRUETYPE_IMPLEMENTATION
//#define IMGUI_DISABLE_STB_RECT_PACK_IMPLEMENTATION
//#define IMGUI_DISABLE_STB_SPRINTF_IMPLEMENTATION // only disabled if IMGUI_USE_STB_SPRINTF is defined.
//---- Use stb_sprintf.h for a faster implementation of vsnprintf instead of the one from libc (unless IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS is defined)
// Compatibility checks of arguments and formats done by clang and GCC will be disabled in order to support the extra formats provided by stb_sprintf.h.
//#define IMGUI_USE_STB_SPRINTF
//---- Use FreeType to build and rasterize the font atlas (instead of stb_truetype which is embedded by default in Dear ImGui)
// Requires FreeType headers to be available in the include path. Requires program to be compiled with 'misc/freetype/imgui_freetype.cpp' (in this repository) + the FreeType library (not provided).
// On Windows you may use vcpkg with 'vcpkg install freetype --triplet=x64-windows' + 'vcpkg integrate install'.
//#define IMGUI_ENABLE_FREETYPE
//---- Use FreeType + plutosvg or lunasvg to render OpenType SVG fonts (SVGinOT)
// Only works in combination with IMGUI_ENABLE_FREETYPE.
// - plutosvg is currently easier to install, as e.g. it is part of vcpkg. It will support more fonts and may load them faster. See misc/freetype/README for instructions.
// - Both require headers to be available in the include path + program to be linked with the library code (not provided).
// - (note: lunasvg implementation is based on Freetype's rsvg-port.c which is licensed under CeCILL-C Free Software License Agreement)
//#define IMGUI_ENABLE_FREETYPE_PLUTOSVG
//#define IMGUI_ENABLE_FREETYPE_LUNASVG
//---- Use stb_truetype to build and rasterize the font atlas (default)
// The only purpose of this define is if you want force compilation of the stb_truetype backend ALONG with the FreeType backend.
//#define IMGUI_ENABLE_STB_TRUETYPE
//---- Define constructor and implicit cast operators to convert back<>forth between your math types and ImVec2/ImVec4.
// This will be inlined as part of ImVec2 and ImVec4 class declarations.
/*
#define IM_VEC2_CLASS_EXTRA \
constexpr ImVec2(const MyVec2& f) : x(f.x), y(f.y) {} \
operator MyVec2() const { return MyVec2(x,y); }
#define IM_VEC4_CLASS_EXTRA \
constexpr ImVec4(const MyVec4& f) : x(f.x), y(f.y), z(f.z), w(f.w) {} \
operator MyVec4() const { return MyVec4(x,y,z,w); }
*/
//---- ...Or use Dear ImGui's own very basic math operators.
//#define IMGUI_DEFINE_MATH_OPERATORS
//---- Use 32-bit vertex indices (default is 16-bit) is one way to allow large meshes with more than 64K vertices.
// Your renderer backend will need to support it (most example renderer backends support both 16/32-bit indices).
// Another way to allow large meshes while keeping 16-bit indices is to handle ImDrawCmd::VtxOffset in your renderer.
// Read about ImGuiBackendFlags_RendererHasVtxOffset for details.
//#define ImDrawIdx unsigned int
//---- Override ImDrawCallback signature (will need to modify renderer backends accordingly)
//struct ImDrawList;
//struct ImDrawCmd;
//typedef void (*MyImDrawCallback)(const ImDrawList* draw_list, const ImDrawCmd* cmd, void* my_renderer_user_data);
//#define ImDrawCallback MyImDrawCallback
//---- Debug Tools: Macro to break in Debugger (we provide a default implementation of this in the codebase)
// (use 'Metrics->Tools->Item Picker' to pick widgets with the mouse and break into them for easy debugging.)
//#define IM_DEBUG_BREAK IM_ASSERT(0)
//#define IM_DEBUG_BREAK __debugbreak()
//---- Debug Tools: Enable slower asserts
//#define IMGUI_DEBUG_PARANOID
//---- Tip: You can add extra functions within the ImGui:: namespace from anywhere (e.g. your own sources/header files)
/*
namespace ImGui
{
void MyFunction(const char* name, MyMatrix44* mtx);
}
*/

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,47 @@
// dear imgui: Platform Backend for SDL3
// This needs to be used along with a Renderer (e.g. SDL_GPU, DirectX11, OpenGL3, Vulkan..)
// (Info: SDL3 is a cross-platform general purpose library for handling windows, inputs, graphics context creation, etc.)
// Implemented features:
// [X] Platform: Clipboard support.
// [X] Platform: Mouse support. Can discriminate Mouse/TouchScreen.
// [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy SDL_SCANCODE_* values are obsolete since 1.87 and not supported since 1.91.5]
// [X] Platform: Gamepad support.
// [X] Platform: Mouse cursor shape and visibility (ImGuiBackendFlags_HasMouseCursors). Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'.
// [X] Platform: IME support.
// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
// Learn about Dear ImGui:
// - FAQ https://dearimgui.com/faq
// - Getting Started https://dearimgui.com/getting-started
// - Documentation https://dearimgui.com/docs (same as your local docs/ folder).
// - Introduction, links and more at the top of imgui.cpp
#pragma once
#include "imgui.h" // IMGUI_IMPL_API
#ifndef IMGUI_DISABLE
struct SDL_Window;
struct SDL_Renderer;
struct SDL_Gamepad;
typedef union SDL_Event SDL_Event;
// Follow "Getting Started" link and check examples/ folder to learn about using backends!
IMGUI_IMPL_API bool ImGui_ImplSDL3_InitForOpenGL(SDL_Window* window, void* sdl_gl_context);
IMGUI_IMPL_API bool ImGui_ImplSDL3_InitForVulkan(SDL_Window* window);
IMGUI_IMPL_API bool ImGui_ImplSDL3_InitForD3D(SDL_Window* window);
IMGUI_IMPL_API bool ImGui_ImplSDL3_InitForMetal(SDL_Window* window);
IMGUI_IMPL_API bool ImGui_ImplSDL3_InitForSDLRenderer(SDL_Window* window, SDL_Renderer* renderer);
IMGUI_IMPL_API bool ImGui_ImplSDL3_InitForSDLGPU(SDL_Window* window);
IMGUI_IMPL_API bool ImGui_ImplSDL3_InitForOther(SDL_Window* window);
IMGUI_IMPL_API void ImGui_ImplSDL3_Shutdown();
IMGUI_IMPL_API void ImGui_ImplSDL3_NewFrame();
IMGUI_IMPL_API bool ImGui_ImplSDL3_ProcessEvent(const SDL_Event* event);
// Gamepad selection automatically starts in AutoFirst mode, picking first available SDL_Gamepad. You may override this.
// When using manual mode, caller is responsible for opening/closing gamepad.
enum ImGui_ImplSDL3_GamepadMode { ImGui_ImplSDL3_GamepadMode_AutoFirst, ImGui_ImplSDL3_GamepadMode_AutoAll, ImGui_ImplSDL3_GamepadMode_Manual };
IMGUI_IMPL_API void ImGui_ImplSDL3_SetGamepadMode(ImGui_ImplSDL3_GamepadMode mode, SDL_Gamepad** manual_gamepads_array = nullptr, int manual_gamepads_count = -1);
#endif // #ifndef IMGUI_DISABLE

View File

@@ -0,0 +1,53 @@
// dear imgui: Renderer Backend for SDL_Renderer for SDL3
// (Requires: SDL 3.1.8+)
// Note that SDL_Renderer is an _optional_ component of SDL3, which IMHO is now largely obsolete.
// For a multi-platform app consider using other technologies:
// - SDL3+SDL_GPU: SDL_GPU is SDL3 new graphics abstraction API.
// - SDL3+DirectX, SDL3+OpenGL, SDL3+Vulkan: combine SDL with dedicated renderers.
// If your application wants to render any non trivial amount of graphics other than UI,
// please be aware that SDL_Renderer currently offers a limited graphic API to the end-user
// and it might be difficult to step out of those boundaries.
// Implemented features:
// [X] Renderer: User texture binding. Use 'SDL_Texture*' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef!
// [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset).
// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures).
// [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'.
// You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
// Learn about Dear ImGui:
// - FAQ https://dearimgui.com/faq
// - Getting Started https://dearimgui.com/getting-started
// - Documentation https://dearimgui.com/docs (same as your local docs/ folder).
// - Introduction, links and more at the top of imgui.cpp
#pragma once
#include "imgui.h" // IMGUI_IMPL_API
#ifndef IMGUI_DISABLE
struct SDL_Renderer;
// Follow "Getting Started" link and check examples/ folder to learn about using backends!
IMGUI_IMPL_API bool ImGui_ImplSDLRenderer3_Init(SDL_Renderer* renderer);
IMGUI_IMPL_API void ImGui_ImplSDLRenderer3_Shutdown();
IMGUI_IMPL_API void ImGui_ImplSDLRenderer3_NewFrame();
IMGUI_IMPL_API void ImGui_ImplSDLRenderer3_RenderDrawData(ImDrawData* draw_data, SDL_Renderer* renderer);
// Called by Init/NewFrame/Shutdown
IMGUI_IMPL_API void ImGui_ImplSDLRenderer3_CreateDeviceObjects();
IMGUI_IMPL_API void ImGui_ImplSDLRenderer3_DestroyDeviceObjects();
// (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = NULL to handle this manually.
IMGUI_IMPL_API void ImGui_ImplSDLRenderer3_UpdateTexture(ImTextureData* tex);
// [BETA] Selected render state data shared with callbacks.
// This is temporarily stored in GetPlatformIO().Renderer_RenderState during the ImGui_ImplSDLRenderer3_RenderDrawData() call.
// (Please open an issue if you feel you need access to more data)
struct ImGui_ImplSDLRenderer3_RenderState
{
SDL_Renderer* Renderer;
};
#endif // #ifndef IMGUI_DISABLE

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,627 @@
// [DEAR IMGUI]
// This is a slightly modified version of stb_rect_pack.h 1.01.
// Grep for [DEAR IMGUI] to find the changes.
//
// stb_rect_pack.h - v1.01 - public domain - rectangle packing
// Sean Barrett 2014
//
// Useful for e.g. packing rectangular textures into an atlas.
// Does not do rotation.
//
// Before #including,
//
// #define STB_RECT_PACK_IMPLEMENTATION
//
// in the file that you want to have the implementation.
//
// Not necessarily the awesomest packing method, but better than
// the totally naive one in stb_truetype (which is primarily what
// this is meant to replace).
//
// Has only had a few tests run, may have issues.
//
// More docs to come.
//
// No memory allocations; uses qsort() and assert() from stdlib.
// Can override those by defining STBRP_SORT and STBRP_ASSERT.
//
// This library currently uses the Skyline Bottom-Left algorithm.
//
// Please note: better rectangle packers are welcome! Please
// implement them to the same API, but with a different init
// function.
//
// Credits
//
// Library
// Sean Barrett
// Minor features
// Martins Mozeiko
// github:IntellectualKitty
//
// Bugfixes / warning fixes
// Jeremy Jaussaud
// Fabian Giesen
//
// Version history:
//
// 1.01 (2021-07-11) always use large rect mode, expose STBRP__MAXVAL in public section
// 1.00 (2019-02-25) avoid small space waste; gracefully fail too-wide rectangles
// 0.99 (2019-02-07) warning fixes
// 0.11 (2017-03-03) return packing success/fail result
// 0.10 (2016-10-25) remove cast-away-const to avoid warnings
// 0.09 (2016-08-27) fix compiler warnings
// 0.08 (2015-09-13) really fix bug with empty rects (w=0 or h=0)
// 0.07 (2015-09-13) fix bug with empty rects (w=0 or h=0)
// 0.06 (2015-04-15) added STBRP_SORT to allow replacing qsort
// 0.05: added STBRP_ASSERT to allow replacing assert
// 0.04: fixed minor bug in STBRP_LARGE_RECTS support
// 0.01: initial release
//
// LICENSE
//
// See end of file for license information.
//////////////////////////////////////////////////////////////////////////////
//
// INCLUDE SECTION
//
#ifndef STB_INCLUDE_STB_RECT_PACK_H
#define STB_INCLUDE_STB_RECT_PACK_H
#define STB_RECT_PACK_VERSION 1
#ifdef STBRP_STATIC
#define STBRP_DEF static
#else
#define STBRP_DEF extern
#endif
#ifdef __cplusplus
extern "C" {
#endif
typedef struct stbrp_context stbrp_context;
typedef struct stbrp_node stbrp_node;
typedef struct stbrp_rect stbrp_rect;
typedef int stbrp_coord;
#define STBRP__MAXVAL 0x7fffffff
// Mostly for internal use, but this is the maximum supported coordinate value.
STBRP_DEF int stbrp_pack_rects (stbrp_context *context, stbrp_rect *rects, int num_rects);
// Assign packed locations to rectangles. The rectangles are of type
// 'stbrp_rect' defined below, stored in the array 'rects', and there
// are 'num_rects' many of them.
//
// Rectangles which are successfully packed have the 'was_packed' flag
// set to a non-zero value and 'x' and 'y' store the minimum location
// on each axis (i.e. bottom-left in cartesian coordinates, top-left
// if you imagine y increasing downwards). Rectangles which do not fit
// have the 'was_packed' flag set to 0.
//
// You should not try to access the 'rects' array from another thread
// while this function is running, as the function temporarily reorders
// the array while it executes.
//
// To pack into another rectangle, you need to call stbrp_init_target
// again. To continue packing into the same rectangle, you can call
// this function again. Calling this multiple times with multiple rect
// arrays will probably produce worse packing results than calling it
// a single time with the full rectangle array, but the option is
// available.
//
// The function returns 1 if all of the rectangles were successfully
// packed and 0 otherwise.
struct stbrp_rect
{
// reserved for your use:
int id;
// input:
stbrp_coord w, h;
// output:
stbrp_coord x, y;
int was_packed; // non-zero if valid packing
}; // 16 bytes, nominally
STBRP_DEF void stbrp_init_target (stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes);
// Initialize a rectangle packer to:
// pack a rectangle that is 'width' by 'height' in dimensions
// using temporary storage provided by the array 'nodes', which is 'num_nodes' long
//
// You must call this function every time you start packing into a new target.
//
// There is no "shutdown" function. The 'nodes' memory must stay valid for
// the following stbrp_pack_rects() call (or calls), but can be freed after
// the call (or calls) finish.
//
// Note: to guarantee best results, either:
// 1. make sure 'num_nodes' >= 'width'
// or 2. call stbrp_allow_out_of_mem() defined below with 'allow_out_of_mem = 1'
//
// If you don't do either of the above things, widths will be quantized to multiples
// of small integers to guarantee the algorithm doesn't run out of temporary storage.
//
// If you do #2, then the non-quantized algorithm will be used, but the algorithm
// may run out of temporary storage and be unable to pack some rectangles.
STBRP_DEF void stbrp_setup_allow_out_of_mem (stbrp_context *context, int allow_out_of_mem);
// Optionally call this function after init but before doing any packing to
// change the handling of the out-of-temp-memory scenario, described above.
// If you call init again, this will be reset to the default (false).
STBRP_DEF void stbrp_setup_heuristic (stbrp_context *context, int heuristic);
// Optionally select which packing heuristic the library should use. Different
// heuristics will produce better/worse results for different data sets.
// If you call init again, this will be reset to the default.
enum
{
STBRP_HEURISTIC_Skyline_default=0,
STBRP_HEURISTIC_Skyline_BL_sortHeight = STBRP_HEURISTIC_Skyline_default,
STBRP_HEURISTIC_Skyline_BF_sortHeight
};
//////////////////////////////////////////////////////////////////////////////
//
// the details of the following structures don't matter to you, but they must
// be visible so you can handle the memory allocations for them
struct stbrp_node
{
stbrp_coord x,y;
stbrp_node *next;
};
struct stbrp_context
{
int width;
int height;
int align;
int init_mode;
int heuristic;
int num_nodes;
stbrp_node *active_head;
stbrp_node *free_head;
stbrp_node extra[2]; // we allocate two extra nodes so optimal user-node-count is 'width' not 'width+2'
};
#ifdef __cplusplus
}
#endif
#endif
//////////////////////////////////////////////////////////////////////////////
//
// IMPLEMENTATION SECTION
//
#ifdef STB_RECT_PACK_IMPLEMENTATION
#ifndef STBRP_SORT
#include <stdlib.h>
#define STBRP_SORT qsort
#endif
#ifndef STBRP_ASSERT
#include <assert.h>
#define STBRP_ASSERT assert
#endif
#ifdef _MSC_VER
#define STBRP__NOTUSED(v) (void)(v)
#define STBRP__CDECL __cdecl
#else
#define STBRP__NOTUSED(v) (void)sizeof(v)
#define STBRP__CDECL
#endif
enum
{
STBRP__INIT_skyline = 1
};
STBRP_DEF void stbrp_setup_heuristic(stbrp_context *context, int heuristic)
{
switch (context->init_mode) {
case STBRP__INIT_skyline:
STBRP_ASSERT(heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight || heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight);
context->heuristic = heuristic;
break;
default:
STBRP_ASSERT(0);
}
}
STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context *context, int allow_out_of_mem)
{
if (allow_out_of_mem)
// if it's ok to run out of memory, then don't bother aligning them;
// this gives better packing, but may fail due to OOM (even though
// the rectangles easily fit). @TODO a smarter approach would be to only
// quantize once we've hit OOM, then we could get rid of this parameter.
context->align = 1;
else {
// if it's not ok to run out of memory, then quantize the widths
// so that num_nodes is always enough nodes.
//
// I.e. num_nodes * align >= width
// align >= width / num_nodes
// align = ceil(width/num_nodes)
context->align = (context->width + context->num_nodes-1) / context->num_nodes;
}
}
STBRP_DEF void stbrp_init_target(stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes)
{
int i;
for (i=0; i < num_nodes-1; ++i)
nodes[i].next = &nodes[i+1];
nodes[i].next = NULL;
context->init_mode = STBRP__INIT_skyline;
context->heuristic = STBRP_HEURISTIC_Skyline_default;
context->free_head = &nodes[0];
context->active_head = &context->extra[0];
context->width = width;
context->height = height;
context->num_nodes = num_nodes;
stbrp_setup_allow_out_of_mem(context, 0);
// node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly)
context->extra[0].x = 0;
context->extra[0].y = 0;
context->extra[0].next = &context->extra[1];
context->extra[1].x = (stbrp_coord) width;
context->extra[1].y = (1<<30);
context->extra[1].next = NULL;
}
// find minimum y position if it starts at x1
static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0, int width, int *pwaste)
{
stbrp_node *node = first;
int x1 = x0 + width;
int min_y, visited_width, waste_area;
STBRP__NOTUSED(c);
STBRP_ASSERT(first->x <= x0);
#if 0
// skip in case we're past the node
while (node->next->x <= x0)
++node;
#else
STBRP_ASSERT(node->next->x > x0); // we ended up handling this in the caller for efficiency
#endif
STBRP_ASSERT(node->x <= x0);
min_y = 0;
waste_area = 0;
visited_width = 0;
while (node->x < x1) {
if (node->y > min_y) {
// raise min_y higher.
// we've accounted for all waste up to min_y,
// but we'll now add more waste for everything we've visted
waste_area += visited_width * (node->y - min_y);
min_y = node->y;
// the first time through, visited_width might be reduced
if (node->x < x0)
visited_width += node->next->x - x0;
else
visited_width += node->next->x - node->x;
} else {
// add waste area
int under_width = node->next->x - node->x;
if (under_width + visited_width > width)
under_width = width - visited_width;
waste_area += under_width * (min_y - node->y);
visited_width += under_width;
}
node = node->next;
}
*pwaste = waste_area;
return min_y;
}
typedef struct
{
int x,y;
stbrp_node **prev_link;
} stbrp__findresult;
static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int width, int height)
{
int best_waste = (1<<30), best_x, best_y = (1 << 30);
stbrp__findresult fr;
stbrp_node **prev, *node, *tail, **best = NULL;
// align to multiple of c->align
width = (width + c->align - 1);
width -= width % c->align;
STBRP_ASSERT(width % c->align == 0);
// if it can't possibly fit, bail immediately
if (width > c->width || height > c->height) {
fr.prev_link = NULL;
fr.x = fr.y = 0;
return fr;
}
node = c->active_head;
prev = &c->active_head;
while (node->x + width <= c->width) {
int y,waste;
y = stbrp__skyline_find_min_y(c, node, node->x, width, &waste);
if (c->heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight) { // actually just want to test BL
// bottom left
if (y < best_y) {
best_y = y;
best = prev;
}
} else {
// best-fit
if (y + height <= c->height) {
// can only use it if it first vertically
if (y < best_y || (y == best_y && waste < best_waste)) {
best_y = y;
best_waste = waste;
best = prev;
}
}
}
prev = &node->next;
node = node->next;
}
best_x = (best == NULL) ? 0 : (*best)->x;
// if doing best-fit (BF), we also have to try aligning right edge to each node position
//
// e.g, if fitting
//
// ____________________
// |____________________|
//
// into
//
// | |
// | ____________|
// |____________|
//
// then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned
//
// This makes BF take about 2x the time
if (c->heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight) {
tail = c->active_head;
node = c->active_head;
prev = &c->active_head;
// find first node that's admissible
while (tail->x < width)
tail = tail->next;
while (tail) {
int xpos = tail->x - width;
int y,waste;
STBRP_ASSERT(xpos >= 0);
// find the left position that matches this
while (node->next->x <= xpos) {
prev = &node->next;
node = node->next;
}
STBRP_ASSERT(node->next->x > xpos && node->x <= xpos);
y = stbrp__skyline_find_min_y(c, node, xpos, width, &waste);
if (y + height <= c->height) {
if (y <= best_y) {
if (y < best_y || waste < best_waste || (waste==best_waste && xpos < best_x)) {
best_x = xpos;
//STBRP_ASSERT(y <= best_y); [DEAR IMGUI]
best_y = y;
best_waste = waste;
best = prev;
}
}
}
tail = tail->next;
}
}
fr.prev_link = best;
fr.x = best_x;
fr.y = best_y;
return fr;
}
static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, int width, int height)
{
// find best position according to heuristic
stbrp__findresult res = stbrp__skyline_find_best_pos(context, width, height);
stbrp_node *node, *cur;
// bail if:
// 1. it failed
// 2. the best node doesn't fit (we don't always check this)
// 3. we're out of memory
if (res.prev_link == NULL || res.y + height > context->height || context->free_head == NULL) {
res.prev_link = NULL;
return res;
}
// on success, create new node
node = context->free_head;
node->x = (stbrp_coord) res.x;
node->y = (stbrp_coord) (res.y + height);
context->free_head = node->next;
// insert the new node into the right starting point, and
// let 'cur' point to the remaining nodes needing to be
// stiched back in
cur = *res.prev_link;
if (cur->x < res.x) {
// preserve the existing one, so start testing with the next one
stbrp_node *next = cur->next;
cur->next = node;
cur = next;
} else {
*res.prev_link = node;
}
// from here, traverse cur and free the nodes, until we get to one
// that shouldn't be freed
while (cur->next && cur->next->x <= res.x + width) {
stbrp_node *next = cur->next;
// move the current node to the free list
cur->next = context->free_head;
context->free_head = cur;
cur = next;
}
// stitch the list back in
node->next = cur;
if (cur->x < res.x + width)
cur->x = (stbrp_coord) (res.x + width);
#ifdef _DEBUG
cur = context->active_head;
while (cur->x < context->width) {
STBRP_ASSERT(cur->x < cur->next->x);
cur = cur->next;
}
STBRP_ASSERT(cur->next == NULL);
{
int count=0;
cur = context->active_head;
while (cur) {
cur = cur->next;
++count;
}
cur = context->free_head;
while (cur) {
cur = cur->next;
++count;
}
STBRP_ASSERT(count == context->num_nodes+2);
}
#endif
return res;
}
static int STBRP__CDECL rect_height_compare(const void *a, const void *b)
{
const stbrp_rect *p = (const stbrp_rect *) a;
const stbrp_rect *q = (const stbrp_rect *) b;
if (p->h > q->h)
return -1;
if (p->h < q->h)
return 1;
return (p->w > q->w) ? -1 : (p->w < q->w);
}
static int STBRP__CDECL rect_original_order(const void *a, const void *b)
{
const stbrp_rect *p = (const stbrp_rect *) a;
const stbrp_rect *q = (const stbrp_rect *) b;
return (p->was_packed < q->was_packed) ? -1 : (p->was_packed > q->was_packed);
}
STBRP_DEF int stbrp_pack_rects(stbrp_context *context, stbrp_rect *rects, int num_rects)
{
int i, all_rects_packed = 1;
// we use the 'was_packed' field internally to allow sorting/unsorting
for (i=0; i < num_rects; ++i) {
rects[i].was_packed = i;
}
// sort according to heuristic
STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_height_compare);
for (i=0; i < num_rects; ++i) {
if (rects[i].w == 0 || rects[i].h == 0) {
rects[i].x = rects[i].y = 0; // empty rect needs no space
} else {
stbrp__findresult fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h);
if (fr.prev_link) {
rects[i].x = (stbrp_coord) fr.x;
rects[i].y = (stbrp_coord) fr.y;
} else {
rects[i].x = rects[i].y = STBRP__MAXVAL;
}
}
}
// unsort
STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_original_order);
// set was_packed flags and all_rects_packed status
for (i=0; i < num_rects; ++i) {
rects[i].was_packed = !(rects[i].x == STBRP__MAXVAL && rects[i].y == STBRP__MAXVAL);
if (!rects[i].was_packed)
all_rects_packed = 0;
}
// return the all_rects_packed status
return all_rects_packed;
}
#endif
/*
------------------------------------------------------------------------------
This software is available under 2 licenses -- choose whichever you prefer.
------------------------------------------------------------------------------
ALTERNATIVE A - MIT License
Copyright (c) 2017 Sean Barrett
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.
------------------------------------------------------------------------------
ALTERNATIVE B - Public Domain (www.unlicense.org)
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
software, either in source code form or as a compiled binary, for any purpose,
commercial or non-commercial, and by any means.
In jurisdictions that recognize copyright laws, the author or authors of this
software dedicate any and all copyright interest in the software to the public
domain. We make this dedication for the benefit of the public at large and to
the detriment of our heirs and successors. We intend this dedication to be an
overt act of relinquishment in perpetuity of all present and future rights to
this software under copyright law.
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 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.
------------------------------------------------------------------------------
*/

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,43 @@
# Definiere die Quelldateien in einer Variable
set(SOURCE_FILES
src/common/InactivityTracker.cpp
src/common/Menu.cpp
src/common/ScrollBar.cpp
src/common/Widget.cpp
src/data/MenuItem.cpp
src/ui/LightMenu.cpp
src/ui/LightSettingsMenu.cpp
src/ui/MainMenu.cpp
src/ui/ScreenSaver.cpp
src/ui/SettingsMenu.cpp
src/ui/SplashScreen.cpp
)
if (DEFINED ENV{IDF_PATH})
idf_component_register(SRCS
${SOURCE_FILES}
INCLUDE_DIRS "include"
PRIV_REQUIRES
u8g2
led-manager
persistence-manager
)
return()
endif ()
cmake_minimum_required(VERSION 3.30)
project(insa)
add_library(${PROJECT_NAME} STATIC
${SOURCE_FILES}
)
include_directories(include)
target_include_directories(${PROJECT_NAME} PUBLIC include)
target_link_libraries(${PROJECT_NAME} PRIVATE
u8g2
led-manager
persistence-manager
)

View File

@@ -0,0 +1,74 @@
/**
* @file MenuOptions.h
* @brief Menu configuration structure and callback definitions
* @details This header defines the menu_options_t structure which contains all
* necessary configuration options and callback functions for menu widgets.
* It provides the interface between menu widgets and the application's
* screen management system, display context, and input handling.
* @author System Control Team
* @date 2025-06-14
*/
#pragma once
// Standard libraries for function objects and smart pointers
#include <functional>
#include <memory>
// Project-specific headers
#include "common/Widget.h"
#include "IPersistenceManager.h"
#include "u8g2.h"
class MenuItem;
/**
* @struct menu_options_t
* @brief Configuration structure for menu widgets containing display context and callbacks
* @details This structure serves as a configuration container that provides menu widgets
* with access to the display system, screen management functions, input
* handling callbacks, and persistent storage.
*
* @see Widget
* @see ButtonType
* @see IPersistenceManager
*/
typedef struct
{
/**
* @brief Pointer to u8g2 display context for graphics output operations
*/
u8g2_t *u8g2;
/**
* @brief Callback function to set the current active screen
*/
std::function<void(std::shared_ptr<Widget>)> setScreen;
/**
* @brief Callback function to add a new screen to the navigation stack
*/
std::function<void(std::shared_ptr<Widget>)> pushScreen;
/**
* @brief Callback function to remove the top screen from the navigation stack
*/
std::function<void()> popScreen;
/**
* @brief Callback function to handle button press events
*/
std::function<void(ButtonType button)> onButtonClicked;
/**
* @brief Shared pointer to platform-independent persistence manager
* @details This provides access to persistent key-value storage across different
* platforms. The actual implementation (SDL3 or ESP32/NVS) is determined
* at compile time based on the target platform.
*
* @note The persistence manager is shared across all menu widgets and maintains
* its state throughout the application lifecycle.
*/
std::shared_ptr<IPersistenceManager> persistenceManager;
} menu_options_t;

View File

@@ -0,0 +1,63 @@
/**
* @file Common.h
* @brief Common definitions and types for the INSA component
* @details This header file contains shared enumerations, type definitions, and
* callback function types used throughout the INSA component system.
* It provides the foundation for button handling and event management.
* @author System Control Team
* @date 2025-06-14
*/
#pragma once
#include <functional>
/**
* @enum ButtonType
* @brief Enumeration defining the different types of buttons available in the system
* @details This enumeration represents all possible button types that can be handled
* by the system's input management. NONE represents no button pressed or
* an invalid button state, while the other values correspond to specific
* directional and action buttons.
*/
enum class ButtonType
{
NONE, ///< No button pressed or invalid button state
UP, ///< Up directional button for navigation
DOWN, ///< Down directional button for navigation
LEFT, ///< Left directional button for navigation
RIGHT, ///< Right directional button for navigation
SELECT, ///< Select/confirm button for accepting choices
BACK ///< Back/cancel button for returning to previous state
};
// Forward declaration of MenuItem to avoid circular dependency
class MenuItem;
/**
* @typedef ButtonCallback
* @brief Type alias for button event callback function
* @details This function type is used to define callback functions that handle
* button press events. The callback receives information about which
* button was pressed and any additional context data.
*
* @param MenuItem menu item for the specific action
* @param ButtonType The type of button that was pressed
*
* @note The first parameter can be used to distinguish between multiple instances
* of the same button type or to pass additional event-specific data.
*
* @example
* @code
* ButtonCallback myCallback = [](const MenuItem& item, ButtonType type) {
* if (type == ButtonType::SELECT) {
* // Handle select button press
* processSelection(item);
* }
* };
* @endcode
*/
typedef std::function<void(MenuItem, ButtonType)> ButtonCallback;
// Include MenuItem.h after the typedef to avoid circular dependency
#include "data/MenuItem.h"

View File

@@ -0,0 +1,194 @@
/**
* @file InactivityTracker.h
* @brief Inactivity tracking system for monitoring user interaction timeouts
* @details This header defines the InactivityTracker class which monitors user
* activity and triggers timeout callbacks when the system remains inactive
* for a specified duration. It provides essential functionality for power
* management, screen savers, and automatic system state transitions.
* @author System Control Team
* @date 2025-06-20
*/
#pragma once
#include <functional>
#include <stdint.h>
/**
* @class InactivityTracker
* @brief Activity monitoring class for detecting user inactivity periods
* @details This class provides a robust mechanism for tracking user activity and
* triggering automatic actions when the system remains inactive for a
* configured timeout period. It is commonly used for implementing power
* saving features, automatic screen savers, session timeouts, and other
* time-based system behaviors.
*
* The InactivityTracker operates by:
* - Continuously tracking elapsed time since the last user activity
* - Comparing elapsed time against a configurable timeout threshold
* - Executing a callback function when the timeout is reached
* - Providing methods to reset the timer when activity is detected
* - Supporting enable/disable functionality for dynamic control
*
* Key features include:
* - Configurable timeout duration in milliseconds
* - Custom callback function execution on timeout
* - Activity reset capability for responsive user interaction
* - Enable/disable control for conditional monitoring
* - High-resolution timing support using 64-bit millisecond precision
*
* Common use cases:
* - Screen saver activation after idle periods
* - Automatic screen dimming or shutdown
* - Session timeout management
* - Power management and battery conservation
* - User interface state transitions
* - Security lockout after inactivity
*
* The class is designed to be lightweight and efficient, suitable for
* real-time applications where precise timing and minimal overhead are important.
*
* @note This class requires regular update calls to function properly.
* @note The timeout callback is executed once per timeout period and will
* not repeat until the tracker is reset and times out again.
*
* @see Widget for integration with UI components
* @see Menu for menu timeout implementations
*/
class InactivityTracker
{
public:
/**
* @brief Constructs an InactivityTracker with specified timeout and callback
* @param timeoutMs Timeout duration in milliseconds before triggering callback
* @param onTimeout Callback function to execute when timeout is reached
*
* @pre timeoutMs must be greater than 0 for meaningful timeout behavior
* @pre onTimeout must be a valid callable function object
* @post InactivityTracker is initialized, enabled, and ready for activity monitoring
*
* @details The constructor initializes the inactivity tracker with the specified
* timeout duration and callback function. The tracker starts in an enabled
* state with zero elapsed time, ready to begin monitoring user activity.
*
* The timeout callback function can perform any necessary actions when inactivity
* is detected, such as:
* - Activating screen savers or power saving modes
* - Transitioning to different application states
* - Logging inactivity events
* - Triggering security lockouts
* - Initiating automatic save operations
*
* @note The tracker begins monitoring immediately upon construction.
* @note The callback function should be lightweight to avoid blocking
* the main application thread during timeout processing.
*
* Example usage:
* @code
* auto tracker = InactivityTracker(30000, []() {
* // Activate screen saver after 30 seconds of inactivity
* activateScreenSaver();
* });
* @endcode
*/
InactivityTracker(uint64_t timeoutMs, const std::function<void()> &onTimeout);
/**
* @brief Updates the inactivity timer and checks for timeout conditions
* @param dt Delta time in milliseconds since the last update call
*
* @details This method must be called regularly (typically every frame) to
* maintain accurate timing and timeout detection. It increments the
* elapsed time counter and triggers the timeout callback when the
* configured timeout duration is reached.
*
* The update process:
* - Adds the delta time to the elapsed time counter (if enabled)
* - Compares elapsed time against the configured timeout threshold
* - Executes the timeout callback if the threshold is exceeded
* - Continues monitoring until reset or disabled
*
* @note This method should be called consistently from the main application
* loop to ensure accurate timing behavior.
* @note The timeout callback is executed only once per timeout period.
* @note If the tracker is disabled, elapsed time is not updated.
*
* @see reset() to restart the inactivity timer
* @see setEnabled() to control monitoring state
*/
void update(uint64_t dt);
/**
* @brief Resets the inactivity timer to indicate recent user activity
*
* @details This method should be called whenever user activity is detected
* to restart the inactivity timeout period. It resets the elapsed
* time counter to zero, effectively extending the timeout deadline
* and preventing timeout callback execution until the full timeout
* duration elapses again without further resets.
*
* Common scenarios for calling reset():
* - Button presses or key events
* - Mouse movement or touch input
* - Menu navigation or selection actions
* - Any user interface interaction
* - System activity that should extend the timeout
*
* @post Elapsed time is reset to zero, restarting the timeout period
*
* @note This method can be called at any time, even when the tracker
* is disabled, to prepare for future monitoring.
* @note Frequent reset calls from active user interaction will prevent
* timeout callback execution, which is the intended behavior.
*
* Example usage:
* @code
* void onButtonPress() {
* tracker.reset(); // User activity detected, restart timeout
* // Handle button press...
* }
* @endcode
*/
void reset();
/**
* @brief Enables or disables inactivity monitoring
* @param enabled True to enable monitoring, false to disable
*
* @details This method controls whether the inactivity tracker actively
* monitors for timeouts. When disabled, the elapsed time counter
* is not updated during update() calls, effectively pausing the
* timeout detection without losing the current elapsed time state.
*
* Use cases for disabling:
* - Temporary suspension during system operations
* - Context-sensitive monitoring (disable in certain application states)
* - Power management control (disable during low-power modes)
* - User preference settings (allow users to disable timeouts)
* - Development and debugging (disable for testing)
*
* When re-enabled, monitoring resumes from the current elapsed time state,
* allowing for seamless pause/resume functionality.
*
* @post Monitoring state is updated according to the enabled parameter
*
* @note Disabling the tracker does not reset the elapsed time counter.
* @note The timeout callback will not be executed while disabled, even
* if the timeout threshold would otherwise be exceeded.
* @note Enabling/disabling can be done at any time during operation.
*
* Example usage:
* @code
* tracker.setEnabled(false); // Pause monitoring during critical operation
* performCriticalOperation();
* tracker.setEnabled(true); // Resume monitoring after completion
* @endcode
*/
void setEnabled(bool enabled);
private:
uint64_t m_timeoutMs; ///< Timeout duration in milliseconds before callback execution
uint64_t m_elapsedTime; ///< Current elapsed time since last reset in milliseconds
bool m_enabled; ///< Flag indicating whether monitoring is currently active
std::function<void()> m_onTimeout; ///< Callback function executed when timeout threshold is reached
};

View File

@@ -0,0 +1,223 @@
/**
* @file Menu.h
* @brief Menu widget class for creating interactive menu systems
* @details This header defines the Menu class which extends the Widget base class
* to provide a comprehensive, customizable menu system supporting various
* types of interactive menu items including text buttons, selections,
* number inputs, and toggles. The menu supports navigation with directional
* input and provides visual feedback through selection highlighting and scrollbars.
* @author System Control Team
* @date 2025-06-14
*/
#pragma once
#include "Common.h"
#include "MenuOptions.h"
#include "Widget.h"
#include "data/MenuItem.h"
/**
* @class Menu
* @brief A comprehensive menu widget class for interactive user interfaces
* @details This class extends the Widget base class to provide a customizable menu system
* with support for various types of interactive menu items. It handles user input
* through directional navigation and action buttons, provides visual feedback
* through selection highlighting, and supports scrolling for long menu lists.
*
* The menu system supports four types of menu items:
* - Text buttons: Simple selectable text items
* - Selection items: Dropdown/list selection with multiple options
* - Number inputs: Numeric value adjustment controls
* - Toggle items: Boolean on/off switches
*
* @note Menu items are identified by unique IDs and can be dynamically added
* after menu creation.
*
* @see Widget
* @see MenuItem
* @see menu_options_t
*/
class Menu : public Widget
{
public:
/**
* @brief Constructs a new Menu instance with the specified configuration
* @param options Pointer to menu configuration options structure
*
* @pre options must not be nullptr and must remain valid for the menu's lifetime
* @post Menu is initialized with the provided configuration and ready for item addition
*
* @note The menu does not take ownership of the options structure and assumes
* it remains valid throughout the menu's lifetime.
*/
explicit Menu(menu_options_t *options);
/**
* @brief Destructor - Cleans up resources when menu is destroyed
* @details Properly releases any allocated resources and ensures clean shutdown
* of the menu system.
*/
~Menu() override;
/**
* @brief Adds a text-based menu item (button) to the menu
* @param id Unique identifier for this menu item (must be unique within the menu)
* @param text Display text shown on the menu item
*
* @pre id must be unique within this menu instance
* @post A new text menu item is added to the menu's item collection
*
* @note Text items act as buttons and generate selection events when activated
*/
void addText(uint8_t id, const std::string &text);
void addTextCounter(uint8_t id, const std::string &text, const uint8_t value);
/**
* @brief Adds a selection menu item (dropdown/list selection) to the menu
* @param id Unique identifier for this menu item (must be unique within the menu)
* @param text Display text/label for the selection item
* @param values Vector of all available options to choose from
* @param index Reference to current selected value (will be modified by user interaction)
*
* @pre id must be unique within this menu instance
* @pre values vector must not be empty
* @pre value must be one of the strings in the values vector
* @post A new selection menu item is added with the specified options
*
* @note The value parameter is modified directly when the user changes the selection
*/
void addSelection(uint8_t id, const std::string &text, const std::vector<std::string> &values, int index);
/**
* @brief Adds a toggle/checkbox menu item to the menu
* @param id Unique identifier for this menu item (must be unique within the menu)
* @param text Display text/label for the toggle item
* @param selected Current state of the toggle (true = on/enabled, false = off/disabled)
*
* @pre id must be unique within this menu instance
* @post A new toggle menu item is added with the specified initial state
*
* @note Toggle state can be changed through user interaction with select button
*/
void addToggle(uint8_t id, const std::string &text, bool selected);
protected:
/**
* @brief Virtual callback method for handling button press events on specific menu items
* @param item The menu item that received the button press
* @param button The type of button that was pressed
*
* @details This method can be overridden by derived classes to implement custom
* button handling logic for specific menu items. The base implementation
* is empty, allowing derived classes to selectively handle events.
*
* @note Override this method in derived classes to implement custom menu item
* interaction behavior beyond the standard navigation and value modification.
*/
virtual void onButtonPressed(const MenuItem &item, const ButtonType button)
{
// Base implementation intentionally empty - override in derived classes as needed
}
/**
* @brief Retrieves a menu item by its index position
* @param index Zero-based index of the menu item to retrieve
* @return MenuItem object at the specified index
*
* @pre index must be within valid range [0, getItemCount()-1]
* @post Returns a copy of the menu item at the specified position
*
* @throws std::out_of_range if index is invalid
*
* @note This method returns a copy of the menu item, not a reference
*/
MenuItem getItem(int index);
/**
* @brief Gets the total number of menu items in the menu
* @return Size of the menu items collection
*
* @post Returns current count of menu items (>= 0)
*
* @note This count includes all types of menu items (text, selection, toggle)
*/
[[nodiscard]] size_t getItemCount() const;
/**
* @brief Dynamically adjusts the number of menu items to the specified size
* @param size Target number of menu items the menu should contain
*
* @details If the target size is larger than current item count, new selection
* items are added using the first item's values as template. If the
* target size is smaller, excess items are removed from the end.
*
* @pre size must be > 0 and at least one menu item must exist as template
* @post Menu contains exactly 'size' number of items
*
* @note New items are created as selection items with auto-generated names
* in the format "Section X" where X is the item number
*/
void setItemSize(size_t size);
/**
* @brief Toggles the boolean state of a toggle menu item
* @param menuItem The toggle menu item whose state should be flipped
*
* @pre menuItem must be of type TOGGLE
* @post The menu item's value is switched between "true" and "false"
*
* @details Changes "true" to "false" and "false" to "true" for toggle items.
* The modified item replaces the original in the menu's item collection.
*
* @note This method directly modifies the menu's internal state
*/
void toggle(const MenuItem &menuItem);
/**
* @brief Changes the selected value of a selection menu item based on button input
* @param menuItem The selection menu item to modify
* @param button The directional button pressed (LEFT or RIGHT)
*
* @pre menuItem must be of type SELECTION with valid values array
* @post The menu item's selected index is adjusted based on button direction
*
* @details LEFT button moves to previous option (wraps to end if at beginning),
* RIGHT button moves to next option (wraps to beginning if at end).
* Other button types are ignored.
*
* @note The modified item replaces the original in the menu's item collection
*/
MenuItem switchValue(const MenuItem &menuItem, ButtonType button);
private:
MenuItem replaceItem(int index, const MenuItem &item);
void render() override;
void onButtonClicked(ButtonType button) override;
void onPressedDown();
void onPressedUp();
void onPressedLeft() const;
void onPressedRight() const;
void onPressedSelect() const;
void onPressedBack() const;
void drawScrollBar() const;
void drawSelectionBox() const;
void renderWidget(const MenuItem *item, const uint8_t *font, int x, int y) const;
// Member variables
size_t m_selected_item = 0;
std::vector<MenuItem> m_items;
menu_options_t *m_options;
};

View File

@@ -0,0 +1,106 @@
/**
* @file ScrollBar.h
* @brief Vertical scrollbar widget for indicating scroll position in long content
* @details This header defines the ScrollBar class which provides a visual scrollbar
* widget for indicating the current position within scrollable content.
* The scrollbar displays a thumb that moves proportionally to represent
* the current scroll position and visible area relative to the total content.
* @author System Control Team
* @date 2025-06-14
*/
#pragma once
#include "MenuOptions.h"
#include "Widget.h"
/**
* @class ScrollBar
* @brief A vertical scrollbar widget that represents scroll position and range
* @details This final class inherits from Widget and provides visual feedback for
* scrollable content. It displays a vertical track with a movable thumb
* that indicates the current position within a scrollable range. The thumb
* size is proportional to the visible area relative to the total content,
* and its position reflects the current scroll offset.
*
* The scrollbar automatically calculates thumb dimensions and position based on
* the provided scroll values (current, minimum, maximum). It is designed to be
* used alongside scrollable content like menus or lists to provide visual
* feedback about scroll state.
*
* @note This class is marked as final and cannot be inherited from.
*
* @see Widget
* @see menu_options_t
*/
class ScrollBar final : public Widget
{
public:
/**
* @brief Constructs a ScrollBar with specified position and dimensions
* @param options Pointer to menu options configuration structure
* @param x X coordinate position of the scrollbar on screen
* @param y Y coordinate position of the scrollbar on screen
* @param width Width of the scrollbar in pixels
* @param height Height of the scrollbar in pixels
*
* @pre options must not be nullptr and must remain valid for the scrollbar's lifetime
* @pre width and height must be greater than 0
* @pre x and y must be valid screen coordinates
* @post ScrollBar is initialized with the specified geometry and ready for use
*
* @note The scrollbar does not take ownership of the options structure and
* assumes it remains valid throughout the scrollbar's lifetime.
*/
ScrollBar(const menu_options_t *options, size_t x, size_t y, size_t width, size_t height);
/**
* @brief Renders the scrollbar to the screen
* @details Overrides the base Widget render method to draw the scrollbar track
* and thumb. The appearance is determined by the current scroll state
* and the menu options configuration.
*
* @pre u8g2 display context must be initialized and ready for drawing
* @post Scrollbar's visual representation is drawn to the display buffer
*
* @note This method is called during each frame's render cycle. The scrollbar
* track and thumb are drawn based on the current scroll values set by refresh().
*/
void render() override;
/**
* @brief Updates the scrollbar state with new scroll values
* @param value Current scroll position value (must be between min and max)
* @param max Maximum scroll value (total content size)
* @param min Minimum scroll value (default: 0, typically the start of content)
*
* @pre value must be between min and max (inclusive)
* @pre max must be greater than or equal to min
* @post Scrollbar thumb position and size are recalculated based on new values
*
* @details This method recalculates the thumb's height and vertical position
* based on the provided scroll range and current position. The thumb
* height represents the proportion of visible content to total content,
* while its position represents the current scroll offset within the range.
*
* @note Call this method whenever the scroll state changes to keep the
* scrollbar visualization synchronized with the actual content position.
*/
void refresh(size_t value, size_t max, size_t min = 0);
private:
// Position and dimensions
size_t m_x; ///< X coordinate of the scrollbar's left edge
size_t m_y; ///< Y coordinate of the scrollbar's top edge
size_t m_width; ///< Width of the scrollbar track in pixels
size_t m_height; ///< Height of the scrollbar track in pixels
// Scroll state values
size_t m_value; ///< Current scroll position within the range [m_min, m_max]
size_t m_max; ///< Maximum scroll value representing the end of content
size_t m_min; ///< Minimum scroll value representing the start of content
// Calculated thumb properties (updated by refresh())
size_t m_thumbHeight; ///< Calculated height of the scroll thumb in pixels
size_t m_thumbY; ///< Calculated Y position of the scroll thumb relative to track
};

View File

@@ -0,0 +1,168 @@
/**
* @file Widget.h
* @brief Base widget class for UI components in the INSA system
* @details This header defines the Widget base class that serves as the foundation
* for all UI components in the system. It provides a standardized interface
* for rendering, updating, and handling user input using the u8g2 graphics library.
* @author System Control Team
* @date 2025-06-14
*/
#pragma once
#include "u8g2.h"
#include "common/Common.h"
/**
* @class Widget
* @brief Base class for UI widgets that can be rendered and interact with user input
* @details This abstract base class provides a common interface for all widgets in the system.
* It manages the u8g2 display context and defines the core methods that all widgets
* must implement or can override. The class follows the template method pattern,
* allowing derived classes to customize behavior while maintaining a consistent
* interface for the UI system.
*
* @note All widgets should inherit from this class to ensure compatibility with
* the UI management system.
*
* @see u8g2_t
* @see ButtonType
*/
class Widget
{
public:
/**
* @brief Constructs a widget with the given u8g2 display context
* @param u8g2 Pointer to the u8g2 display context used for rendering operations
*
* @pre u8g2 must not be nullptr
* @post Widget is initialized with the provided display context
*
* @note The widget does not take ownership of the u8g2 context and assumes
* it remains valid for the lifetime of the widget.
*/
explicit Widget(u8g2_t *u8g2);
/**
* @brief Virtual destructor to ensure proper cleanup of derived classes
* @details Ensures that derived class destructors are called correctly when
* a widget is destroyed through a base class pointer.
*/
virtual ~Widget() = default;
/**
* @brief Called when the widget becomes active or enters the foreground
* @details This method is invoked when the widget transitions from inactive
* to active state, such as when it becomes the current screen or
* gains focus. Derived classes can override this method to perform
* initialization tasks, reset state, or prepare for user interaction.
*
* @note The base implementation is empty, allowing derived classes to override
* only if entry behavior is needed.
* @note This method is typically called by the UI management system during
* screen transitions or focus changes.
*/
virtual void enter();
/**
* @brief Called when the widget is temporarily paused or loses focus
* @details This method is invoked when the widget needs to suspend its
* operations temporarily, such as when another widget takes focus
* or the system enters a paused state. Derived classes can override
* this method to pause animations, save state, or reduce resource usage.
*
* @note The base implementation is empty, allowing derived classes to override
* only if pause behavior is needed.
* @note The widget should be prepared to resume from this state when resume() is called.
*/
virtual void pause();
/**
* @brief Called when the widget resumes from a paused state
* @details This method is invoked when the widget transitions from paused
* to active state, typically after a previous pause() call. Derived
* classes can override this method to restore animations, reload
* resources, or continue interrupted operations.
*
* @note The base implementation is empty, allowing derived classes to override
* only if resume behavior is needed.
* @note This method should restore the widget to the state it was in before pause() was called.
*/
virtual void resume();
/**
* @brief Called when the widget is being destroyed or exits the system
* @details This method is invoked when the widget is about to be removed
* from the system or transitions to an inactive state permanently.
* Derived classes can override this method to perform cleanup tasks,
* save final state, or release resources that are not automatically freed.
*
* @note The base implementation is empty, allowing derived classes to override
* only if exit behavior is needed.
* @note This method is called before the widget's destructor and provides
* an opportunity for controlled shutdown of widget-specific resources.
*/
virtual void exit();
/**
* @brief Updates the widget's internal state based on elapsed time
* @param dt Delta time in milliseconds since the last update call
*
* @details This method is called once per frame by the UI system to handle
* animations, timers, state transitions, or other time-dependent behavior.
* The base implementation is empty, allowing derived classes to override
* only if time-based updates are needed.
*
* @note Override this method in derived classes to implement time-based behavior
* such as animations, blinking effects, or timeout handling.
*/
virtual void update(uint64_t dt);
/**
* @brief Renders the widget to the display
* @details This method should be overridden by derived classes to implement
* their specific rendering logic using the u8g2 display context.
* The base implementation is empty, requiring derived classes to
* provide their own rendering code.
*
* @pre u8g2 context must be initialized and ready for drawing operations
* @post Widget's visual representation is drawn to the display buffer
*
* @note This method is called during the rendering phase of each frame.
* Derived classes should use the u8g2 member variable to perform
* drawing operations.
*/
virtual void render();
/**
* @brief Handles button press events
* @param button The type of button that was pressed
*
* @details This method is called when a button press event occurs and allows
* the widget to respond to user input. The base implementation is empty,
* allowing derived classes to override only if input handling is needed.
*
* @note Override this method in derived classes to implement button-specific
* behavior such as navigation, selection, or state changes.
*
* @see ButtonType for available button types
*/
virtual void onButtonClicked(ButtonType button);
protected:
/**
* @brief Pointer to the u8g2 display context used for rendering operations
* @details This member provides access to the u8g2 graphics library functions
* for drawing text, shapes, bitmaps, and other UI elements. It is
* initialized during construction and remains valid for the widget's lifetime.
*
* @note This member is protected to allow derived classes direct access while
* preventing external modification. Derived classes should use this
* context for all rendering operations.
*
* @warning Do not modify or delete this pointer. The widget does not own
* the u8g2 context and assumes it is managed externally.
*/
u8g2_t *u8g2;
};

View File

@@ -0,0 +1,310 @@
/**
* @file MenuItem.h
* @brief Menu item data structure for user interface menu systems
* @details This header defines the MenuItem class which represents individual menu
* items that can be displayed and interacted with in user interface menus.
* It supports various types of menu items including simple buttons, toggles,
* value selectors, and multi-option selections with flexible callback handling.
* @author System Control Team
* @date 2025-06-14
*/
#pragma once
#include <functional>
#include <string>
#include <vector>
#include "common/Common.h"
/**
* @class MenuItem
* @brief Flexible menu item class supporting various interaction types and behaviors
* @details This class represents individual menu items that can be displayed in user
* interface menus. It provides a flexible foundation for different types of
* menu interactions including simple navigation buttons, toggle switches,
* value adjustments, and multi-option selections.
*
* The MenuItem class supports multiple interaction patterns:
* - **Simple Actions**: Basic menu items that execute a function when activated
* - **Value Display**: Items that show a current value (read-only or editable)
* - **Selection Lists**: Items that cycle through multiple predefined values
* - **Toggle States**: Boolean items that can be switched on/off
* - **Custom Behaviors**: Flexible callback system for specialized interactions
*
* Each menu item is identified by a unique ID and has a type that defines its
* visual appearance and interaction behavior. The callback system allows for
* flexible event handling while maintaining type safety through std::function.
*
* Key features include:
* - Multiple constructor overloads for different menu item types
* - Type-safe callback system with ButtonCallback function objects
* - Support for both single values and value lists
* - Flexible text and value management
* - Efficient state management and validation
*
* @note This class is designed to be lightweight and efficient for embedded
* systems while providing rich functionality for complex user interfaces.
*
* @see ButtonCallback for callback function signature
* @see ButtonType for available button types
* @see Menu for menu container functionality
*/
class MenuItem
{
public:
/**
* @brief Constructs a simple action menu item with text and callback
* @param id Unique identifier for this menu item within its parent menu
* @param type Type identifier defining the item's behavior and visual appearance
* @param text Display text shown to the user for this menu item
* @param callback Function to call when the item is activated
*
* @pre id must be unique within the parent menu context
* @pre text should not be empty for proper user interface display
* @pre callback should be a valid callable object
* @post MenuItem is initialized as a simple action item ready for display
*
* @details Creates a basic menu item that displays text and executes a callback
* when activated. This is the most common type of menu item used for
* navigation, simple actions, and command execution.
*
* Typical use cases include:
* - Navigation items (e.g., "Settings", "Back", "Exit")
* - Action items (e.g., "Save", "Reset", "Start")
* - Sub-menu entries (e.g., "Light Control", "System Info")
*
* @note The callback is stored as a std::function and can be a lambda,
* function pointer, or any callable object matching the ButtonCallback signature.
*/
MenuItem(uint8_t id, uint8_t type, std::string text, ButtonCallback callback);
/**
* @brief Constructs a value-displaying menu item with text, value, and callback
* @param id Unique identifier for this menu item within its parent menu
* @param type Type identifier defining the item's behavior and visual appearance
* @param text Display text shown to the user for this menu item
* @param value Current value associated with this item (displayed to user)
* @param callback Function to call when the item is activated
*
* @pre id must be unique within the parent menu context
* @pre text should not be empty for proper user interface display
* @pre callback should be a valid callable object
* @post MenuItem is initialized with text and value display capabilities
*
* @details Creates a menu item that displays both text and a current value.
* This type is commonly used for settings display, status information,
* or items where the current state needs to be visible to the user.
*
* Typical use cases include:
* - Setting displays (e.g., "Brightness: 75%")
* - Status information (e.g., "Connection: WiFi")
* - Editable values (e.g., "Timeout: 30s")
* - Current selections (e.g., "Mode: Auto")
*
* @note The value can be updated later using setValue() to reflect changes
* in the underlying system state.
*/
MenuItem(uint8_t id, uint8_t type, std::string text, std::string value, ButtonCallback callback);
/**
* @brief Constructs a multi-selection menu item with selectable values
* @param id Unique identifier for this menu item within its parent menu
* @param type Type identifier defining the item's behavior and visual appearance
* @param text Display text shown to the user for this menu item
* @param values List of all available values that can be selected
* @param index Currently selected value from the available options
* @param callback Function to call when the item is activated
*
* @pre id must be unique within the parent menu context
* @pre text should not be empty for proper user interface display
* @pre value should be present in the values vector
* @pre values should not be empty and should contain valid options
* @pre callback should be a valid callable object
* @post MenuItem is initialized with multiple selectable values
*
* @details Creates a menu item that allows selection from multiple predefined
* values. This type enables cycling through options or displaying
* selection dialogs, making it ideal for configuration settings
* with discrete choices.
*
* Typical use cases include:
* - Mode selection (e.g., "Display Mode: [Day, Night, Auto]")
* - Configuration options (e.g., "Language: [English, Deutsch, Français]")
* - Preset selection (e.g., "Profile: [Home, Office, Travel]")
* - Format selection (e.g., "Time Format: [12H, 24H]")
*
* @note The callback can implement cycling logic to move through the values
* or open a selection dialog for user choice.
* @note The values vector is stored by copy, so modifications to the original
* vector after construction do not affect the menu item.
*/
MenuItem(uint8_t id, uint8_t type, std::string text, std::vector<std::string> values, int index,
ButtonCallback callback);
/**
* @brief Gets the unique identifier of this menu item
* @return The menu item's unique ID as assigned during construction
*
* @details Returns the unique identifier that distinguishes this menu item
* from others within the same menu context. This ID is used by
* menu systems for item identification, event routing, and state management.
*
* @note The ID is immutable after construction and guaranteed to be unique
* within the menu context where this item is used.
*/
[[nodiscard]] uint8_t getId() const;
/**
* @brief Gets the type identifier of this menu item
* @return The menu item's type identifier defining its behavior
*
* @details Returns the type identifier that defines how this menu item
* behaves and appears in the user interface. The type determines
* rendering style, interaction patterns, and event handling behavior.
*
* @note The type is immutable after construction and should correspond
* to predefined menu item type constants defined in the system.
*/
[[nodiscard]] uint8_t getType() const;
/**
* @brief Gets the display text of this menu item
* @return Const reference to the menu item's display text
*
* @details Returns the text that is displayed to the user for this menu item.
* This text serves as the primary label and should be descriptive
* enough for users to understand the item's purpose.
*
* @note Returns a const reference for efficiency while preventing
* accidental modification of the text content.
*/
[[nodiscard]] const std::string &getText() const;
/**
* @brief Gets the current value of this menu item
* @return Const reference to the menu item's current value
*
* @details Returns the current value associated with this menu item.
* For simple action items, this may be empty. For value-based
* items, this represents the current state, selection, or setting.
*
* @note Returns a const reference for efficiency while preventing
* accidental modification through the getter.
* @note For boolean items, the value is typically "true"/"false" or similar.
*/
[[nodiscard]] const std::string &getValue() const;
/**
* @brief Sets a new value for this menu item
* @param value The new value to assign to this menu item
*
* @details Updates the current value of this menu item. This is commonly
* used to reflect changes in system state, user selections, or
* configuration updates. The new value should be appropriate
* for the menu item's type and purpose.
*
* @note For multi-selection items, the value should be one of the
* predefined values in the values vector.
* @note For boolean items, typical values are "true"/"false", "ON"/"OFF",
* or other boolean representations.
* @note The value update does not automatically trigger the callback;
* this is purely for state management.
*/
void setValue(const std::string &value);
/**
* @brief Handles button press events for this menu item
* @param button The type of button that was pressed
*
* @details Processes button press events by invoking the associated callback
* function if one exists. This method serves as the event handler
* that connects user interactions to the menu item's functionality.
*
* The method performs the following actions:
* - Validates that the ID matches this menu item
* - Checks if a callback function is available
* - Invokes the callback with the provided button type
* - Handles any callback-related error conditions gracefully
*
* @note This method is typically called by the parent menu system when
* user interaction occurs on this menu item.
* @note If no callback is set, the method returns without error.
* @note The callback is responsible for implementing the specific behavior
* for the button press (navigation, value changes, actions, etc.).
*/
void onButtonPressed(ButtonType button) const;
/**
* @brief Checks if this menu item has an associated callback function
* @return true if a callback function is set, false otherwise
*
* @details Determines whether this menu item has a valid callback function
* that can be invoked when the item is activated. This is useful
* for menu systems that need to distinguish between interactive
* and non-interactive items.
*
* @note Menu items without callbacks are typically used for display-only
* purposes such as headers, separators, or status information.
* @note Interactive menu items should always have callbacks to provide
* meaningful user interaction.
*/
[[nodiscard]] bool hasCallback() const;
[[nodiscard]] int getIndex() const;
[[nodiscard]] std::vector<std::string> getValues() const;
[[nodiscard]] size_t getItemCount() const;
[[nodiscard]] MenuItem copyWith(const std::string &value) const;
[[nodiscard]] MenuItem copyWith(size_t index) const;
private:
/**
* @brief Unique identifier for this menu item
* @details Stores the unique ID that distinguishes this menu item from others
* within the same menu context. Used for event routing and item identification.
*/
uint8_t m_id;
/**
* @brief Type identifier defining the item's behavior and appearance
* @details Stores the type that determines how this menu item behaves,
* how it's rendered, and what interaction patterns it supports.
*/
uint8_t m_type;
/**
* @brief Display text shown to the user
* @details Stores the primary text label that is displayed to users,
* serving as the main identifier and description of the menu item's purpose.
*/
std::string m_text;
/**
* @brief Current value associated with this menu item
* @details Stores the current value for value-based menu items, representing
* the current state, selection, or setting that should be displayed to the user.
*/
std::string m_value;
/**
* @brief Available values for selection-based menu items
* @details Stores the list of all possible values that can be selected for
* multi-option menu items. Used for cycling through options or
* displaying selection dialogs.
*/
std::vector<std::string> m_values;
int m_index = -1;
/**
* @brief Callback function invoked when the menu item is activated
* @details Stores the function object that implements the menu item's behavior
* when activated by user interaction. Uses std::function for flexibility
* and type safety.
*/
ButtonCallback m_callback;
};

View File

@@ -0,0 +1,23 @@
#define road_horizontal_width 16
#define road_horizontal_height 16
static unsigned char road_horizontal_bits[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x8c, 0xc7, 0x8c, 0xc7, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
#define road_vertical_width 16
#define road_vertical_height 16
static unsigned char road_vertical_bits[] = {0x80, 0x01, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x80, 0x01, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00};
#define road_t_up_width 16
#define road_t_up_height 16
static unsigned char road_t_up_bits[] = {0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00,
0x03, 0xcc, 0xcf, 0xcc, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
#define road_t_crossing_width 16
#define road_t_crossing_height 16
static unsigned char road_t_crossing_bits[] = {0x80, 0x01, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x80,
0x01, 0xe6, 0x67, 0xe6, 0x67, 0x80, 0x01, 0x80, 0x01, 0x00, 0x00,
0x00, 0x00, 0x80, 0x01, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00};

View File

@@ -0,0 +1,55 @@
#pragma once
#define car_width 16
#define car_height 16
const unsigned char car_left_bits [] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x07, 0xf0, 0x0f, 0xee, 0x77, 0xee, 0x77, 0xee, 0x77,
0x2e, 0x74, 0xde, 0x78, 0x02, 0x40, 0x3c, 0x3c, 0x42, 0x42, 0xda, 0x5b, 0x18, 0x18, 0x00, 0x00
};
const unsigned char car_right_bits [] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x07, 0xf0, 0x0f, 0xee, 0x77, 0xee, 0x77, 0xee, 0x77,
0x2e, 0x74, 0x1e, 0x7b, 0x02, 0x40, 0x3c, 0x3c, 0x42, 0x42, 0xda, 0x5b, 0x18, 0x18, 0x00, 0x00
};
#define convertable_width 16
#define convertable_height 16
static unsigned char convertable_left_bits[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x30, 0x0c, 0xee,
0x77, 0x2e, 0x74, 0xae, 0x75, 0xae, 0x75, 0xfe, 0x7f, 0x82, 0x41,
0x3c, 0x3c, 0x42, 0x42, 0xda, 0x5b, 0x18, 0x18, 0x00, 0x00};
static unsigned char convertable_right_bits[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x30, 0x0c, 0xee,
0x77, 0x2e, 0x74, 0xae, 0x75, 0xae, 0x75, 0xfe, 0x7f, 0x82, 0x41,
0x3c, 0x3c, 0x42, 0x42, 0xda, 0x5b, 0x18, 0x18, 0x00, 0x00};
#define lorry_width 32
#define lorry_height 16
const unsigned char lorry_left_bits[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0xe0,
0x0b, 0x00, 0x00, 0xf0, 0x0b, 0x00, 0x00, 0xee, 0x0b, 0x00, 0x00, 0xee, 0x0f,
0xbc, 0x07, 0x2e, 0xfa, 0xff, 0x7f, 0xae, 0xfa, 0xff, 0x7f, 0xbe, 0x0a, 0x00,
0x60, 0x02, 0x0a, 0x00, 0x60, 0x3c, 0x0a, 0xbc, 0x67, 0x42, 0xfe, 0x43, 0x78,
0xda, 0xff, 0x5b, 0x7b, 0x18, 0x00, 0x18, 0x03, 0x00, 0x00, 0x00, 0x00};
const unsigned char lorry_right_bits[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00,
0x00, 0xd0, 0x07, 0x00, 0x00, 0xd0, 0x0f, 0x00, 0x00, 0xd0, 0x77, 0xe0, 0x3d,
0xf0, 0x77, 0xfe, 0xff, 0x5f, 0x74, 0xfe, 0xff, 0x5f, 0x75, 0x06, 0x00, 0x50,
0x7d, 0x06, 0x00, 0x50, 0x40, 0xe6, 0x3d, 0x50, 0x3c, 0x1e, 0xc2, 0x7f, 0x42,
0xde, 0xda, 0xff, 0x5b, 0xc0, 0x18, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00};
#define suv_width 16
#define suv_height 16
const unsigned char suv_left_bits[] = {0x00, 0x00, 0x00, 0x00, 0xe0, 0x07, 0xf0, 0x0f, 0xee, 0x77, 0xee,
0x77, 0xee, 0x77, 0x2e, 0x74, 0xde, 0x78, 0x02, 0x40, 0x02, 0x40,
0x3c, 0x3c, 0x42, 0x42, 0xda, 0x5b, 0x18, 0x18, 0x00, 0x00};
const unsigned char suv_right_bits[] = {0x00, 0x00, 0x00, 0x00, 0xe0, 0x07, 0xf0, 0x0f, 0xee, 0x77, 0xee,
0x77, 0xee, 0x77, 0x2e, 0x74, 0x1e, 0x7b, 0x02, 0x40, 0x02, 0x40,
0x3c, 0x3c, 0x42, 0x42, 0xda, 0x5b, 0x18, 0x18, 0x00, 0x00};
#define truck_width 32
#define truck_height 16
const unsigned char truck_left_bits[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x3f, 0x00, 0xff, 0xff, 0x7f, 0xe0,
0xff, 0xff, 0x7f, 0xf0, 0xff, 0xff, 0x7f, 0xee, 0xff, 0xff, 0x7f, 0xee, 0x03,
0x00, 0x60, 0x2e, 0x02, 0x00, 0x40, 0xae, 0x02, 0x00, 0x40, 0xbe, 0x02, 0x00,
0x40, 0x02, 0x02, 0x00, 0x40, 0x3c, 0x02, 0xbc, 0x47, 0x42, 0x02, 0x42, 0x48,
0xda, 0xff, 0x5b, 0x7b, 0x18, 0x38, 0x18, 0x03, 0x00, 0x00, 0x00, 0x00};
const unsigned char truck_right_bits[] = {0x00, 0x00, 0x00, 0x00, 0xfc, 0xff, 0xff, 0x00, 0xfe, 0xff, 0xff, 0x00, 0xfe,
0xff, 0xff, 0x07, 0xfe, 0xff, 0xff, 0x0f, 0xfe, 0xff, 0xff, 0x77, 0x06, 0x00,
0xc0, 0x77, 0x02, 0x00, 0x40, 0x74, 0x02, 0x00, 0x40, 0x75, 0x02, 0x00, 0x40,
0x7d, 0x02, 0x00, 0x40, 0x40, 0xe2, 0x3d, 0x40, 0x3c, 0x12, 0x42, 0x40, 0x42,
0xde, 0xda, 0xff, 0x5b, 0xc0, 0x18, 0x1c, 0x18, 0x00, 0x00, 0x00, 0x00};

View File

@@ -0,0 +1,141 @@
/**
* @file LightMenu.h
* @brief Light control menu implementation for lighting system management
* @details This header defines the LightMenu class which provides a specialized
* user interface for controlling lighting systems and illumination features.
* It extends the Menu base class to offer comprehensive light control
* functionality including brightness adjustment, color selection, and
* lighting mode configuration.
* @author System Control Team
* @date 2025-06-14
*/
#pragma once
#include "common/Menu.h"
/**
* @class LightMenu
* @brief Light control menu interface class for illumination system management
* @details This final class inherits from Menu and provides a comprehensive interface
* for controlling various aspects of the lighting system. It allows users to
* adjust brightness levels, select colors, configure lighting modes, and
* manage automated lighting behaviors.
*
* The LightMenu class extends the base Menu functionality by:
* - Providing light-specific control options (brightness, color, modes)
* - Implementing real-time lighting preview and feedback
* - Managing lighting presets and custom configurations
* - Handling immediate lighting adjustments and scheduled operations
*
* Typical lighting control features include:
* - Brightness adjustment (0-100% or similar range)
* - Color selection (RGB, HSV, or predefined colors)
* - Lighting modes (solid, fade, strobe, custom patterns)
* - Timer-based automation (on/off schedules)
* - Preset management (save/load favorite configurations)
* - Zone-based control (if multiple light zones are supported)
*
* The menu provides immediate visual feedback by applying changes to the
* connected lighting hardware in real-time as users navigate through options.
*
* @note This class is marked as final and cannot be inherited from.
* @note Lighting changes are typically applied immediately for instant feedback,
* with the option to save configurations as presets.
*
* @see Menu for base menu functionality
* @see menu_options_t for configuration structure
* @see MainMenu for navigation back to main interface
*/
class LightMenu final : public Menu
{
public:
/**
* @brief Constructs the light control menu with the specified configuration
* @param options Pointer to menu options configuration structure
*
* @pre options must not be nullptr and must remain valid for the menu's lifetime
* @pre options->u8g2 must be initialized and ready for graphics operations
* @pre All callback functions in options must be properly configured
* @pre Lighting hardware must be initialized and responsive
* @post LightMenu is initialized with current lighting state and ready for user interaction
*
* @details The constructor initializes the light control menu by:
* - Reading current lighting system state and parameters
* - Creating appropriate menu items for available lighting features
* - Setting up real-time preview capabilities
* - Loading saved lighting presets and configurations
* - Configuring value ranges and validation for lighting parameters
*
* The menu automatically detects available lighting capabilities and presents
* only the controls that are supported by the connected hardware. This ensures
* a consistent user experience across different lighting system configurations.
*
* @note The menu does not take ownership of the options structure and assumes
* it remains valid throughout the menu's lifetime.
* @note Current lighting state is preserved and can be restored if the user
* exits without saving changes.
*
* @see Menu::Menu for base class construction details
*/
explicit LightMenu(menu_options_t *options);
private:
/**
* @brief Handles button press events specific to light control menu items
* @param menuItem
* @param button Type of button that was pressed
*
* @details Overrides the base Menu class method to provide light control-specific
* button handling logic. This method processes user interactions with
* lighting control items and performs appropriate actions such as:
* - Adjusting brightness levels (increment/decrement)
* - Changing color values or selecting predefined colors
* - Switching between lighting modes and patterns
* - Saving/loading lighting presets
* - Toggling lighting zones on/off
* - Applying lighting changes immediately to hardware
*
* The method provides real-time feedback by immediately applying lighting
* changes to the connected hardware, allowing users to see the effects of
* their adjustments instantly. It also handles validation to ensure that
* lighting parameters remain within safe and supported ranges.
*
* Special handling includes:
* - Smooth transitions for brightness adjustments
* - Color wheel navigation for color selection
* - Mode cycling for lighting patterns
* - Confirmation prompts for preset operations
*
* @note This method is called by the base Menu class when a button press
* occurs on a menu item, after the base class has handled standard
* navigation operations.
* @note Changes are applied immediately to provide instant visual feedback,
* but can be reverted if the user cancels or exits without saving.
*
* @see Menu::onButtonPressed for the base implementation
* @see ButtonType for available button types
*/
void onButtonPressed(const MenuItem& menuItem, ButtonType button) override;
/**
* @brief Pointer to menu options configuration structure
* @details Stores a reference to the menu configuration passed during construction.
* This pointer provides access to the display context and callback functions
* needed for menu operations, screen transitions, and lighting control
* communication with the hardware subsystem.
*
* The configuration enables:
* - Display context for rendering lighting control interface
* - Screen management callbacks for navigation to other menus
* - Hardware communication for real-time lighting control
* - System callbacks for lighting state persistence
*
* @note This pointer is not owned by the LightMenu and must remain valid
* throughout the menu's lifetime. It is managed by the application framework.
*
* @warning Must not be modified after construction as it may be shared
* with other components and contains critical system callbacks.
*/
menu_options_t *m_options;
};

View File

@@ -0,0 +1,35 @@
#pragma once
#include "common/Menu.h"
/**
* @class LightSettingsMenu
* @brief Menu for configuring light system settings including sections and LED parameters
* @details This menu extends the base Menu class to provide specialized functionality
* for managing light system configurations. It handles dynamic section management
* and provides persistence for user settings.
*/
class LightSettingsMenu final : public Menu
{
public:
/**
* @brief Constructs a LightSettingsMenu with the specified options
* @param options Pointer to menu configuration options structure
* @details Initializes the menu with section counter and default section settings
*/
explicit LightSettingsMenu(menu_options_t *options);
private:
/**
* @brief Handles button press events for light settings menu items
* @param menuItem The menu item that received the button press
* @param button The type of button that was pressed
* @details Manages value switching, dynamic section list updates, and
* persistence of section values when settings are modified
*/
void onButtonPressed(const MenuItem& menuItem, ButtonType button) override;
static std::string CreateKey(int index);
menu_options_t *m_options; ///< Pointer to menu configuration options
};

View File

@@ -0,0 +1,104 @@
/**
* @file MainMenu.h
* @brief Main menu implementation for the application's primary interface
* @details This header defines the MainMenu class which serves as the primary
* user interface entry point for the application. It extends the Menu
* base class to provide a customized main menu experience with
* application-specific menu items and navigation behavior.
* @author System Control Team
* @date 2025-06-14
*/
#pragma once
#include "common/Menu.h"
/**
* @class MainMenu
* @brief Main menu interface class providing the primary application navigation
* @details This final class inherits from Menu and represents the main menu interface
* of the application. It serves as the primary entry point for user interaction
* and provides navigation to all major application features and sub-menus.
*
* The MainMenu class customizes the base Menu functionality by:
* - Defining application-specific menu items during construction
* - Implementing custom button handling for main menu navigation
* - Managing transitions to sub-menus and application features
*
* This class is typically the first screen presented to users after the splash
* screen and serves as the central hub for all application functionality.
*
* @note This class is marked as final and cannot be inherited from.
*
* @see Menu for base menu functionality
* @see menu_options_t for configuration structure
*/
class MainMenu final : public Menu
{
public:
/**
* @brief Constructs the main menu with the specified configuration
* @param options Pointer to menu options configuration structure
*
* @pre options must not be nullptr and must remain valid for the menu's lifetime
* @pre options->u8g2 must be initialized and ready for graphics operations
* @pre All callback functions in options must be properly configured
* @post MainMenu is initialized with application-specific menu items and ready for use
*
* @details The constructor initializes the main menu by setting up all the
* primary application menu items such as:
* - Settings access
* - Feature-specific menus
* - System controls
* - Application exit options
*
* @note The menu does not take ownership of the options structure and assumes
* it remains valid throughout the menu's lifetime.
*/
explicit MainMenu(menu_options_t *options);
private:
/**
* @brief Handles button press events specific to main menu items
* @param menuItem
* @param button Type of button that was pressed
*
* @details Overrides the base Menu class method to provide main menu-specific
* button handling logic. This method processes user interactions with
* main menu items and initiates appropriate actions such as:
* - Navigation to sub-menus (Settings, Light Control, etc.)
* - Direct feature activation
* - System operations
*
* The method uses the menu item ID to determine which action to take and
* utilizes the callback functions provided in m_options to perform screen
* transitions or other application-level operations.
*
* @note This method is called by the base Menu class when a button press
* occurs on a menu item, after the base class has handled standard
* navigation operations.
*
* @see Menu::onButtonPressed for the base implementation
* @see ButtonType for available button types
*/
void onButtonPressed(const MenuItem& menuItem, ButtonType button) override;
/**
* @brief Pointer to menu options configuration structure
* @details Stores a reference to the menu configuration passed during construction.
* This pointer provides access to the display context and callback functions
* needed for menu operations, screen transitions, and user interaction handling.
*
* The configuration includes:
* - Display context for rendering operations
* - Screen management callbacks for navigation
* - Input handling callbacks for button events
*
* @note This pointer is not owned by the MainMenu and must remain valid
* throughout the menu's lifetime. It is managed by the application framework.
*
* @warning Must not be modified after construction as it may be shared
* with other components.
*/
menu_options_t *m_options;
};

View File

@@ -0,0 +1,142 @@
/**
* @file ScreenSaver.h
* @brief Animated screensaver implementation with vehicle effect
* @details This header defines the ScreenSaver class which provides an animated
* vehicle screensaver that activates during periods of user inactivity.
* The screensaver displays moving vehicles to prevent screen burn-in while
* providing visual feedback that the system is still active and responsive.
* @author System Control Team
* @date 2025-06-14
*/
#pragma once
#include "MenuOptions.h"
#include "common/Widget.h"
#include <vector>
/**
* @class ScreenSaver
* @brief Animated vehicle screensaver widget for system idle periods
*/
class ScreenSaver final : public Widget
{
public:
explicit ScreenSaver(menu_options_t *options);
void update(uint64_t dt) override;
void render() override;
void onButtonClicked(ButtonType button) override;
private:
/**
* @enum VehicleType
* @brief Types of available vehicles
*/
enum class VehicleType
{
CAR,
CONVERTABLE,
SUV,
LORRY,
TRUCK
};
/**
* @enum Direction
* @brief Movement direction for vehicles
*/
enum class Direction
{
LEFT,
RIGHT
};
/**
* @struct Vehicle
* @brief Individual vehicle object for animation
*/
struct Vehicle
{
int x; // X position on screen
int y; // Y position on screen
float speed; // Movement speed
VehicleType type; // Type of vehicle
Direction direction; // Movement direction
bool active; // Whether a vehicle is currently active
};
static constexpr int MAX_LEFT_VEHICLES = 2;
static constexpr int MAX_RIGHT_VEHICLES = 2;
static constexpr int MAX_VEHICLES = MAX_LEFT_VEHICLES + MAX_RIGHT_VEHICLES;
static constexpr int VEHICLE_SPAWN_DELAY = 2500; // milliseconds
static constexpr float MIN_SPEED = 1.0f;
static constexpr float MAX_SPEED = 2.0f;
static constexpr int MIN_SAME_DIRECTION_DISTANCE = 48; // 32 + 16 pixels
static constexpr int MAX_SAME_DIRECTION_DISTANCE = 64; // 32 + 32 pixels
menu_options_t *m_options;
uint64_t m_animationCounter;
uint64_t m_lastSpawnTime;
std::vector<Vehicle> m_vehicles;
int m_leftVehicleCount;
int m_rightVehicleCount;
int m_sceneOffsetX = 0;
int m_sceneOffsetY = 0;
uint64_t m_sceneShiftTimer = 0;
/**
* @brief Initialize vehicle system
*/
void initVehicles();
/**
* @brief Spawn a new vehicle if conditions are met
*/
void trySpawnVehicle();
/**
* @brief Get a random vehicle type
* @return Random VehicleType
*/
static VehicleType getRandomVehicleType();
/**
* @brief Get a random direction with constraint checking
* @return Direction for new vehicle
*/
static Direction getRandomDirection();
/**
* @brief Draw a vehicle at a specified position
* @param vehicle Vehicle to draw
*/
void drawVehicle(const Vehicle &vehicle) const;
/**
* @brief Draw a bitmap with transparency (black pixels are transparent)
* @param x X position
* @param y Y position
* @param width Bitmap width
* @param height Bitmap height
* @param bitmap Bitmap data
*/
void drawTransparentBitmap(int x, int y, int width, int height, const unsigned char *bitmap) const;
/**
* @brief Get vehicle bitmap data
* @param type Vehicle type
* @param direction Movement direction
* @param width Output parameter for bitmap width
* @param height Output parameter for bitmap height
* @return Pointer to bitmap data
*/
static const unsigned char *getVehicleBitmap(VehicleType type, Direction direction, int &width, int &height);
/**
* @brief Check if there's enough distance to spawn a vehicle in a specific direction
* @param direction Direction to check
* @return true if spawning is allowed
*/
bool canSpawnInDirection(Direction direction) const;
};

View File

@@ -0,0 +1,77 @@
/**
* @file SettingsMenu.h
* @brief Settings menu implementation for application configuration
* @details This header defines the SettingsMenu class which provides a user interface
* for configuring application settings and preferences. It extends the Menu
* base class to offer a specialized settings management interface with
* various configuration options and system parameters.
* @author System Control Team
* @date 2025-06-14
*/
#pragma once
#include "common/Menu.h"
/**
* @class SettingsMenu
* @brief Settings menu interface class for application configuration management
* @details This final class inherits from Menu and provides a comprehensive settings
* interface for the application. It allows users to configure various aspects
* of the system including display preferences, system behavior, network
* settings, and other configurable parameters.
*
* The SettingsMenu class extends the base Menu functionality by:
* - Providing settings-specific menu items (toggles, selections, number inputs)
* - Managing configuration persistence and validation
* - Organizing settings into logical categories and sections
* - Implementing real-time preview of setting changes where applicable
*
* Typical settings categories include:
* - Display settings (brightness, contrast, theme)
* - System preferences (timeouts, auto-save, etc.)
* - Network configuration (if applicable)
* - User interface preferences
* - Hardware-specific parameters
*
* @note This class is marked as final and cannot be inherited from.
* @note Settings changes are typically applied immediately or after confirmation,
* depending on the specific setting type and system requirements.
*
* @see Menu for base menu functionality
* @see menu_options_t for configuration structure
* @see MainMenu for navigation back to main interface
*/
class SettingsMenu final : public Menu
{
public:
/**
* @brief Constructs the settings menu with the specified configuration
* @param options Pointer to menu options configuration structure
*
* @pre options must not be nullptr and must remain valid for the menu's lifetime
* @pre options->u8g2 must be initialized and ready for graphics operations
* @pre All callback functions in options must be properly configured
* @post SettingsMenu is initialized with all available configuration options and ready for use
*
* @details The constructor initializes the settings menu by creating all the
* configuration menu items based on the current system state and
* available options. This includes:
* - Loading current setting values from persistent storage
* - Creating appropriate menu items (toggles, selections, number inputs)
* - Setting up validation ranges and allowed values
* - Organizing items in a logical, user-friendly order
*
* The menu automatically detects which settings are available based on
* hardware capabilities and system configuration, ensuring only relevant
* options are presented to the user.
*
* @note The menu does not take ownership of the options structure and assumes
* it remains valid throughout the menu's lifetime.
* @note Setting values are loaded from persistent storage during construction
* and changes are typically saved automatically or on confirmation.
*
* @see Menu::Menu for base class construction details
*/
explicit SettingsMenu(menu_options_t *options);
};

View File

@@ -0,0 +1,177 @@
/**
* @file SplashScreen.h
* @brief Application splash screen implementation for startup presentation
* @details This header defines the SplashScreen class which provides the initial
* visual presentation when the application starts. It serves as a loading
* screen that displays branding information, initialization progress, and
* provides visual feedback during the application startup sequence.
* @author System Control Team
* @date 2025-06-14
*/
#pragma once
#include "MenuOptions.h"
#include "common/Widget.h"
/**
* @class SplashScreen
* @brief Application startup screen widget with branding and initialization feedback
* @details This final class inherits from Widget and represents the initial screen
* displayed when the application starts. It serves multiple purposes including
* brand presentation, system initialization feedback, and smooth transition
* preparation to the main application interface.
*
* The SplashScreen class provides:
* - Brand identity display (logos, company information, product name)
* - System initialization progress indication
* - Smooth animations and visual effects for professional appearance
* - Automatic transition timing to main menu after initialization
* - Error indication if initialization fails
*
* Key features include:
* - Time-based automatic progression to main menu
* - Animated elements (fade-in effects, progress indicators)
* - Version information display
* - Loading status messages
* - Graceful handling of initialization delays
*
* The splash screen typically displays for a minimum duration to ensure users
* can read branding information, even if system initialization completes quickly.
* It automatically transitions to the main menu once both the minimum display
* time and system initialization are complete.
*
* @note This class is marked as final and cannot be inherited from.
* @note The splash screen does not handle user input - it operates on timing
* and system state rather than user interaction.
*
* @see Widget for base widget functionality
* @see menu_options_t for configuration structure
* @see MainMenu for the target transition screen
*/
class SplashScreen final : public Widget
{
public:
/**
* @brief Constructs the splash screen with specified configuration
* @param options Pointer to menu options configuration structure
*
* @pre options must not be nullptr and must remain valid for the splash screen's lifetime
* @pre options->u8g2 must be initialized and ready for graphics operations
* @pre Screen transition callbacks in options must be properly configured
* @post SplashScreen is initialized and ready to display startup sequence
*
* @details The constructor initializes the splash screen by:
* - Setting up the initial display state and animations
* - Preparing branding elements (logos, text, version info)
* - Initializing timing controls for minimum display duration
* - Configuring transition parameters for smooth progression
* - Loading any required graphics resources or assets
*
* The splash screen prepares all visual elements during construction to
* ensure smooth rendering performance during the critical startup phase.
* It also establishes the timing framework for controlling display duration
* and transition timing.
*
* @note The splash screen does not take ownership of the options structure
* and assumes it remains valid throughout the screen's lifetime.
* @note Graphics resources are loaded during construction, so any required
* assets must be available at initialization time.
*
* @see Widget::Widget for base class construction details
*/
explicit SplashScreen(menu_options_t *options);
/**
* @brief Updates the splash screen state, animations, and timing logic
* @param dt Delta time in milliseconds since the last update call
*
* @details Overrides the base Widget update method to handle splash screen-specific
* logic including:
* - Animation progression (fade effects, transitions, progress indicators)
* - Timing control for minimum display duration
* - System initialization status monitoring
* - Automatic transition preparation to main menu
* - Loading progress updates and status message changes
*
* The update method manages the splash screen's lifecycle by tracking
* elapsed time and system readiness state. It ensures the splash screen
* remains visible for a minimum duration while also monitoring system
* initialization completion. Once both conditions are met, it initiates
* the transition to the main application interface.
*
* Animation updates include:
* - Fade-in effects for branding elements
* - Progress bar or spinner animations
* - Text transitions for status messages
* - Smooth preparation for screen transition
*
* @note This method is called every frame during the splash screen display
* and must be efficient to maintain smooth visual presentation.
* @note The method automatically handles transition to the main menu when
* appropriate, using the callback functions provided in m_options.
*
* @see Widget::update for the base update interface
*/
void update(uint64_t dt) override;
/**
* @brief Renders the splash screen visual elements to the display
*
* @details Overrides the base Widget render method to draw all splash screen
* elements including branding, status information, and visual effects.
* The rendering includes:
* - Company/product logos and branding elements
* - Application name and version information
* - Loading progress indicators (progress bars, spinners, etc.)
* - Status messages indicating initialization progress
* - Background graphics and visual effects
*
* The render method creates a professional, polished appearance that
* reinforces brand identity while providing useful feedback about the
* application startup process. All elements are positioned and styled
* to create a cohesive, attractive presentation.
*
* Rendering features include:
* - Centered layout with balanced visual hierarchy
* - Smooth animations and transitions
* - Consistent typography and color scheme
* - Progress feedback elements
* - Error indication if initialization problems occur
*
* @pre u8g2 display context must be initialized and ready for drawing
* @post All splash screen visual elements are drawn to the display buffer
*
* @note This method is called every frame and must be efficient to maintain
* smooth visual presentation during the startup sequence.
* @note The visual design should be consistent with the overall application
* theme and branding guidelines.
*
* @see Widget::render for the base render interface
*/
void render() override;
private:
/**
* @brief Pointer to menu options configuration structure
* @details Stores a reference to the menu configuration passed during construction.
* This provides access to the display context for rendering operations
* and screen transition callbacks for automatic progression to the main menu.
*
* The configuration enables:
* - Display context (u8g2) for graphics rendering operations
* - Screen transition callbacks for automatic progression to main menu
* - System integration for initialization status monitoring
*
* The splash screen uses the setScreen callback to automatically transition
* to the main menu once the startup sequence is complete. This ensures a
* seamless user experience from application launch to main interface.
*
* @note This pointer is not owned by the SplashScreen and must remain valid
* throughout the screen's lifetime. It is managed by the application framework.
*
* @warning Must not be modified after construction as it contains critical
* system callbacks required for proper application flow.
*/
menu_options_t *m_options;
};

View File

@@ -0,0 +1,35 @@
#include "common/InactivityTracker.h"
InactivityTracker::InactivityTracker(const uint64_t timeoutMs, const std::function<void()> &onTimeout)
: m_timeoutMs(timeoutMs), m_elapsedTime(0), m_enabled(true), m_onTimeout(onTimeout)
{
}
void InactivityTracker::update(uint64_t dt)
{
if (!m_enabled)
return;
m_elapsedTime += dt;
if (m_elapsedTime >= m_timeoutMs && m_onTimeout)
{
m_onTimeout();
m_enabled = false;
}
}
void InactivityTracker::reset()
{
m_elapsedTime = 0;
m_enabled = true;
}
void InactivityTracker::setEnabled(bool enabled)
{
m_enabled = enabled;
if (enabled)
{
reset();
}
}

View File

@@ -0,0 +1,386 @@
#include "common/Menu.h"
#include "common/ScrollBar.h"
#include "u8g2.h"
// Menu item type constants for better readability
namespace MenuItemTypes
{
constexpr uint8_t TEXT = 0;
constexpr uint8_t SELECTION = 1;
constexpr uint8_t TOGGLE = 2;
constexpr uint8_t TEXT_COUNTER = 3;
} // namespace MenuItemTypes
// UI layout constants
namespace UIConstants
{
constexpr int LEFT_MARGIN = 8;
constexpr int RIGHT_PADDING = 8;
constexpr int SCROLLBAR_WIDTH = 3;
constexpr int FRAME_BOX_SIZE = 14;
constexpr int FRAME_OFFSET = 11;
constexpr int SELECTION_MARGIN = 10;
constexpr int CORNER_RADIUS = 3;
constexpr int LINE_SPACING = 14;
constexpr int BOTTOM_OFFSET = 10;
} // namespace UIConstants
Menu::Menu(menu_options_t *options) : Widget(options->u8g2), m_options(options)
{
// Set up button callback using lambda to forward to member function
m_options->onButtonClicked = [this](const ButtonType button) { onButtonClicked(button); };
}
Menu::~Menu()
{
// Clean up callback to prevent dangling pointer
if (m_options)
{
m_options->onButtonClicked = nullptr;
}
}
MenuItem Menu::getItem(const int index)
{
return m_items.at(index);
}
size_t Menu::getItemCount() const
{
return m_items.size();
}
void Menu::setItemSize(const size_t size)
{
if ((m_items.size() - 1) < size)
{
for (size_t i = m_items.size() - 1; i < size; i++)
{
auto caption = std::string("Bereich ") + std::to_string(i + 1);
auto index = 0;
if (m_options && m_options->persistenceManager)
{
constexpr int key_length = 20;
char key[key_length] = "";
snprintf(key, key_length, "section_%zu", i + 1);
index = m_options->persistenceManager->GetValue(key, index);
}
addSelection(i + 1, caption, m_items.at(0).getValues(), index);
}
}
else
{
m_items.erase(m_items.begin() + static_cast<int>(size + 1), m_items.end());
}
}
void Menu::toggle(const MenuItem &menuItem)
{
const auto item =
menuItem.copyWith(menuItem.getValue() == std::to_string(true) ? std::to_string(false) : std::to_string(true));
replaceItem(menuItem.getId(), item);
}
MenuItem Menu::switchValue(const MenuItem &menuItem, ButtonType button)
{
MenuItem result = menuItem;
switch (button)
{
case ButtonType::LEFT:
if (menuItem.getIndex() > 0)
{
const auto item = menuItem.copyWith(menuItem.getIndex() - 1);
result = replaceItem(menuItem.getId(), item);
}
else
{
const auto item = menuItem.copyWith(menuItem.getItemCount() - 1);
result = replaceItem(menuItem.getId(), item);
}
break;
case ButtonType::RIGHT:
if (menuItem.getIndex() < menuItem.getItemCount() - 1)
{
const auto item = menuItem.copyWith(menuItem.getIndex() + 1);
result = replaceItem(menuItem.getId(), item);
}
else
{
const auto item = menuItem.copyWith(0);
result = replaceItem(menuItem.getId(), item);
}
break;
default:
break;
}
return result;
}
MenuItem Menu::replaceItem(const int index, const MenuItem &item)
{
m_items.at(index) = item;
return item;
}
void Menu::render()
{
// Initialize selection if not set
if (m_selected_item >= m_items.size() && !m_items.empty())
{
m_selected_item = 0;
}
// Early return if no items to render
if (m_items.empty())
{
return;
}
// Clear screen with black background
u8g2_SetDrawColor(u8g2, 0);
u8g2_DrawBox(u8g2, 0, 0, u8g2->width, u8g2->height);
// Set white foreground color for drawing
u8g2_SetDrawColor(u8g2, 1);
// Draw UI components
drawScrollBar();
drawSelectionBox();
// Calculate center position for main item
const int centerY = u8g2->height / 2 + 3;
// Render the currently selected item (main/center item)
const auto &selectedItem = m_items[m_selected_item];
renderWidget(&selectedItem, u8g2_font_helvB08_tr, UIConstants::LEFT_MARGIN, centerY);
// Render previous item (above) if available
if (m_selected_item > 0)
{
const auto &prevItem = m_items[m_selected_item - 1];
renderWidget(&prevItem, u8g2_font_haxrcorp4089_tr, UIConstants::LEFT_MARGIN, UIConstants::LINE_SPACING);
}
// Render next item (below) if available
if (m_selected_item < m_items.size() - 1)
{
const auto &nextItem = m_items[m_selected_item + 1];
renderWidget(&nextItem, u8g2_font_haxrcorp4089_tr, UIConstants::LEFT_MARGIN,
u8g2->height - UIConstants::BOTTOM_OFFSET);
}
}
void Menu::renderWidget(const MenuItem *item, const uint8_t *font, const int x, const int y) const
{
// Set font and draw main text
u8g2_SetFont(u8g2, font);
u8g2_DrawStr(u8g2, x, y, item->getText().c_str());
// Render type-specific elements
switch (item->getType())
{
case MenuItemTypes::TEXT: {
const std::string formattedValue = ">";
const u8g2_uint_t textWidth = u8g2_GetStrWidth(u8g2, formattedValue.c_str());
u8g2_DrawStr(u8g2, u8g2->width - textWidth - UIConstants::SELECTION_MARGIN, y, formattedValue.c_str());
break;
}
case MenuItemTypes::TEXT_COUNTER: {
const std::string formattedValue = "(" + item->getValue() + ") >";
const u8g2_uint_t textWidth = u8g2_GetStrWidth(u8g2, formattedValue.c_str());
u8g2_DrawStr(u8g2, u8g2->width - textWidth - UIConstants::SELECTION_MARGIN, y, formattedValue.c_str());
break;
}
case MenuItemTypes::SELECTION: {
// Format selection value with angle brackets
const std::string formattedValue = "< " + item->getValue() + " >";
const u8g2_uint_t textWidth = u8g2_GetStrWidth(u8g2, formattedValue.c_str());
u8g2_DrawStr(u8g2, u8g2->width - textWidth - UIConstants::SELECTION_MARGIN, y, formattedValue.c_str());
break;
}
case MenuItemTypes::TOGGLE: {
// Draw checkbox frame
const int frameX = u8g2->width - UIConstants::FRAME_BOX_SIZE - UIConstants::SELECTION_MARGIN;
const int frameY = y - UIConstants::FRAME_OFFSET;
u8g2_DrawFrame(u8g2, frameX, frameY, UIConstants::FRAME_BOX_SIZE, UIConstants::FRAME_BOX_SIZE);
// Draw checkmark (X) if toggle is true
if (item->getValue() == std::to_string(true))
{
const int checkX1 = frameX + 2;
const int checkY1 = frameY + 2;
const int checkX2 = frameX + UIConstants::FRAME_BOX_SIZE - 3;
const int checkY2 = frameY + UIConstants::FRAME_BOX_SIZE - 3;
// Draw X pattern for checked state
u8g2_DrawLine(u8g2, checkX1, checkY1, checkX2, checkY2);
u8g2_DrawLine(u8g2, checkX1, checkY2, checkX2, checkY1);
}
break;
}
default:
// No additional rendering needed for text and number types
break;
}
}
void Menu::onButtonClicked(const ButtonType button)
{
// Map button input to navigation functions
switch (button)
{
case ButtonType::UP:
onPressedUp();
break;
case ButtonType::DOWN:
onPressedDown();
break;
case ButtonType::LEFT:
onPressedLeft();
break;
case ButtonType::RIGHT:
onPressedRight();
break;
case ButtonType::SELECT:
onPressedSelect();
break;
case ButtonType::BACK:
onPressedBack();
break;
default:
// Ignore unknown button inputs
break;
}
}
void Menu::onPressedDown()
{
if (m_items.empty())
return;
// Wrap around to first item when at the end
m_selected_item = (m_selected_item + 1) % m_items.size();
}
void Menu::onPressedUp()
{
if (m_items.empty())
return;
// Wrap around to last item when at the beginning
m_selected_item = (m_selected_item == 0) ? m_items.size() - 1 : m_selected_item - 1;
}
void Menu::onPressedLeft() const
{
if (m_selected_item < m_items.size())
{
const auto &item = m_items.at(m_selected_item);
item.onButtonPressed(ButtonType::LEFT);
}
}
void Menu::onPressedRight() const
{
if (m_selected_item < m_items.size())
{
const auto &item = m_items.at(m_selected_item);
item.onButtonPressed(ButtonType::RIGHT);
}
}
void Menu::onPressedSelect() const
{
if (m_selected_item < m_items.size())
{
const auto &item = m_items.at(m_selected_item);
item.onButtonPressed(ButtonType::SELECT);
}
}
void Menu::onPressedBack() const
{
// Navigate back to previous screen if callback is available
if (m_options && m_options->popScreen)
{
m_options->popScreen();
}
}
void Menu::addText(uint8_t id, const std::string &text)
{
addTextCounter(id, text, 0);
}
void Menu::addTextCounter(uint8_t id, const std::string &text, const uint8_t value)
{
auto callback = [this](const MenuItem &menuItem, const ButtonType button) -> void {
onButtonPressed(menuItem, button);
};
if (value > 0)
{
m_items.emplace_back(id, MenuItemTypes::TEXT_COUNTER, text, std::to_string(value), callback);
}
else
{
m_items.emplace_back(id, MenuItemTypes::TEXT, text, callback);
}
}
void Menu::addSelection(uint8_t id, const std::string &text, const std::vector<std::string> &values, const int index)
{
auto callback = [this](const MenuItem &menuItem, const ButtonType button) -> void {
onButtonPressed(menuItem, button);
};
m_items.emplace_back(id, MenuItemTypes::SELECTION, text, values, index, callback);
}
void Menu::addToggle(uint8_t id, const std::string &text, const bool selected)
{
auto callback = [this](const MenuItem &menuItem, const ButtonType button) -> void {
onButtonPressed(menuItem, button);
};
m_items.emplace_back(id, MenuItemTypes::TOGGLE, text, std::to_string(selected), callback);
}
void Menu::drawScrollBar() const
{
// Create scrollbar instance
ScrollBar scrollBar(m_options, u8g2->width - UIConstants::SCROLLBAR_WIDTH, 3, 1, u8g2->height - 6);
scrollBar.refresh(m_selected_item, m_items.size());
scrollBar.render();
}
void Menu::drawSelectionBox() const
{
// Calculate dimensions for the selection box
const auto displayHeight = u8g2->height;
const auto displayWidth = u8g2->width;
const auto boxHeight = displayHeight / 3;
const auto y = boxHeight * 2 - 2;
const auto x = displayWidth - UIConstants::RIGHT_PADDING;
// Draw the rounded frame for the selection box
u8g2_DrawRFrame(u8g2, 2, boxHeight, displayWidth - UIConstants::RIGHT_PADDING, boxHeight,
UIConstants::CORNER_RADIUS);
// Draw horizontal line separator
u8g2_DrawLine(u8g2, 4, y, displayWidth - UIConstants::RIGHT_PADDING, y);
// Draw vertical line on the right side
u8g2_DrawLine(u8g2, x, y - boxHeight + 3, x, y - 1);
}

View File

@@ -0,0 +1,41 @@
#include "common/ScrollBar.h"
ScrollBar::ScrollBar(const menu_options_t *options, const size_t x, const size_t y, const size_t width,
const size_t height)
: Widget(options->u8g2), m_x(x), m_y(y), m_width(width), m_height(height), m_value(0), m_max(0), m_min(0),
m_thumbHeight(0), m_thumbY(0)
{
}
void ScrollBar::render()
{
if (m_max <= 1)
{
return;
}
for (size_t y = m_y; y < m_y + m_height; y += 2)
{
u8g2_DrawPixel(u8g2, m_x, y);
}
u8g2_DrawBox(u8g2, u8g2->width - 4, m_thumbY, 3, m_thumbHeight);
}
void ScrollBar::refresh(const size_t value, const size_t max, const size_t min)
{
m_value = value;
m_max = max;
m_min = min;
if (m_max <= 1)
{
return;
}
m_thumbHeight = std::max(m_height / 4, static_cast<size_t>(3));
const size_t trackLength = m_height - m_thumbHeight;
m_thumbY = m_y + (m_value * trackLength) / (m_max - 1);
}

View File

@@ -0,0 +1,33 @@
#include "common/Widget.h"
Widget::Widget(u8g2_t *u8g2) : u8g2(u8g2)
{
}
void Widget::enter()
{
}
void Widget::pause()
{
}
void Widget::resume()
{
}
void Widget::exit()
{
}
void Widget::update(uint64_t dt)
{
}
void Widget::render()
{
}
void Widget::onButtonClicked(ButtonType button)
{
}

View File

@@ -0,0 +1,99 @@
#include "data/MenuItem.h"
// Constructor for basic menu items (text buttons)
MenuItem::MenuItem(const uint8_t id, const uint8_t type, std::string text, ButtonCallback callback)
: m_id(id), m_type(type), m_text(std::move(text)), m_callback(std::move(callback))
{
}
// Constructor for menu items with a single value (toggles)
MenuItem::MenuItem(const uint8_t id, const uint8_t type, std::string text, std::string value, ButtonCallback callback)
: m_id(id), m_type(type), m_text(std::move(text)), m_value(std::move(value)), m_callback(std::move(callback))
{
}
// Constructor for menu items with multiple values (selections)
MenuItem::MenuItem(const uint8_t id, const uint8_t type, std::string text, std::vector<std::string> values, int index,
ButtonCallback callback)
: m_id(id), m_type(type), m_text(std::move(text)), m_values(std::move(values)), m_index(index),
m_callback(std::move(callback))
{
}
uint8_t MenuItem::getId() const
{
return m_id;
}
uint8_t MenuItem::getType() const
{
return m_type;
}
const std::string &MenuItem::getText() const
{
return m_text;
}
const std::string &MenuItem::getValue() const
{
// Return the selected value from values array if available and index is valid
if (!m_values.empty() && m_index >= 0 && m_index < m_values.size())
{
return m_values.at(m_index);
}
// Otherwise return the direct value
return m_value;
}
void MenuItem::setValue(const std::string &value)
{
m_value = value;
}
void MenuItem::onButtonPressed(const ButtonType button) const
{
// Execute the callback function if one is registered
if (m_callback)
{
m_callback(*this, button);
}
}
bool MenuItem::hasCallback() const
{
return (m_callback != nullptr);
}
int MenuItem::getIndex() const
{
return m_index;
}
std::vector<std::string> MenuItem::getValues() const
{
return m_values;
}
size_t MenuItem::getItemCount() const
{
return m_values.size();
}
MenuItem MenuItem::copyWith(const std::string &value) const
{
// Create a copy of this menu item with a new value
MenuItem copy = *this;
copy.m_value = value;
return copy;
}
MenuItem MenuItem::copyWith(const size_t index) const
{
// Create a copy of this menu item with a new selected index
MenuItem copy = *this;
// Check for potential overflow when converting size_t to int
copy.m_index = static_cast<int>(index);
return copy;
}

View File

@@ -0,0 +1,114 @@
#include "ui/LightMenu.h"
#include "led_manager.h"
#include "ui/LightSettingsMenu.h"
/**
* @namespace LightMenuItem
* @brief Constants for light menu item identifiers
*/
namespace LightMenuItem
{
constexpr uint8_t ACTIVATE = 0; ///< ID for the light activation toggle
constexpr uint8_t MODE = 1; ///< ID for the light mode selection
constexpr uint8_t LED_SETTINGS = 2; ///< ID for the LED settings menu item
} // namespace LightMenuItem
namespace LightMenuOptions
{
constexpr std::string LIGHT_ACTIVE = "light_active";
constexpr std::string LIGHT_MODE = "light_mode";
} // namespace LightMenuOptions
LightMenu::LightMenu(menu_options_t *options) : Menu(options), m_options(options)
{
// Add toggle for enabling/disabling the light system
bool active = false;
if (m_options && m_options->persistenceManager)
{
active = m_options->persistenceManager->GetValue(LightMenuOptions::LIGHT_ACTIVE, active);
}
addToggle(LightMenuItem::ACTIVATE, "Einschalten", active);
// Create mode selection options (Day/Night modes)
std::vector<std::string> values;
values.emplace_back("Tag"); // Day mode
values.emplace_back("Nacht"); // Night mode
int mode_value = 0;
if (m_options && m_options->persistenceManager)
{
mode_value = m_options->persistenceManager->GetValue(LightMenuOptions::LIGHT_MODE, mode_value);
}
addSelection(LightMenuItem::MODE, "Modus", values, mode_value);
// Add menu item for accessing LED settings submenu
addText(LightMenuItem::LED_SETTINGS, "Einstellungen");
}
void LightMenu::onButtonPressed(const MenuItem &menuItem, const ButtonType button)
{
std::shared_ptr<Widget> widget;
// Handle different menu items based on their ID
switch (menuItem.getId())
{
case LightMenuItem::ACTIVATE: {
// Toggle the light activation state when SELECT is pressed
if (button == ButtonType::SELECT)
{
toggle(menuItem);
if (getItem(menuItem.getId()).getValue() == "1")
{
led_event_data_t payload = {.value = 42};
send_event(EVENT_LED_ON, &payload);
}
else
{
led_event_data_t payload = {.value = 0};
send_event(EVENT_LED_OFF, &payload);
}
if (m_options && m_options->persistenceManager)
{
const auto value = getItem(menuItem.getId()).getValue() == "1";
m_options->persistenceManager->SetValue(LightMenuOptions::LIGHT_ACTIVE, value);
m_options->persistenceManager->Save();
}
}
break;
}
case LightMenuItem::MODE: {
// Switch between day/night modes using left/right buttons
const auto item = switchValue(menuItem, button);
if (button == ButtonType::LEFT || button == ButtonType::RIGHT)
{
if (m_options && m_options->persistenceManager)
{
const auto value = getItem(item.getId()).getIndex();
m_options->persistenceManager->SetValue(LightMenuOptions::LIGHT_MODE, value);
m_options->persistenceManager->Save();
}
}
break;
}
case LightMenuItem::LED_SETTINGS: {
// Open the LED settings submenu when SELECT is pressed
if (button == ButtonType::SELECT)
{
widget = std::make_shared<LightSettingsMenu>(m_options);
}
break;
}
default:
// Handle unknown menu items (no action required)
break;
}
// Push the new widget to the screen stack if one was created
if (m_options && m_options->pushScreen)
{
m_options->pushScreen(widget);
}
}

View File

@@ -0,0 +1,57 @@
#include "ui/LightSettingsMenu.h"
/**
* @namespace LightSettingsMenuItem
* @brief Constants for light settings menu item identifiers
*/
namespace LightSettingsMenuItem
{
constexpr uint8_t SECTION_COUNTER = 0; ///< ID for the section counter menu item
}
std::string LightSettingsMenu::CreateKey(const int index)
{
constexpr int key_length = 20;
char key[key_length] = "";
snprintf(key, key_length, "section_%d", index);
return key;
}
LightSettingsMenu::LightSettingsMenu(menu_options_t *options) : Menu(options), m_options(options)
{
// Create values vector for section counts (1-99)
std::vector<std::string> values;
for (size_t i = 1; i <= 99; i++)
{
values.emplace_back(std::to_string(i));
}
// Add section counter selection (allows choosing number of sections)
auto value = 7;
if (m_options && m_options->persistenceManager)
{
value = m_options->persistenceManager->GetValue(CreateKey(0), value);
}
addSelection(LightSettingsMenuItem::SECTION_COUNTER, "Sektionen", values, value);
setItemSize(std::stoull(getItem(0).getValue()));
}
void LightSettingsMenu::onButtonPressed(const MenuItem &menuItem, const ButtonType button)
{
// Handle value switching for the current menu item
switchValue(menuItem, button);
// Update the section list size based on the section counter value
if (menuItem.getId() == 0)
{
setItemSize(std::stoull(getItem(0).getValue()));
}
// Persist the changed section values if persistence is available
if (m_options && m_options->persistenceManager)
{
const auto value = getItem(menuItem.getId()).getIndex();
m_options->persistenceManager->SetValue(CreateKey(menuItem.getId()), value);
}
}

View File

@@ -0,0 +1,44 @@
#include "ui/MainMenu.h"
#include "common/Widget.h"
#include "ui/LightMenu.h"
#include "ui/SettingsMenu.h"
namespace MainMenuItem
{
constexpr uint8_t LIGHT = 0;
constexpr uint8_t EXTERNAL_DEVICES = 1;
constexpr uint8_t SETTINGS = 2;
} // namespace MainMenuItem
MainMenu::MainMenu(menu_options_t *options) : Menu(options), m_options(options)
{
addText(MainMenuItem::LIGHT, "Lichtsteuerung");
addTextCounter(MainMenuItem::EXTERNAL_DEVICES, "ext. Geraete", 0);
addText(MainMenuItem::SETTINGS, "Einstellungen");
}
void MainMenu::onButtonPressed(const MenuItem &menuItem, const ButtonType button)
{
if (button == ButtonType::SELECT)
{
std::shared_ptr<Widget> widget;
switch (menuItem.getId())
{
case MainMenuItem::LIGHT:
widget = std::make_shared<LightMenu>(m_options);
break;
case MainMenuItem::SETTINGS:
widget = std::make_shared<SettingsMenu>(m_options);
break;
default:
break;
}
if (m_options && m_options->pushScreen)
{
m_options->pushScreen(widget);
}
}
}

View File

@@ -0,0 +1,329 @@
#include "ui/ScreenSaver.h"
#include "data/roads.h"
#include "data/vehicles.h"
#include <cstdlib>
ScreenSaver::ScreenSaver(menu_options_t *options)
: Widget(options->u8g2), m_options(options), m_animationCounter(0), m_lastSpawnTime(0), m_leftVehicleCount(0),
m_rightVehicleCount(0)
{
initVehicles();
}
void ScreenSaver::initVehicles()
{
m_vehicles.resize(MAX_VEHICLES);
for (auto &vehicle : m_vehicles)
{
vehicle.active = false;
}
}
void ScreenSaver::update(const uint64_t dt)
{
m_animationCounter += dt;
m_lastSpawnTime += dt;
m_sceneShiftTimer += dt;
// Shift entire scene every 30 seconds
if (m_sceneShiftTimer > 30000)
{
m_sceneOffsetX = (random() % 7) - 3; // -3 to +3 pixels
m_sceneOffsetY = (random() % 7) - 3; // -3 to +3 pixels
m_sceneShiftTimer = 0;
}
// Try to spawn a new vehicle every few seconds
if (m_lastSpawnTime > VEHICLE_SPAWN_DELAY)
{
trySpawnVehicle();
m_lastSpawnTime = 0;
}
// Update vehicle positions
if (m_animationCounter > 16) // ~60 FPS
{
m_animationCounter = 0;
for (auto &vehicle : m_vehicles)
{
if (!vehicle.active)
continue;
// Move vehicle
if (vehicle.direction == Direction::LEFT)
{
vehicle.x -= static_cast<int>(vehicle.speed);
// Remove the vehicle if it goes off-screen
if (vehicle.x <= -32) // Allow for largest vehicle width
{
vehicle.active = false;
m_leftVehicleCount--;
}
}
else // Direction::RIGHT
{
vehicle.x += static_cast<int>(vehicle.speed);
// Remove the vehicle if it goes off-screen
if (vehicle.x >= (u8g2->width + 32)) // Allow for largest vehicle width
{
vehicle.active = false;
m_rightVehicleCount--;
}
}
}
}
}
bool ScreenSaver::canSpawnInDirection(Direction direction) const
{
// Minimalen Abstand zwischen 48 und 64 Pixel zufällig wählen
int requiredDistance =
MIN_SAME_DIRECTION_DISTANCE + (random() % (MAX_SAME_DIRECTION_DISTANCE - MIN_SAME_DIRECTION_DISTANCE + 1));
for (const auto &vehicle : m_vehicles)
{
if (!vehicle.active || vehicle.direction != direction)
continue;
// Abstand zum nächsten Fahrzeug in gleicher Richtung prüfen
if (direction == Direction::LEFT)
{
// Fahrzeuge fahren von rechts nach links
// Neues Fahrzeug würde bei u8g2->width + 16 starten
int newVehicleX = u8g2->width + 16;
// Prüfen ob genug Abstand zum existierenden Fahrzeug
if (newVehicleX - vehicle.x < requiredDistance)
return false;
}
else // Direction::RIGHT
{
// Fahrzeuge fahren von links nach rechts
// Neues Fahrzeug würde bei -32 starten
int newVehicleX = -32;
// Prüfen ob genug Abstand zum existierenden Fahrzeug
if (vehicle.x - newVehicleX < requiredDistance)
return false;
}
}
return true;
}
void ScreenSaver::trySpawnVehicle()
{
// Check if we can spawn a new vehicle
int activeVehicles = 0;
int availableSlot = -1;
for (int i = 0; i < MAX_VEHICLES; i++)
{
if (m_vehicles[i].active)
{
activeVehicles++;
}
else if (availableSlot == -1)
{
availableSlot = i;
}
}
// Don't spawn if we're at max capacity or no slot available
if (activeVehicles >= MAX_VEHICLES || availableSlot == -1)
{
return;
}
Direction direction = getRandomDirection();
// Check direction constraints
if ((direction == Direction::LEFT && m_leftVehicleCount >= MAX_LEFT_VEHICLES) ||
(direction == Direction::RIGHT && m_rightVehicleCount >= MAX_RIGHT_VEHICLES))
{
return;
}
if (!canSpawnInDirection(direction))
{
return;
}
// Create new vehicle
Vehicle &newVehicle = m_vehicles[availableSlot];
newVehicle.type = getRandomVehicleType();
newVehicle.direction = direction;
newVehicle.speed = MIN_SPEED + (static_cast<float>(random()) / RAND_MAX) * (MAX_SPEED - MIN_SPEED);
// Set Y position based on a direction (simulate opposing traffic lanes)
const int halfHeight = u8g2->height / 2;
if (direction == Direction::RIGHT)
{
// Vehicles going LEFT use bottom half of screen
newVehicle.y = halfHeight + 8 + (random() % (halfHeight - 24));
m_rightVehicleCount++;
}
else // Direction::RIGHT
{
// Vehicles going RIGHT use top half of screen
newVehicle.y = 8 + (random() % (halfHeight - 24));
m_leftVehicleCount++;
}
// Set the starting X position based on the direction
if (direction == Direction::LEFT)
{
// Vehicles going LEFT (from right to left) start from RIGHT side of screen
newVehicle.x = u8g2->width + 16;
}
else // Direction::RIGHT
{
// Vehicles going RIGHT (from left to right) start from LEFT side of screen
newVehicle.x = -32; // Account for the largest vehicle width
}
newVehicle.active = true;
}
ScreenSaver::VehicleType ScreenSaver::getRandomVehicleType()
{
switch (random() % 5)
{
case 0:
return VehicleType::CAR;
case 1:
return VehicleType::CONVERTABLE;
case 2:
return VehicleType::SUV;
case 3:
return VehicleType::LORRY;
case 4:
return VehicleType::TRUCK;
default:
return VehicleType::CAR;
}
}
ScreenSaver::Direction ScreenSaver::getRandomDirection()
{
// Simple 50/50 chance for each direction
return (random() % 2 == 0) ? Direction::LEFT : Direction::RIGHT;
}
void ScreenSaver::render()
{
// Clear screen with a black background
u8g2_SetDrawColor(u8g2, 0);
u8g2_DrawBox(u8g2, 0, 0, u8g2->width, u8g2->height);
u8g2_SetDrawColor(u8g2, 1);
// Calculate offsets
const int roadOffset = (m_animationCounter / 100) % road_horizontal_width;
// Draw all active vehicles with a scene offset
for (const auto &vehicle : m_vehicles)
{
if (vehicle.active)
{
Vehicle offsetVehicle = vehicle;
offsetVehicle.x += m_sceneOffsetX;
offsetVehicle.y += m_sceneOffsetY;
drawVehicle(offsetVehicle);
}
}
// Draw road with offsets
const int y = u8g2->height / 2 - road_horizontal_height / 2 + m_sceneOffsetY;
for (int x = -road_horizontal_width + roadOffset + m_sceneOffsetX; x <= u8g2->width; x += road_horizontal_width)
{
drawTransparentBitmap(x, y, road_horizontal_width, road_horizontal_height, road_horizontal_bits);
}
}
void ScreenSaver::drawVehicle(const Vehicle &vehicle) const
{
int width, height;
if (const unsigned char *bitmap = getVehicleBitmap(vehicle.type, vehicle.direction, width, height))
{
drawTransparentBitmap(vehicle.x, vehicle.y, width, height, bitmap);
// u8g2_DrawXBM(u8g2, vehicle.x, vehicle.y, width, height, bitmap);
}
}
void ScreenSaver::drawTransparentBitmap(const int x, const int y, const int width, const int height,
const unsigned char *bitmap) const
{
for (int py = 0; py < height; py++)
{
for (int px = 0; px < width; px++)
{
// Calculate byte and a bit of position in bitmap
const int byteIndex = (py * ((width + 7) / 8)) + (px / 8);
// Check if the pixel is set (white)
if (const int bitIndex = px % 8; bitmap[byteIndex] & (1 << bitIndex))
{
// Only draw white pixels, skip black (transparent) pixels
const int screenX = x + px;
// Bounds checking
if (const int screenY = y + py;
screenX >= 0 && screenX < u8g2->width && screenY >= 0 && screenY < u8g2->height)
{
u8g2_DrawPixel(u8g2, screenX, screenY);
}
}
// Black pixels are simply not drawn (transparent)
}
}
}
const unsigned char *ScreenSaver::getVehicleBitmap(const VehicleType type, const Direction direction, int &width,
int &height)
{
switch (type)
{
case VehicleType::CAR:
width = car_width;
height = car_height;
return (direction == Direction::LEFT) ? car_left_bits : car_right_bits;
case VehicleType::CONVERTABLE:
width = convertable_width;
height = convertable_height;
return (direction == Direction::LEFT) ? convertable_left_bits : convertable_right_bits;
case VehicleType::SUV:
width = suv_width;
height = suv_height;
return (direction == Direction::LEFT) ? suv_left_bits : suv_right_bits;
case VehicleType::LORRY:
width = lorry_width;
height = lorry_height;
return (direction == Direction::LEFT) ? lorry_left_bits : lorry_right_bits;
case VehicleType::TRUCK:
width = truck_width;
height = truck_height;
return (direction == Direction::LEFT) ? truck_left_bits : truck_right_bits;
default:
width = car_width;
height = car_height;
return car_left_bits;
}
}
void ScreenSaver::onButtonClicked(ButtonType button)
{
if (m_options && m_options->popScreen)
{
m_options->popScreen();
}
}

View File

@@ -0,0 +1,11 @@
#include "ui/SettingsMenu.h"
namespace SettingsMenuItem
{
constexpr uint8_t OTA_UPLOAD = 0;
}
SettingsMenu::SettingsMenu(menu_options_t *options) : Menu(options)
{
addText(SettingsMenuItem::OTA_UPLOAD, "OTA Einspielen");
}

View File

@@ -0,0 +1,39 @@
#include "ui/SplashScreen.h"
#include "ui/MainMenu.h"
#ifndef ESP32
#include <chrono>
#include <thread>
#endif
uint64_t counter = 0;
SplashScreen::SplashScreen(menu_options_t *options) : Widget(options->u8g2), m_options(options)
{
}
void SplashScreen::update(const uint64_t dt)
{
counter += dt;
if (counter >= 3000)
{
counter = 0;
if (m_options && m_options->setScreen)
{
m_options->setScreen(std::make_shared<MainMenu>(m_options));
}
}
#ifndef ESP32
std::this_thread::sleep_for(std::chrono::milliseconds(1));
#endif
}
void SplashScreen::render()
{
u8g2_SetFont(u8g2, u8g2_font_DigitalDisco_tr);
u8g2_DrawStr(u8g2, 28, u8g2->height / 2 - 10, "HO Anlage");
u8g2_DrawStr(u8g2, 30, u8g2->height / 2 + 5, "Axel Janz");
u8g2_SetFont(u8g2, u8g2_font_haxrcorp4089_tr);
u8g2_DrawStr(u8g2, 35, 50, "Initialisierung...");
}

View File

@@ -0,0 +1,28 @@
if (DEFINED ENV{IDF_PATH})
idf_component_register(SRCS
src/hal_esp32/led_manager.cpp
INCLUDE_DIRS "include"
PRIV_REQUIRES
u8g2
esp_event
persistence-manager
)
return()
endif ()
cmake_minimum_required(VERSION 3.30)
project(led-manager)
add_library(${PROJECT_NAME} STATIC
src/hal_native/led_manager.cpp
src/hal_native/Matrix.cpp
)
include_directories(include)
target_include_directories(${PROJECT_NAME} PUBLIC include)
target_link_libraries(${PROJECT_NAME} PRIVATE
SDL3::SDL3
persistence-manager
)

View File

@@ -0,0 +1,2 @@
dependencies:
espressif/led_strip: '~3.0.1'

View File

@@ -0,0 +1,29 @@
#pragma once
#include "SDL3/SDL_render.h"
#include <cstdint>
class Matrix
{
public:
explicit Matrix(SDL_WindowID windowId, SDL_Renderer *renderer, uint8_t cols, uint8_t rows);
[[nodiscard]] SDL_Renderer *renderer() const;
void Render() const;
[[nodiscard]] SDL_WindowID windowId() const;
private:
void DrawColoredGrid() const;
SDL_WindowID m_windowId;
SDL_Renderer *m_renderer;
uint8_t m_cols;
uint8_t m_rows;
static constexpr float cellSize = 50.0f;
static constexpr float spacing = 1.0f;
};

View File

@@ -0,0 +1,22 @@
#pragma once
#include <cstdint>
enum
{
EVENT_LED_ON,
EVENT_LED_OFF,
EVENT_LED_DAY,
EVENT_LED_NIGHT,
};
typedef struct
{
int value;
} led_event_data_t;
uint64_t wled_init();
uint64_t register_handler();
uint64_t send_event(uint32_t event, led_event_data_t *payload);

View File

@@ -0,0 +1,92 @@
#include "led_manager.h"
#include "esp_event.h"
#include "esp_log.h"
#include "led_strip.h"
#include "sdkconfig.h"
led_strip_handle_t led_strip;
static const uint32_t value = 5;
ESP_EVENT_DECLARE_BASE(LED_EVENTS_BASE);
ESP_EVENT_DEFINE_BASE(LED_EVENTS_BASE);
esp_event_loop_handle_t loop_handle;
const char *TAG = "LED";
uint64_t wled_init(void)
{
led_strip_config_t strip_config = {.strip_gpio_num = CONFIG_WLED_DIN_PIN,
.max_leds = 64,
.led_model = LED_MODEL_WS2812,
.color_component_format = LED_STRIP_COLOR_COMPONENT_FMT_RGB,
.flags = {
.invert_out = false,
}};
led_strip_rmt_config_t rmt_config = {.clk_src = RMT_CLK_SRC_DEFAULT,
.resolution_hz = 0,
.mem_block_symbols = 0,
.flags = {
.with_dma = true,
}};
ESP_ERROR_CHECK(led_strip_new_rmt_device(&strip_config, &rmt_config, &led_strip));
for (uint32_t i = 0; i < 64; i++)
{
led_strip_set_pixel(led_strip, i, 0, 0, 0);
}
led_strip_refresh(led_strip);
return ESP_OK;
}
void event_handler(void *arg, esp_event_base_t base, int32_t id, void *event_data)
{
if (id == EVENT_LED_ON || id == EVENT_LED_OFF)
{
auto brightness = (id == EVENT_LED_ON) ? value : 0;
for (uint32_t i = 0; i < 64; i++)
{
led_strip_set_pixel(led_strip, i, brightness, brightness, brightness);
}
led_strip_refresh(led_strip);
}
}
uint64_t register_handler(void)
{
esp_event_loop_args_t loop_args = {
.queue_size = 2, .task_name = "led_manager", .task_priority = 5, .task_stack_size = 4096, .task_core_id = 1};
esp_event_loop_create(&loop_args, &loop_handle);
esp_event_handler_register_with(loop_handle, LED_EVENTS_BASE, ESP_EVENT_ANY_ID, event_handler, NULL);
return ESP_OK;
}
uint64_t send_event(uint32_t event, led_event_data_t *payload)
{
if (payload == nullptr)
{
return ESP_ERR_INVALID_ARG;
}
esp_err_t err = esp_event_post_to(loop_handle, // Event-Loop Handle
LED_EVENTS_BASE, // Event Base
event, // Event ID (EVENT_LED_ON, EVENT_LED_OFF, etc.)
payload, // Daten-Pointer
sizeof(led_event_data_t), // Datengröße
portMAX_DELAY // Wartezeit
);
if (err != ESP_OK)
{
ESP_LOGE("LED", "Failed to post event: %s", esp_err_to_name(err));
return err;
}
return ESP_OK;
}

View File

@@ -0,0 +1,64 @@
#include "Matrix.h"
#include "SDL3/SDL.h"
Matrix::Matrix(uint32_t windowID, SDL_Renderer *renderer, const uint8_t cols, const uint8_t rows)
: m_windowId(windowID), m_renderer(renderer), m_cols(cols), m_rows(rows)
{
}
SDL_Renderer *Matrix::renderer() const
{
return m_renderer;
}
SDL_WindowID Matrix::windowId() const
{
return m_windowId;
}
void Matrix::DrawColoredGrid() const
{
int i = 0;
for (int w = 0; w < m_cols; w++)
{
const auto phase = w % (2 * m_rows);
for (int h_raw = 0; h_raw < m_rows; h_raw++)
{
int h;
if (phase < m_rows)
{
h = h_raw;
}
else
{
h = m_rows - 1 - h_raw;
}
constexpr auto rectSize = cellSize - 2.0f * spacing;
const auto x = static_cast<float>(w) * cellSize + spacing;
const auto y = static_cast<float>(h) * cellSize + spacing;
auto rect = SDL_FRect{x, y, rectSize, rectSize};
i++;
const auto red = static_cast<Uint8>(static_cast<float>(i) * 255.0f);
const auto green = static_cast<Uint8>(static_cast<float>(i) * 255.0f);
const auto blue = static_cast<Uint8>(static_cast<float>(i) * 255.0f);
SDL_SetRenderDrawColor(m_renderer, red, green, blue, 255);
SDL_RenderFillRect(m_renderer, &rect);
}
}
}
void Matrix::Render() const
{
SDL_SetRenderDrawColor(m_renderer, 0, 0, 0, 255);
SDL_RenderClear(m_renderer);
DrawColoredGrid();
SDL_RenderPresent(m_renderer);
}

View File

@@ -0,0 +1,16 @@
#include "led_manager.h"
uint64_t wled_init(void)
{
return 0;
}
uint64_t register_handler(void)
{
return 0;
}
uint64_t send_event(uint32_t event, led_event_data_t *payload)
{
return 0;
}

View File

@@ -0,0 +1,24 @@
if (DEFINED ENV{IDF_PATH})
idf_component_register(SRCS
src/hal_esp32/PersistenceManager.cpp
INCLUDE_DIRS "include"
REQUIRES
nvs_flash
)
return()
endif ()
cmake_minimum_required(VERSION 3.30)
project(persistence-manager)
add_library(${PROJECT_NAME} STATIC
src/hal_native/PersistenceManager.cpp
)
include_directories(include)
target_include_directories(${PROJECT_NAME} PUBLIC include)
target_link_libraries(${PROJECT_NAME} PRIVATE
SDL3::SDL3
)

View File

@@ -0,0 +1,108 @@
#pragma once
#include <string>
#include <type_traits>
/**
* @interface IPersistenceManager
* @brief Abstract interface for platform-independent persistence management
* @details This interface defines the contract for key-value storage and retrieval
* systems across different platforms (Desktop/SDL3 and ESP32).
*/
class IPersistenceManager
{
public:
virtual ~IPersistenceManager() = default;
/**
* @brief Template methods for type-safe setting and retrieving of values
* @tparam T The type of value to set (must be one of: bool, int, float, double, std::string)
* @param key The key to associate with the value
* @param value The value to store
*/
template<typename T>
void SetValue(const std::string& key, const T& value) {
static_assert(std::is_same_v<T, bool> ||
std::is_same_v<T, int> ||
std::is_same_v<T, float> ||
std::is_same_v<T, double> ||
std::is_same_v<T, std::string>,
"Unsupported type for IPersistenceManager");
SetValueImpl(key, value);
}
/**
* @brief Template method for type-safe retrieval of values
* @tparam T The type of value to retrieve
* @param key The key to look up
* @param defaultValue The default value to return if the key is not found
* @return The stored value or default value if the key doesn't exist
*/
template<typename T>
[[nodiscard]] T GetValue(const std::string& key, const T& defaultValue = T{}) const {
return GetValueImpl<T>(key, defaultValue);
}
/**
* @brief Utility methods for key management
*/
[[nodiscard]] virtual bool HasKey(const std::string& key) const = 0; ///< Check if a key exists
virtual void RemoveKey(const std::string& key) = 0; ///< Remove a key-value pair
virtual void Clear() = 0; ///< Clear all stored data
[[nodiscard]] virtual size_t GetKeyCount() const = 0; ///< Get the number of stored keys
/**
* @brief Persistence operations
*/
virtual bool Save() = 0; ///< Save data to persistent storage
virtual bool Load() = 0; ///< Load data from persistent storage
protected:
/**
* @brief Template-specific implementations that must be overridden by derived classes
* @details These methods handle the actual storage and retrieval of different data types
*/
virtual void SetValueImpl(const std::string& key, bool value) = 0;
virtual void SetValueImpl(const std::string& key, int value) = 0;
virtual void SetValueImpl(const std::string& key, float value) = 0;
virtual void SetValueImpl(const std::string& key, double value) = 0;
virtual void SetValueImpl(const std::string& key, const std::string& value) = 0;
[[nodiscard]] virtual bool GetValueImpl(const std::string& key, bool defaultValue) const = 0;
[[nodiscard]] virtual int GetValueImpl(const std::string& key, int defaultValue) const = 0;
[[nodiscard]] virtual float GetValueImpl(const std::string& key, float defaultValue) const = 0;
[[nodiscard]] virtual double GetValueImpl(const std::string& key, double defaultValue) const = 0;
[[nodiscard]] virtual std::string GetValueImpl(const std::string& key, const std::string& defaultValue) const = 0;
private:
/**
* @brief Template dispatch methods for type-safe value retrieval
* @tparam T The type to retrieve
* @param key The key to look up
* @param defaultValue The default value to return
* @return The retrieved value or default if not found
*/
template<typename T>
[[nodiscard]] T GetValueImpl(const std::string& key, const T& defaultValue) const
{
if constexpr (std::is_same_v<T, bool>) {
return GetValueImpl(key, static_cast<bool>(defaultValue));
} else if constexpr (std::is_same_v<T, int>) {
return GetValueImpl(key, static_cast<int>(defaultValue));
} else if constexpr (std::is_same_v<T, float>) {
return GetValueImpl(key, static_cast<float>(defaultValue));
} else if constexpr (std::is_same_v<T, double>) {
return GetValueImpl(key, static_cast<double>(defaultValue));
} else if constexpr (std::is_same_v<T, std::string>) {
return GetValueImpl(key, static_cast<const std::string&>(defaultValue));
} else {
static_assert(std::is_same_v<T, bool> ||
std::is_same_v<T, int> ||
std::is_same_v<T, float> ||
std::is_same_v<T, double> ||
std::is_same_v<T, std::string>,
"Unsupported type for IPersistenceManager");
return defaultValue; // This line will never be reached, but satisfies the compiler
}
}
};

View File

@@ -0,0 +1,58 @@
#pragma once
#include "IPersistenceManager.h"
#include <string>
#include <unordered_map>
#include <nvs.h>
#include <nvs_flash.h>
/**
* @class PersistenceManager
* @brief ESP32-specific implementation using NVS (Non-Volatile Storage)
* @details This implementation uses ESP32's NVS API for persistent storage
* in flash memory, providing a platform-optimized solution for
* embedded systems.
*/
class PersistenceManager final : public IPersistenceManager
{
private:
nvs_handle_t nvs_handle_;
std::string namespace_;
bool initialized_;
public:
explicit PersistenceManager(const std::string &nvs_namespace = "config");
~PersistenceManager() override;
bool HasKey(const std::string &key) const override;
void RemoveKey(const std::string &key) override;
void Clear() override;
size_t GetKeyCount() const override;
bool Save() override;
bool Load() override;
bool Initialize();
void Deinitialize();
bool IsInitialized() const
{
return initialized_;
}
protected:
void SetValueImpl(const std::string &key, bool value) override;
void SetValueImpl(const std::string &key, int value) override;
void SetValueImpl(const std::string &key, float value) override;
void SetValueImpl(const std::string &key, double value) override;
void SetValueImpl(const std::string &key, const std::string &value) override;
bool GetValueImpl(const std::string &key, bool defaultValue) const override;
int GetValueImpl(const std::string &key, int defaultValue) const override;
float GetValueImpl(const std::string &key, float defaultValue) const override;
double GetValueImpl(const std::string &key, double defaultValue) const override;
std::string GetValueImpl(const std::string &key, const std::string &defaultValue) const override;
private:
bool EnsureInitialized() const;
};

View File

@@ -0,0 +1,63 @@
#pragma once
#include "../IPersistenceManager.h"
#include <SDL3/SDL.h>
#include <unordered_map>
#include <variant>
class PersistenceManager final : public IPersistenceManager {
public:
using ValueType = std::variant<
bool,
int,
float,
double,
std::string
>;
private:
std::unordered_map<std::string, ValueType> m_data;
std::string m_filename;
public:
explicit PersistenceManager(std::string filename = "settings.dat");
~PersistenceManager() override;
[[nodiscard]] bool HasKey(const std::string& key) const override;
void RemoveKey(const std::string& key) override;
void Clear() override;
[[nodiscard]] size_t GetKeyCount() const override { return m_data.size(); }
bool Save() override;
bool Load() override;
bool SaveToFile(const std::string& filename);
bool LoadFromFile(const std::string& filename);
protected:
void SetValueImpl(const std::string& key, bool value) override;
void SetValueImpl(const std::string& key, int value) override;
void SetValueImpl(const std::string& key, float value) override;
void SetValueImpl(const std::string& key, double value) override;
void SetValueImpl(const std::string& key, const std::string& value) override;
[[nodiscard]] bool GetValueImpl(const std::string& key, bool defaultValue) const override;
[[nodiscard]] int GetValueImpl(const std::string& key, int defaultValue) const override;
[[nodiscard]] float GetValueImpl(const std::string& key, float defaultValue) const override;
[[nodiscard]] double GetValueImpl(const std::string& key, double defaultValue) const override;
[[nodiscard]] std::string GetValueImpl(const std::string& key, const std::string& defaultValue) const override;
private:
static bool WriteValueToStream(SDL_IOStream* stream, const ValueType& value) ;
static bool ReadValueFromStream(SDL_IOStream* stream, ValueType& value) ;
enum class TypeId : uint8_t {
BOOL = 0,
INT = 1,
FLOAT = 2,
DOUBLE = 3,
STRING = 4
};
static TypeId GetTypeId(const ValueType& value);
};

View File

@@ -0,0 +1,293 @@
#include "hal_esp32/PersistenceManager.h"
#include <cstring>
#include <esp_log.h>
static const char *TAG = "PersistenceManager";
PersistenceManager::PersistenceManager(const std::string &nvs_namespace)
: namespace_(nvs_namespace), initialized_(false)
{
Initialize();
Load();
}
PersistenceManager::~PersistenceManager()
{
Deinitialize();
}
bool PersistenceManager::Initialize()
{
if (initialized_)
{
return true;
}
// Initialize NVS
esp_err_t err = nvs_flash_init();
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND)
{
ESP_ERROR_CHECK(nvs_flash_erase());
err = nvs_flash_init();
}
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to initialize NVS flash: %s", esp_err_to_name(err));
return false;
}
// Open NVS handle
err = nvs_open(namespace_.c_str(), NVS_READWRITE, &nvs_handle_);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to open NVS handle: %s", esp_err_to_name(err));
return false;
}
initialized_ = true;
ESP_LOGI(TAG, "PersistenceManager initialized with namespace: %s", namespace_.c_str());
return true;
}
void PersistenceManager::Deinitialize()
{
if (initialized_)
{
nvs_close(nvs_handle_);
initialized_ = false;
}
}
bool PersistenceManager::EnsureInitialized() const
{
if (!initialized_)
{
ESP_LOGE(TAG, "PersistenceManager not initialized");
return false;
}
return true;
}
bool PersistenceManager::HasKey(const std::string &key) const
{
if (!EnsureInitialized())
return false;
size_t required_size = 0;
esp_err_t err = nvs_get_blob(nvs_handle_, key.c_str(), nullptr, &required_size);
return err == ESP_OK;
}
void PersistenceManager::RemoveKey(const std::string &key)
{
if (!EnsureInitialized())
return;
esp_err_t err = nvs_erase_key(nvs_handle_, key.c_str());
if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND)
{
ESP_LOGE(TAG, "Failed to remove key '%s': %s", key.c_str(), esp_err_to_name(err));
}
}
void PersistenceManager::Clear()
{
if (!EnsureInitialized())
return;
esp_err_t err = nvs_erase_all(nvs_handle_);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to clear all keys: %s", esp_err_to_name(err));
}
}
size_t PersistenceManager::GetKeyCount() const
{
if (!EnsureInitialized())
return 0;
nvs_iterator_t it = nullptr;
esp_err_t err = nvs_entry_find(NVS_DEFAULT_PART_NAME, namespace_.c_str(), NVS_TYPE_ANY, &it);
if (err != ESP_OK || it == nullptr)
{
return 0;
}
size_t count = 0;
while (it != nullptr)
{
count++;
err = nvs_entry_next(&it);
if (err != ESP_OK)
{
break;
}
}
nvs_release_iterator(it);
return count;
}
bool PersistenceManager::Save()
{
if (!EnsureInitialized())
return false;
esp_err_t err = nvs_commit(nvs_handle_);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to commit NVS: %s", esp_err_to_name(err));
return false;
}
return true;
}
bool PersistenceManager::Load()
{
return EnsureInitialized();
}
void PersistenceManager::SetValueImpl(const std::string &key, bool value)
{
if (!EnsureInitialized())
return;
uint8_t val = value ? 1 : 0;
esp_err_t err = nvs_set_u8(nvs_handle_, key.c_str(), val);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to set bool key '%s': %s", key.c_str(), esp_err_to_name(err));
}
}
void PersistenceManager::SetValueImpl(const std::string &key, int value)
{
if (!EnsureInitialized())
return;
esp_err_t err = nvs_set_i32(nvs_handle_, key.c_str(), value);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to set int key '%s': %s", key.c_str(), esp_err_to_name(err));
}
}
void PersistenceManager::SetValueImpl(const std::string &key, float value)
{
if (!EnsureInitialized())
return;
esp_err_t err = nvs_set_blob(nvs_handle_, key.c_str(), &value, sizeof(float));
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to set float key '%s': %s", key.c_str(), esp_err_to_name(err));
}
}
void PersistenceManager::SetValueImpl(const std::string &key, double value)
{
if (!EnsureInitialized())
return;
esp_err_t err = nvs_set_blob(nvs_handle_, key.c_str(), &value, sizeof(double));
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to set double key '%s': %s", key.c_str(), esp_err_to_name(err));
}
}
void PersistenceManager::SetValueImpl(const std::string &key, const std::string &value)
{
if (!EnsureInitialized())
return;
esp_err_t err = nvs_set_str(nvs_handle_, key.c_str(), value.c_str());
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to set string key '%s': %s", key.c_str(), esp_err_to_name(err));
}
}
bool PersistenceManager::GetValueImpl(const std::string &key, bool defaultValue) const
{
if (!EnsureInitialized())
return defaultValue;
uint8_t value;
esp_err_t err = nvs_get_u8(nvs_handle_, key.c_str(), &value);
if (err != ESP_OK)
{
return defaultValue;
}
return value != 0;
}
int PersistenceManager::GetValueImpl(const std::string &key, int defaultValue) const
{
if (!EnsureInitialized())
return defaultValue;
int32_t value;
esp_err_t err = nvs_get_i32(nvs_handle_, key.c_str(), &value);
if (err != ESP_OK)
{
return defaultValue;
}
return static_cast<int>(value);
}
float PersistenceManager::GetValueImpl(const std::string &key, float defaultValue) const
{
if (!EnsureInitialized())
return defaultValue;
float value;
size_t required_size = sizeof(float);
esp_err_t err = nvs_get_blob(nvs_handle_, key.c_str(), &value, &required_size);
if (err != ESP_OK || required_size != sizeof(float))
{
return defaultValue;
}
return value;
}
double PersistenceManager::GetValueImpl(const std::string &key, double defaultValue) const
{
if (!EnsureInitialized())
return defaultValue;
double value;
size_t required_size = sizeof(double);
esp_err_t err = nvs_get_blob(nvs_handle_, key.c_str(), &value, &required_size);
if (err != ESP_OK || required_size != sizeof(double))
{
return defaultValue;
}
return value;
}
std::string PersistenceManager::GetValueImpl(const std::string &key, const std::string &defaultValue) const
{
if (!EnsureInitialized())
return defaultValue;
size_t required_size = 0;
esp_err_t err = nvs_get_str(nvs_handle_, key.c_str(), nullptr, &required_size);
if (err != ESP_OK)
{
return defaultValue;
}
std::string value(required_size - 1, '\0'); // -1 for null terminator
err = nvs_get_str(nvs_handle_, key.c_str(), value.data(), &required_size);
if (err != ESP_OK)
{
return defaultValue;
}
return value;
}

View File

@@ -0,0 +1,338 @@
#include "hal_native/PersistenceManager.h"
#include <SDL3/SDL.h>
#include <utility>
PersistenceManager::PersistenceManager(std::string filename) : m_filename(std::move(filename))
{
if (!SDL_WasInit(SDL_INIT_EVENTS))
{
SDL_Init(SDL_INIT_EVENTS);
}
Load();
}
PersistenceManager::~PersistenceManager()
{
Save();
}
bool PersistenceManager::HasKey(const std::string &key) const
{
return m_data.contains(key);
}
void PersistenceManager::RemoveKey(const std::string &key)
{
m_data.erase(key);
}
void PersistenceManager::Clear()
{
m_data.clear();
}
bool PersistenceManager::Save()
{
return SaveToFile(m_filename);
}
bool PersistenceManager::Load()
{
return LoadFromFile(m_filename);
}
bool PersistenceManager::SaveToFile(const std::string &filename)
{
SDL_IOStream *stream = SDL_IOFromFile(filename.c_str(), "wb");
if (!stream)
{
SDL_Log("Error opening file for writing: %s", SDL_GetError());
return false;
}
const size_t count = m_data.size();
if (SDL_WriteIO(stream, &count, sizeof(count)) != sizeof(count))
{
SDL_Log("Error writing count: %s", SDL_GetError());
SDL_CloseIO(stream);
return false;
}
for (const auto &[key, value] : m_data)
{
size_t keyLength = key.length();
if (SDL_WriteIO(stream, &keyLength, sizeof(keyLength)) != sizeof(keyLength))
{
SDL_Log("Error writing key length: %s", SDL_GetError());
SDL_CloseIO(stream);
return false;
}
if (SDL_WriteIO(stream, key.c_str(), keyLength) != keyLength)
{
SDL_Log("Error writing key: %s", SDL_GetError());
SDL_CloseIO(stream);
return false;
}
if (!WriteValueToStream(stream, value))
{
SDL_CloseIO(stream);
return false;
}
}
SDL_CloseIO(stream);
return true;
}
bool PersistenceManager::LoadFromFile(const std::string &filename)
{
SDL_IOStream *stream = SDL_IOFromFile(filename.c_str(), "rb");
if (!stream)
{
SDL_Log("File not found or error opening: %s", SDL_GetError());
return false;
}
m_data.clear();
size_t count;
if (SDL_ReadIO(stream, &count, sizeof(count)) != sizeof(count))
{
SDL_Log("Error reading count: %s", SDL_GetError());
SDL_CloseIO(stream);
return false;
}
for (size_t i = 0; i < count; ++i)
{
size_t keyLength;
if (SDL_ReadIO(stream, &keyLength, sizeof(keyLength)) != sizeof(keyLength))
{
SDL_Log("Error reading key length: %s", SDL_GetError());
SDL_CloseIO(stream);
return false;
}
std::string key(keyLength, '\0');
if (SDL_ReadIO(stream, key.data(), keyLength) != keyLength)
{
SDL_Log("Error reading key: %s", SDL_GetError());
SDL_CloseIO(stream);
return false;
}
ValueType value;
if (!ReadValueFromStream(stream, value))
{
SDL_CloseIO(stream);
return false;
}
m_data[key] = value;
}
SDL_CloseIO(stream);
return true;
}
void PersistenceManager::SetValueImpl(const std::string &key, bool value)
{
m_data[key] = value;
}
void PersistenceManager::SetValueImpl(const std::string &key, int value)
{
m_data[key] = value;
}
void PersistenceManager::SetValueImpl(const std::string &key, float value)
{
m_data[key] = value;
}
void PersistenceManager::SetValueImpl(const std::string &key, double value)
{
m_data[key] = value;
}
void PersistenceManager::SetValueImpl(const std::string &key, const std::string &value)
{
m_data[key] = value;
}
bool PersistenceManager::GetValueImpl(const std::string &key, bool defaultValue) const
{
if (const auto it = m_data.find(key); it != m_data.end() && std::holds_alternative<bool>(it->second))
{
return std::get<bool>(it->second);
}
return defaultValue;
}
int PersistenceManager::GetValueImpl(const std::string &key, int defaultValue) const
{
if (const auto it = m_data.find(key); it != m_data.end() && std::holds_alternative<int>(it->second))
{
return std::get<int>(it->second);
}
return defaultValue;
}
float PersistenceManager::GetValueImpl(const std::string &key, float defaultValue) const
{
if (const auto it = m_data.find(key); it != m_data.end() && std::holds_alternative<float>(it->second))
{
return std::get<float>(it->second);
}
return defaultValue;
}
double PersistenceManager::GetValueImpl(const std::string &key, double defaultValue) const
{
if (const auto it = m_data.find(key); it != m_data.end() && std::holds_alternative<double>(it->second))
{
return std::get<double>(it->second);
}
return defaultValue;
}
std::string PersistenceManager::GetValueImpl(const std::string &key, const std::string &defaultValue) const
{
if (const auto it = m_data.find(key); it != m_data.end() && std::holds_alternative<std::string>(it->second))
{
return std::get<std::string>(it->second);
}
return defaultValue;
}
bool PersistenceManager::WriteValueToStream(SDL_IOStream *stream, const ValueType &value)
{
const TypeId typeId = GetTypeId(value);
if (SDL_WriteIO(stream, &typeId, sizeof(typeId)) != sizeof(typeId))
{
SDL_Log("Error writing type ID: %s", SDL_GetError());
return false;
}
switch (typeId)
{
case TypeId::BOOL: {
const bool val = std::get<bool>(value);
return SDL_WriteIO(stream, &val, sizeof(val)) == sizeof(val);
}
case TypeId::INT: {
const int val = std::get<int>(value);
return SDL_WriteIO(stream, &val, sizeof(val)) == sizeof(val);
}
case TypeId::FLOAT: {
const float val = std::get<float>(value);
return SDL_WriteIO(stream, &val, sizeof(val)) == sizeof(val);
}
case TypeId::DOUBLE: {
const double val = std::get<double>(value);
return SDL_WriteIO(stream, &val, sizeof(val)) == sizeof(val);
}
case TypeId::STRING: {
const auto &str = std::get<std::string>(value);
const size_t length = str.length();
if (SDL_WriteIO(stream, &length, sizeof(length)) != sizeof(length))
{
return false;
}
return SDL_WriteIO(stream, str.c_str(), length) == length;
}
}
return false;
}
bool PersistenceManager::ReadValueFromStream(SDL_IOStream *stream, ValueType &value)
{
TypeId typeId;
if (SDL_ReadIO(stream, &typeId, sizeof(typeId)) != sizeof(typeId))
{
SDL_Log("Error reading type ID: %s", SDL_GetError());
return false;
}
switch (typeId)
{
case TypeId::BOOL: {
bool val;
if (SDL_ReadIO(stream, &val, sizeof(val)) == sizeof(val))
{
value = val;
return true;
}
break;
}
case TypeId::INT: {
int val;
if (SDL_ReadIO(stream, &val, sizeof(val)) == sizeof(val))
{
value = val;
return true;
}
break;
}
case TypeId::FLOAT: {
float val;
if (SDL_ReadIO(stream, &val, sizeof(val)) == sizeof(val))
{
value = val;
return true;
}
break;
}
case TypeId::DOUBLE: {
double val;
if (SDL_ReadIO(stream, &val, sizeof(val)) == sizeof(val))
{
value = val;
return true;
}
break;
}
case TypeId::STRING: {
size_t length;
if (SDL_ReadIO(stream, &length, sizeof(length)) != sizeof(length))
{
return false;
}
std::string str(length, '\0');
if (SDL_ReadIO(stream, str.data(), length) == length)
{
value = str;
return true;
}
break;
}
}
SDL_Log("Error reading value: %s", SDL_GetError());
return false;
}
PersistenceManager::TypeId PersistenceManager::GetTypeId(const ValueType &value)
{
if (std::holds_alternative<bool>(value))
return TypeId::BOOL;
if (std::holds_alternative<int>(value))
return TypeId::INT;
if (std::holds_alternative<float>(value))
return TypeId::FLOAT;
if (std::holds_alternative<double>(value))
return TypeId::DOUBLE;
if (std::holds_alternative<std::string>(value))
return TypeId::STRING;
return TypeId::BOOL;
}

BIN
firmware/config.dat Normal file

Binary file not shown.

16
firmware/main/CMakeLists.txt Executable file
View File

@@ -0,0 +1,16 @@
idf_component_register(SRCS
main.cpp
app_task.cpp
../components/persistence-manager/src/hal_esp32/PersistenceManager.cpp
button_handling.c
hal/u8g2_esp32_hal.c
INCLUDE_DIRS "."
PRIV_REQUIRES
insa
led-manager
persistence-manager
u8g2
driver
esp_timer
esp_event
)

View File

@@ -0,0 +1,7 @@
menu "Warnemuende Lighthouse"
config WLED_DIN_PIN
int "WLED Data In Pin"
default 14
help
The number of the WLED data in pin.
endmenu

185
firmware/main/app_task.cpp Normal file
View File

@@ -0,0 +1,185 @@
#include "app_task.h"
#include "esp_log.h"
#include "esp_timer.h"
#include "hal/u8g2_esp32_hal.h"
#include "u8g2.h"
#include "button_handling.h"
#include "common/InactivityTracker.h"
#include "hal_esp32/PersistenceManager.h"
#include "ui/ScreenSaver.h"
#include "ui/SplashScreen.h"
#if defined(CONFIG_IDF_TARGET_ESP32S3)
#define PIN_SDA GPIO_NUM_35
#define PIN_SCL GPIO_NUM_36
#else
/// just dummy pins, because of compile check
#define PIN_SDA GPIO_NUM_20
#define PIN_SCL GPIO_NUM_21
#endif
#define PIN_RST GPIO_NUM_NC
static const char *TAG = "app_task";
u8g2_t u8g2;
uint8_t last_value = 0;
menu_options_t options;
uint8_t received_signal;
std::shared_ptr<Widget> m_widget;
std::vector<std::shared_ptr<Widget>> m_history;
std::unique_ptr<InactivityTracker> m_inactivityTracker;
extern QueueHandle_t buttonQueue;
static void setup_screen(void)
{
u8g2_esp32_hal_t u8g2_esp32_hal = U8G2_ESP32_HAL_DEFAULT;
u8g2_esp32_hal.bus.i2c.sda = PIN_SDA;
u8g2_esp32_hal.bus.i2c.scl = PIN_SCL;
u8g2_esp32_hal.reset = PIN_RST;
u8g2_esp32_hal_init(u8g2_esp32_hal);
u8g2_Setup_sh1106_i2c_128x64_noname_f(&u8g2, U8G2_R0, u8g2_esp32_i2c_byte_cb, u8g2_esp32_gpio_and_delay_cb);
u8x8_SetI2CAddress(&u8g2.u8x8, 0x3C * 2);
ESP_LOGI(TAG, "u8g2_InitDisplay");
u8g2_InitDisplay(&u8g2);
ESP_LOGI(TAG, "u8g2_SetPowerSave");
u8g2_SetPowerSave(&u8g2, 0);
}
void setScreen(const std::shared_ptr<Widget> &screen)
{
if (screen != nullptr)
{
m_widget = screen;
m_history.clear();
m_history.emplace_back(m_widget);
m_widget->enter();
}
}
void pushScreen(const std::shared_ptr<Widget> &screen)
{
if (screen != nullptr)
{
if (m_widget)
{
m_widget->pause();
}
m_widget = screen;
m_widget->enter();
m_history.emplace_back(m_widget);
}
}
void popScreen()
{
if (m_history.size() >= 2)
{
m_history.pop_back();
if (m_widget)
{
m_widget->exit();
}
m_widget = m_history.back();
m_widget->resume();
}
}
static void init_ui(void)
{
options = {
.u8g2 = &u8g2,
.setScreen = [](const std::shared_ptr<Widget> &screen) { setScreen(screen); },
.pushScreen = [](const std::shared_ptr<Widget> &screen) { pushScreen(screen); },
.popScreen = []() { popScreen(); },
.onButtonClicked = nullptr,
.persistenceManager = std::make_shared<PersistenceManager>(),
};
m_widget = std::make_shared<SplashScreen>(&options);
m_inactivityTracker = std::make_unique<InactivityTracker>(60000, []() {
auto screensaver = std::make_shared<ScreenSaver>(&options);
options.pushScreen(screensaver);
});
}
static void handle_button(uint8_t button)
{
m_inactivityTracker->reset();
if (m_widget)
{
switch (button)
{
case 1:
m_widget->onButtonClicked(ButtonType::UP);
break;
case 3:
m_widget->onButtonClicked(ButtonType::LEFT);
break;
case 5:
m_widget->onButtonClicked(ButtonType::RIGHT);
break;
case 6:
m_widget->onButtonClicked(ButtonType::DOWN);
break;
case 16:
m_widget->onButtonClicked(ButtonType::BACK);
break;
case 18:
m_widget->onButtonClicked(ButtonType::SELECT);
break;
default:
ESP_LOGI(TAG, "Unhandled button: %u", button);
break;
}
}
}
void app_task(void *args)
{
setup_screen();
setup_buttons();
init_ui();
auto oldTime = esp_timer_get_time();
while (true)
{
u8g2_ClearBuffer(&u8g2);
if (m_widget != nullptr)
{
auto currentTime = esp_timer_get_time();
auto delta = currentTime - oldTime;
oldTime = currentTime;
uint64_t deltaMs = delta / 1000;
m_widget->update(deltaMs);
m_widget->render();
m_inactivityTracker->update(deltaMs);
}
u8g2_SendBuffer(&u8g2);
if (xQueueReceive(buttonQueue, &received_signal, pdMS_TO_TICKS(10)) == pdTRUE)
{
handle_button(received_signal);
}
}
cleanup_buttons();
}

3
firmware/main/app_task.h Normal file
View File

@@ -0,0 +1,3 @@
#pragma once
void app_task(void *args);

View File

@@ -0,0 +1,88 @@
#include "button_handling.h"
#include "button_gpio.h"
#include "common.h"
#include "driver/gpio.h"
#include "esp_err.h"
#include "esp_log.h"
#include "esp_mac.h"
#include "freertos/FreeRTOS.h"
#include "iot_button.h"
#include "sdkconfig.h"
#include <stdio.h>
#include <string.h>
static const char *TAG = "button_handling";
const uint8_t gpios[] = {BUTTON_DOWN, BUTTON_UP, BUTTON_LEFT, BUTTON_RIGHT, BUTTON_SELECT, BUTTON_BACK};
typedef struct
{
uint8_t gpio;
} button_user_data_t;
static button_user_data_t button_data[6];
QueueHandle_t buttonQueue = NULL;
static void button_event_cb(void *arg, void *usr_data)
{
if (buttonQueue == NULL)
{
ESP_LOGE(TAG, "Button queue not initialized!");
return;
}
button_user_data_t *data = (button_user_data_t *)usr_data;
uint8_t gpio_num = data->gpio;
ESP_LOGI(TAG, "Button pressed on GPIO %d", gpio_num);
if (xQueueSend(buttonQueue, &gpio_num, 0) != pdTRUE)
{
ESP_LOGW(TAG, "Failed to send button press to queue");
}
}
static void create_button(uint8_t gpio, int index)
{
const button_config_t btn_cfg = {0};
const button_gpio_config_t btn_gpio_cfg = {
.gpio_num = gpio,
.active_level = 0,
.enable_power_save = true,
};
button_handle_t gpio_btn = NULL;
const esp_err_t ret = iot_button_new_gpio_device(&btn_cfg, &btn_gpio_cfg, &gpio_btn);
if (ret != ESP_OK)
{
ESP_LOGE(TAG, "Button create failed");
}
button_data[index].gpio = gpio;
iot_button_register_cb(gpio_btn, BUTTON_SINGLE_CLICK, NULL, button_event_cb, &button_data[index]);
}
void setup_buttons(void)
{
buttonQueue = xQueueCreate(10, sizeof(uint8_t));
if (buttonQueue == NULL)
{
ESP_LOGE(TAG, "Failed to create button queue");
return;
}
ESP_LOGI(TAG, "Button queue created successfully");
for (int i = 0; i < sizeof(gpios) / sizeof(gpios[0]); i++)
{
create_button(gpios[i], i);
}
}
// Cleanup function (optional)
void cleanup_buttons(void)
{
if (buttonQueue != NULL)
{
vQueueDelete(buttonQueue);
}
}

View File

@@ -0,0 +1,11 @@
#pragma once
#ifdef __cplusplus
extern "C"
{
#endif
void setup_buttons(void);
void cleanup_buttons(void);
#ifdef __cplusplus
}
#endif

8
firmware/main/common.h Normal file
View File

@@ -0,0 +1,8 @@
#pragma once
#define BUTTON_UP GPIO_NUM_1
#define BUTTON_DOWN GPIO_NUM_6
#define BUTTON_LEFT GPIO_NUM_3
#define BUTTON_RIGHT GPIO_NUM_5
#define BUTTON_SELECT GPIO_NUM_18
#define BUTTON_BACK GPIO_NUM_16

View File

@@ -0,0 +1,262 @@
#include <stdio.h>
#include <string.h>
#include "esp_log.h"
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "u8g2_esp32_hal.h"
static const char* TAG = "u8g2_hal";
static const unsigned int I2C_TIMEOUT_MS = 1000;
static spi_device_handle_t handle_spi; // SPI handle.
static i2c_cmd_handle_t handle_i2c; // I2C handle.
static u8g2_esp32_hal_t u8g2_esp32_hal; // HAL state data.
#define HOST SPI2_HOST
#undef ESP_ERROR_CHECK
#define ESP_ERROR_CHECK(x) \
do { \
esp_err_t rc = (x); \
if (rc != ESP_OK) { \
ESP_LOGE("err", "esp_err_t = %d", rc); \
assert(0 && #x); \
} \
} while (0);
/*
* Initialze the ESP32 HAL.
*/
void u8g2_esp32_hal_init(u8g2_esp32_hal_t u8g2_esp32_hal_param) {
u8g2_esp32_hal = u8g2_esp32_hal_param;
} // u8g2_esp32_hal_init
/*
* HAL callback function as prescribed by the U8G2 library. This callback is
* invoked to handle SPI communications.
*/
uint8_t u8g2_esp32_spi_byte_cb(u8x8_t* u8x8,
uint8_t msg,
uint8_t arg_int,
void* arg_ptr) {
ESP_LOGD(TAG, "spi_byte_cb: Received a msg: %d, arg_int: %d, arg_ptr: %p",
msg, arg_int, arg_ptr);
switch (msg) {
case U8X8_MSG_BYTE_SET_DC:
if (u8g2_esp32_hal.dc != U8G2_ESP32_HAL_UNDEFINED) {
gpio_set_level(u8g2_esp32_hal.dc, arg_int);
}
break;
case U8X8_MSG_BYTE_INIT: {
if (u8g2_esp32_hal.bus.spi.clk == U8G2_ESP32_HAL_UNDEFINED ||
u8g2_esp32_hal.bus.spi.mosi == U8G2_ESP32_HAL_UNDEFINED ||
u8g2_esp32_hal.bus.spi.cs == U8G2_ESP32_HAL_UNDEFINED) {
break;
}
spi_bus_config_t bus_config;
memset(&bus_config, 0, sizeof(spi_bus_config_t));
bus_config.sclk_io_num = u8g2_esp32_hal.bus.spi.clk; // CLK
bus_config.mosi_io_num = u8g2_esp32_hal.bus.spi.mosi; // MOSI
bus_config.miso_io_num = GPIO_NUM_NC; // MISO
bus_config.quadwp_io_num = GPIO_NUM_NC; // Not used
bus_config.quadhd_io_num = GPIO_NUM_NC; // Not used
// ESP_LOGI(TAG, "... Initializing bus.");
ESP_ERROR_CHECK(spi_bus_initialize(HOST, &bus_config, 1));
spi_device_interface_config_t dev_config;
dev_config.address_bits = 0;
dev_config.command_bits = 0;
dev_config.dummy_bits = 0;
dev_config.mode = 0;
dev_config.duty_cycle_pos = 0;
dev_config.cs_ena_posttrans = 0;
dev_config.cs_ena_pretrans = 0;
dev_config.clock_speed_hz = 10000;
dev_config.spics_io_num = u8g2_esp32_hal.bus.spi.cs;
dev_config.flags = 0;
dev_config.queue_size = 200;
dev_config.pre_cb = NULL;
dev_config.post_cb = NULL;
// ESP_LOGI(TAG, "... Adding device bus.");
ESP_ERROR_CHECK(spi_bus_add_device(HOST, &dev_config, &handle_spi));
break;
}
case U8X8_MSG_BYTE_SEND: {
spi_transaction_t trans_desc;
trans_desc.addr = 0;
trans_desc.cmd = 0;
trans_desc.flags = 0;
trans_desc.length = 8 * arg_int; // Number of bits NOT number of bytes.
trans_desc.rxlength = 0;
trans_desc.tx_buffer = arg_ptr;
trans_desc.rx_buffer = NULL;
// ESP_LOGI(TAG, "... Transmitting %d bytes.", arg_int);
ESP_ERROR_CHECK(spi_device_transmit(handle_spi, &trans_desc));
break;
}
}
return 0;
} // u8g2_esp32_spi_byte_cb
/*
* HAL callback function as prescribed by the U8G2 library. This callback is
* invoked to handle I2C communications.
*/
uint8_t u8g2_esp32_i2c_byte_cb(u8x8_t* u8x8,
uint8_t msg,
uint8_t arg_int,
void* arg_ptr) {
ESP_LOGD(TAG, "i2c_cb: Received a msg: %d, arg_int: %d, arg_ptr: %p", msg,
arg_int, arg_ptr);
switch (msg) {
case U8X8_MSG_BYTE_SET_DC: {
if (u8g2_esp32_hal.dc != U8G2_ESP32_HAL_UNDEFINED) {
gpio_set_level(u8g2_esp32_hal.dc, arg_int);
}
break;
}
case U8X8_MSG_BYTE_INIT: {
if (u8g2_esp32_hal.bus.i2c.sda == U8G2_ESP32_HAL_UNDEFINED ||
u8g2_esp32_hal.bus.i2c.scl == U8G2_ESP32_HAL_UNDEFINED) {
break;
}
i2c_config_t conf = {0};
conf.mode = I2C_MODE_MASTER;
ESP_LOGI(TAG, "sda_io_num %d", u8g2_esp32_hal.bus.i2c.sda);
conf.sda_io_num = u8g2_esp32_hal.bus.i2c.sda;
conf.sda_pullup_en = GPIO_PULLUP_ENABLE;
ESP_LOGI(TAG, "scl_io_num %d", u8g2_esp32_hal.bus.i2c.scl);
conf.scl_io_num = u8g2_esp32_hal.bus.i2c.scl;
conf.scl_pullup_en = GPIO_PULLUP_ENABLE;
ESP_LOGI(TAG, "clk_speed %d", I2C_MASTER_FREQ_HZ);
conf.master.clk_speed = I2C_MASTER_FREQ_HZ;
ESP_LOGI(TAG, "i2c_param_config %d", conf.mode);
ESP_ERROR_CHECK(i2c_param_config(I2C_MASTER_NUM, &conf));
ESP_LOGI(TAG, "i2c_driver_install %d", I2C_MASTER_NUM);
ESP_ERROR_CHECK(i2c_driver_install(I2C_MASTER_NUM, conf.mode,
I2C_MASTER_RX_BUF_DISABLE,
I2C_MASTER_TX_BUF_DISABLE, 0));
break;
}
case U8X8_MSG_BYTE_SEND: {
uint8_t* data_ptr = (uint8_t*)arg_ptr;
ESP_LOG_BUFFER_HEXDUMP(TAG, data_ptr, arg_int, ESP_LOG_VERBOSE);
while (arg_int > 0) {
ESP_ERROR_CHECK(
i2c_master_write_byte(handle_i2c, *data_ptr, ACK_CHECK_EN));
data_ptr++;
arg_int--;
}
break;
}
case U8X8_MSG_BYTE_START_TRANSFER: {
uint8_t i2c_address = u8x8_GetI2CAddress(u8x8);
handle_i2c = i2c_cmd_link_create();
ESP_LOGD(TAG, "Start I2C transfer to %02X.", i2c_address >> 1);
ESP_ERROR_CHECK(i2c_master_start(handle_i2c));
ESP_ERROR_CHECK(i2c_master_write_byte(
handle_i2c, i2c_address | I2C_MASTER_WRITE, ACK_CHECK_EN));
break;
}
case U8X8_MSG_BYTE_END_TRANSFER: {
ESP_LOGD(TAG, "End I2C transfer.");
ESP_ERROR_CHECK(i2c_master_stop(handle_i2c));
ESP_ERROR_CHECK(i2c_master_cmd_begin(I2C_MASTER_NUM, handle_i2c,
pdMS_TO_TICKS(I2C_TIMEOUT_MS)));
i2c_cmd_link_delete(handle_i2c);
break;
}
}
return 0;
} // u8g2_esp32_i2c_byte_cb
/*
* HAL callback function as prescribed by the U8G2 library. This callback is
* invoked to handle callbacks for GPIO and delay functions.
*/
uint8_t u8g2_esp32_gpio_and_delay_cb(u8x8_t* u8x8,
uint8_t msg,
uint8_t arg_int,
void* arg_ptr) {
ESP_LOGD(TAG,
"gpio_and_delay_cb: Received a msg: %d, arg_int: %d, arg_ptr: %p",
msg, arg_int, arg_ptr);
switch (msg) {
// Initialize the GPIO and DELAY HAL functions. If the pins for DC and
// RESET have been specified then we define those pins as GPIO outputs.
case U8X8_MSG_GPIO_AND_DELAY_INIT: {
uint64_t bitmask = 0;
if (u8g2_esp32_hal.dc != U8G2_ESP32_HAL_UNDEFINED) {
bitmask = bitmask | (1ull << u8g2_esp32_hal.dc);
}
if (u8g2_esp32_hal.reset != U8G2_ESP32_HAL_UNDEFINED) {
bitmask = bitmask | (1ull << u8g2_esp32_hal.reset);
}
if (u8g2_esp32_hal.bus.spi.cs != U8G2_ESP32_HAL_UNDEFINED) {
bitmask = bitmask | (1ull << u8g2_esp32_hal.bus.spi.cs);
}
if (bitmask == 0) {
break;
}
gpio_config_t gpioConfig;
gpioConfig.pin_bit_mask = bitmask;
gpioConfig.mode = GPIO_MODE_OUTPUT;
gpioConfig.pull_up_en = GPIO_PULLUP_DISABLE;
gpioConfig.pull_down_en = GPIO_PULLDOWN_ENABLE;
gpioConfig.intr_type = GPIO_INTR_DISABLE;
gpio_config(&gpioConfig);
break;
}
// Set the GPIO reset pin to the value passed in through arg_int.
case U8X8_MSG_GPIO_RESET:
if (u8g2_esp32_hal.reset != U8G2_ESP32_HAL_UNDEFINED) {
gpio_set_level(u8g2_esp32_hal.reset, arg_int);
}
break;
// Set the GPIO client select pin to the value passed in through arg_int.
case U8X8_MSG_GPIO_CS:
if (u8g2_esp32_hal.bus.spi.cs != U8G2_ESP32_HAL_UNDEFINED) {
gpio_set_level(u8g2_esp32_hal.bus.spi.cs, arg_int);
}
break;
// Set the Software I²C pin to the value passed in through arg_int.
case U8X8_MSG_GPIO_I2C_CLOCK:
if (u8g2_esp32_hal.bus.i2c.scl != U8G2_ESP32_HAL_UNDEFINED) {
gpio_set_level(u8g2_esp32_hal.bus.i2c.scl, arg_int);
// printf("%c",(arg_int==1?'C':'c'));
}
break;
// Set the Software I²C pin to the value passed in through arg_int.
case U8X8_MSG_GPIO_I2C_DATA:
if (u8g2_esp32_hal.bus.i2c.sda != U8G2_ESP32_HAL_UNDEFINED) {
gpio_set_level(u8g2_esp32_hal.bus.i2c.sda, arg_int);
// printf("%c",(arg_int==1?'D':'d'));
}
break;
// Delay for the number of milliseconds passed in through arg_int.
case U8X8_MSG_DELAY_MILLI:
vTaskDelay(arg_int / portTICK_PERIOD_MS);
break;
}
return 0;
} // u8g2_esp32_gpio_and_delay_cb

View File

@@ -0,0 +1,92 @@
#ifndef U8G2_ESP32_HAL_H
#define U8G2_ESP32_HAL_H
/*
* u8g2_esp32_hal.h
*
* Created on: Feb 12, 2017
* Author: kolban
*/
#ifndef U8G2_ESP32_HAL_H_
#define U8G2_ESP32_HAL_H_
#include "u8g2.h"
#include "driver/gpio.h"
#include "driver/i2c.h"
#include "driver/spi_master.h"
#define U8G2_ESP32_HAL_UNDEFINED GPIO_NUM_NC
#if SOC_I2C_NUM > 1
#define I2C_MASTER_NUM I2C_NUM_1 // I2C port number for master dev
#else
#define I2C_MASTER_NUM I2C_NUM_0 // I2C port number for master dev
#endif
#define I2C_MASTER_TX_BUF_DISABLE 0 // I2C master do not need buffer
#define I2C_MASTER_RX_BUF_DISABLE 0 // I2C master do not need buffer
#define I2C_MASTER_FREQ_HZ 400000 // I2C master clock frequency
#define ACK_CHECK_EN 0x1 // I2C master will check ack from slave
#define ACK_CHECK_DIS 0x0 // I2C master will not check ack from slave
/** @public
* HAL configuration structure.
*/
typedef struct
{
union {
/* SPI settings. */
struct
{
/* GPIO num for clock. */
gpio_num_t clk;
/* GPIO num for SPI mosi. */
gpio_num_t mosi;
/* GPIO num for SPI slave/chip select. */
gpio_num_t cs;
} spi;
/* I2C settings. */
struct
{
/* GPIO num for I2C data. */
gpio_num_t sda;
/* GPIO num for I2C clock. */
gpio_num_t scl;
} i2c;
} bus;
/* GPIO num for reset. */
gpio_num_t reset;
/* GPIO num for DC. */
gpio_num_t dc;
} u8g2_esp32_hal_t;
/**
* Construct a default HAL configuration with all fields undefined.
*/
#define U8G2_ESP32_HAL_DEFAULT \
{.bus = {.spi = {.clk = U8G2_ESP32_HAL_UNDEFINED, \
.mosi = U8G2_ESP32_HAL_UNDEFINED, \
.cs = U8G2_ESP32_HAL_UNDEFINED}}, \
.reset = U8G2_ESP32_HAL_UNDEFINED, \
.dc = U8G2_ESP32_HAL_UNDEFINED}
#ifdef __cplusplus
extern "C"
{
#endif
/**
* Initialize the HAL with the given configuration.
*
* @see u8g2_esp32_hal_t
* @see U8G2_ESP32_HAL_DEFAULT
*/
void u8g2_esp32_hal_init(u8g2_esp32_hal_t u8g2_esp32_hal_param);
uint8_t u8g2_esp32_spi_byte_cb(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr);
uint8_t u8g2_esp32_i2c_byte_cb(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr);
uint8_t u8g2_esp32_gpio_and_delay_cb(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr);
#ifdef __cplusplus
}
#endif
#endif /* U8G2_ESP32_HAL_H_ */
#endif

View File

@@ -0,0 +1,6 @@
dependencies:
u8g2:
git: https://github.com/olikraus/u8g2.git
# u8g2_hal:
# git: https://github.com/mkfrey/u8g2-hal-esp-idf.git
espressif/button: ^4.1.3

20
firmware/main/main.cpp Normal file
View File

@@ -0,0 +1,20 @@
#include "app_task.h"
#include "esp_event.h"
#include "freertos/FreeRTOS.h"
#include "led_manager.h"
#include "sdkconfig.h"
#ifdef __cplusplus
extern "C"
{
#endif
void app_main(void)
{
xTaskCreatePinnedToCore(app_task, "main_loop", 4096, NULL, tskIDLE_PRIORITY + 1, NULL, portNUM_PROCESSORS - 1);
wled_init();
register_handler();
}
#ifdef __cplusplus
}
#endif

158
firmware/main/setup.c Normal file
View File

@@ -0,0 +1,158 @@
#include "setup.h"
#include "driver/gpio.h"
#include "esp_err.h"
#include "esp_log.h"
#include "esp_timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "freertos/task.h"
#include <stdio.h>
#include <string.h>
#include "driver/rmt_encoder.h"
#include "driver/rmt_tx.h"
#include "u8g2.h"
#include "u8g2_esp32_hal.h"
#include "button_handling.h"
#include "common.h"
#define PIN_SDA GPIO_NUM_35
#define PIN_SCL GPIO_NUM_36
#define PIN_RST GPIO_NUM_NC
#define WLED_GPIO GPIO_NUM_47
#define WLED_RMT_CHANNEL RMT_CHANNEL_0
#define WLED_RESOLUTION_HZ (10000000)
#define WLED_ON_DURATION_MS (100)
#define NUM_LEDS (1)
uint8_t last_value = 0;
static const char *TAG = "main";
extern QueueHandle_t buttonQueue;
rmt_channel_handle_t rmt_led_chan = NULL;
rmt_encoder_handle_t rmt_led_encoder = NULL;
bool wled_is_on = false;
int64_t wled_turn_off_time = 0;
u8g2_t u8g2;
uint8_t received_signal;
static void init_rmt_ws2812b(void)
{
ESP_LOGI(TAG, "Initialize RMT TX Channel for WS2812B on GPIO %d", WLED_GPIO);
rmt_tx_channel_config_t tx_chan_config = {
.gpio_num = WLED_GPIO,
.clk_src = RMT_CLK_SRC_DEFAULT,
.resolution_hz = WLED_RESOLUTION_HZ,
.mem_block_symbols = 64,
.trans_queue_depth = 4,
.intr_priority = 0,
};
ESP_ERROR_CHECK(rmt_new_tx_channel(&tx_chan_config, &rmt_led_chan));
ESP_LOGI(TAG, "Install RMT Bytes Encoder");
rmt_bytes_encoder_config_t bytes_encoder_config = {
.bit0 = {.duration0 = 4, .level0 = 1, .duration1 = 8, .level1 = 0},
.bit1 = {.duration0 = 8, .level0 = 1, .duration1 = 4, .level1 = 0},
.flags = {.msb_first = 1}};
ESP_ERROR_CHECK(rmt_new_bytes_encoder(&bytes_encoder_config, &rmt_led_encoder));
ESP_LOGI(TAG, "Activate RMT TX Kanal");
ESP_ERROR_CHECK(rmt_enable(rmt_led_chan));
}
static void set_wled_color(uint8_t r, uint8_t g, uint8_t b)
{
if (!rmt_led_chan || !rmt_led_encoder)
{
ESP_LOGE(TAG, "RMT Channel or Encoder not initialized!");
return;
}
size_t buffer_size = 3 * NUM_LEDS;
uint8_t led_data[buffer_size];
for (int i = 0; i < NUM_LEDS; i++)
{
led_data[i * 3 + 0] = g;
led_data[i * 3 + 1] = r;
led_data[i * 3 + 2] = b;
}
rmt_transmit_config_t tx_config = {
.loop_count = 0,
};
esp_err_t ret = rmt_transmit(rmt_led_chan, rmt_led_encoder, led_data, sizeof(led_data), &tx_config);
if (ret != ESP_OK)
{
ESP_LOGE(TAG, "RMT Transmit failed: %s", esp_err_to_name(ret));
}
ESP_ERROR_CHECK(rmt_tx_wait_all_done(rmt_led_chan, pdMS_TO_TICKS(100)));
}
void setup(void)
{
setup_buttons();
u8g2_esp32_hal_t u8g2_esp32_hal = U8G2_ESP32_HAL_DEFAULT;
u8g2_esp32_hal.bus.i2c.sda = PIN_SDA;
u8g2_esp32_hal.bus.i2c.scl = PIN_SCL;
u8g2_esp32_hal.reset = PIN_RST;
u8g2_esp32_hal_init(u8g2_esp32_hal);
u8g2_Setup_sh1106_i2c_128x64_noname_f(&u8g2, U8G2_R0, u8g2_esp32_i2c_byte_cb, u8g2_esp32_gpio_and_delay_cb);
u8x8_SetI2CAddress(&u8g2.u8x8, 0x3C * 2);
ESP_LOGI(TAG, "u8g2_InitDisplay");
u8g2_InitDisplay(&u8g2);
ESP_LOGI(TAG, "u8g2_SetPowerSave");
u8g2_SetPowerSave(&u8g2, 0);
init_rmt_ws2812b();
set_wled_color(0, 0, 0);
ESP_LOGI(TAG, "Start of main loop. Waiting for button press...");
}
void loop(void)
{
u8g2_ClearBuffer(&u8g2);
u8g2_SetFont(&u8g2, u8g2_font_ncenB10_tr);
u8g2_DrawStr(&u8g2, 5, 20, "Ready!");
char count_str[50];
snprintf(count_str, sizeof(count_str), "Signal Value: %u", last_value);
u8g2_DrawStr(&u8g2, 5, 45, count_str);
u8g2_SendBuffer(&u8g2);
if (xQueueReceive(buttonQueue, &received_signal, pdMS_TO_TICKS(10)) == pdTRUE)
{
ESP_LOGI(TAG, "Button event from Queue received!");
last_value = received_signal;
u8g2_ClearBuffer(&u8g2);
u8g2_DrawStr(&u8g2, 5, 20, "Pressed!");
u8g2_SetFont(&u8g2, u8g2_font_ncenB10_tr);
char count_str[50];
snprintf(count_str, sizeof(count_str), "Signal Value: %u", last_value);
u8g2_DrawStr(&u8g2, 5, 45, count_str);
u8g2_SendBuffer(&u8g2);
ESP_LOGI(TAG, "Display refreshed with signal value: %u", last_value);
ESP_LOGD(TAG, "Switch WLED ON");
set_wled_color(255, 0, 255);
wled_is_on = true;
wled_turn_off_time = esp_timer_get_time() + (WLED_ON_DURATION_MS * 1000);
}
if (wled_is_on && esp_timer_get_time() >= wled_turn_off_time)
{
ESP_LOGD(TAG, "Switch WLED OFF");
set_wled_color(0, 0, 0);
wled_is_on = false;
}
}

71
firmware/main/setup.h Normal file
View File

@@ -0,0 +1,71 @@
/**
* @file setup.h
* @brief System initialization and main loop declarations for embedded application
* @details This header defines the core system initialization and main loop functions
* required for embedded ESP32 applications. It provides the essential entry
* points for hardware setup, system configuration, and continuous operation
* management following standard embedded system patterns.
* @author System Control Team
* @date 2025-06-20
*/
#pragma once
#ifdef __cplusplus
extern "C"
{
#endif
/**
* @brief Initializes all system components and hardware peripherals
*
* @details This function performs complete system initialization including:
* - Hardware peripheral configuration (GPIO, I2C, SPI, etc.)
* - Display system initialization
* - Button and input device setup
* - Communication subsystem initialization
* - Memory and storage system preparation
* - Application-specific component initialization
*
* This function is called once during system startup before entering
* the main application loop. It ensures all required subsystems are
* properly configured and ready for operation.
*
* @pre System must be in a clean startup state
* @post All system components are initialized and ready for operation
*
* @note This function must complete successfully before loop() is called
* @note Any initialization failures should be handled gracefully with
* appropriate error reporting or system recovery
*
* @see loop() for the main application execution function
*/
void setup(void);
/**
* @brief Main application execution loop for continuous operation
*
* @details This function contains the main application logic that executes
* continuously after system initialization. It typically handles:
* - User input processing and event handling
* - Display updates and rendering operations
* - System state management and transitions
* - Background tasks and periodic operations
* - Communication handling and data processing
* - Power management and system monitoring
*
* The loop function is called repeatedly in an infinite cycle, providing
* the main execution context for the embedded application. It should be
* designed to execute efficiently without blocking to maintain system
* responsiveness.
*
* @note This function runs continuously and should not block indefinitely
* @note All operations within this function should be non-blocking or
* use appropriate task scheduling for time-consuming operations
* @note The function should handle all runtime errors gracefully
*
* @see setup() for system initialization before loop execution
*/
void loop(void);
#ifdef __cplusplus
}
#endif

7
firmware/partitions.csv Normal file
View File

@@ -0,0 +1,7 @@
# Name , Type , SubType , Offset , Size , Flags
nvs , data , nvs , 0x9000 , 20k ,
otadata , data , ota , 0xe000 , 8k ,
app0 , app , ota_0 , 0x10000 , 1024k ,
app1 , app , ota_1 , , 1024k ,
spiffs , data , spiffs , , 1536k ,
coredump , data , coredump , , 64k ,
1 # Name Type SubType Offset Size Flags
2 nvs data nvs 0x9000 20k
3 otadata data ota 0xe000 8k
4 app0 app ota_0 0x10000 1024k
5 app1 app ota_1 1024k
6 spiffs data spiffs 1536k
7 coredump data coredump 64k

19
firmware/sdkconfig.defaults Executable file
View File

@@ -0,0 +1,19 @@
# activate Bluetooth Low Energy (BLE)
CONFIG_BT_ENABLED=y
CONFIG_BT_NIMBLE_ENABLED=y
# Logging
CONFIG_LOG_DEFAULT_LEVEL_INFO=y
CONFIG_LOG_DEFAULT_LEVEL=3
CONFIG_LOG_MAXIMUM_LEVEL=3
# Flash Size
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
CONFIG_ESPTOOLPY_FLASHSIZE="4MB"
# Partitions
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_OFFSET=0x8000
CONFIG_PARTITION_TABLE_MD5=y

View File

@@ -0,0 +1,2 @@
# default ESP target
CONFIG_IDF_TARGET="esp32c3"

View File

@@ -0,0 +1,2 @@
# default ESP target
CONFIG_IDF_TARGET="esp32h2"

View File

@@ -0,0 +1,2 @@
# default ESP target
CONFIG_IDF_TARGET="esp32p4"

View File

@@ -0,0 +1,2 @@
# default ESP target
CONFIG_IDF_TARGET="esp32s3"

View File

26
firmware/src/Common.cpp Normal file
View File

@@ -0,0 +1,26 @@
#include "Common.h"
#include <SDL3/SDL.h>
auto CreateWindow(const char *title, const int width, const int height) -> Window *
{
constexpr uint32_t window_flag = SDL_WINDOW_HIDDEN;
const auto window = SDL_CreateWindow(title, width, height, window_flag);
if (window == nullptr)
{
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Couldn't create window", SDL_GetError(), nullptr);
return nullptr;
}
const SDL_PropertiesID props = SDL_CreateProperties();
SDL_SetPointerProperty(props, SDL_PROP_RENDERER_CREATE_WINDOW_POINTER, window);
SDL_SetNumberProperty(props, SDL_PROP_RENDERER_CREATE_OUTPUT_COLORSPACE_NUMBER, SDL_COLORSPACE_SRGB_LINEAR);
if (const auto renderer = SDL_CreateRendererWithProperties(props); renderer == nullptr)
{
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Couldn't create renderer", SDL_GetError(), nullptr);
return nullptr;
}
return new Window(window);
}

60
firmware/src/Common.h Normal file
View File

@@ -0,0 +1,60 @@
/**
* @file Common.h
* @brief Common utility functions and window management for application framework
* @details This header defines common utility functions that are shared across
* the application framework. It provides essential functionality for
* window creation and management, serving as a bridge between the
* application layer and the underlying windowing system.
* @author System Control Team
* @date 2025-06-20
*/
#pragma once
#include "model/Window.h"
/**
* @brief Creates a new window with specified title and dimensions
* @param title Null-terminated string containing the window title text
* @param width Window width in pixels
* @param height Window height in pixels
* @return Pointer to the newly created Window object, or nullptr on failure
*
* @pre title must not be nullptr and should contain valid display text
* @pre width and height must be positive values within system display limits
* @post A new Window object is allocated and initialized with the specified parameters
* @post The returned Window pointer is ready for use with window management functions
*
* @details This function creates a new Window instance with the specified
* title and dimensions. It handles the underlying window system
* initialization, memory allocation, and setup required to create
* a functional window object.
*
* The window creation process includes:
* - Memory allocation for the Window structure
* - Initialization of window properties (title, dimensions, state)
* - Registration with the window management system
* - Setup of default window behavior and event handling
*
* The returned window pointer can be used with other window management
* functions to display content, handle events, and manage the window
* lifecycle. The caller is responsible for properly managing the window
* lifetime and ensuring proper cleanup when the window is no longer needed.
*
* @note The returned pointer must be properly managed by the caller
* @note Window resources should be freed when no longer needed
* @note The title string is copied internally and can be safely modified
* or freed after this function returns
*
* @see Window class for window object interface and methods
*
* Example usage:
* @code
* auto* window = CreateWindow("System Control", 800, 600);
* if (window != nullptr) {
* // Use the window...
* // Clean up when done
* }
* @endcode
*/
auto CreateWindow(const char *title, int width, int height) -> Window *;

10
firmware/src/Version.h Normal file
View File

@@ -0,0 +1,10 @@
#pragma once
#include "string"
const std::string MyProject = "system_control";
const std::string MyProjectVersion = "0.0.1";
constexpr uint8_t MyProjectVersionMajor = 0;
constexpr uint8_t MyProjectVersionMinor = 0;
constexpr uint8_t MyProjectVersionPatch = 1;
const std::string MyProjectBuildDate = "";

10
firmware/src/Version.h.in Normal file
View File

@@ -0,0 +1,10 @@
#pragma once
#include "string"
const std::string MyProject = "@PROJECT_NAME@";
const std::string MyProjectVersion = "@PROJECT_VERSION@";
constexpr uint8_t MyProjectVersionMajor = @PROJECT_VERSION_MAJOR@;
constexpr uint8_t MyProjectVersionMinor = @PROJECT_VERSION_MINOR@;
constexpr uint8_t MyProjectVersionPatch = @PROJECT_VERSION_PATCH@;
const std::string MyProjectBuildDate = "@PROJECT_BUILD_DATE@";

View File

@@ -0,0 +1,110 @@
#include "debug/debug_overlay.h"
#include "Common.h"
#include "Matrix.h"
#include "Version.h"
#include "imgui.h"
#include "imgui_impl_sdl3.h"
#include <imgui_impl_sdlrenderer3.h>
namespace DebugOverlay
{
void Init(const AppContext *context)
{
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO &io{ImGui::GetIO()};
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
ImGui::StyleColorsDark();
ImGui_ImplSDL3_InitForSDLRenderer(context->MainWindow(), context->MainRenderer());
ImGui_ImplSDLRenderer3_Init(context->MainRenderer());
}
void Update(AppContext *context, const SDL_Event *event)
{
ImGui_ImplSDL3_ProcessEvent(event);
if (show_led_matrix)
{
if (!context->LedMatrixRenderer())
{
const auto win = CreateWindow("LED Matrix", width * 50, height * 50);
SDL_SetWindowFocusable(win->window(), false);
SDL_SetRenderVSync(win->renderer(), SDL_RENDERER_VSYNC_ADAPTIVE);
SDL_SetWindowPosition(win->window(), 0, 0);
SDL_ShowWindow(win->window());
const auto windowId = SDL_GetWindowID(win->window());
context->SetMatrix(new Matrix(windowId, win->renderer(), width, height));
}
}
else
{
if (context->LedMatrixRenderer())
{
int window_count = 0;
if (SDL_Window **windows = SDL_GetWindows(&window_count))
{
for (int i = 0; i < window_count; ++i)
{
if (SDL_Window *window = windows[i]; context->LedMatrixId() == SDL_GetWindowID(window))
{
SDL_DestroyRenderer(context->LedMatrixRenderer());
SDL_DestroyWindow(window);
break;
}
}
SDL_free(windows);
}
SDL_DestroyRenderer(context->LedMatrixRenderer());
context->SetMatrix(nullptr);
}
}
}
void Render(const AppContext *context)
{
ImGui_ImplSDLRenderer3_NewFrame();
ImGui_ImplSDL3_NewFrame();
ImGui::NewFrame();
if (show_debug_window && ImGui::BeginMainMenuBar())
{
if (ImGui::BeginMenu("Config"))
{
ImGui::Checkbox("Show LED Matrix", &show_led_matrix);
ImGui::Checkbox("Show Unhandled Events", &show_unhandled_events);
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Help"))
{
ImGui::Text("FPS: %.2f", ImGui::GetIO().Framerate);
ImGui::SeparatorText("App Info");
ImGui::Text("Project: %s", MyProject.c_str());
ImGui::Text("Version: %s", MyProjectVersion.c_str());
ImGui::Text("Build Date: %s", MyProjectBuildDate.c_str());
ImGui::Text("ImGui Version: %s", ImGui::GetVersion());
ImGui::EndMenu();
}
ImGui::EndMainMenuBar();
}
// Rendering
ImGui::Render();
ImGui_ImplSDLRenderer3_RenderDrawData(ImGui::GetDrawData(), context->MainRenderer());
}
void Cleanup()
{
ImGui_ImplSDLRenderer3_Shutdown();
ImGui_ImplSDL3_Shutdown();
ImGui::DestroyContext();
}
} // namespace DebugOverlay

View File

@@ -0,0 +1,23 @@
#pragma once
#include <SDL3/SDL.h>
#include "model/AppContext.h"
namespace DebugOverlay
{
inline bool show_debug_window = false;
inline bool show_unhandled_events = false;
inline bool show_led_matrix = false;
constexpr auto width = 8;
constexpr auto height = 8;
void Init(const AppContext *context);
void Update(AppContext *context, const SDL_Event *event);
void Render(const AppContext *context);
void Cleanup();
} // namespace DebugOverlay

View File

@@ -0,0 +1,11 @@
#pragma once
#include "u8g2.h"
#define U8G2_SCREEN_WIDTH (128)
#define U8G2_SCREEN_HEIGHT (64)
#define U8G2_SCREEN_FACTOR (3)
#define U8G2_SCREEN_PADDING (25)
uint8_t u8x8_byte_sdl_hw_spi(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr);
uint8_t u8x8_gpio_and_delay_sdl(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr);

Some files were not shown because too many files have changed in this diff Show More