From 9efce2c8f696b34550a0816dc7f855beee8a6a15 Mon Sep 17 00:00:00 2001 From: raphaelcoeffic Date: Mon, 2 Oct 2023 06:49:33 +0200 Subject: [PATCH 01/14] Fix simu driver signatures --- radio/src/targets/simu/gyro_driver.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/radio/src/targets/simu/gyro_driver.cpp b/radio/src/targets/simu/gyro_driver.cpp index d406052fa92..f23d46669dd 100644 --- a/radio/src/targets/simu/gyro_driver.cpp +++ b/radio/src/targets/simu/gyro_driver.cpp @@ -19,6 +19,7 @@ * GNU General Public License for more details. */ -void gyroInit() {} -void gyroRead() {} -void gyroRead(unsigned char*) {} +#include "gyro.h" + +int gyroInit() { return -1; } +int gyroRead(uint8_t*) { return -1; } From a506eddf6b6c63eeeb4b45492d5f5ae7a7171361 Mon Sep 17 00:00:00 2001 From: raphaelcoeffic Date: Mon, 2 Oct 2023 06:49:15 +0200 Subject: [PATCH 02/14] PoC: SDL simu and wasm target --- CMakeLists.txt | 71 +- cmake/FetchImgui.cmake | 26 + cmake/FindFox.cmake | 63 -- cmake/FindPhonon.cmake | 71 -- cmake/FindXercesC.cmake | 68 -- cmake/FindXsd.cmake | 54 -- cmake/GenericDefinitions.cmake | 1 + cmake/NativeTargets.cmake | 48 +- cmake/QtDefs.cmake | 16 +- radio/src/CMakeLists.txt | 13 +- radio/src/audio.cpp | 40 +- radio/src/audio.h | 5 +- radio/src/simu.cpp | 727 ------------------ radio/src/targets/simu/CMakeLists.txt | 117 +-- .../simu/assets/images/gimbal_frame.png | Bin 0 -> 12736 bytes radio/src/targets/simu/assets/images/icon.png | Bin 0 -> 8957 bytes .../targets/simu/assets/images/stick_dot.png | Bin 0 -> 1843 bytes radio/src/targets/simu/backlight_driver.cpp | 18 +- radio/src/targets/simu/html/simu.html | 31 + radio/src/targets/simu/sdl_simu.cpp | 717 +++++++++++++++++ radio/src/targets/simu/simpgmspace.cpp | 125 +-- radio/src/targets/simu/simu.h | 38 + radio/src/targets/simu/simuaudio.cpp | 102 +++ radio/src/targets/simu/simuaudio.h | 25 + radio/src/targets/simu/simulcd.cpp | 46 -- radio/src/targets/simu/stick_dot.bmp | Bin 0 -> 6134 bytes 26 files changed, 1164 insertions(+), 1258 deletions(-) create mode 100644 cmake/FetchImgui.cmake delete mode 100644 cmake/FindFox.cmake delete mode 100644 cmake/FindPhonon.cmake delete mode 100644 cmake/FindXercesC.cmake delete mode 100644 cmake/FindXsd.cmake delete mode 100644 radio/src/simu.cpp create mode 100644 radio/src/targets/simu/assets/images/gimbal_frame.png create mode 100755 radio/src/targets/simu/assets/images/icon.png create mode 100644 radio/src/targets/simu/assets/images/stick_dot.png create mode 100644 radio/src/targets/simu/html/simu.html create mode 100644 radio/src/targets/simu/sdl_simu.cpp create mode 100644 radio/src/targets/simu/simu.h create mode 100644 radio/src/targets/simu/simuaudio.cpp create mode 100644 radio/src/targets/simu/simuaudio.h create mode 100644 radio/src/targets/simu/stick_dot.bmp diff --git a/CMakeLists.txt b/CMakeLists.txt index 95f0b6495c1..101e87c7d19 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,33 +48,66 @@ if(EdgeTX_SUPERBUILD) # Add explicit targets for triggering cmake in the external projects set_property(DIRECTORY PROPERTY EP_STEP_TARGETS configure clean) - - # Native targets - ExternalProject_Add(native + + # ARM targets + ExternalProject_Add(arm-none-eabi SOURCE_DIR ${CMAKE_SOURCE_DIR} - BINARY_DIR ${CMAKE_BINARY_DIR}/native + BINARY_DIR ${CMAKE_BINARY_DIR}/arm-none-eabi CMAKE_ARGS ${CMAKE_ARGS} -Wno-dev -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} CMAKE_CACHE_ARGS - -DCMAKE_TOOLCHAIN_FILE:FILEPATH=${CMAKE_SOURCE_DIR}/cmake/toolchain/native.cmake + -DCMAKE_TOOLCHAIN_FILE:FILEPATH=${CMAKE_SOURCE_DIR}/cmake/toolchain/arm-none-eabi.cmake -DEdgeTX_SUPERBUILD:BOOL=0 - -DNATIVE_BUILD:BOOL=1 + -DNATIVE_BUILD:BOOL=0 INSTALL_COMMAND "" EXCLUDE_FROM_ALL TRUE ) - # ARM targets - ExternalProject_Add(arm-none-eabi + # Native targets + ExternalProject_Add(native SOURCE_DIR ${CMAKE_SOURCE_DIR} - BINARY_DIR ${CMAKE_BINARY_DIR}/arm-none-eabi + BINARY_DIR ${CMAKE_BINARY_DIR}/native CMAKE_ARGS ${CMAKE_ARGS} -Wno-dev -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} CMAKE_CACHE_ARGS - -DCMAKE_TOOLCHAIN_FILE:FILEPATH=${CMAKE_SOURCE_DIR}/cmake/toolchain/arm-none-eabi.cmake + -DCMAKE_TOOLCHAIN_FILE:FILEPATH=${CMAKE_SOURCE_DIR}/cmake/toolchain/native.cmake -DEdgeTX_SUPERBUILD:BOOL=0 - -DNATIVE_BUILD:BOOL=0 + -DNATIVE_BUILD:BOOL=1 INSTALL_COMMAND "" EXCLUDE_FROM_ALL TRUE ) + # Webasm targets + if(ENABLE_WASM) + if(NOT DEFINED $ENV{EMSCRIPTEN_ROOT}) + # fetch EMSCRIPTEN_ROOT + message("-- Retrieving EMSCRIPTEN_ROOT") + execute_process(COMMAND + em-config EMSCRIPTEN_ROOT + OUTPUT_VARIABLE EMSCRIPTEN_ROOT + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + else() + message("-- Using EMSCRIPTEN_ROOT from environment") + set(EMSCRIPTEN_ROOT $ENV{EMSCRIPTEN_ROOT}) + endif() + + set(EMSCRIPTEN_TOOLCHAIN_FILE ${EMSCRIPTEN_ROOT}/cmake/Modules/Platform/Emscripten.cmake) + message("-- EMSCRIPTEN_ROOT = ${EMSCRIPTEN_ROOT}") + message("-- EMSCRIPTEN_TOOLCHAIN_FILE = ${EMSCRIPTEN_TOOLCHAIN_FILE}") + + ExternalProject_Add(wasm + SOURCE_DIR ${CMAKE_SOURCE_DIR} + BINARY_DIR ${CMAKE_BINARY_DIR}/wasm + CMAKE_ARGS ${CMAKE_ARGS} -Wno-dev -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} + CMAKE_CACHE_ARGS + -DCMAKE_TOOLCHAIN_FILE:FILEPATH=${EMSCRIPTEN_TOOLCHAIN_FILE} + -DEdgeTX_SUPERBUILD:BOOL=0 + -DNATIVE_BUILD:BOOL=1 + -DDISABLE_COMPANION:BOOL=1 + INSTALL_COMMAND "" + EXCLUDE_FROM_ALL TRUE + ) + endif() + add_custom_target(configure DEPENDS native-configure arm-none-eabi-configure) @@ -93,6 +126,13 @@ if(EdgeTX_SUPERBUILD) DEPENDS native-configure ) + if(ENABLE_WASM) + add_custom_target(wasm-simu + COMMAND $(MAKE) -C wasm simu + DEPENDS wasm-configure + ) + endif() + add_custom_target(companion COMMAND $(MAKE) -C native companion DEPENDS native-configure @@ -127,8 +167,6 @@ if(EdgeTX_SUPERBUILD) return() endif() -enable_language(ASM) - set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) include(GenericDefinitions) @@ -139,10 +177,15 @@ else() endif() if(NATIVE_BUILD) - include(NativeTargets) + if(NOT EMSCRIPTEN) + include(NativeTargets) + endif() else() # Prevent CMake warnings set(IGNORE "${SDL2_LIBRARY_PATH}" "${LIBSSL1_ROOT_DIR}" "${OPENSSL_ROOT_DIR}") endif() +# Prevent CMake warnings +set(IGNORE "${ENABLE_WASM}") + add_subdirectory(${RADIO_SRC_DIR}) diff --git a/cmake/FetchImgui.cmake b/cmake/FetchImgui.cmake new file mode 100644 index 00000000000..1eced2113fa --- /dev/null +++ b/cmake/FetchImgui.cmake @@ -0,0 +1,26 @@ +# Fetch imgui source code from Github + +include(FetchContent) + +FetchContent_Declare( + imgui + GIT_REPOSITORY https://github.com/ocornut/imgui + GIT_TAG dbb5eeaadffb6a3ba6a60de1290312e5802dba5a # v1.91.8 + SOURCE_DIR imgui +) + +FetchContent_MakeAvailable(imgui) + +add_library(imgui STATIC + imgui/imgui.cpp + imgui/imgui_draw.cpp + imgui/imgui_tables.cpp + imgui/imgui_widgets.cpp + imgui/backends/imgui_impl_sdl2.cpp + imgui/backends/imgui_impl_sdlrenderer2.cpp +) + +target_include_directories(imgui PUBLIC + ${CMAKE_CURRENT_BINARY_DIR}/imgui + ${CMAKE_CURRENT_BINARY_DIR}/imgui/backends +) diff --git a/cmake/FindFox.cmake b/cmake/FindFox.cmake deleted file mode 100644 index 9d76b61b0c9..00000000000 --- a/cmake/FindFox.cmake +++ /dev/null @@ -1,63 +0,0 @@ -# Imported from the DeskVox project (LPGL 2.1) - -include(FindPackageHandleStandardArgs) - -set(hints - $ENV{LIB_BASE_PATH}/fox -) - -set(paths - /usr - /usr/local -) - -find_path(FOX_INCLUDE_DIR - NAMES - fx.h - HINTS - ${hints} - PATHS - ${paths} - PATH_SUFFIXES - include - include/fox - include/fox-1.6 -) - -find_library(FOX_LIBRARY - NAMES - FOX-1.6 - fox-1.6 - HINTS - ${hints} - PATHS - ${paths} - PATH_SUFFIXES - lib64 - lib -) - -find_library(FOX_LIBRARY_DEBUG - NAMES - FOXD-1.6 - foxd-1.6 - HINTS - ${hints} - PATHS - ${paths} - PATH_SUFFIXES - lib64 - lib -) - -if(FOX_LIBRARY_DEBUG) - set(FOX_LIBRARIES optimized ${FOX_LIBRARY} debug ${FOX_LIBRARY_DEBUG}) -else() - set(FOX_LIBRARIES ${FOX_LIBRARY}) -endif() - -find_package_handle_standard_args(Fox - DEFAULT_MSG - FOX_INCLUDE_DIR - FOX_LIBRARY -) \ No newline at end of file diff --git a/cmake/FindPhonon.cmake b/cmake/FindPhonon.cmake deleted file mode 100644 index 23ec18dfc10..00000000000 --- a/cmake/FindPhonon.cmake +++ /dev/null @@ -1,71 +0,0 @@ -# Find libphonon -# Once done this will define -# -# PHONON_FOUND - system has Phonon Library -# PHONON_INCLUDES - the Phonon include directory -# PHONON_LIBS - link these to use Phonon -# PHONON_VERSION - the version of the Phonon Library - -# Copyright (c) 2008, Matthias Kretz -# -# Redistribution and use is allowed according to the terms of the BSD license. -# For details see the accompanying COPYING-CMAKE-SCRIPTS file. - -macro(_phonon_find_version) - set(_phonon_namespace_header_file "${PHONON_INCLUDE_DIR}/phonon/phononnamespace.h") - if (APPLE AND EXISTS "${PHONON_INCLUDE_DIR}/Headers/phononnamespace.h") - set(_phonon_namespace_header_file "${PHONON_INCLUDE_DIR}/Headers/phononnamespace.h") - endif (APPLE AND EXISTS "${PHONON_INCLUDE_DIR}/Headers/phononnamespace.h") - file(READ ${_phonon_namespace_header_file} _phonon_header LIMIT 5000 OFFSET 1000) - string(REGEX MATCH "define PHONON_VERSION_STR \"(4\\.[0-9]+\\.[0-9a-z]+)\"" _phonon_version_match "${_phonon_header}") - set(PHONON_VERSION "${CMAKE_MATCH_1}") - message(STATUS "Phonon Version: ${PHONON_VERSION}") -endmacro(_phonon_find_version) - -if(PHONON_FOUND) - # Already found, nothing more to do except figuring out the version - _phonon_find_version() -else(PHONON_FOUND) - if(PHONON_INCLUDE_DIR AND PHONON_LIBRARY) - set(PHONON_FIND_QUIETLY TRUE) - endif(PHONON_INCLUDE_DIR AND PHONON_LIBRARY) - - # As discussed on kde-buildsystem: first look at CMAKE_PREFIX_PATH, then at the suggested PATHS (kde4 install dir) - find_library(PHONON_LIBRARY NAMES phonon phonon4 PATHS ${KDE4_LIB_INSTALL_DIR} ${QT_LIBRARY_DIR} NO_SYSTEM_ENVIRONMENT_PATH NO_CMAKE_SYSTEM_PATH) - # then at the default system locations (CMAKE_SYSTEM_PREFIX_PATH, i.e. /usr etc.) - find_library(PHONON_LIBRARY NAMES phonon) - - find_path(PHONON_INCLUDE_DIR NAMES phonon/phonon_export.h PATHS ${KDE4_INCLUDE_INSTALL_DIR} ${QT_INCLUDE_DIR} ${INCLUDE_INSTALL_DIR} ${QT_LIBRARY_DIR} NO_SYSTEM_ENVIRONMENT_PATH NO_CMAKE_SYSTEM_PATH) - find_path(PHONON_INCLUDE_DIR NAMES phonon/phonon_export.h) - - if(PHONON_INCLUDE_DIR AND PHONON_LIBRARY) - set(PHONON_LIBS ${phonon_LIB_DEPENDS} ${PHONON_LIBRARY}) - set(PHONON_INCLUDES ${PHONON_INCLUDE_DIR}/KDE ${PHONON_INCLUDE_DIR}) - set(PHONON_FOUND TRUE) - _phonon_find_version() - else(PHONON_INCLUDE_DIR AND PHONON_LIBRARY) - set(PHONON_FOUND FALSE) - endif(PHONON_INCLUDE_DIR AND PHONON_LIBRARY) - - if(PHONON_FOUND) - if(NOT PHONON_FIND_QUIETLY) - message(STATUS "Found Phonon: ${PHONON_LIBRARY}") - message(STATUS "Found Phonon Includes: ${PHONON_INCLUDES}") - endif(NOT PHONON_FIND_QUIETLY) - else(PHONON_FOUND) - if(Phonon_FIND_REQUIRED) - if(NOT PHONON_INCLUDE_DIR) - message(STATUS "Phonon includes NOT found!") - endif(NOT PHONON_INCLUDE_DIR) - if(NOT PHONON_LIBRARY) - message(STATUS "Phonon library NOT found!") - endif(NOT PHONON_LIBRARY) - message(FATAL_ERROR "Phonon library or includes NOT found!") - else(Phonon_FIND_REQUIRED) - message(STATUS "Unable to find Phonon") - endif(Phonon_FIND_REQUIRED) - endif(PHONON_FOUND) - - - mark_as_advanced(PHONON_INCLUDE_DIR PHONON_LIBRARY PHONON_INCLUDES) -endif(PHONON_FOUND) diff --git a/cmake/FindXercesC.cmake b/cmake/FindXercesC.cmake deleted file mode 100644 index 4431cbdf598..00000000000 --- a/cmake/FindXercesC.cmake +++ /dev/null @@ -1,68 +0,0 @@ -# Find Xerces-C -# The following setings are defined -# XERCESC_ROOT_DIR, the root of the include and lib directory -# XERCESC_INCLUDE_DIR, the full path of the include dir (ADVANCED) -# XERCESC_LIBRARIES, the name of the xerces-c library (ADVANCED) - -# Look for a root installation -IF( MSVC ) -SET( XERCES_WINDIR C:/Programs/xerces-vc ) -ELSE( ) -SET( XERCES_WINDIR C:/Programs/xerces ) -ENDIF( ) - -FIND_PATH(XERCESC_ROOT_DIR include/xercesc/parsers/SAXParser.hpp - ${XERCES_WINDIR} - "C:/mingw/msys/1.0/local" - "C:/Program Files/CodeSynthesis XSD 3.2" - /usr - /usr/local - "C:/MinGW" - $ENV{CODESYNTH} - DOC "The root of an installed xerces-c installation" -) - -# try to find the header -FIND_PATH(XERCESC_INCLUDE_DIR xercesc/parsers/SAXParser.hpp - ${XERCESC_ROOT_DIR}/include - /usr/include - /usr/local/include -) - -# Find the library -FIND_LIBRARY(XERCESC_LIBRARY - NAMES xerces-c xerces-c_3 - PATHS - ${XERCESC_ROOT_DIR}/lib - ${XERCESC_ROOT_DIR}/lib/vc-9.0 - ${XERCESC_ROOT_DIR}/lib64/vc-9.0 - /usr/lib - /usr/local/lib - DOC "The name of the xerces-c library" -) - -IF (XERCESC_ROOT_DIR) - IF (XERCESC_INCLUDE_DIR AND XERCESC_LIBRARY) - SET (XERCESC_FOUND TRUE) - SET (XERCESC_LIBRARIES "${XERCESC_LIBRARY}") - # FIXME: There should be a better way of handling this? - # FIXME: How can we test to see if the lib dir isn't - # FIXME: one of the default dirs? - LINK_DIRECTORIES(${XERCESC_ROOT_DIR}/lib) - ENDIF (XERCESC_INCLUDE_DIR AND XERCESC_LIBRARY) -ENDIF (XERCESC_ROOT_DIR) - -IF (XERCESC_FOUND) - IF (NOT XERCESC_FIND_QUIETLY) - MESSAGE (STATUS " found xerces-c: ${XERCESC_LIBRARY}") - ENDIF (NOT XERCESC_FIND_QUIETLY) -ELSE (XERCESC_FOUND) - IF (XERCESC_FIND_REQUIRED) - MESSAGE(FATAL_ERROR "Could not find Xerces-C") - ENDIF (XERCESC_FIND_REQUIRED) -ENDIF (XERCESC_FOUND) - -MARK_AS_ADVANCED( - XERCESC_INCLUDE_DIR - XERCESC_LIBRARY -) \ No newline at end of file diff --git a/cmake/FindXsd.cmake b/cmake/FindXsd.cmake deleted file mode 100644 index 1e50f6269c0..00000000000 --- a/cmake/FindXsd.cmake +++ /dev/null @@ -1,54 +0,0 @@ -# Locate Xsd from code synthesis include paths and binary -# Xsd can be found at http://codesynthesis.com/products/xsd/ -# Written by Frederic Heem, frederic.heem _at_ telsey.it - -# This module defines -# XSD_INCLUDE_DIR, where to find elements.hxx, etc. -# XSD_EXECUTABLE, where is the xsd compiler -# XSD_FOUND, If false, don't try to use xsd - -FIND_PATH( XSD_INCLUDE_DIR xsd/cxx/parser/elements.hxx - "/opt/local/xsd-3.3.0-i686-macosx/libxsd" - "C:/Programs/xsd-3.3.0/libxsd" - "C:/Program Files/CodeSynthesis XSD 3.2/include" - "C:/mingw/xsd-3.3.0-i686-windows/libxsd" - $ENV{XSDDIR}/include - $ENV{CODESYNTH}/include - /usr/local/include /usr/include - $ENV{XSDDIR}/libxsd -) - -IF( WIN32 ) - SET( XSDCXX_FILENAME1 xsd-cxx.exe ) -ELSE( ) - SET( XSDCXX_FILENAME1 xsdcxx ) - SET( XSDCXX_FILENAME2 xsd ) -ENDIF( ) - -FIND_PROGRAM( XSDCXX_EXECUTABLE - NAMES - ${XSDCXX_FILENAME1} ${XSDCXX_FILENAME2} - PATHS - "/opt/local/xsd-3.3.0-i686-macosx/bin" - "C:/Programs/xsd-3.3.0/bin" - "C:/mingw/xsd-3.3.0-i686-windows/bin" - "C:/Program Files/CodeSynthesis XSD 3.2/bin" - $ENV{XSDDIR}/bin - /usr/local/bin - /usr/bin - $ENV{XSDDIR}/xsd -) - -MESSAGE(STATUS ${XSDCXX_EXECUTABLE}) - -# if the include and the program are found then we have it -IF( XSD_INCLUDE_DIR ) - IF( XSDCXX_EXECUTABLE ) - SET( XSD_FOUND "YES" ) - ENDIF( XSDCXX_EXECUTABLE ) -ENDIF( XSD_INCLUDE_DIR ) - -MARK_AS_ADVANCED( - XSD_INCLUDE_DIR - XSDCXX_EXECUTABLE -) diff --git a/cmake/GenericDefinitions.cmake b/cmake/GenericDefinitions.cmake index daf9d25448d..b7737be56cf 100644 --- a/cmake/GenericDefinitions.cmake +++ b/cmake/GenericDefinitions.cmake @@ -28,6 +28,7 @@ endif() set(CMAKE_COLOR_MAKEFILE ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +set(CMAKE_CXX_STANDARD 17) add_definitions(-D_GLIBCXX_USE_C99=1) # proper to_string definition diff --git a/cmake/NativeTargets.cmake b/cmake/NativeTargets.cmake index ceb10da2d66..d2df4d32d14 100644 --- a/cmake/NativeTargets.cmake +++ b/cmake/NativeTargets.cmake @@ -1,41 +1,27 @@ option(DISABLE_COMPANION "Disable building companion and simulators" OFF) -# if(WIN32) -# set(WIN_EXTRA_LIBS_PATH "C:/Programs" CACHE PATH -# "Base path to extra libs/headers on Windows (SDL & pthreads folders should be in here).") -# list(APPEND CMAKE_PREFIX_PATH "${WIN_EXTRA_LIBS_PATH}" "${WIN_EXTRA_LIBS_PATH}/SDL") # hints for FindSDL -# endif() - -# libfox -find_package(Fox QUIET) # QUIET not working on WIN32? -if (FOX_FOUND) - message(STATUS "Foxlib found at ${FOX_LIBRARY}") -else() - message("Libfox not found, simu target will not be available") -endif() - if(NOT DISABLE_COMPANION) include(QtDefs) endif(NOT DISABLE_COMPANION) -if(Qt5Core_FOUND OR FOX_FOUND) - set(SDL2_BUILDING_LIBRARY YES) # this prevents FindSDL from appending SDLmain lib to the results, which we don't want - find_package("SDL2") - if(SDL2_FOUND) - # find_package("SDL2") does not set a variable holding the path to the location of the SDL2 shared library - find_file(SDL2_LIB_PATH - NAMES - libSDL2.so - SDL2.dll - SDL2.dylib - HINTS - "/usr/lib/x86_64-linux-gnu" - ${SDL2_LIBRARY_PATH}) - message(STATUS "SDL2 Lib: ${SDL2_LIB_PATH} Libs: ${SDL2_LIBRARIES}; Headers: ${SDL2_INCLUDE_DIRS}") - else() - message(STATUS "SDL2 not found! Simulator audio, and joystick inputs, will not work.") - endif() +# this prevents FindSDL from appending SDLmain lib to the results, which we don't want +set(SDL2_BUILDING_LIBRARY YES) +find_package("SDL2") + +if(SDL2_FOUND) + # find_package("SDL2") does not set a variable holding the path to the location of the SDL2 shared library + find_file(SDL2_LIB_PATH + NAMES + libSDL2.so + SDL2.dll + SDL2.dylib + HINTS + "/usr/lib/x86_64-linux-gnu" + ${SDL2_LIBRARY_PATH}) + message(STATUS "SDL2 Lib: ${SDL2_LIB_PATH} Libs: ${SDL2_LIBRARIES}; Headers: ${SDL2_INCLUDE_DIRS}") +else() + message(STATUS "SDL not found! Simulator audio, and joystick inputs, will not work.") endif() if(Qt5Core_FOUND AND NOT DISABLE_COMPANION) diff --git a/cmake/QtDefs.cmake b/cmake/QtDefs.cmake index be859da68b7..f5adf7b38d0 100644 --- a/cmake/QtDefs.cmake +++ b/cmake/QtDefs.cmake @@ -6,14 +6,14 @@ endif() set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOUIC ON) -find_package(Qt5Core) -find_package(Qt5Widgets) -find_package(Qt5Xml) -find_package(Qt5LinguistTools) -find_package(Qt5PrintSupport) -find_package(Qt5Multimedia) -find_package(Qt5Svg) -find_package(Qt5SerialPort) +find_package(Qt5Core QUIET) +find_package(Qt5Widgets QUIET) +find_package(Qt5Xml QUIET) +find_package(Qt5LinguistTools QUIET) +find_package(Qt5PrintSupport QUIET) +find_package(Qt5Multimedia QUIET) +find_package(Qt5Svg QUIET) +find_package(Qt5SerialPort QUIET) if(Qt5Core_FOUND) message(STATUS "Qt Version: ${Qt5Core_VERSION}") diff --git a/radio/src/CMakeLists.txt b/radio/src/CMakeLists.txt index dd8d56d7d92..0ad452bd211 100644 --- a/radio/src/CMakeLists.txt +++ b/radio/src/CMakeLists.txt @@ -28,9 +28,6 @@ option(SEMIHOSTING "Enable debugger semihosting" OFF) option(JITTER_MEASURE "Enable ADC jitter measurement" OFF) option(WATCHDOG "Enable hardware Watchdog" ON) option(ASTERISK "Enable asterisk icon (test only firmware)" OFF) -if(SDL2_FOUND) - option(SIMU_AUDIO "Enable simulator audio." ON) -endif() option(LUA "Enable LUA support" ON) option(SIMU_DISKIO "Enable disk IO simulation in simulator. Simulator will use FatFs module and simulated IO layer that uses \"./sdcard.image\" file as image of SD card. This file must contain whole SD card from first to last sector" OFF) option(SIMU_LUA_COMPILER "Pre-compile and save Lua scripts in simulator." ON) @@ -245,10 +242,6 @@ if(WATCHDOG AND NOT NATIVE_BUILD) add_definitions(-DUSE_WATCHDOG) endif() -if(SIMU_AUDIO) - add_definitions(-DSIMU_AUDIO) -endif() - if(SIMU_DISKIO) add_definitions(-DSIMU_DISKIO) endif() @@ -497,6 +490,10 @@ if(NATIVE_BUILD) add_dependencies(radiolib_native ${RADIO_DEPENDENCIES}) set_property(TARGET radiolib_native PROPERTY POSITION_INDEPENDENT_CODE ON) + if(EMSCRIPTEN) + target_compile_options(radiolib_native PUBLIC -pthread -g) + endif() + add_subdirectory(targets/simu) add_subdirectory(tests) endif() @@ -518,6 +515,8 @@ if(NOT USE_UNSUPPORTED_TOOLCHAIN AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_EQUA message(FATAL_ERROR "Only Arm GNU Toolchain version 14.2.rel1 is supported!!") endif() +enable_language(ASM) + set(CMAKE_C_FLAGS "${FIRMWARE_C_FLAGS}") set(CMAKE_C_FLAGS_DEBUG "${FIRMWARE_C_FLAGS_DEBUG}") set(CMAKE_C_FLAGS_RELEASE "${FIRMWARE_C_FLAGS}") diff --git a/radio/src/audio.cpp b/radio/src/audio.cpp index 9d9e24873ea..73c0aee3a0a 100644 --- a/radio/src/audio.cpp +++ b/radio/src/audio.cpp @@ -19,6 +19,10 @@ * GNU General Public License for more details. */ +#include "tasks.h" +#include "audio.h" +#include "rtos.h" + #include #include "os/sleep.h" @@ -376,6 +380,17 @@ AudioQueue::AudioQueue() #define CODEC_ID_PCM_S16LE 1 + +static void _audio_lock() +{ + mutex_lock(&audioMutex); +} + +static void _audio_unlock() +{ + mutex_unlock(&audioMutex); +} + #if !defined(__SSAT) #define _sat_s16(x) ((int16_t)limit(INT16_MIN, (x), INT16_MAX)) #else @@ -619,9 +634,9 @@ void AudioQueue::wakeup() // mix the normal context (tones and wavs) if (normalContext.isEmpty() && !fragmentsFifo.empty()) { - mutex_lock(&audioMutex); + _audio_lock(); normalContext.setFragment(fragmentsFifo.get()); - mutex_unlock(&audioMutex); + _audio_unlock(); } result = normalContext.mixBuffer(buffer, g_eeGeneral.beepVolume, g_eeGeneral.wavVolume, fade); if (result > 0) { @@ -702,10 +717,11 @@ bool AudioQueue::isPlaying(uint8_t id) void AudioQueue::playTone(uint16_t freq, uint16_t len, uint16_t pause, uint8_t flags, int8_t freqIncr, int8_t fragmentVolume) { #if defined(SIMU) && !defined(SIMU_AUDIO) + #warning "NO audio" return; #endif - mutex_lock(&audioMutex); + _audio_lock(); freq = limit(BEEP_MIN_FREQ, freq, BEEP_MAX_FREQ); @@ -728,7 +744,7 @@ void AudioQueue::playTone(uint16_t freq, uint16_t len, uint16_t pause, uint8_t f } } - mutex_unlock(&audioMutex); + _audio_unlock(); } void AudioQueue::playFile(const char * filename, uint8_t flags, uint8_t id, int8_t fragmentVolume) @@ -755,7 +771,7 @@ void AudioQueue::playFile(const char * filename, uint8_t flags, uint8_t id, int8 return; } - mutex_lock(&audioMutex); + _audio_lock(); if (flags & PLAY_BACKGROUND) { backgroundContext.clear(); @@ -765,7 +781,7 @@ void AudioQueue::playFile(const char * filename, uint8_t flags, uint8_t id, int8 fragmentsFifo.push(AudioFragment(filename, flags & 0x0f, fragmentVolume, id)); } - mutex_unlock(&audioMutex); + _audio_unlock(); } void AudioQueue::stopPlay(uint8_t id) @@ -778,12 +794,12 @@ void AudioQueue::stopPlay(uint8_t id) return; #endif - mutex_lock(&audioMutex); + _audio_lock(); fragmentsFifo.removePromptById(id); backgroundContext.stop(id); - mutex_unlock(&audioMutex); + _audio_unlock(); } void AudioQueue::stopSD() @@ -796,19 +812,19 @@ void AudioQueue::stopSD() void AudioQueue::stopAll() { flush(); - mutex_lock(&audioMutex); + _audio_lock(); priorityContext.clear(); normalContext.clear(); - mutex_unlock(&audioMutex); + _audio_unlock(); } void AudioQueue::flush() { - mutex_lock(&audioMutex); + _audio_lock(); fragmentsFifo.clear(); varioContext.clear(); backgroundContext.clear(); - mutex_unlock(&audioMutex); + _audio_unlock(); } void audioPlay(unsigned int index, uint8_t id) diff --git a/radio/src/audio.h b/radio/src/audio.h index f42e873e335..92d3950447d 100644 --- a/radio/src/audio.h +++ b/radio/src/audio.h @@ -466,10 +466,13 @@ enum { ID_PLAY_FROM_SD_MANAGER = 255, }; +void audioTaskInit(); +void audioTaskStart(); + void codecsInit(); + void audioEvent(unsigned int index); void audioPlay(unsigned int index, uint8_t id=0); -void audioStart(); #if defined(AUDIO) && defined(BUZZER) #define AUDIO_BUZZER(a, b) do { a; b; } while(0) diff --git a/radio/src/simu.cpp b/radio/src/simu.cpp deleted file mode 100644 index 69f874e4346..00000000000 --- a/radio/src/simu.cpp +++ /dev/null @@ -1,727 +0,0 @@ -/* - * Copyright (C) EdgeTX - * - * Based on code named - * opentx - https://github.com/opentx/opentx - * th9x - http://code.google.com/p/th9x - * er9x - http://code.google.com/p/er9x - * gruvin9x - http://code.google.com/p/gruvin9x - * - * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * 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 General Public License for more details. - */ - -#if defined(SIMU_AUDIO) - #include - #undef main -#endif - -#include "fx.h" -#include "FXExpression.h" -#include "FXPNGImage.h" -#include -#include "fxkeys.h" -#include "edgetx.h" -#include -#include - -#include "targets/simu/simulcd.h" -#include "hal/adc_driver.h" -#include "hal/rotary_encoder.h" - -#include "os/time.h" - -#if LCD_W > 212 - #define LCD_ZOOM 1 -#else - #define LCD_ZOOM 2 -#endif - -#define W2 LCD_W*LCD_ZOOM -#define H2 LCD_H*LCD_ZOOM - -#if defined(HARDWARE_TOUCH) - #define TAP_TIME 25 - - tmr10ms_t now = 0; - tmr10ms_t downTime = 0; - tmr10ms_t tapTime = 0; - short tapCount = 0; -#endif - -class OpenTxSim: public FXMainWindow -{ - FXDECLARE(OpenTxSim) - - public: - OpenTxSim(){}; - OpenTxSim(FXApp* a); - ~OpenTxSim(); - void updateKeysAndSwitches(bool start=false); - long onKeypress(FXObject*, FXSelector, void*); - long onMouseDown(FXObject*,FXSelector,void*); - long onMouseUp(FXObject*,FXSelector,void*); - long onMouseMove(FXObject*,FXSelector,void*); - long onTimeout(FXObject*, FXSelector, void*); - void createBitmap(int index, uint16_t *data, int x, int y, int w, int h); - void makeSnapshot(const FXDrawable* drawable); - void doEvents(); - void refreshDisplay(); - void setPixel(int x, int y, FXColor color); - void fsLedRGB(uint8_t idx, uint32_t color); - - private: - FXImage * bmp; - FXImageFrame * bmf; - - public: - FXSlider * sliders[MAX_STICKS]; - FXKnob * knobs[MAX_POTS]; -#if defined(FUNCTION_SWITCHES) - FXButton * fctButtons[NUM_FUNCTIONS_SWITCHES] = {0}; -#endif -}; - -// Message Map -FXDEFMAP(OpenTxSim) OpenTxSimMap[] = { - // Message_Type _______ID____Message_Handler_______ - FXMAPFUNC(SEL_TIMEOUT, 2, OpenTxSim::onTimeout), - FXMAPFUNC(SEL_KEYPRESS, 0, OpenTxSim::onKeypress), - FXMAPFUNC(SEL_LEFTBUTTONPRESS, 0, OpenTxSim::onMouseDown), - FXMAPFUNC(SEL_LEFTBUTTONRELEASE, 0, OpenTxSim::onMouseUp), - FXMAPFUNC(SEL_MOTION, 0, OpenTxSim::onMouseMove), -}; - -FXIMPLEMENT(OpenTxSim, FXMainWindow, OpenTxSimMap, ARRAYNUMBER(OpenTxSimMap)) - -OpenTxSim::OpenTxSim(FXApp* a): - FXMainWindow(a, "EdgeTX Simu", nullptr, nullptr, DECOR_ALL, 20, 90, 0, 0) -{ - bmp = new FXPPMImage(getApp(), nullptr, IMAGE_OWNED|IMAGE_KEEP|IMAGE_SHMI|IMAGE_SHMP, W2, H2); - -#if defined(SIMU_AUDIO) - #if defined(_WIN32) || defined(_WIN64) - putenv("SDL_AUDIODRIVER=directsound"); - #endif - SDL_Init(SDL_INIT_AUDIO); -#endif - - FXHorizontalFrame * hf11 = new FXHorizontalFrame(this, LAYOUT_CENTER_X); -#if defined(FUNCTION_SWITCHES) - FXHorizontalFrame * hf12 = new FXHorizontalFrame(this, LAYOUT_CENTER_X); -#endif - FXHorizontalFrame * hf1 = new FXHorizontalFrame(this, LAYOUT_FILL_X); - - //rh lv rv lh - for (int i=0; i<4; i++) { - switch (i) { -#define L LAYOUT_FIX_WIDTH|LAYOUT_FIX_HEIGHT|LAYOUT_FIX_X|LAYOUT_FIX_Y - case 0: - sliders[i]=new FXSlider(hf1, nullptr, 0, L|SLIDER_HORIZONTAL, 10, 110, 100, 20); - break; - case 1: - sliders[i]=new FXSlider(hf1, nullptr, 0, L|SLIDER_VERTICAL, 110, 10, 20, 100); - break; - case 2: - sliders[i]=new FXSlider(hf1, nullptr, 0, L|SLIDER_VERTICAL, 130, 10, 20, 100); - break; - case 3: - sliders[i]=new FXSlider(hf1, nullptr, 0, L|SLIDER_HORIZONTAL, 150, 110, 100, 20); - break; - default:; - } - sliders[i]->setTickDelta(14); - sliders[i]->setRange(0, 4095); - sliders[i]->setValue(2047); - } - - auto max_pots = adcGetMaxInputs(ADC_INPUT_FLEX); - memset(knobs, 0, sizeof(knobs)); - - for (int i = 0; i < max_pots; i++) { - knobs[i]= new FXKnob(hf11, nullptr, 0, KNOB_TICKS|LAYOUT_LEFT); - knobs[i]->setRange(0, 4095); - knobs[i]->setValue(2047); - -#if defined(PCBHORUS) && !defined(FUNCTION_SWITCHES) - if (i == 1) { // 6-pos switch - knobs[i]->setIncrement(4095 / 5); - knobs[i]->setTickDelta(4095 / 5); - knobs[i]->setValue(0); - continue; - } -#endif - } - -#if defined(FUNCTION_SWITCHES) - for(int i = 0; i < NUM_FUNCTIONS_SWITCHES; ++i) - { - fctButtons[i] = new FXButton(hf12, " ", nullptr, nullptr, 123, LAYOUT_LEFT|BUTTON_NORMAL, 0, 0, 50, 0, 15, 15); - fctButtons[i]->setBorderColor(FXColor(0)); - } -#endif - - bmf = new FXImageFrame(this, bmp); - bmf->enable(); - bmf->setTarget(this); - - updateKeysAndSwitches(true); - - getApp()->addTimeout(this, 2, 100); -} - -OpenTxSim::~OpenTxSim() -{ - TRACE("OpenTxSim::~OpenTxSim()"); - - simuStop(); - - delete bmp; - delete sliders[0]; - delete sliders[1]; - delete sliders[2]; - delete sliders[3]; - - for (int i = 0; i < MAX_POTS; i++) { - delete knobs[i]; - } - - delete bmf; - -#if defined(SIMU_AUDIO) - SDL_Quit(); -#endif -} - -void OpenTxSim::createBitmap(int index, uint16_t *data, int x, int y, int w, int h) -{ - FXPNGImage snapshot(getApp(), nullptr, IMAGE_OWNED, w, h); - - for (int i=0; i>8)/0x0f, 255*((z&0x0F0)>>4)/0x0f, 255*(z&0x00F)/0x0f); - snapshot.setPixel(i, j, color); - } - } - - FXFileStream stream; - char buf[32]; - sprintf(buf, "%02d.png", index); - if (stream.open(buf, FXStreamSave)) { - snapshot.savePixels(stream); - stream.close(); - TRACE("Bitmap %d (w=%d, h=%d) created", index, w, h); - } - else { - TRACE("Bitmap %d (w=%d, h=%d) error", index, w, h); - } -} - -void OpenTxSim::makeSnapshot(const FXDrawable* drawable) -{ - // Construct and create an FXImage object - FXPNGImage snapshot(getApp(), nullptr, 0, drawable->getWidth(), drawable->getHeight()); - snapshot.create(); - - // Create a window device context and lock it onto the image - FXDCWindow dc(&snapshot); - - // Draw from the widget to this - dc.drawArea(drawable, 0, 0, drawable->getWidth(), drawable->getHeight(), 0, 0); - - // Release lock - dc.end(); - - // Grab pixels from server side back to client side - snapshot.restore(); - - // Save recovered pixels to a file - FXFileStream stream; - char buf[100]; - - do { - stream.close(); - sprintf(buf, "snapshot_%02d.png", ++g_snapshot_idx); - } while (stream.open(buf, FXStreamLoad)); - - if (stream.open(buf, FXStreamSave)) { - snapshot.savePixels(stream); - stream.close(); - printf("Snapshot written: %s\n", buf); - } - else { - printf("Cannot create snapshot %s\n", buf); - } -} - -void OpenTxSim::doEvents() -{ - getApp()->runOneEvent(false); -} - -long OpenTxSim::onKeypress(FXObject *, FXSelector, void * v) -{ - auto * evt = (FXEvent *)v; - - // TRACE("keypress %x", evt->code); - - if (evt->code == 's') { - makeSnapshot(bmf); - } - - return 0; -} - -long OpenTxSim::onMouseDown(FXObject *, FXSelector, void * v) -{ - FXEvent * evt = (FXEvent *)v; - UNUSED(evt); - - TRACE_WINDOWS("[Mouse Press] %d %d", evt->win_x, evt->win_y); - -#if defined(HARDWARE_TOUCH) - now = get_tmr10ms(); - simTouchState.tapCount = 0; - - simTouchState.event = TE_DOWN; - simTouchState.startX = simTouchState.x = evt->win_x; - simTouchState.startY = simTouchState.y = evt->win_y; - downTime = now; - simTouchOccured = true; -#endif - - return 0; -} - -long OpenTxSim::onMouseUp(FXObject*,FXSelector,void*v) -{ - FXEvent * evt = (FXEvent *)v; - UNUSED(evt); - - TRACE_WINDOWS("[Mouse Release] %d %d", evt->win_x, evt->win_y); - -#if defined(HARDWARE_TOUCH) - now = get_tmr10ms(); - - if (simTouchState.event == TE_DOWN) { - simTouchState.event = TE_UP; - simTouchState.x = simTouchState.startX; - simTouchState.y = simTouchState.startY; - if (now - downTime <= TAP_TIME) { - if (now - tapTime > TAP_TIME) - tapCount = 1; - else - tapCount++; - simTouchState.tapCount = tapCount; - tapTime = now; - } - } - else { - simTouchState.event = TE_SLIDE_END; - } - simTouchOccured = true; -#endif - - return 0; -} - -long OpenTxSim::onMouseMove(FXObject*,FXSelector,void*v) -{ - FXEvent * evt = (FXEvent *)v; - UNUSED(evt); - - if (evt->state & LEFTBUTTONMASK) { - TRACE_WINDOWS("[Mouse Move] %d %d", evt->win_x, evt->win_y); - -#if defined(HARDWARE_TOUCH) - simTouchState.deltaX += evt->win_x - simTouchState.x; - simTouchState.deltaY += evt->win_y - simTouchState.y; - if (simTouchState.event == TE_SLIDE || abs(simTouchState.deltaX) >= SLIDE_RANGE || abs(simTouchState.deltaY) >= SLIDE_RANGE) { - simTouchState.event = TE_SLIDE; - simTouchState.x = evt->win_x; - simTouchState.y = evt->win_y; - } - simTouchOccured = true; -#endif - } - - return 0; -} - -void OpenTxSim::updateKeysAndSwitches(bool start) -{ - static int keys[] = { -#if defined(PCBFLYSKY) - // no keys -#elif defined(PCBHORUS) - KEY_Page_Up, KEY_PAGEUP, - KEY_Page_Down, KEY_PAGEDN, - KEY_Return, KEY_ENTER, - KEY_BackSpace, KEY_EXIT, - KEY_Up, KEY_UP, - KEY_Down, KEY_DOWN, - KEY_Right, KEY_RIGHT, - KEY_Left, KEY_LEFT, -#elif defined(PCBXLITE) || defined(RADIO_FAMILY_JUMPER_T12) - #if defined(KEYS_GPIO_REG_SHIFT) - KEY_Shift_L, KEY_SHIFT, - #endif - KEY_Return, KEY_ENTER, - KEY_BackSpace, KEY_EXIT, - KEY_Right, KEY_RIGHT, - KEY_Left, KEY_LEFT, - KEY_Up, KEY_UP, - KEY_Down, KEY_DOWN, -#elif defined(NAVIGATION_X7_TX12) - KEY_Page_Up, KEY_PAGEUP, - KEY_Page_Down, KEY_PAGEDN, - KEY_Return, KEY_ENTER, - KEY_BackSpace, KEY_EXIT, - KEY_Up, KEY_MODEL, - KEY_Down, KEY_EXIT, - KEY_Right, KEY_TELE, - KEY_Left, KEY_SYS, -#elif defined(RADIO_T8) || defined(RADIO_COMMANDO8) - KEY_Page_Up, KEY_PAGEUP, - KEY_Page_Down, KEY_PAGEDN, - KEY_Return, KEY_ENTER, - KEY_Right, KEY_MODEL, - KEY_BackSpace, KEY_EXIT, - KEY_Left, KEY_SYS, - KEY_Up, KEY_PLUS, - KEY_Down, KEY_MINUS, -#elif defined(PCBTARANIS) - KEY_Page_Up, KEY_MENU, - KEY_Page_Down, KEY_PAGEDN, - KEY_Return, KEY_ENTER, - KEY_BackSpace, KEY_EXIT, - KEY_Up, KEY_PLUS, - KEY_Down, KEY_MINUS, -#else - KEY_Return, KEY_MENU, - KEY_BackSpace, KEY_EXIT, - KEY_Right, KEY_RIGHT, - KEY_Left, KEY_LEFT, - KEY_Up, KEY_UP, - KEY_Down, KEY_DOWN, -#endif - }; - - for (unsigned int i=0; igetKeyState(keys[i])); - } - -#ifdef __APPLE__ - static FXuint trimKeys[] = { KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_8, KEY_9, KEY_0 }; -#else - static FXuint trimKeys[] = { KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10, KEY_F11, KEY_F12 }; -#endif - - for (unsigned i = 0; i < sizeof(trimKeys) / sizeof(FXuint); i++) { - simuSetTrim(i, getApp()->getKeyState(trimKeys[i])); - } - -#define SWITCH_KEY(key, swtch, states) \ - static bool state##key = 0; \ - static int8_t state_##swtch = -1; \ - static int8_t inc_##swtch = 4-states; \ - if (getApp()->getKeyState(KEY_##key)) { \ - if (!state##key) { \ - state_##swtch = (state_##swtch+inc_##swtch); \ - if (state_##swtch == -1+((states-1)*inc_##swtch)) inc_##swtch = -4+states; \ - else if (state_##swtch == -1) inc_##swtch = 4-states; \ - state##key = true; \ - } \ - } \ - else { \ - state##key = false; \ - } \ - simuSetSwitch(swtch, state_##swtch) \ - - SWITCH_KEY(A, 0, 3); - SWITCH_KEY(B, 1, 3); - SWITCH_KEY(C, 2, 3); - SWITCH_KEY(D, 3, 3); - - #if defined(RADIO_TPRO) || defined(RADIO_TPROV2) || defined(RADIO_TPROS) || defined(RADIO_BUMBLEBEE) - SWITCH_KEY(1, 4, 2); - SWITCH_KEY(2, 5, 2); - SWITCH_KEY(3, 6, 2); - SWITCH_KEY(4, 7, 2); - SWITCH_KEY(5, 8, 2); - SWITCH_KEY(6, 9, 2); - #elif defined(HARDWARE_SWITCH_G) && defined(HARDWARE_SWITCH_H) - SWITCH_KEY(E, 4, 3); - SWITCH_KEY(F, 5, 2); - SWITCH_KEY(G, 6, 3); - SWITCH_KEY(H, 7, 2); - #elif defined(HARDWARE_SWITCH_F) && defined(HARDWARE_SWITCH_H) - SWITCH_KEY(F, 4, 2); - SWITCH_KEY(H, 5, 2); - #endif - - #if defined(PCBX9E) - SWITCH_KEY(I, 8, 3); - SWITCH_KEY(J, 9, 3); - SWITCH_KEY(K, 10, 3); - SWITCH_KEY(L, 11, 3); - SWITCH_KEY(M, 12, 3); - SWITCH_KEY(N, 13, 3); - SWITCH_KEY(O, 14, 3); - SWITCH_KEY(P, 15, 3); - SWITCH_KEY(Q, 16, 3); - SWITCH_KEY(R, 17, 3); - #endif - -#if defined(FUNCTION_SWITCHES) - for(int i = 0; i < NUM_FUNCTIONS_SWITCHES; ++i) - { - int8_t newState = -1; - if(fctButtons[i]->getState()==STATE_DOWN) - { - newState = 1; - } - - simuSetSwitch(switchGetMaxSwitches() + i, newState); - } -#endif -} - -extern volatile rotenc_t rotencValue; -extern volatile uint32_t rotencDt; - -long OpenTxSim::onTimeout(FXObject*, FXSelector, void*) -{ - if (hasFocus()) { - - updateKeysAndSwitches(); - -#if defined(ROTARY_ENCODER_NAVIGATION) - static bool rotencAction = false; - if (getApp()->getKeyState(KEY_X) || getApp()->getKeyState(KEY_plus)) { - if (!rotencAction) rotencValue += ROTARY_ENCODER_GRANULARITY; - rotencAction = true; - } - else if (getApp()->getKeyState(KEY_W) || getApp()->getKeyState(KEY_minus)) { - if (!rotencAction) rotencValue -= ROTARY_ENCODER_GRANULARITY; - rotencAction = true; - } - else { - rotencAction = false; - } - - static uint32_t last_tick = 0; - if (rotencAction) { - uint32_t now = time_get_ms(); - uint32_t dt = now - last_tick; - rotencDt += dt; - last_tick = now; - } -#endif - } - -#if defined(SIMU_BOOTLOADER) - void interrupt10ms(); - interrupt10ms(); -#endif - - refreshDisplay(); - getApp()->addTimeout(this, 2, 10); - return 0; -} - -#if LCD_W >= 212 - #define BL_COLOR FXRGB(47, 123, 227) -#else - #define BL_COLOR FXRGB(150, 200, 152) -#endif - -void OpenTxSim::setPixel(int x, int y, FXColor color) -{ -#if LCD_ZOOM > 1 - for (int i=0; isetPixel(LCD_ZOOM*x+i, LCD_ZOOM*y+j, color); - } - } -#else - bmp->setPixel(x, y, color); -#endif -} - -// from lcd driver -void lcdFlushed(); - -void OpenTxSim::refreshDisplay() -{ - static bool lightEnabled = (bool)isBacklightEnabled(); - - if ((bool(isBacklightEnabled()) != lightEnabled) || simuLcdRefresh) { - - if (bool(isBacklightEnabled()) != lightEnabled) { - lightEnabled = (bool)isBacklightEnabled(); - TRACE("backlight %s", lightEnabled ? "ON" : "OFF"); - } - -#if !defined(COLORLCD) - FXColor offColor = isBacklightEnabled() ? BL_COLOR : FXRGB(200, 200, 200); -#if LCD_DEPTH == 1 - FXColor onColor = FXRGB(0, 0, 0); -#endif -#endif - for (int x = 0; x < LCD_W; x++) { - for (int y = 0; y < LCD_H; y++) { -#if defined(COLORLCD) - pixel_t z = simuLcdBuf[y * LCD_W + x]; - FXColor color = - FXRGB(((z & 0xF800) >> 8) + ((z & 0xE000) >> 13), - ((z & 0x07E0) >> 3) + ((z & 0x0600) >> 9), - (((z & 0x001F) << 3) & 0x00F8) + ((z & 0x001C) >> 2)); - setPixel(x, y, color); -#else -#if LCD_DEPTH == 4 - pixel_t *p = &simuLcdBuf[y / 2 * LCD_W + x]; - uint8_t z = (y & 1) ? (*p >> 4) : (*p & 0x0F); - if (z) { - FXColor color; - if (isBacklightEnabled()) - color = FXRGB(47 - (z * 47) / 15, 123 - (z * 123) / 15, - 227 - (z * 227) / 15); - else - color = FXRGB(200 - (z * 200) / 15, 200 - (z * 200) / 15, - 200 - (z * 200) / 15); - setPixel(x, y, color); - } -#else // LCD_DEPTH == 1 - if (simuLcdBuf[x + (y / 8) * LCD_W] & (1 << (y % 8))) { - setPixel(x, y, onColor); - } -#endif - else { - setPixel(x, y, offColor); - } -#endif // !defined(COLORLCD) - } - } -#if defined(COLORLCD) - if (!lightEnabled) { bmp->fade(0, 50); } -#endif - - bmp->render(); - bmf->setImage(bmp); - - simuLcdRefresh = false; - lcdFlushed(); - } -} - -void OpenTxSim::fsLedRGB(uint8_t idx, uint32_t color) -{ -#if defined(FUNCTION_SWITCHES) - uint32_t col = (color&0x00FF0000)>>16 | (color&0x0000FF00) | (color&0x000000FF)<<16; - fctButtons[idx]->setBaseColor(col); - fctButtons[idx]->setBackColor(col); -#endif -} - -OpenTxSim * opentxSim; - -void doFxEvents() -{ - //puts("doFxEvents"); - opentxSim->getApp()->runOneEvent(false); - opentxSim->refreshDisplay(); -} - -int main(int argc, char ** argv) -{ - // Each FOX GUI program needs one, and only one, application object. - // The application objects coordinates some common stuff shared between - // all the widgets; for example, it dispatches events, keeps track of - // all the windows, and so on. - // We pass the "name" of the application, and its "vendor", the name - // and vendor are used to search the registry database (which stores - // persistent information e.g. fonts and colors). - FXApp application("OpenTX Simu", "OpenTX"); - - // Here we initialize the application. We pass the command line arguments - // because FOX may sometimes need to filter out some of the arguments. - // This opens up the display as well, and reads the registry database - // so that persistent settings are now available. - application.init(argc, argv); - - simuInit(); - - // This creates the main window. We pass in the title to be displayed - // above the window, and possibly some icons for when its iconified. - // The decorations determine stuff like the borders, close buttons, - // drag handles, and so on the Window Manager is supposed to give this - // window. - //FXMainWindow *main=new FXMainWindow(&application, "Hello", nullptr, nullptr, DECOR_ALL); - opentxSim = new OpenTxSim(&application); - application.create(); - - // Pretty self-explanatory:- this shows the window, and places it in the - // middle of the screen. -#ifndef __APPLE__ - opentxSim->show(PLACEMENT_SCREEN); -#else - opentxSim->show(); // Otherwise the main window gets centred across my two monitors, split down the middle. -#endif - - printf("Model size = %d\n", (int)sizeof(g_model)); - -#if !defined(SIMU_BOOTLOADER) - startAudio(); -#endif - - simuStart(true/*false*/, argc >= 3 ? argv[2] : 0, argc >= 4 ? argv[3] : 0); - - return application.run(); -} - -uint16_t simu_get_analog(uint8_t idx) -{ - auto max_sticks = adcGetMaxInputs(ADC_INPUT_MAIN); - if (idx < max_sticks) - return opentxSim->sliders[idx]->getValue(); - - idx -= max_sticks; - - auto max_pots = adcGetMaxInputs(ADC_INPUT_FLEX); - if (idx < max_pots) - return opentxSim->knobs[idx]->getValue(); - - // probably RTC_BAT - return 0; -} - -void createBitmap(int index, uint16_t *data, int x, int y, int w, int h) -{ - opentxSim->createBitmap(index, data, x, y, w, h); -} - - -void fsLedRGB(uint8_t idx, uint32_t color) -{ - opentxSim->fsLedRGB(idx, color); -} - -void fsLedOn(uint8_t idx) -{ - opentxSim->fsLedRGB(idx, 0x00ffffff); -} - -void fsLedOff(uint8_t idx) -{ - opentxSim->fsLedRGB(idx, 0); -} diff --git a/radio/src/targets/simu/CMakeLists.txt b/radio/src/targets/simu/CMakeLists.txt index a97d2899c94..0262b5dd558 100644 --- a/radio/src/targets/simu/CMakeLists.txt +++ b/radio/src/targets/simu/CMakeLists.txt @@ -1,15 +1,35 @@ -option(SIMU_TARGET "Configure libsimulator/simu targets (can be turned off for compiling Companion only)" ON) +option(SIMU_TARGET "Configure libsimulator/simu targets" ON) if(NOT SIMU_TARGET) message(STATUS "libsimulator/simu targets disabled") return() endif() +target_compile_options(radiolib_native PUBLIC -DSIMU) + +if(SDL2_FOUND OR EMSCRIPTEN) + option(SIMU_AUDIO "Enable simulator audio." ON) + if(SIMU_AUDIO) + target_compile_options(radiolib_native PUBLIC -DSIMU_AUDIO) + endif() +endif() + +if(EMSCRIPTEN) + add_compile_options(-sUSE_SDL=2 -pthread -g) + add_link_options( + -sUSE_SDL=2 -pthread -g + -sPTHREAD_POOL_SIZE=4 + -sMODULARIZE -sEXPORT_NAME=createSimu + ) + set(CMAKE_EXECUTABLE_SUFFIX ".mjs") +endif() + set(SIMU_DRIVERS simpgmspace.cpp simufatfs.cpp simudisk.cpp simulcd.cpp + simuaudio.cpp switch_driver.cpp adc_driver.cpp module_drivers.cpp @@ -41,6 +61,8 @@ if(SDL2_FOUND) if(TARGET SDL2::SDL2) set(SDL2_LIBRARIES SDL2::SDL2) endif() +elseif(EMSCRIPTEN) + add_definitions(-DJOYSTICKS) endif() if(SIMU_LUA_COMPILER) @@ -155,60 +177,59 @@ if(WIN32) endif() endif(WIN32) -if(MSVC) - set(CMAKE_CXX_FLAGS "/EHsc") - if(NOT CLANG) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /LD /MP") - endif() -else() - if(MINGW) - # struct packing breaks on MinGW w/out -mno-ms-bitfields: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=52991 & http://stackoverflow.com/questions/24015852/struct-packing-and-alignment-with-mingw - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mno-ms-bitfields") - endif() - set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -O0") - set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0") - if(ASAN) - set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -fsanitize=address") - set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=address") - endif() +if(MINGW) + # struct packing breaks on MinGW w/out -mno-ms-bitfields: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=52991 & http://stackoverflow.com/questions/24015852/struct-packing-and-alignment-with-mingw + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mno-ms-bitfields") +endif() + +set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -O0") +set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0") + +if(ASAN) + set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -fsanitize=address") + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=address") endif() set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WARNING_FLAGS}") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WARNING_FLAGS}") +add_executable(simu + EXCLUDE_FROM_ALL + ${SIMU_SRC} + ${IMGUI_SRC} + sdl_simu.cpp +) -if(FOX_FOUND) - add_executable(simu WIN32 - EXCLUDE_FROM_ALL - ${SIMU_SRC} - ${RADIO_SRC_DIR}/simu.cpp) +include(FetchImgui) +target_compile_options(simu PUBLIC -DSIMU) - target_include_directories(simu PUBLIC ${FOX_INCLUDE_DIR} ) - target_link_libraries(simu ${FOX_LIBRARY} pthread ${SDL2_LIBRARIES}) - target_compile_options(simu PRIVATE ${SIMU_SRC_OPTIONS}) -endif() +add_png_target(image_assets "assets/images/*.png") +add_dependencies(simu image_assets) -if(APPLE) - # OS X compiler no longer automatically includes /Library/Frameworks in search path - set(CMAKE_SHARED_LINKER_FLAGS -F/Library/Frameworks) - - set(SIMULATOR_BUNDLES) - foreach(library ${OPENTX_LIBRARIES}) - set(SIMULATOR_BUNDLE "${library}-bundle") - add_custom_target(${SIMULATOR_BUNDLE} - COMMAND install_name_tool -change /opt/local/Library/Frameworks/QtCore.framework/Versions/4/QtCore @executable_path/../Frameworks/QtCore.framework/Versions/4/QtCore lib${library}.dylib - COMMAND install_name_tool -change /opt/local/Library/Frameworks/QtNetwork.framework/Versions/4/QtNetwork @executable_path/../Frameworks/QtNetwork.framework/Versions/4/QtNetwork lib${library}.dylib - COMMAND install_name_tool -change /opt/local/Library/Frameworks/QtXml.framework/Versions/4/QtXml @executable_path/../Frameworks/QtXml.framework/Versions/4/QtXml lib${library}.dylib - COMMAND install_name_tool -change /opt/local/Library/Frameworks/QtGui.framework/Versions/4/QtGui @executable_path/../Frameworks/QtGui.framework/Versions/4/QtGui lib${library}.dylib - COMMAND install_name_tool -change @rpath/SDL.framework/Versions/A/SDL @executable_path/../Frameworks/SDL.framework/Versions/A/SDL lib${library}.dylib - WORKING_DIRECTORY ${PROJECT_BINARY_DIR} - COMMAND pwd - COMMAND cp lib${library}.dylib companion.app/Contents/Resources/ - DEPENDS ${library} - ) - list(APPEND SIMULATOR_BUNDLES ${SIMULATOR_BUNDLE}) - endforeach() - add_custom_target(opentx-simulators-bundle DEPENDS ${SIMULATOR_BUNDLES}) -endif(APPLE) +set(STB_DIR ${RADIO_SRC_DIR}/thirdparty/libopenui/thirdparty/stb) +target_include_directories(simu PUBLIC ${STB_DIR}) +target_link_libraries(simu PUBLIC imgui) + +if(EMSCRIPTEN) + # Add dependency on simu.html + add_custom_command( + OUTPUT simu.html + DEPENDS html/simu.html + COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_CURRENT_SOURCE_DIR}/html/simu.html + ${CMAKE_BINARY_DIR} + ) + add_custom_target(simu_html DEPENDS simu.html) + add_dependencies(simu simu_html) + + target_link_options(simu PUBLIC + -sMAX_WEBGL_VERSION=2 + ) +else() + target_link_libraries(simu PUBLIC + pthread + ${SDL2_LIBRARIES} + ) +endif() PrintTargetReport("simu/libsimulator") diff --git a/radio/src/targets/simu/assets/images/gimbal_frame.png b/radio/src/targets/simu/assets/images/gimbal_frame.png new file mode 100644 index 0000000000000000000000000000000000000000..a8edd8faa2f42ed155417d6b0fbafd38428de36d GIT binary patch literal 12736 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 literal 0 HcmV?d00001 diff --git a/radio/src/targets/simu/assets/images/icon.png b/radio/src/targets/simu/assets/images/icon.png new file mode 100755 index 0000000000000000000000000000000000000000..7812d2062793e1d9458c06e2b523ff7ed7d213a6 GIT binary patch literal 8957 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 literal 0 HcmV?d00001 diff --git a/radio/src/targets/simu/backlight_driver.cpp b/radio/src/targets/simu/backlight_driver.cpp index c50583cb4ce..15187ff834f 100644 --- a/radio/src/targets/simu/backlight_driver.cpp +++ b/radio/src/targets/simu/backlight_driver.cpp @@ -24,6 +24,18 @@ bool isBacklightEnabled() { return boardBacklightOn; } void backlightFullOn() { boardBacklightOn = true; } void backlightInit() {} -void backlightEnable(unsigned char) {} -void backlightEnable(unsigned char, unsigned char) {} -void backlightDisable() {} + +void backlightEnable(unsigned char) +{ + boardBacklightOn = true; +} + +void backlightEnable(unsigned char, unsigned char) +{ + boardBacklightOn = true; +} + +void backlightDisable() +{ + boardBacklightOn = false; +} diff --git a/radio/src/targets/simu/html/simu.html b/radio/src/targets/simu/html/simu.html new file mode 100644 index 00000000000..19b74b67cd9 --- /dev/null +++ b/radio/src/targets/simu/html/simu.html @@ -0,0 +1,31 @@ + + + + + + + + + diff --git a/radio/src/targets/simu/sdl_simu.cpp b/radio/src/targets/simu/sdl_simu.cpp new file mode 100644 index 00000000000..e55ad20ed96 --- /dev/null +++ b/radio/src/targets/simu/sdl_simu.cpp @@ -0,0 +1,717 @@ +/* + * Copyright (C) EdgeTX + * + * Based on code named + * opentx - https://github.com/opentx/opentx + * th9x - http://code.google.com/p/th9x + * er9x - http://code.google.com/p/er9x + * gruvin9x - http://code.google.com/p/gruvin9x + * + * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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 General Public License for more details. + */ + +#include + +#include +#include +#include + +#ifdef __EMSCRIPTEN__ +#include +#endif + +#if !SDL_VERSION_ATLEAST(2,0,19) +#error This backend requires SDL 2.0.19+ because of SDL_RenderGeometryRaw() function +#endif + +#if defined(__clang__) +#pragma clang diagnostic ignored "-Wunused-function" +#elif defined(__GNUC__) +#pragma GCC diagnostic ignored "-Wunused-function" +#endif + +#define STB_IMAGE_IMPLEMENTATION +#define STB_IMAGE_STATIC +#include "stb_image.h" + +#if defined(STBI_NO_STDIO) + #error "STBI_NO_STDIO is defined" +#endif + +#include "simu.h" +#include "simuaudio.h" +#include "hal/key_driver.h" + +#include "audio.h" +#include "debug.h" +#include "opentx.h" + +#include + +#define TIMER_INTERVAL 10 // 10ms + +SDL_Window* window; +SDL_Renderer* renderer; + +SDL_Texture* screen_frame_buffer; +SDL_Texture* gimbal_frame; +SDL_Texture* stick_dot; + +static ImVec2 stick_l_pos(0.5f, 0.5f); +static ImVec2 stick_r_pos(0.5f, 0.5f); + +static const unsigned char _icon_png[] = { + #include "icon.lbm" +}; + +static const unsigned char _gimbal_frame_png[] = { + #include "gimbal_frame.lbm" +}; + +static const unsigned char _stick_dot_png[] = { + #include "stick_dot.lbm" +}; + +static void _set_pixel(uint8_t* pixel, const SDL_Color& color) +{ + pixel[0] = color.a; + pixel[1] = color.b; + pixel[2] = color.g; + pixel[3] = color.r; +} + +static void _blit_simu_screen_color(void* screen_buffer, Uint32 format, int w, int h, int pitch) +{ + pixel_t* src_buffer = simuLcdBuf; + uint8_t* line_buffer = (uint8_t*)screen_buffer; + + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + pixel_t z = *src_buffer++; + // Alpha + line_buffer[0] = SDL_ALPHA_OPAQUE; + // Blue + line_buffer[1] = (((z & 0x001F) << 3) & 0x00F8) + ((z & 0x001C) >> 2); + // Green + line_buffer[2] = ((z & 0x07E0) >> 3) + ((z & 0x0600) >> 9); + // Red + line_buffer[3] = ((z & 0xF800) >> 8) + ((z & 0xE000) >> 13); + line_buffer += SDL_BYTESPERPIXEL(format); + } + } +} + +static void _blit_simu_screen_1bit(void* screen_buffer, Uint32 format, int w, int h, int pitch) +{ + const SDL_Color on_color = {.r = 0, .g = 0, .b = 0}; + + uint8_t* line_buffer = (uint8_t*)screen_buffer; + + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + SDL_Color c = on_color; + if (simuLcdBuf[x + (y / 8) * w] & (1 << (y % 8))) { + c.a = SDL_ALPHA_OPAQUE; + } else { + c.a = SDL_ALPHA_TRANSPARENT; + } + _set_pixel(line_buffer, c); + line_buffer += SDL_BYTESPERPIXEL(format); + } + } +} + +static inline uint8_t _4bit_blend(uint16_t px, uint16_t bg) +{ + uint16_t c = bg * (16 - px); + return c / 16; +} + +static void _blit_simu_screen_4bit(void* screen_buffer, Uint32 format, int w, int h, int pitch) +{ + const SDL_Color on_color = {.r = 0, .g = 0, .b = 0}; + + uint8_t* line_buffer = (uint8_t*)screen_buffer; + + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + + pixel_t p = simuLcdBuf[y / 2 * LCD_W + x]; + uint8_t z = (y & 1) ? (p >> 4) : (p & 0x0F); + + SDL_Color c = on_color; + c.a = (uint16_t)z * SDL_ALPHA_OPAQUE / 16; + + _set_pixel(line_buffer, c); + line_buffer += SDL_BYTESPERPIXEL(format); + } + } +} + +void refreshDisplay(SDL_Texture* screen) +{ + if (simuLcdRefresh) { + + // fetch texture format + Uint32 format = 0; + int width = 0, height = 0; + + if (SDL_QueryTexture(screen, &format, nullptr, &width, &height) != 0) { + TRACE("SDL_QueryTexture: %s", SDL_GetError()); + return; + } + + // raw pixel buffer + void* screen_buffer = nullptr; + + // length of one row in bytes + int pitch = 0; + + if (SDL_LockTexture(screen, nullptr, &screen_buffer, &pitch) != 0) { + TRACE("SDL_LockTexture: %s", SDL_GetError()); + return; + } + + if (LCD_DEPTH == 1) { + _blit_simu_screen_1bit(screen_buffer, format, width, height, pitch); + } else if (LCD_DEPTH == 4) { + _blit_simu_screen_4bit(screen_buffer, format, width, height, pitch); + } else if (LCD_DEPTH == 16) { + _blit_simu_screen_color(screen_buffer, format, width, height, pitch); + } + + SDL_UnlockTexture(screen); + simuLcdRefresh = false; + lcdFlushed(); + } +} + +Uint32 timer_10ms_cb(Uint32 interval, void* name) +{ + per10ms(); + return TIMER_INTERVAL; +} + +void handleTouchEvents(const ImVec2& screen_p0, float scale_ratio) +{ +#if defined(HARDWARE_TOUCH) + static bool mouse_was_down = false; + + if (ImGui::IsItemHovered() && ImGui::IsMouseDown(ImGuiMouseButton_Left)) { + + ImGuiIO& io = ImGui::GetIO(); + const ImVec2 mouse_pos(io.MousePos.x - screen_p0.x, io.MousePos.y - screen_p0.y); + ImGui::Text("Mouse: x = %f; y = %f", mouse_pos.x, mouse_pos.y); + + const ImVec2 scaled_pos(mouse_pos.x / scale_ratio, mouse_pos.y / scale_ratio); + ImGui::Text("Pos: x = %f; y = %f", scaled_pos.x, scaled_pos.y); + + touchPanelDown((short)scaled_pos.x, (short)scaled_pos.y); + mouse_was_down = true; + } else if (mouse_was_down) { + touchPanelUp(); + mouse_was_down = false; + } +#endif +} + +bool simuProcessEvents(SDL_Event& event) +{ + if (event.type == SDL_KEYDOWN || event.type == SDL_KEYUP) { + const auto& key_event = event.key; + + bool key_handled = false; + uint8_t key = 0; + + switch(key_event.keysym.sym) { + + case SDLK_ESCAPE: + key = KEY_EXIT; + key_handled = true; + break; + + case SDLK_RETURN: + key = KEY_ENTER; + key_handled = true; + break; + + case SDLK_LEFT: + if (keysGetSupported() & (1 << KEY_LEFT)) { + key = KEY_LEFT; + key_handled = true; + } + break; + + case SDLK_RIGHT: + if (keysGetSupported() & (1 << KEY_RIGHT)) { + key = KEY_RIGHT; + key_handled = true; + } + break; + + case SDLK_UP: + if (keysGetSupported() & (1 << KEY_UP)) { + key = KEY_UP; + key_handled = true; + } + break; + + case SDLK_DOWN: + if (keysGetSupported() & (1 << KEY_DOWN)) { + key = KEY_DOWN; + key_handled = true; + } + break; + + case SDLK_PLUS: + if (keysGetSupported() & (1 << KEY_PLUS)) { + key = KEY_PLUS; + key_handled = true; + } + break; + + case SDLK_MINUS: + if (keysGetSupported() & (1 << KEY_MINUS)) { + key = KEY_MINUS; + key_handled = true; + } + break; + + case SDLK_m: + if (keysGetSupported() & (1 << KEY_MENU)) { + key = KEY_MENU; + key_handled = true; + } else if (keysGetSupported() & (1 << KEY_MODEL)) { + key = KEY_MODEL; + key_handled = true; + } + break; + + case SDLK_s: + if (keysGetSupported() & (1 << KEY_SYS)) { + key = KEY_SYS; + key_handled = true; + } + break; + + case SDLK_t: + if (keysGetSupported() & (1 << KEY_TELE)) { + key = KEY_TELE; + key_handled = true; + } + break; + + default: + key_handled = false; + break; + } + + if (key_handled) { + simuSetKey(key, key_event.type == SDL_KEYDOWN ? true : false); + } + + return key_handled; + } + + return false; +} + +SDL_Surface* LoadImage(const unsigned char* pixels, size_t len) +{ + // Read data + int32_t w, h, bpp; + + void* data = stbi_load_from_memory(pixels, len, &w, &h, &bpp, 0); + if (!data) return NULL; + + Uint32 format = (bpp == 3) ? SDL_PIXELFORMAT_RGB24 : SDL_PIXELFORMAT_RGBA32; + + SDL_Surface* surface = SDL_CreateRGBSurfaceWithFormat(0, w, h, bpp * 8, format); + if (surface) { + SDL_LockSurface(surface); + memcpy(surface->pixels, data, w * h * bpp); + SDL_UnlockSurface(surface); + } + + stbi_image_free(data); + return surface; +} + +SDL_Texture* LoadTexture(SDL_Renderer* renderer, const unsigned char* pixels, size_t len) +{ + SDL_Surface* surface = LoadImage(pixels, len); + if (!surface) return NULL; + + SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface); + SDL_FreeSurface(surface); + + return texture; +} + +void SingleGimbal(const char* name, SDL_Texture* gimbal_frame, + SDL_Texture* stick_dot, ImVec2& stick_pos, + bool lock_y) +{ + auto frame_width = ImGui::GetContentRegionAvail().x; + auto gimbal_width = frame_width < 91.0f ? 91.0f : frame_width > 200.0f ? 200.0f : frame_width; + + float ratio = gimbal_width / 182.0f; + + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + + ImVec2 pos = ImGui::GetCursorScreenPos(); + ImVec2 gimbal_p0(pos.x + (frame_width - gimbal_width) / 2.0f, pos.y); + ImVec2 gimbal_p1(gimbal_p0.x + gimbal_width, gimbal_p0.y + gimbal_width); + draw_list->AddImage(gimbal_frame, gimbal_p0, gimbal_p1); + + float dot_width = 38.0f * ratio; + float dot_height = 40.0f * ratio; + float dot_mid = 17.0f * ratio; + float dot_range = gimbal_width - dot_mid * 2.0f; + + ImVec2 dot_p0(gimbal_p0.x + dot_range * stick_pos.x, + gimbal_p0.y + dot_range * stick_pos.y); + ImVec2 dot_p1(dot_p0.x + dot_width, dot_p0.y + dot_height); + draw_list->AddImage(stick_dot, dot_p0, dot_p1); + + ImGui::InvisibleButton(name, ImVec2(frame_width, gimbal_width)); + if (ImGui::IsItemHovered() && ImGui::IsMouseDown(ImGuiMouseButton_Left)) { + + const auto& io = ImGui::GetIO(); + ImVec2 mouse_pos(io.MousePos.x - gimbal_p0.x, + io.MousePos.y - gimbal_p0.y); + + if (mouse_pos.x >= 0 && mouse_pos.x <= gimbal_width && + mouse_pos.y >= 0 && mouse_pos.y <= gimbal_width) { + + stick_pos.x = (mouse_pos.x - dot_mid) / dot_range; + stick_pos.y = (mouse_pos.y - dot_mid) / dot_range; + + if (stick_pos.x < 0.0f) stick_pos.x = 0.0f; + if (stick_pos.x > 1.0f) stick_pos.x = 1.0f; + if (stick_pos.y < 0.0f) stick_pos.y = 0.0f; + if (stick_pos.y > 1.0f) stick_pos.y = 1.0f; + + // ImGui::Text("X = %0.2f Y = %0.2f", stick_pos.x, stick_pos.y); + } + } else { + if (stick_pos.x != 0.5f) { + auto diff = 0.5 - stick_pos.x; + if (std::abs(diff) > 0.01) { + stick_pos.x += diff / 10; + } else { + stick_pos.x = 0.5; + } + } + if (!lock_y && stick_pos.y != 0.5f) { + auto diff = 0.5 - stick_pos.y; + if (std::abs(diff) > 0.01) { + stick_pos.y += diff / 10; + } else { + stick_pos.y = 0.5; + } + } + } +} + +void DisplayGimbals(SDL_Texture* gimbal_frame, SDL_Texture* stick_dot) +{ + // TODO: minimum width + if (ImGui::BeginTable("gimbals", 2, 0)) { + ImGui::TableNextRow(); + + ImGui::TableSetColumnIndex(0); + SingleGimbal("GimbalL", gimbal_frame, stick_dot, stick_l_pos, g_eeGeneral.stickMode == 1); + + ImGui::TableSetColumnIndex(1); + SingleGimbal("GimbalR", gimbal_frame, stick_dot, stick_r_pos, g_eeGeneral.stickMode == 0); + + ImGui::EndTable(); + } +} + +Uint32 get_bg_color() +{ + if (isBacklightEnabled()) { + return IM_COL32(47, 123, 227, 255); + } else { + return IM_COL32(200, 200, 200, 255); + } +} + +void redraw() +{ + ImGuiStyle& style = ImGui::GetStyle(); + + // poll audio + audioQueue.wakeup(); + + refreshDisplay(screen_frame_buffer); + + // Start the Dear ImGui frame + ImGui_ImplSDLRenderer2_NewFrame(); + ImGui_ImplSDL2_NewFrame(); + ImGui::NewFrame(); + + static ImGuiWindowFlags flags = + ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoCollapse | + ImGuiWindowFlags_NoTitleBar; + + const ImGuiViewport* viewport = ImGui::GetMainViewport(); + ImGui::SetNextWindowPos(viewport->WorkPos); + ImGui::SetNextWindowSize(viewport->WorkSize); + + bool show_win = true; + if (ImGui::Begin("Main window", &show_win, flags)) { + + // show gimbals + DisplayGimbals(gimbal_frame, stick_dot); + + ImVec2 screen_p0 = ImGui::GetCursorScreenPos(); + float aspect_ratio = float(LCD_H) / float(LCD_W); + + float width = viewport->WorkSize.x - 2 * style.WindowPadding.x; + float scale_ratio = width / float(LCD_W); + + ImVec2 size(width, width * aspect_ratio); + + if (LCD_DEPTH == 1 || LCD_DEPTH == 4) { + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + ImVec2 p1 = { screen_p0.x + size.x, screen_p0.y + size.y }; + draw_list->AddRectFilled(screen_p0, p1, get_bg_color()); + } + + ImGui::Image(screen_frame_buffer, size); + + if (LCD_DEPTH == 1 || LCD_DEPTH == 4) { + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + float dx = size.x / float(LCD_W); + float thickness = dx / 50.0; + auto col = get_bg_color() & 0x80FFFFFF; + for (int x = 1; x < LCD_W; x++) { + ImVec2 p1 = { screen_p0.x + x * size.x / float(LCD_W), screen_p0.y }; + ImVec2 p2 = { p1.x, screen_p0.y + size.y - 1 }; + draw_list->AddLine(p1, p2, col, thickness); + } + for (int y = 1; y < LCD_H; y++) { + ImVec2 p1 = { screen_p0.x, screen_p0.y + y * size.y / float(LCD_H) }; + ImVec2 p2 = { screen_p0.x + size.x - 1, p1.y }; + draw_list->AddLine(p1, p2, col, thickness); + } + } + + handleTouchEvents(screen_p0, scale_ratio); + + ImGui::Text("tmr10ms: %u", g_tmr10ms); + ImGui::Text("rtos time: %u", RTOS_GET_MS()); + } + ImGui::End(); + + // Rendering + ImGui::Render(); + + ImGuiIO& io = ImGui::GetIO(); + SDL_RenderSetScale(renderer, io.DisplayFramebufferScale.x, + io.DisplayFramebufferScale.y); + + SDL_SetRenderDrawColor(renderer, 114, 140, 153, 255); + SDL_RenderClear(renderer); + + ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData()); + SDL_RenderPresent(renderer); +} + +bool handle_events() +{ + SDL_Event event; + while (SDL_PollEvent(&event)) { + + if(simuProcessEvents(event)) + continue; + + ImGui_ImplSDL2_ProcessEvent(&event); + + if (event.type == SDL_QUIT) + return false; + + if (event.type == SDL_WINDOWEVENT && + event.window.event == SDL_WINDOWEVENT_CLOSE && + event.window.windowID == SDL_GetWindowID(window)) + return false; + } + + redraw(); + return true; +} + +int main(int argc, char** argv) +{ + // Init simulation + simuInit(); + simuStart(false, nullptr, nullptr); + + // Setup SDL + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER | SDL_INIT_GAMECONTROLLER) != 0) { + printf("Error: %s\n", SDL_GetError()); + return -1; + } + + simuAudioInit(); + + // From 2.0.18: Enable native IME. +#ifdef SDL_HINT_IME_SHOW_UI + SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1"); +#endif + + // Create window with SDL_Renderer graphics context + SDL_WindowFlags window_flags = + (SDL_WindowFlags)(SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); + window = SDL_CreateWindow("EdgeTx Simu", SDL_WINDOWPOS_CENTERED, + SDL_WINDOWPOS_CENTERED, 600, 600, window_flags); + + renderer = SDL_CreateRenderer( + window, -1, SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_ACCELERATED); + if (renderer == NULL) { + SDL_Log("Error creating SDL_Renderer!"); + return 0; + } + + SDL_RendererInfo info; + SDL_GetRendererInfo(renderer, &info); + SDL_Log("Current SDL_Renderer: %s", info.name); + + SDL_Surface* icon = LoadImage(_icon_png, sizeof(_icon_png)); + if (window && icon) { + SDL_SetWindowIcon(window, icon); + } + + // Setup Dear ImGui context + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + + // Setup Dear ImGui style + ImGui::StyleColorsLight(); + // ImGui::StyleColorsDark(); + + // Setup Platform/Renderer backends + ImGui_ImplSDL2_InitForSDLRenderer(window, renderer); + ImGui_ImplSDLRenderer2_Init(renderer); + + // Create textures for radio screen + screen_frame_buffer = + SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, + SDL_TEXTUREACCESS_STREAMING, LCD_W, LCD_H); + + if (!screen_frame_buffer) { + SDL_Log("Could not create frame buffer textures"); + return 0; + } + + // SDL_SetTextureScaleMode(screen_frame_buffer, SDL_ScaleModeBest); + SDL_SetTextureBlendMode(screen_frame_buffer, SDL_BLENDMODE_BLEND); + + gimbal_frame = LoadTexture(renderer, _gimbal_frame_png, sizeof(_gimbal_frame_png)); + stick_dot = LoadTexture(renderer, _stick_dot_png, sizeof(_stick_dot_png)); + + if (!gimbal_frame || !stick_dot) { + SDL_Log("Could not load textures"); + return 0; + } + + // 10ms timer + SDL_TimerID timerID_10ms = + SDL_AddTimer(TIMER_INTERVAL, timer_10ms_cb, const_cast("10ms")); + if (!timerID_10ms) { + SDL_Log("Error creating SDL_AddTimer!"); + return 0; + } + + // race condition on YAML loaded... + if (g_eeGeneral.stickMode == 1) { + stick_l_pos.y = 1.0f; + } else if (g_eeGeneral.stickMode == 0) { + stick_r_pos.y = 1.0f; + } + + // Main loop + SDL_SetEventFilter([](void*, SDL_Event* event){ + if (event->type == SDL_WINDOWEVENT && + event->window.event == SDL_WINDOWEVENT_SIZE_CHANGED) { + redraw(); + return 0; + } + return 1; + }, NULL); + +#if defined(__EMSCRIPTEN__) + emscripten_set_main_loop([]() { handle_events(); }, 0, true); +#else + do { + Uint64 start_ts = SDL_GetPerformanceCounter(); + if (!handle_events()) break; + + Uint64 end_ts = SDL_GetPerformanceCounter(); + float elapsedMS = + (end_ts - start_ts) / (float)SDL_GetPerformanceFrequency() * 1000.0f; + + // Cap to 60 FPS + SDL_Delay(floor(16.666f - elapsedMS)); + + } while(true); +#endif + + // Cleanup + ImGui_ImplSDLRenderer2_Shutdown(); + ImGui_ImplSDL2_Shutdown(); + ImGui::DestroyContext(); + + SDL_DestroyTexture(screen_frame_buffer); + SDL_DestroyTexture(gimbal_frame); + SDL_DestroyTexture(stick_dot); + + SDL_RemoveTimer(timerID_10ms); + SDL_DestroyRenderer(renderer); + SDL_DestroyWindow(window); + SDL_FreeSurface(icon); + SDL_CloseAudio(); + SDL_Quit(); + + return 0; +} + +uint16_t simu_get_analog(uint8_t idx) +{ + auto max_sticks = adcGetMaxInputs(ADC_INPUT_MAIN); + if (idx < max_sticks) { + switch(idx) { + case 0: return stick_l_pos.x * 4096; + case 1: return (1.0 - stick_l_pos.y) * 4096; + case 2: return (1.0 - stick_r_pos.y) * 4096; + case 3: return stick_r_pos.x * 4096; + } + } + + // idx -= max_sticks; + + // auto max_pots = adcGetMaxInputs(ADC_INPUT_POT); + // if (idx < max_pots) + // return opentxSim->knobs[idx]->getValue(); + + // idx -= max_pots; + + // auto max_axes = adcGetMaxInputs(ADC_INPUT_AXIS); + // if (idx < max_axes) return 0; + + // probably RTC_BAT + return 0; +} diff --git a/radio/src/targets/simu/simpgmspace.cpp b/radio/src/targets/simu/simpgmspace.cpp index 86a41f8bcb1..f99b98ce6f7 100644 --- a/radio/src/targets/simu/simpgmspace.cpp +++ b/radio/src/targets/simu/simpgmspace.cpp @@ -269,111 +269,6 @@ bool simuIsRunning() return simu_running; } -void audioConsumeCurrentBuffer() -{ -} - -void audioSetVolume(uint8_t volume) -{ - simuAudio.currentVolume = 127 * volume * simuAudio.volumeGain / VOLUME_LEVEL_MAX / 10; - // TRACE_SIMPGMSPACE("setVolume(): in: %u, out: %u", volume, simuAudio.currentVolume); -} - -#if defined(SIMU_AUDIO) -void copyBuffer(void* dest, const int16_t* buff, unsigned samples) -{ - int16_t* i16_dst = (int16_t*)dest; - for (unsigned i = 0; i < samples; i++) { - int32_t sample = (((int32_t)buff[i] * (int32_t)simuAudio.currentVolume) / 127); - if (sample > INT16_MAX) sample = INT16_MAX; - else if (sample < INT16_MIN) sample = INT16_MIN; - *(i16_dst++) = (int16_t)sample; - } -} - -void fillAudioBuffer(void *udata, Uint8 *stream, int len) -{ - SDL_memset(stream, 0, len); - - if (simuAudio.leftoverLen) { - int len1 = min(len/2, simuAudio.leftoverLen); - copyBuffer(stream, simuAudio.leftoverData, len1); - len -= len1*2; - stream += len1*2; - simuAudio.leftoverLen -= len1; - // putchar('l'); - if (simuAudio.leftoverLen) return; // buffer fully filled - } - - if (audioQueue.buffersFifo.filledAtleast(len / (AUDIO_BUFFER_SIZE * 2) + 1)) { - while (true) { - const AudioBuffer* nextBuffer = - audioQueue.buffersFifo.getNextFilledBuffer(); - if (nextBuffer) { - if (len >= nextBuffer->size * 2) { - copyBuffer(stream, nextBuffer->data, nextBuffer->size); - stream += nextBuffer->size * 2; - len -= nextBuffer->size * 2; - // putchar('+'); - audioQueue.buffersFifo.freeNextFilledBuffer(); - } else { - // partial - copyBuffer(stream, nextBuffer->data, len / 2); - simuAudio.leftoverLen = (nextBuffer->size - len / 2); - memcpy(simuAudio.leftoverData, &nextBuffer->data[len / 2], - simuAudio.leftoverLen * 2); - len = 0; - // putchar('p'); - audioQueue.buffersFifo.freeNextFilledBuffer(); - break; - } - } else { - break; - } - } - } - - // fill the rest of buffer with silence - if (len > 0) { - SDL_memset(stream, 0x8000, len); // make sure this is silence. - } -} - -int startAudio(int volumeGain) -{ - simuAudio = { - .volumeGain = volumeGain, - .leftoverLen = 0, - }; - - TRACE("startAudioThread(%d)", volumeGain); - audioSetVolume(VOLUME_LEVEL_DEF); - - /* Set the audio format */ - SDL_AudioSpec desired = { - .freq = AUDIO_SAMPLE_RATE, - .format = AUDIO_S16SYS, - .channels = 1, - .samples = AUDIO_BUFFER_SIZE * 2, - .callback = fillAudioBuffer, - .userdata = nullptr, - }; - - SDL_AudioSpec obtained; - if ( SDL_OpenAudio(&desired, &obtained) < 0 ) { - fprintf(stderr, "Couldn't open audio: %s\n", SDL_GetError()); - return -1; - } - SDL_PauseAudio(0); - return 0; -} - -void stopAudio() -{ - SDL_CloseAudio(); -} -#endif // #if defined(SIMU_AUDIO) - #if !defined(COLORLCD) void lcdSetRefVolt(uint8_t val) { @@ -616,6 +511,12 @@ const etx_serial_port_t* auxSerialGetPort(int port_nr) struct TouchState simTouchState = {}; bool simTouchOccured = false; +bool touchPanelInit() +{ + simTouchState.x = simTouchState.y = 0; + return true; +} + bool touchPanelEventOccured() { if(simTouchOccured) @@ -626,6 +527,20 @@ bool touchPanelEventOccured() return false; } +void touchPanelDown(short x, short y) +{ + simTouchState.x = x; + simTouchState.y = y; + simTouchState.event = TE_DOWN; + simTouchOccured = true; +} + +void touchPanelUp() +{ + simTouchState.event = TE_UP; + simTouchOccured = true; +} + struct TouchState touchPanelRead() { struct TouchState st = simTouchState; diff --git a/radio/src/targets/simu/simu.h b/radio/src/targets/simu/simu.h new file mode 100644 index 00000000000..9086f13935c --- /dev/null +++ b/radio/src/targets/simu/simu.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) EdgeTX + * + * Based on code named + * opentx - https://github.com/opentx/opentx + * th9x - http://code.google.com/p/th9x + * er9x - http://code.google.com/p/er9x + * gruvin9x - http://code.google.com/p/gruvin9x + * + * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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 General Public License for more details. + */ + +#pragma once + +#if defined(COLORLCD) + #include "gui/colorlcd/lcd.h" +#endif + +#include "targets/simu/simulcd.h" +#include "hal/adc_driver.h" +#include "hal/rotary_encoder.h" + +// 10ms ISR +void per10ms(); + +// touch panel methods +bool touchPanelInit(); +void touchPanelDown(short x, short y); +void touchPanelUp(); diff --git a/radio/src/targets/simu/simuaudio.cpp b/radio/src/targets/simu/simuaudio.cpp new file mode 100644 index 00000000000..b22b30adffe --- /dev/null +++ b/radio/src/targets/simu/simuaudio.cpp @@ -0,0 +1,102 @@ +/* + * Copyright (C) EdgeTX + * + * Based on code named + * opentx - https://github.com/opentx/opentx + * th9x - http://code.google.com/p/th9x + * er9x - http://code.google.com/p/er9x + * gruvin9x - http://code.google.com/p/gruvin9x + * + * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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 General Public License for more details. + */ + +#include "audio.h" +#include "simuaudio.h" + +#include +#include + +#define SIMU_AUDIO_FMT AUDIO_U16SYS + +static SDL_AudioDeviceID _sdl_audio_device = 0; + +#if !defined(SOFTWARE_VOLUME) + +static Uint8 _tmp_buf[AUDIO_BUFFER_SIZE * sizeof(audio_data_t)]; +static int _simu_volume = 0; + +void audioSetVolume(uint8_t volume) +{ + _simu_volume = SDL_MIX_MAXVOLUME * volume / VOLUME_LEVEL_MAX; +} + +static void _fill_with_silence(Uint8* dst, uint32_t len) +{ + auto p = (audio_data_t*)dst; + auto end = p + len / sizeof(audio_data_t); + + while (p < end) { + *p++ = AUDIO_DATA_SILENCE; + } +} + +#endif + +void audioConsumeCurrentBuffer() +{ + while(true) { + auto nextBuffer = audioQueue.buffersFifo.getNextFilledBuffer(); + if (!nextBuffer) return; + + auto data = (const Uint8*)nextBuffer->data; + uint32_t len = nextBuffer->size * sizeof(audio_data_t); + +#if !defined(SOFTWARE_VOLUME) + assert(len <= sizeof(_tmp_buf)); + _fill_with_silence(_tmp_buf, len); + + SDL_MixAudioFormat(_tmp_buf, data, SIMU_AUDIO_FMT, len, _simu_volume); + data = _tmp_buf; +#endif + + SDL_QueueAudio(_sdl_audio_device, data, len); + audioQueue.buffersFifo.freeNextFilledBuffer(); + } +} + +bool simuAudioInit() +{ + SDL_AudioSpec wanted = { + .freq = AUDIO_SAMPLE_RATE, + .format = SIMU_AUDIO_FMT, + .channels = 1, + .samples = 1024, + }; + + SDL_AudioSpec have; + _sdl_audio_device = SDL_OpenAudioDevice(0, 0, &wanted, &have, 0); + if (!_sdl_audio_device) { + SDL_Log("Couldn't open audio: %s\n", SDL_GetError()); + return false; + } + + SDL_PauseAudioDevice(_sdl_audio_device, 0); + return true; +} + +void simuAudioDeInit() +{ + if(_sdl_audio_device > 0) { + SDL_CloseAudioDevice(_sdl_audio_device); + _sdl_audio_device = 0; + } +} diff --git a/radio/src/targets/simu/simuaudio.h b/radio/src/targets/simu/simuaudio.h new file mode 100644 index 00000000000..e9677443bc9 --- /dev/null +++ b/radio/src/targets/simu/simuaudio.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) EdgeTX + * + * Based on code named + * opentx - https://github.com/opentx/opentx + * th9x - http://code.google.com/p/th9x + * er9x - http://code.google.com/p/er9x + * gruvin9x - http://code.google.com/p/gruvin9x + * + * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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 General Public License for more details. + */ + +#pragma once + +bool simuAudioInit(); +void simuAudioDeInit(); diff --git a/radio/src/targets/simu/simulcd.cpp b/radio/src/targets/simu/simulcd.cpp index 1286c40172f..03b738ca36e 100644 --- a/radio/src/targets/simu/simulcd.cpp +++ b/radio/src/targets/simu/simulcd.cpp @@ -57,51 +57,7 @@ static pixel_t _LCD_BUF2[DISPLAY_BUFFER_SIZE] __SDRAM; pixel_t* simuLcdBuf = _LCD_BUF1; pixel_t* simuLcdBackBuf = _LCD_BUF2; -#if 0 -// Copy 2 pixels at once to speed up a little -static void _copy_rotate_180(uint16_t* dst, uint16_t* src, const rect_t& copy_area) -{ - coord_t x1 = LCD_W - copy_area.w - copy_area.x; - coord_t y1 = LCD_H - copy_area.h - copy_area.y; - - auto total = copy_area.w * copy_area.h; - uint16_t* px_src = src + total - 2; - - auto px_dst = dst + y1 * LCD_W + x1; - for (auto line = 0; line < copy_area.h; line++) { - - auto line_end = px_dst + (copy_area.w & ~1); - while (px_dst != line_end) { - uint32_t* px2_src = (uint32_t*)px_src; - uint32_t* px2_dst = (uint32_t*)px_dst; - - uint32_t px = ((*px2_src & 0xFFFF0000) >> 16) | ((*px2_src & 0xFFFF) << 16); - *px2_dst = px; - - px_src -= 2; - px_dst += 2; - } - if (copy_area.w & 1) { - *(px_dst++) = *(px_src+1); - px_src--; - } - - px_dst += LCD_W - copy_area.w; - } -} - -static void _rotate_area_180(lv_area_t& area) -{ - lv_coord_t tmp_coord; - tmp_coord = area.y2; - area.y2 = LCD_H - area.y1 - 1; - area.y1 = LCD_H - tmp_coord - 1; - tmp_coord = area.x2; - area.x2 = LCD_W - area.x1 - 1; - area.x1 = LCD_W - tmp_coord - 1; -} -#endif static void _copy_screen_area(uint16_t* dst, uint16_t* src, const lv_area_t& copy_area) { lv_coord_t x1 = copy_area.x1; @@ -150,8 +106,6 @@ static void simuRefreshLcd(lv_disp_drv_t * disp_drv, uint16_t *buffer, const rec simuLcdRefresh = true; #else - // // copy / rotate current area - // _copy_rotate_180(simuLcdBackBuf, buffer, copy_area); _copy_area(simuLcdBackBuf, buffer, copy_area); if (lv_disp_flush_is_last(disp_drv)) { diff --git a/radio/src/targets/simu/stick_dot.bmp b/radio/src/targets/simu/stick_dot.bmp new file mode 100644 index 0000000000000000000000000000000000000000..e50c17ef03263bd594262ea8f99ca9c67005ef2d GIT binary patch literal 6134 zcmcJTdu&rx9LKw<-~$m~sFAJR#wJ2ALIxPaLy?U5Am9Tv{$*lD$YN3BWDnN%tZTas za2S>hLclOF6AFwzCOE-4fdmMI1q7KHGRAPPKDKnU>nPpvd%AX%?wq@}NR!iZ?s@$_ z_j`W#ch0?wmwBF%1%7k!KMg4ZshLumAXA_ulQp6}J$X0`4)<^CSCe86T-w%N>zlMZWgHYKwAW1K$b?sjWx0c`g&hNzQC?q`gB7I{j{ z2FDoCQ)!CoYRTp?r2VP4@h$4_zk z4r~d*r3++^pVE>wK;jA=ky4%t4h(_cfll?J}iOwOV(9kh1x!R;_v<#0I9zJTAwtuZm2>RnOuH&kn2GxA(O!s zSWmV;@cQ?kSI1>vYLMMJmCI(T`e;a{DrqY!B1^zFj%fz2n>;S>b*q$#xY|Vi-?05n zww5>4>XOZ+rKOKEE>SG?^<9k1w$vc!Z*Oq*I=yQFB}9KqqtTs1+(-phBDBIeWOR)1 z^_`b85m%e@$m?9LQ#%(iy;$l}tMzXym7B+dX^_X|y|ynd+fsuZhh}lP4sUxW$ffTC zS6+E}`J-UsohV z6SG}e_Em^$0P$ikAse5xSHwUtx~t#UPaQwc5OP&lWQTLHxL;jR zQ1B#+Vcq5_?}@lrz4y9#w83a<{DahSWTB9$b>H-GCRTP4Qew!<%X^G*v0t}I(JExK zD5(2Vc{C&=ef;hYDvApsJKHv_j^uKvRN7DBUk?vc$z&a-=?l=G8)HNBIP2%(Jl{BC zn_KV=P}i9+pbk+PXDTc#41UeHO$PlYUaFbh92$Qw z@@`WKtYlm_kg?Q?8F&xOZgfelMnuWJU^dI z1qt^(1YDB{F{rIo?|Ve&>v;HD_s*aOZ4r-&`Bf**IN724DUyjOWM67tuY2%J8s?lh9OsuXSXTn`;*ODnFBz3x=g%BM)kc6Tobff{fF4!FETbHS^^uE zst@RenoP>p<=@(L%KW(NKq|eq{{`Bz=cNFPz=uY7*j$(wOuEzXtE`HOiiaY#k+@D^ z?@k!I66q|G2Z{MY#QZT|<1Ek4%=%+$b@9@mPf4yHhY@UK0^SD}tJT^%%w+@Dt-E0J zVQeR!s~VN6bPncz5H@3F8o^X5mE-X&q+yT9$GlC5e@^6VZKKhT^^#oSzOX%+o#~LHylE2i>3G-cKc>m!j{pDw literal 0 HcmV?d00001 From 67bfcca065ecc1deca42f38284c49660759ca525 Mon Sep 17 00:00:00 2001 From: raphaelcoeffic Date: Sun, 8 Oct 2023 10:16:44 +0200 Subject: [PATCH 03/14] web: resizable canvas --- radio/src/targets/simu/html/simu.html | 48 +++++++++++++++++++-------- radio/src/targets/simu/sdl_simu.cpp | 3 +- 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/radio/src/targets/simu/html/simu.html b/radio/src/targets/simu/html/simu.html index 19b74b67cd9..a2cefbf40bf 100644 --- a/radio/src/targets/simu/html/simu.html +++ b/radio/src/targets/simu/html/simu.html @@ -1,29 +1,49 @@ - +
+ +
diff --git a/radio/src/targets/simu/sdl_simu.cpp b/radio/src/targets/simu/sdl_simu.cpp index e55ad20ed96..108bc43ce84 100644 --- a/radio/src/targets/simu/sdl_simu.cpp +++ b/radio/src/targets/simu/sdl_simu.cpp @@ -646,7 +646,8 @@ int main(int argc, char** argv) // Main loop SDL_SetEventFilter([](void*, SDL_Event* event){ if (event->type == SDL_WINDOWEVENT && - event->window.event == SDL_WINDOWEVENT_SIZE_CHANGED) { + (event->window.event == SDL_WINDOWEVENT_SIZE_CHANGED || + event->window.event == SDL_WINDOWEVENT_RESIZED)) { redraw(); return 0; } From b1a18d5f58c0faf7f867d03c56edd107bbfc35b4 Mon Sep 17 00:00:00 2001 From: raphaelcoeffic Date: Mon, 9 Oct 2023 13:00:14 +0200 Subject: [PATCH 04/14] Better widgets --- radio/src/targets/simu/CMakeLists.txt | 2 +- radio/src/targets/simu/html/simu.html | 1 + radio/src/targets/simu/sdl_simu.cpp | 293 ++++++++------------------ radio/src/targets/simu/widgets.cpp | 218 +++++++++++++++++++ radio/src/targets/simu/widgets.h | 50 +++++ 5 files changed, 360 insertions(+), 204 deletions(-) create mode 100644 radio/src/targets/simu/widgets.cpp create mode 100644 radio/src/targets/simu/widgets.h diff --git a/radio/src/targets/simu/CMakeLists.txt b/radio/src/targets/simu/CMakeLists.txt index 0262b5dd558..6aed56ec9d9 100644 --- a/radio/src/targets/simu/CMakeLists.txt +++ b/radio/src/targets/simu/CMakeLists.txt @@ -196,7 +196,7 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WARNING_FLAGS}") add_executable(simu EXCLUDE_FROM_ALL ${SIMU_SRC} - ${IMGUI_SRC} + widgets.cpp sdl_simu.cpp ) diff --git a/radio/src/targets/simu/html/simu.html b/radio/src/targets/simu/html/simu.html index a2cefbf40bf..6bac46ccb8e 100644 --- a/radio/src/targets/simu/html/simu.html +++ b/radio/src/targets/simu/html/simu.html @@ -6,6 +6,7 @@ height: 600px; margin: auto; border: 1px solid black; + padding: 4px; display: block; } .resizable-content { diff --git a/radio/src/targets/simu/sdl_simu.cpp b/radio/src/targets/simu/sdl_simu.cpp index 108bc43ce84..1c946228da9 100644 --- a/radio/src/targets/simu/sdl_simu.cpp +++ b/radio/src/targets/simu/sdl_simu.cpp @@ -48,6 +48,8 @@ #endif #include "simu.h" +#include "widgets.h" + #include "simuaudio.h" #include "hal/key_driver.h" @@ -59,27 +61,18 @@ #define TIMER_INTERVAL 10 // 10ms -SDL_Window* window; -SDL_Renderer* renderer; - -SDL_Texture* screen_frame_buffer; -SDL_Texture* gimbal_frame; -SDL_Texture* stick_dot; +static SDL_Window* window; +static SDL_Renderer* renderer; +static SDL_Texture* screen_frame_buffer; -static ImVec2 stick_l_pos(0.5f, 0.5f); -static ImVec2 stick_r_pos(0.5f, 0.5f); +static GimbalState stick_left = {{0.5f, 0.5f}, false}; +static GimbalState stick_right = {{0.5f, 0.5f}, false}; +#if !defined(__EMSCRIPTEN__) static const unsigned char _icon_png[] = { - #include "icon.lbm" -}; - -static const unsigned char _gimbal_frame_png[] = { - #include "gimbal_frame.lbm" -}; - -static const unsigned char _stick_dot_png[] = { - #include "stick_dot.lbm" +#include "icon.lbm" }; +#endif static void _set_pixel(uint8_t* pixel, const SDL_Color& color) { @@ -157,7 +150,7 @@ static void _blit_simu_screen_4bit(void* screen_buffer, Uint32 format, int w, in } } -void refreshDisplay(SDL_Texture* screen) +static void refreshDisplay(SDL_Texture* screen) { if (simuLcdRefresh) { @@ -195,36 +188,13 @@ void refreshDisplay(SDL_Texture* screen) } } -Uint32 timer_10ms_cb(Uint32 interval, void* name) +static Uint32 timer_10ms_cb(Uint32 interval, void* name) { per10ms(); return TIMER_INTERVAL; } -void handleTouchEvents(const ImVec2& screen_p0, float scale_ratio) -{ -#if defined(HARDWARE_TOUCH) - static bool mouse_was_down = false; - - if (ImGui::IsItemHovered() && ImGui::IsMouseDown(ImGuiMouseButton_Left)) { - - ImGuiIO& io = ImGui::GetIO(); - const ImVec2 mouse_pos(io.MousePos.x - screen_p0.x, io.MousePos.y - screen_p0.y); - ImGui::Text("Mouse: x = %f; y = %f", mouse_pos.x, mouse_pos.y); - - const ImVec2 scaled_pos(mouse_pos.x / scale_ratio, mouse_pos.y / scale_ratio); - ImGui::Text("Pos: x = %f; y = %f", scaled_pos.x, scaled_pos.y); - - touchPanelDown((short)scaled_pos.x, (short)scaled_pos.y); - mouse_was_down = true; - } else if (mouse_was_down) { - touchPanelUp(); - mouse_was_down = false; - } -#endif -} - -bool simuProcessEvents(SDL_Event& event) +static bool handleKeyEvents(SDL_Event& event) { if (event.type == SDL_KEYDOWN || event.type == SDL_KEYUP) { const auto& key_event = event.key; @@ -310,6 +280,14 @@ bool simuProcessEvents(SDL_Event& event) } break; + case SDLK_l: + ImGui::StyleColorsLight(); + break; + + case SDLK_d: + ImGui::StyleColorsDark(); + break; + default: key_handled = false; break; @@ -325,7 +303,32 @@ bool simuProcessEvents(SDL_Event& event) return false; } -SDL_Surface* LoadImage(const unsigned char* pixels, size_t len) +static void redraw(); + +static bool handleEvents() +{ + SDL_Event event; + while (SDL_PollEvent(&event)) { + + if(handleKeyEvents(event)) + continue; + + ImGui_ImplSDL2_ProcessEvent(&event); + + if (event.type == SDL_QUIT) + return false; + + if (event.type == SDL_WINDOWEVENT && + event.window.event == SDL_WINDOWEVENT_CLOSE && + event.window.windowID == SDL_GetWindowID(window)) + return false; + } + + redraw(); + return true; +} + +static SDL_Surface* LoadImage(const unsigned char* pixels, size_t len) { // Read data int32_t w, h, bpp; @@ -346,7 +349,7 @@ SDL_Surface* LoadImage(const unsigned char* pixels, size_t len) return surface; } -SDL_Texture* LoadTexture(SDL_Renderer* renderer, const unsigned char* pixels, size_t len) +static SDL_Texture* LoadTexture(SDL_Renderer* renderer, const unsigned char* pixels, size_t len) { SDL_Surface* surface = LoadImage(pixels, len); if (!surface) return NULL; @@ -357,88 +360,6 @@ SDL_Texture* LoadTexture(SDL_Renderer* renderer, const unsigned char* pixels, si return texture; } -void SingleGimbal(const char* name, SDL_Texture* gimbal_frame, - SDL_Texture* stick_dot, ImVec2& stick_pos, - bool lock_y) -{ - auto frame_width = ImGui::GetContentRegionAvail().x; - auto gimbal_width = frame_width < 91.0f ? 91.0f : frame_width > 200.0f ? 200.0f : frame_width; - - float ratio = gimbal_width / 182.0f; - - ImDrawList* draw_list = ImGui::GetWindowDrawList(); - - ImVec2 pos = ImGui::GetCursorScreenPos(); - ImVec2 gimbal_p0(pos.x + (frame_width - gimbal_width) / 2.0f, pos.y); - ImVec2 gimbal_p1(gimbal_p0.x + gimbal_width, gimbal_p0.y + gimbal_width); - draw_list->AddImage(gimbal_frame, gimbal_p0, gimbal_p1); - - float dot_width = 38.0f * ratio; - float dot_height = 40.0f * ratio; - float dot_mid = 17.0f * ratio; - float dot_range = gimbal_width - dot_mid * 2.0f; - - ImVec2 dot_p0(gimbal_p0.x + dot_range * stick_pos.x, - gimbal_p0.y + dot_range * stick_pos.y); - ImVec2 dot_p1(dot_p0.x + dot_width, dot_p0.y + dot_height); - draw_list->AddImage(stick_dot, dot_p0, dot_p1); - - ImGui::InvisibleButton(name, ImVec2(frame_width, gimbal_width)); - if (ImGui::IsItemHovered() && ImGui::IsMouseDown(ImGuiMouseButton_Left)) { - - const auto& io = ImGui::GetIO(); - ImVec2 mouse_pos(io.MousePos.x - gimbal_p0.x, - io.MousePos.y - gimbal_p0.y); - - if (mouse_pos.x >= 0 && mouse_pos.x <= gimbal_width && - mouse_pos.y >= 0 && mouse_pos.y <= gimbal_width) { - - stick_pos.x = (mouse_pos.x - dot_mid) / dot_range; - stick_pos.y = (mouse_pos.y - dot_mid) / dot_range; - - if (stick_pos.x < 0.0f) stick_pos.x = 0.0f; - if (stick_pos.x > 1.0f) stick_pos.x = 1.0f; - if (stick_pos.y < 0.0f) stick_pos.y = 0.0f; - if (stick_pos.y > 1.0f) stick_pos.y = 1.0f; - - // ImGui::Text("X = %0.2f Y = %0.2f", stick_pos.x, stick_pos.y); - } - } else { - if (stick_pos.x != 0.5f) { - auto diff = 0.5 - stick_pos.x; - if (std::abs(diff) > 0.01) { - stick_pos.x += diff / 10; - } else { - stick_pos.x = 0.5; - } - } - if (!lock_y && stick_pos.y != 0.5f) { - auto diff = 0.5 - stick_pos.y; - if (std::abs(diff) > 0.01) { - stick_pos.y += diff / 10; - } else { - stick_pos.y = 0.5; - } - } - } -} - -void DisplayGimbals(SDL_Texture* gimbal_frame, SDL_Texture* stick_dot) -{ - // TODO: minimum width - if (ImGui::BeginTable("gimbals", 2, 0)) { - ImGui::TableNextRow(); - - ImGui::TableSetColumnIndex(0); - SingleGimbal("GimbalL", gimbal_frame, stick_dot, stick_l_pos, g_eeGeneral.stickMode == 1); - - ImGui::TableSetColumnIndex(1); - SingleGimbal("GimbalR", gimbal_frame, stick_dot, stick_r_pos, g_eeGeneral.stickMode == 0); - - ImGui::EndTable(); - } -} - Uint32 get_bg_color() { if (isBacklightEnabled()) { @@ -448,7 +369,7 @@ Uint32 get_bg_color() } } -void redraw() +static void redraw() { ImGuiStyle& style = ImGui::GetStyle(); @@ -467,6 +388,7 @@ void redraw() ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar; + // Use full work area (without menu-bars, task-bars etc.) const ImGuiViewport* viewport = ImGui::GetMainViewport(); ImGui::SetNextWindowPos(viewport->WorkPos); ImGui::SetNextWindowSize(viewport->WorkSize); @@ -475,45 +397,33 @@ void redraw() if (ImGui::Begin("Main window", &show_win, flags)) { // show gimbals - DisplayGimbals(gimbal_frame, stick_dot); + stick_left.lock_y = (g_eeGeneral.stickMode == 1); + stick_right.lock_y = (g_eeGeneral.stickMode == 0); + GimbalPair("#gimbals", stick_left, stick_right); - ImVec2 screen_p0 = ImGui::GetCursorScreenPos(); float aspect_ratio = float(LCD_H) / float(LCD_W); - float width = viewport->WorkSize.x - 2 * style.WindowPadding.x; - float scale_ratio = width / float(LCD_W); - ImVec2 size(width, width * aspect_ratio); - if (LCD_DEPTH == 1 || LCD_DEPTH == 4) { - ImDrawList* draw_list = ImGui::GetWindowDrawList(); - ImVec2 p1 = { screen_p0.x + size.x, screen_p0.y + size.y }; - draw_list->AddRectFilled(screen_p0, p1, get_bg_color()); - } + const ScreenDesc desc = { + .width = LCD_W, + .height = LCD_H, + .is_dot_matrix = LCD_DEPTH == 1 || LCD_DEPTH == 4, + }; + SimuScreen(screen_frame_buffer, size, get_bg_color(), desc); - ImGui::Image(screen_frame_buffer, size); - - if (LCD_DEPTH == 1 || LCD_DEPTH == 4) { - ImDrawList* draw_list = ImGui::GetWindowDrawList(); - float dx = size.x / float(LCD_W); - float thickness = dx / 50.0; - auto col = get_bg_color() & 0x80FFFFFF; - for (int x = 1; x < LCD_W; x++) { - ImVec2 p1 = { screen_p0.x + x * size.x / float(LCD_W), screen_p0.y }; - ImVec2 p2 = { p1.x, screen_p0.y + size.y - 1 }; - draw_list->AddLine(p1, p2, col, thickness); - } - for (int y = 1; y < LCD_H; y++) { - ImVec2 p1 = { screen_p0.x, screen_p0.y + y * size.y / float(LCD_H) }; - ImVec2 p2 = { screen_p0.x + size.x - 1, p1.y }; - draw_list->AddLine(p1, p2, col, thickness); +#if defined(HARDWARE_TOUCH) + ScreenMouseEvent touch_event; + if (SimuScreenMouseEvent(desc, touch_event)) { + if (touch_event.type == ScreenMouseEventType::MouseDown) { + touchPanelDown(touch_event.pos_x, touch_event.pos_y); + } else { + touchPanelUp(); } - } - - handleTouchEvents(screen_p0, scale_ratio); - - ImGui::Text("tmr10ms: %u", g_tmr10ms); - ImGui::Text("rtos time: %u", RTOS_GET_MS()); + } +#endif + // ImGui::Text("tmr10ms: %u", g_tmr10ms); + // ImGui::Text("rtos time: %u", RTOS_GET_MS()); } ImGui::End(); @@ -524,36 +434,19 @@ void redraw() SDL_RenderSetScale(renderer, io.DisplayFramebufferScale.x, io.DisplayFramebufferScale.y); - SDL_SetRenderDrawColor(renderer, 114, 140, 153, 255); + auto bg_col = ImGui::GetColorU32(ImGuiCol_WindowBg); + SDL_SetRenderDrawColor(renderer, + (bg_col >> IM_COL32_R_SHIFT) & 0xFF, + (bg_col >> IM_COL32_G_SHIFT) & 0xFF, + (bg_col >> IM_COL32_B_SHIFT) & 0xFF, + (bg_col >> IM_COL32_A_SHIFT) & 0xFF); + SDL_RenderClear(renderer); ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData()); SDL_RenderPresent(renderer); } -bool handle_events() -{ - SDL_Event event; - while (SDL_PollEvent(&event)) { - - if(simuProcessEvents(event)) - continue; - - ImGui_ImplSDL2_ProcessEvent(&event); - - if (event.type == SDL_QUIT) - return false; - - if (event.type == SDL_WINDOWEVENT && - event.window.event == SDL_WINDOWEVENT_CLOSE && - event.window.windowID == SDL_GetWindowID(window)) - return false; - } - - redraw(); - return true; -} - int main(int argc, char** argv) { // Init simulation @@ -578,6 +471,7 @@ int main(int argc, char** argv) (SDL_WindowFlags)(SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); window = SDL_CreateWindow("EdgeTx Simu", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 600, 600, window_flags); + SDL_SetWindowMinimumSize(window, 300, 400); renderer = SDL_CreateRenderer( window, -1, SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_ACCELERATED); @@ -590,10 +484,12 @@ int main(int argc, char** argv) SDL_GetRendererInfo(renderer, &info); SDL_Log("Current SDL_Renderer: %s", info.name); +#if !defined(__EMSCRIPTEN__) SDL_Surface* icon = LoadImage(_icon_png, sizeof(_icon_png)); if (window && icon) { SDL_SetWindowIcon(window, icon); } +#endif // Setup Dear ImGui context IMGUI_CHECKVERSION(); @@ -620,14 +516,6 @@ int main(int argc, char** argv) // SDL_SetTextureScaleMode(screen_frame_buffer, SDL_ScaleModeBest); SDL_SetTextureBlendMode(screen_frame_buffer, SDL_BLENDMODE_BLEND); - gimbal_frame = LoadTexture(renderer, _gimbal_frame_png, sizeof(_gimbal_frame_png)); - stick_dot = LoadTexture(renderer, _stick_dot_png, sizeof(_stick_dot_png)); - - if (!gimbal_frame || !stick_dot) { - SDL_Log("Could not load textures"); - return 0; - } - // 10ms timer SDL_TimerID timerID_10ms = SDL_AddTimer(TIMER_INTERVAL, timer_10ms_cb, const_cast("10ms")); @@ -638,9 +526,9 @@ int main(int argc, char** argv) // race condition on YAML loaded... if (g_eeGeneral.stickMode == 1) { - stick_l_pos.y = 1.0f; + stick_left.pos.y = 1.0f; } else if (g_eeGeneral.stickMode == 0) { - stick_r_pos.y = 1.0f; + stick_right.pos.y = 1.0f; } // Main loop @@ -655,11 +543,11 @@ int main(int argc, char** argv) }, NULL); #if defined(__EMSCRIPTEN__) - emscripten_set_main_loop([]() { handle_events(); }, 0, true); + emscripten_set_main_loop([]() { handleEvents(); }, 0, true); #else do { Uint64 start_ts = SDL_GetPerformanceCounter(); - if (!handle_events()) break; + if (!handleEvents()) break; Uint64 end_ts = SDL_GetPerformanceCounter(); float elapsedMS = @@ -677,13 +565,12 @@ int main(int argc, char** argv) ImGui::DestroyContext(); SDL_DestroyTexture(screen_frame_buffer); - SDL_DestroyTexture(gimbal_frame); - SDL_DestroyTexture(stick_dot); - SDL_RemoveTimer(timerID_10ms); SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); +#if !defined(__EMSCRIPTEN__) SDL_FreeSurface(icon); +#endif SDL_CloseAudio(); SDL_Quit(); @@ -695,10 +582,10 @@ uint16_t simu_get_analog(uint8_t idx) auto max_sticks = adcGetMaxInputs(ADC_INPUT_MAIN); if (idx < max_sticks) { switch(idx) { - case 0: return stick_l_pos.x * 4096; - case 1: return (1.0 - stick_l_pos.y) * 4096; - case 2: return (1.0 - stick_r_pos.y) * 4096; - case 3: return stick_r_pos.x * 4096; + case 0: return stick_left.pos.x * 4096; + case 1: return (1.0 - stick_left.pos.y) * 4096; + case 2: return (1.0 - stick_right.pos.y) * 4096; + case 3: return stick_right.pos.x * 4096; } } diff --git a/radio/src/targets/simu/widgets.cpp b/radio/src/targets/simu/widgets.cpp new file mode 100644 index 00000000000..263565ef2e7 --- /dev/null +++ b/radio/src/targets/simu/widgets.cpp @@ -0,0 +1,218 @@ +/* + * Copyright (C) EdgeTX + * + * Based on code named + * opentx - https://github.com/opentx/opentx + * th9x - http://code.google.com/p/th9x + * er9x - http://code.google.com/p/er9x + * gruvin9x - http://code.google.com/p/gruvin9x + * + * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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 General Public License for more details. + */ + +#include "widgets.h" + +#include +#include +#include + +#define _PI_ 3.14159265358979323846f + +static inline float _clamp(float f) +{ + return (f < 0.0) ? 0.0 : (f > 1.0) ? 1.0 : f; +} + +static inline void adjust_gimbal(float& pos) +{ + if (pos != 0.5f) { + auto diff = 0.5 - pos; + if (std::abs(diff) > 0.01) { + pos += diff / 10; + } else { + pos = 0.5; + } + } +} + +static void draw_tick(const ImVec2& center, ImU32 col, float angle, + float start, float end, float thickness) +{ + auto angle_cos = std::cos(angle); + auto angle_sin = std::sin(angle); + + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + draw_list->AddLine({center.x + angle_cos * end, center.y + angle_sin * end}, + {center.x + angle_cos * start, center.y + angle_sin * start}, + col, thickness); +} + +static void draw_gimbal_frame(const ImVec2& p0, const ImVec2& p1, float rounding, float thickness) +{ + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + auto bg_col = ImGui::GetColorU32(ImGuiCol_FrameBg); + draw_list->AddRectFilled(p0, p1, bg_col, rounding, + ImDrawFlags_RoundCornersAll); + + auto frame_col = ImGui::GetColorU32(ImGuiCol_Border); + draw_list->AddRect(p0, p1, frame_col, rounding, + ImDrawFlags_RoundCornersAll, thickness); + + float gimbal_mid = (p1.x - p0.x) / 2.0; + float circle_radius = gimbal_mid * 0.75; + ImVec2 center = {p0.x + gimbal_mid, p0.y + gimbal_mid}; + + float circle_thickness = thickness * 1.8f; + float inner_radius = circle_radius - circle_thickness / 2.0; + + draw_list->AddCircle(center, circle_radius, frame_col, 0, thickness * 1.8f); + + for (float deg = 0.0f; deg < 360.0f; deg += 10.0f) { + float angle = _PI_ * deg / 180.0f; + draw_tick(center, IM_COL32_WHITE, angle, + circle_radius - circle_thickness * 0.3f, + circle_radius + circle_thickness * 0.3f, + circle_thickness * 0.2f); + } + + for (float deg = 0.0f; deg < 360.0f; deg += 90.0f) { + float angle = _PI_ * deg / 180.0f; + draw_tick(center, frame_col, angle, + inner_radius, circle_thickness, circle_thickness * 0.2f); + } +} + +void SingleGimbal(const char* name, GimbalState& gs) +{ + const auto gimbal_width = 120.0f; + const auto border_rounding = 12.0f; + const auto border_thickness = 6.0f; + const auto default_dot_radius = 12.0f; + + const auto frame_width = ImGui::GetContentRegionAvail().x; + + ImVec2 pos = ImGui::GetCursorScreenPos(); + ImVec2 gimbal_p0(pos.x + (frame_width - gimbal_width) / 2.0f, pos.y); + ImVec2 gimbal_p1(gimbal_p0.x + gimbal_width, gimbal_p0.y + gimbal_width); + ImGui::SetCursorScreenPos(gimbal_p0); + + ImGui::InvisibleButton(name, ImVec2(gimbal_width, gimbal_width)); + draw_gimbal_frame(gimbal_p0, gimbal_p1, border_rounding, border_thickness); + + float dot_radius = default_dot_radius; + ImU32 col = ImGui::GetColorU32(ImGuiCol_Button); + if (ImGui::IsItemActive()) { + dot_radius += 2.0; + col = ImGui::GetColorU32(ImGuiCol_ButtonActive); + } else if (ImGui::IsItemHovered()) { + col = ImGui::GetColorU32(ImGuiCol_ButtonHovered); + } + + float eff_width = gimbal_width - 2.0 * default_dot_radius; + float half_width = gimbal_width / 2.0; + + if (ImGui::IsItemActive()) { + const auto& io = ImGui::GetIO(); + ImVec2 mouse_pos(io.MousePos.x - gimbal_p0.x, io.MousePos.y - gimbal_p0.y); + gs.pos.x = _clamp((mouse_pos.x - half_width) / eff_width + 0.5); + gs.pos.y = _clamp((mouse_pos.y - half_width) / eff_width + 0.5); + } else { + adjust_gimbal(gs.pos.x); + if (!gs.lock_y) adjust_gimbal(gs.pos.y); + } + + ImVec2 dot_center(gimbal_p0.x + eff_width * (gs.pos.x - 0.5) + half_width, + gimbal_p0.y + eff_width * (gs.pos.y - 0.5) + half_width); + + // Make color opaque + col |= IM_COL32_BLACK; + ImGui::GetWindowDrawList()->AddCircle(dot_center, dot_radius, col, 0, dot_radius - 1.0); +} + +void GimbalPair(const char* str_id, GimbalState& left, GimbalState& right) +{ + if (ImGui::BeginTable(str_id, 2)) { + ImGui::TableNextColumn(); + SingleGimbal("#g-left", left); + + ImGui::TableNextColumn(); + SingleGimbal("#g-right", right); + + ImGui::EndTable(); + ImGui::Spacing(); + } +} + +void SimuScreen(ImTextureID screen_img, ImVec2 size, ImU32 bg_col, const ScreenDesc& desc) +{ + ImVec2 p0 = ImGui::GetCursorScreenPos(); + + if (bg_col != IM_COL32_BLACK_TRANS) { + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + ImVec2 p1 = { p0.x + size.x, p0.y + size.y }; + draw_list->AddRectFilled(p0, p1, bg_col); + } + + // ImGui::Image(screen_img, size); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); + ImGui::ImageButton("#simu-screen", screen_img, size); + ImGui::PopStyleVar(); + + if (desc.is_dot_matrix) { + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + float dx = size.x / float(desc.width); + float thickness = dx / 50.0; + auto col = bg_col & 0x80FFFFFF; + for (int x = 1; x < desc.width; x++) { + ImVec2 p1 = { p0.x + x * size.x / float(desc.width), p0.y }; + ImVec2 p2 = { p1.x, p0.y + size.y - 1 }; + draw_list->AddLine(p1, p2, col, thickness); + } + for (int y = 1; y < desc.height; y++) { + ImVec2 p1 = { p0.x, p0.y + y * size.y / float(desc.height) }; + ImVec2 p2 = { p0.x + size.x - 1, p1.y }; + draw_list->AddLine(p1, p2, col, thickness); + } + } +} + +bool SimuScreenMouseEvent(const ScreenDesc& desc, ScreenMouseEvent& event) +{ + bool active = ImGui::IsItemActive() && ImGui::IsMouseDown(ImGuiMouseButton_Left); + bool deactivated = ImGui::IsItemDeactivated() && ImGui::IsMouseReleased(ImGuiMouseButton_Left); + + if (active || deactivated) { + + auto size = ImGui::GetItemRectSize(); + float scale_x = float(desc.width) / size.x; + float scale_y = float(desc.height) / size.y; + + ImGuiIO& io = ImGui::GetIO(); + auto p0 = ImGui::GetItemRectMin(); + + const ImVec2 mouse_pos(io.MousePos.x - p0.x, io.MousePos.y - p0.y); + const ImVec2 scaled_pos(mouse_pos.x * scale_x, mouse_pos.y * scale_y); + + if (active) { + event.type = ScreenMouseEventType::MouseDown; + } else { + event.type = ScreenMouseEventType::MouseUp; + } + + event.pos_x = (int)std::round(scaled_pos.x); + event.pos_y = (int)std::round(scaled_pos.y); + + return true; + } + + return false; +} diff --git a/radio/src/targets/simu/widgets.h b/radio/src/targets/simu/widgets.h new file mode 100644 index 00000000000..b36c44b9790 --- /dev/null +++ b/radio/src/targets/simu/widgets.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) EdgeTX + * + * Based on code named + * opentx - https://github.com/opentx/opentx + * th9x - http://code.google.com/p/th9x + * er9x - http://code.google.com/p/er9x + * gruvin9x - http://code.google.com/p/gruvin9x + * + * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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 General Public License for more details. + */ + +#pragma once + +#include + +struct GimbalState { + ImVec2 pos; + bool lock_y; +}; + +struct ScreenDesc { + int width; + int height; + bool is_dot_matrix; +}; + +enum class ScreenMouseEventType { + MouseDown, + MouseUp, +}; + +struct ScreenMouseEvent { + ScreenMouseEventType type; + int pos_x; + int pos_y; +}; + +void GimbalPair(const char* str_id, GimbalState& left, GimbalState& right); +void SimuScreen(ImTextureID screen_img, ImVec2 size, ImU32 bg_col, const ScreenDesc& desc); +bool SimuScreenMouseEvent(const ScreenDesc& desc, ScreenMouseEvent& event); From bfeee3bdd7ae02b209d5c7f229ebcae2c0199010 Mon Sep 17 00:00:00 2001 From: raphaelcoeffic Date: Tue, 10 Oct 2023 09:50:38 +0200 Subject: [PATCH 05/14] Show switches & fix color backlight --- radio/src/targets/simu/backlight_driver.cpp | 14 ++- radio/src/targets/simu/sdl_simu.cpp | 128 +++++++++++++++----- radio/src/targets/simu/widgets.cpp | 13 +- radio/src/targets/simu/widgets.h | 3 +- 4 files changed, 119 insertions(+), 39 deletions(-) diff --git a/radio/src/targets/simu/backlight_driver.cpp b/radio/src/targets/simu/backlight_driver.cpp index 15187ff834f..3b6d82dd27d 100644 --- a/radio/src/targets/simu/backlight_driver.cpp +++ b/radio/src/targets/simu/backlight_driver.cpp @@ -19,12 +19,17 @@ * GNU General Public License for more details. */ +#include "board.h" + bool boardBacklightOn = false; bool isBacklightEnabled() { return boardBacklightOn; } -void backlightFullOn() { boardBacklightOn = true; } void backlightInit() {} +#if LCD_DEPTH != 16 + +void backlightFullOn() { boardBacklightOn = true; } + void backlightEnable(unsigned char) { boardBacklightOn = true; @@ -39,3 +44,10 @@ void backlightDisable() { boardBacklightOn = false; } + +#else + +void backlightFullOn() { backlightEnable(BACKLIGHT_LEVEL_MAX); } +void backlightEnable(uint8_t) {} + +#endif diff --git a/radio/src/targets/simu/sdl_simu.cpp b/radio/src/targets/simu/sdl_simu.cpp index 1c946228da9..8f85c70ce41 100644 --- a/radio/src/targets/simu/sdl_simu.cpp +++ b/radio/src/targets/simu/sdl_simu.cpp @@ -24,6 +24,7 @@ #include #include #include +#include "targets/simu/simpgmspace.h" #ifdef __EMSCRIPTEN__ #include @@ -52,6 +53,7 @@ #include "simuaudio.h" #include "hal/key_driver.h" +#include "switches.h" #include "audio.h" #include "debug.h" @@ -360,19 +362,100 @@ static SDL_Texture* LoadTexture(SDL_Renderer* renderer, const unsigned char* pix return texture; } -Uint32 get_bg_color() +static void draw_switches() { - if (isBacklightEnabled()) { - return IM_COL32(47, 123, 227, 255); + const float spacing = 4; + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(spacing, spacing)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 4.0f); + ImGui::PushStyleVar(ImGuiStyleVar_GrabRounding, 4.0f); + ImGui::PushID("switches"); + { + static int switches[MAX_SWITCHES] = {0}; + + ImGui::BeginGroup(); + int sw_idx = 0; + for (int i = 0; i < switchGetMaxSwitches(); i++) { + if (!SWITCH_EXISTS(i)) { + switches[i] = 0; + } else { + if (sw_idx > 0) ImGui::SameLine(); + ImGui::PushID(i); + ImGui::VSliderInt("##sw", ImVec2(18, 60), + &switches[i], IS_CONFIG_3POS(i) ? 2 : 1, + 0, "", ImGuiSliderFlags_NoInput); + if (ImGui::IsItemActive() || ImGui::IsItemHovered()) { + ImGui::SetTooltip("%s", switchGetCanonicalName(i)); + } + ImGui::PopID(); + + if (++sw_idx >= MAX_SWITCHES/2) sw_idx = 0; + } + + if (IS_CONFIG_3POS(i)) { + simuSetSwitch(i, switches[i] == 0 ? -1 : switches[i] == 1 ? 0 : 1); + } else { + simuSetSwitch(i, switches[i] == 0 ? -1 : 1); + } + } + ImGui::EndGroup(); + } + ImGui::PopID(); + ImGui::PopStyleVar(3); +} + +static void draw_gimbals() +{ + stick_left.lock_y = (g_eeGeneral.stickMode == 1); + stick_right.lock_y = (g_eeGeneral.stickMode == 0); + + GimbalPair("#gimbals", stick_left, stick_right); +} + +ImU32 get_bg_color() +{ + if (LCD_DEPTH < 16) { + if (isBacklightEnabled()) { + return IM_COL32(47, 123, 227, 255); + } else { + return IM_COL32(200, 200, 200, 255); + } } else { - return IM_COL32(200, 200, 200, 255); + return IM_COL32_BLACK_TRANS; } } -static void redraw() +static void draw_screen() { - ImGuiStyle& style = ImGui::GetStyle(); + const ImGuiViewport* viewport = ImGui::GetMainViewport(); + float width = viewport->WorkSize.x - 2 * ImGui::GetStyle().WindowPadding.x; + + const ScreenDesc desc = { + .width = LCD_W, + .height = LCD_H, + .is_dot_matrix = LCD_DEPTH == 1 || LCD_DEPTH == 4, + }; + + float aspect_ratio = float(LCD_H) / float(LCD_W); + ImVec2 size(width, width * aspect_ratio); + SimuScreen(desc, screen_frame_buffer, size, get_bg_color(), + (LCD_DEPTH == 16 && !isBacklightEnabled()) ? + IM_COL32(0,0,0,127) : IM_COL32_BLACK_TRANS); + +#if defined(HARDWARE_TOUCH) + ScreenMouseEvent touch_event; + if (SimuScreenMouseEvent(desc, touch_event)) { + if (touch_event.type == ScreenMouseEventType::MouseDown) { + touchPanelDown(touch_event.pos_x, touch_event.pos_y); + } else { + touchPanelUp(); + } + } +#endif +} + +static void redraw() +{ // poll audio audioQueue.wakeup(); @@ -396,34 +479,15 @@ static void redraw() bool show_win = true; if (ImGui::Begin("Main window", &show_win, flags)) { - // show gimbals - stick_left.lock_y = (g_eeGeneral.stickMode == 1); - stick_right.lock_y = (g_eeGeneral.stickMode == 0); - GimbalPair("#gimbals", stick_left, stick_right); - - float aspect_ratio = float(LCD_H) / float(LCD_W); - float width = viewport->WorkSize.x - 2 * style.WindowPadding.x; - ImVec2 size(width, width * aspect_ratio); - - const ScreenDesc desc = { - .width = LCD_W, - .height = LCD_H, - .is_dot_matrix = LCD_DEPTH == 1 || LCD_DEPTH == 4, - }; - SimuScreen(screen_frame_buffer, size, get_bg_color(), desc); - -#if defined(HARDWARE_TOUCH) - ScreenMouseEvent touch_event; - if (SimuScreenMouseEvent(desc, touch_event)) { - if (touch_event.type == ScreenMouseEventType::MouseDown) { - touchPanelDown(touch_event.pos_x, touch_event.pos_y); - } else { - touchPanelUp(); - } - } -#endif + draw_switches(); + ImGui::SameLine(); + draw_gimbals(); + ImGui::SameLine(); + + draw_screen(); // ImGui::Text("tmr10ms: %u", g_tmr10ms); // ImGui::Text("rtos time: %u", RTOS_GET_MS()); + // ImGui::Text("Backlight: %s", isBacklightEnabled() ? "on" : "off"); } ImGui::End(); diff --git a/radio/src/targets/simu/widgets.cpp b/radio/src/targets/simu/widgets.cpp index 263565ef2e7..3b5e01cb984 100644 --- a/radio/src/targets/simu/widgets.cpp +++ b/radio/src/targets/simu/widgets.cpp @@ -152,23 +152,22 @@ void GimbalPair(const char* str_id, GimbalState& left, GimbalState& right) } } -void SimuScreen(ImTextureID screen_img, ImVec2 size, ImU32 bg_col, const ScreenDesc& desc) +void SimuScreen(const ScreenDesc& desc, ImTextureID screen_img, ImVec2 size, + ImU32 bg_col, ImU32 overlay_col) { ImVec2 p0 = ImGui::GetCursorScreenPos(); + ImVec2 p1 = { p0.x + size.x, p0.y + size.y }; + ImDrawList* draw_list = ImGui::GetWindowDrawList(); if (bg_col != IM_COL32_BLACK_TRANS) { - ImDrawList* draw_list = ImGui::GetWindowDrawList(); - ImVec2 p1 = { p0.x + size.x, p0.y + size.y }; draw_list->AddRectFilled(p0, p1, bg_col); } - // ImGui::Image(screen_img, size); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); ImGui::ImageButton("#simu-screen", screen_img, size); ImGui::PopStyleVar(); if (desc.is_dot_matrix) { - ImDrawList* draw_list = ImGui::GetWindowDrawList(); float dx = size.x / float(desc.width); float thickness = dx / 50.0; auto col = bg_col & 0x80FFFFFF; @@ -183,6 +182,10 @@ void SimuScreen(ImTextureID screen_img, ImVec2 size, ImU32 bg_col, const ScreenD draw_list->AddLine(p1, p2, col, thickness); } } + + if (overlay_col != IM_COL32_BLACK_TRANS) { + draw_list->AddRectFilled(p0, p1, overlay_col); + } } bool SimuScreenMouseEvent(const ScreenDesc& desc, ScreenMouseEvent& event) diff --git a/radio/src/targets/simu/widgets.h b/radio/src/targets/simu/widgets.h index b36c44b9790..08efba78865 100644 --- a/radio/src/targets/simu/widgets.h +++ b/radio/src/targets/simu/widgets.h @@ -46,5 +46,6 @@ struct ScreenMouseEvent { }; void GimbalPair(const char* str_id, GimbalState& left, GimbalState& right); -void SimuScreen(ImTextureID screen_img, ImVec2 size, ImU32 bg_col, const ScreenDesc& desc); +void SimuScreen(const ScreenDesc& desc, ImTextureID screen_img, ImVec2 size, + ImU32 bg_col, ImU32 overlay_col); bool SimuScreenMouseEvent(const ScreenDesc& desc, ScreenMouseEvent& event); From c1f5b9d6263248d1d55288402f11ec90715bf1cd Mon Sep 17 00:00:00 2001 From: raphaelcoeffic Date: Wed, 11 Oct 2023 15:13:10 +0200 Subject: [PATCH 06/14] Pots & Sliders --- radio/src/targets/simu/CMakeLists.txt | 1 + radio/src/targets/simu/knobs.cpp | 259 ++++++++++++++++++ radio/src/targets/simu/knobs.h | 57 ++++ radio/src/targets/simu/sdl_simu.cpp | 106 +++++-- radio/src/targets/simu/widgets.cpp | 59 ++-- .../src/targets/taranis/backlight_driver.cpp | 10 +- radio/src/targets/taranis/board.h | 2 +- 7 files changed, 433 insertions(+), 61 deletions(-) create mode 100644 radio/src/targets/simu/knobs.cpp create mode 100644 radio/src/targets/simu/knobs.h diff --git a/radio/src/targets/simu/CMakeLists.txt b/radio/src/targets/simu/CMakeLists.txt index 6aed56ec9d9..65b45e8e0d1 100644 --- a/radio/src/targets/simu/CMakeLists.txt +++ b/radio/src/targets/simu/CMakeLists.txt @@ -197,6 +197,7 @@ add_executable(simu EXCLUDE_FROM_ALL ${SIMU_SRC} widgets.cpp + knobs.cpp sdl_simu.cpp ) diff --git a/radio/src/targets/simu/knobs.cpp b/radio/src/targets/simu/knobs.cpp new file mode 100644 index 00000000000..6b1c5f25daa --- /dev/null +++ b/radio/src/targets/simu/knobs.cpp @@ -0,0 +1,259 @@ +// +// Code from: +// https://github.com/altschuler/imgui-knobs +// +// Copyright (c) 2022 Simon Altschuler +// +// MIT License +// + +#include "knobs.h" + +#include +#include +#include +#include + +#define IMGUIKNOBS_PI 3.14159265358979323846f + +namespace ImGuiKnobs +{ +namespace detail +{ + +template +struct knob { + float radius; + bool value_changed; + ImVec2 center; + bool is_active; + bool is_hovered; + float angle_min; + float angle_max; + float t; + float angle; + float angle_cos; + float angle_sin; + + knob(const char *_label, ImGuiDataType data_type, DataType *p_value, + DataType v_min, DataType v_max, float _radius, + const char *format, ImGuiKnobFlags flags) + { + radius = _radius; + t = ((float)*p_value - v_min) / (v_max - v_min); + auto screen_pos = ImGui::GetCursorScreenPos(); + + ImGui::InvisibleButton(_label, {radius * 2.0f, radius * 2.0f}); + + angle_min = IMGUIKNOBS_PI * 0.75f; + angle_max = IMGUIKNOBS_PI * 2.25f; + center = {screen_pos.x + radius, screen_pos.y + radius}; + is_active = ImGui::IsItemActive(); + is_hovered = ImGui::IsItemHovered(); + angle = angle_min + (angle_max - angle_min) * t; + angle_cos = cosf(angle); + angle_sin = sinf(angle); + + if (is_active) { + ImVec2 mp = ImGui::GetIO().MousePos; + float angle_mid = (angle_max + angle_min) / 2.0; + float alpha = atan2f(mp.x - center.x, center.y - mp.y) + angle_mid; + alpha = ImClamp(alpha, angle_min, angle_max); + float ratio = (alpha - angle_min) / (angle_max - angle_min); + switch(data_type){ + case ImGuiDataType_Float: + *p_value = ImGui::ScaleValueFromRatioT( + data_type, ratio, v_min, v_max, false, 0.0f, 0.0f); + break; + case ImGuiDataType_S32: + *p_value = ImGui::ScaleValueFromRatioT( + data_type, ratio, v_min, v_max, false, 0.0f, 0.0f); + break; + default: break; + } + } + } + + void draw_dot(float size, float radius, float angle, color_set color, + bool filled, int segments) + { + auto dot_size = size * this->radius; + auto dot_radius = radius * this->radius; + + ImGui::GetWindowDrawList()->AddCircleFilled( + {center[0] + cosf(angle) * dot_radius, + center[1] + sinf(angle) * dot_radius}, + dot_size, + is_active ? color.active : (is_hovered ? color.hovered : color.base), + segments); + } + + void draw_tick(float start, float end, float width, float angle, + color_set color) + { + auto tick_start = start * radius; + auto tick_end = end * radius; + auto angle_cos = cosf(angle); + auto angle_sin = sinf(angle); + + ImGui::GetWindowDrawList()->AddLine( + {center[0] + angle_cos * tick_end, center[1] + angle_sin * tick_end}, + {center[0] + angle_cos * tick_start, + center[1] + angle_sin * tick_start}, + is_active ? color.active : (is_hovered ? color.hovered : color.base), + width * radius); + } + + void draw_circle(float size, color_set color, bool filled, int segments) + { + auto circle_radius = size * radius; + + ImGui::GetWindowDrawList()->AddCircleFilled( + center, circle_radius, + is_active ? color.active : (is_hovered ? color.hovered : color.base)); + } +}; + +template +knob knob_with_title(const char *label, ImGuiDataType data_type, + DataType *p_value, DataType v_min, DataType v_max, + const char *format, float size, + ImGuiKnobFlags flags) +{ + ImGui::PushID(label); + auto width = size == 0 ? ImGui::GetTextLineHeight() * 4.0f + : size * ImGui::GetIO().FontGlobalScale; + ImGui::PushItemWidth(width); + + ImGui::BeginGroup(); + + // There's an issue with `SameLine` and Groups, see + // https://github.com/ocornut/imgui/issues/4190. This is probably not the best + // solution, but seems to work for now + ImGui::GetCurrentWindow()->DC.CurrLineTextBaseOffset = 0; + + // Draw title + if (!(flags & ImGuiKnobFlags_NoTitle)) { + auto title_size = ImGui::CalcTextSize(label, NULL, false, width); + + // Center title + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + + (width - title_size[0]) * 0.5f); + + ImGui::Text("%s", label); + } + + // Draw knob + knob k(label, data_type, p_value, v_min, v_max, width * 0.5f, + format, flags); + + // Draw tooltip + if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) || + ImGui::IsItemActive()) { + + if (flags & ImGuiKnobFlags_ValueTooltip && ImGui::IsItemActive()) { + ImGui::SetTooltip(format, *p_value); + } else if (flags & ImGuiKnobFlags_TitleTooltip) { + ImGui::SetTooltip("%s", label); + } + } + + ImGui::EndGroup(); + ImGui::PopItemWidth(); + ImGui::PopID(); + + return k; +} + +color_set GetPrimaryColorSet() +{ + auto *colors = ImGui::GetStyle().Colors; + + return {colors[ImGuiCol_ButtonActive], colors[ImGuiCol_ButtonHovered], + colors[ImGuiCol_ButtonHovered]}; +} + +color_set GetSecondaryColorSet() +{ + auto *colors = ImGui::GetStyle().Colors; + auto active = ImVec4(colors[ImGuiCol_ButtonActive].x * 0.5f, + colors[ImGuiCol_ButtonActive].y * 0.5f, + colors[ImGuiCol_ButtonActive].z * 0.5f, + colors[ImGuiCol_ButtonActive].w); + + auto hovered = ImVec4(colors[ImGuiCol_ButtonHovered].x * 0.5f, + colors[ImGuiCol_ButtonHovered].y * 0.5f, + colors[ImGuiCol_ButtonHovered].z * 0.5f, + colors[ImGuiCol_ButtonHovered].w); + + return {active, hovered, hovered}; +} + +color_set GetTrackColorSet() +{ + auto *colors = ImGui::GetStyle().Colors; + + return {colors[ImGuiCol_FrameBg], colors[ImGuiCol_FrameBg], + colors[ImGuiCol_FrameBg]}; +} +} // namespace detail + +template +bool BaseKnob(const char *label, ImGuiDataType data_type, DataType *p_value, + DataType v_min, DataType v_max, const char *format, + ImGuiKnobVariant variant, float size, ImGuiKnobFlags flags, + int steps = 10) +{ + auto knob = detail::knob_with_title(label, data_type, p_value, v_min, v_max, + format, size, flags); + + switch (variant) { + case ImGuiKnobVariant_Tick: { + knob.draw_circle(0.85f, detail::GetSecondaryColorSet(), true, 32); + knob.draw_tick(0.5f, 0.85f, 0.1f, knob.angle, + detail::GetPrimaryColorSet()); + break; + } + case ImGuiKnobVariant_Dot: { + knob.draw_circle(0.85f, detail::GetSecondaryColorSet(), true, 32); + knob.draw_dot(0.12f, 0.6f, knob.angle, detail::GetPrimaryColorSet(), true, + 12); + break; + } + + case ImGuiKnobVariant_Stepped: { + for (auto n = 0.f; n < steps; n++) { + auto a = n / (steps - 1); + auto angle = knob.angle_min + (knob.angle_max - knob.angle_min) * a; + knob.draw_tick(0.85f, 1.0f, 0.04f, angle, detail::GetPrimaryColorSet()); + } + + knob.draw_circle(0.75f, detail::GetSecondaryColorSet(), true, 32); + knob.draw_dot(0.12f, 0.4f, knob.angle, detail::GetPrimaryColorSet(), true, + 12); + break; + } + } + + return knob.value_changed; +} + +bool Knob(const char *label, float *p_value, float v_min, float v_max, + float speed, const char *format, ImGuiKnobVariant variant, float size, + ImGuiKnobFlags flags, int steps) +{ + const char *_format = format == NULL ? "%.3f" : format; + return BaseKnob(label, ImGuiDataType_Float, p_value, v_min, v_max, + _format, variant, size, flags, steps); +} + +bool KnobInt(const char *label, int *p_value, int v_min, int v_max, float speed, + const char *format, ImGuiKnobVariant variant, float size, + ImGuiKnobFlags flags, int steps) +{ + const char *_format = format == NULL ? "%i" : format; + return BaseKnob(label, ImGuiDataType_S32, p_value, v_min, v_max, + _format, variant, size, flags, steps); +} + +} // namespace ImGuiKnobs diff --git a/radio/src/targets/simu/knobs.h b/radio/src/targets/simu/knobs.h new file mode 100644 index 00000000000..0779ee172a8 --- /dev/null +++ b/radio/src/targets/simu/knobs.h @@ -0,0 +1,57 @@ +// +// Code from: +// https://github.com/altschuler/imgui-knobs +// +// Copyright (c) 2022 Simon Altschuler +// +// MIT License +// + +#include + +typedef int ImGuiKnobFlags; + +enum ImGuiKnobFlags_ { + ImGuiKnobFlags_NoTitle = 1 << 0, + ImGuiKnobFlags_ValueTooltip = 1 << 1, + ImGuiKnobFlags_TitleTooltip = 1 << 2, +}; + +typedef int ImGuiKnobVariant; + +enum ImGuiKnobVariant_ { + ImGuiKnobVariant_Tick = 1, + ImGuiKnobVariant_Dot, + ImGuiKnobVariant_Stepped, +}; + +namespace ImGuiKnobs { + + struct color_set { + ImColor base; + ImColor hovered; + ImColor active; + + color_set(ImColor base, ImColor hovered, ImColor active) : + base(base), hovered(hovered), active(active) + { + } + + color_set(ImColor color) { + base = color; + hovered = color; + active = color; + } + }; + + bool Knob(const char* label, float* p_value, float v_min, float v_max, + float speed = 0, const char* format = NULL, + ImGuiKnobVariant variant = ImGuiKnobVariant_Tick, float size = 0, + ImGuiKnobFlags flags = 0, int steps = 10); + + bool KnobInt(const char* label, int* p_value, int v_min, int v_max, + float speed = 0, const char* format = NULL, + ImGuiKnobVariant variant = ImGuiKnobVariant_Tick, + float size = 0, ImGuiKnobFlags flags = 0, int steps = 10); + +} // namespace ImGuiKnobs diff --git a/radio/src/targets/simu/sdl_simu.cpp b/radio/src/targets/simu/sdl_simu.cpp index 8f85c70ce41..c4a18f5af3e 100644 --- a/radio/src/targets/simu/sdl_simu.cpp +++ b/radio/src/targets/simu/sdl_simu.cpp @@ -20,11 +20,11 @@ */ #include +#include #include #include #include -#include "targets/simu/simpgmspace.h" #ifdef __EMSCRIPTEN__ #include @@ -50,16 +50,17 @@ #include "simu.h" #include "widgets.h" +#include "knobs.h" #include "simuaudio.h" +#include "simpgmspace.h" + #include "hal/key_driver.h" #include "switches.h" #include "audio.h" #include "debug.h" -#include "opentx.h" - -#include +#include "edgetx.h" #define TIMER_INTERVAL 10 // 10ms @@ -76,6 +77,8 @@ static const unsigned char _icon_png[] = { }; #endif +int pots[MAX_POTS] = {0}; + static void _set_pixel(uint8_t* pixel, const SDL_Color& color) { pixel[0] = color.a; @@ -364,15 +367,16 @@ static SDL_Texture* LoadTexture(SDL_Renderer* renderer, const unsigned char* pix static void draw_switches() { - const float spacing = 4; - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(spacing, spacing)); - ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 4.0f); - ImGui::PushStyleVar(ImGuiStyleVar_GrabRounding, 4.0f); ImGui::PushID("switches"); { static int switches[MAX_SWITCHES] = {0}; + const float spacing = 4; ImGui::BeginGroup(); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(spacing, spacing)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 4.0f); + ImGui::PushStyleVar(ImGuiStyleVar_GrabRounding, 4.0f); + int sw_idx = 0; for (int i = 0; i < switchGetMaxSwitches(); i++) { if (!SWITCH_EXISTS(i)) { @@ -397,10 +401,10 @@ static void draw_switches() simuSetSwitch(i, switches[i] == 0 ? -1 : 1); } } + ImGui::PopStyleVar(3); ImGui::EndGroup(); } ImGui::PopID(); - ImGui::PopStyleVar(3); } static void draw_gimbals() @@ -411,6 +415,46 @@ static void draw_gimbals() GimbalPair("#gimbals", stick_left, stick_right); } +static void draw_pots() +{ + const float spacing = 2; + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(spacing, spacing)); + ImGui::PushID("pots"); + { + ImGui::BeginGroup(); + int pot_idx = 0; + for (int i = 0; i < adcGetMaxInputs(ADC_INPUT_FLEX); i++) { + if (!IS_POT_AVAILABLE(i)) { + pots[i] = 0; + } else { + if (pot_idx > 0) ImGui::SameLine(); + ImGui::PushID(i); + auto flags = ImGuiKnobFlags_NoTitle | ImGuiKnobFlags_ValueTooltip | + ImGuiKnobFlags_TitleTooltip; + auto label = adcGetInputLabel(ADC_INPUT_FLEX, i); + switch(getPotType(i)) { + case FLEX_POT: + case FLEX_POT_CENTER: + case FLEX_SLIDER: + ImGuiKnobs::KnobInt(label, &pots[i], -100, 100, 1, "%d", + ImGuiKnobVariant_Tick, 0, flags); + break; + + case FLEX_MULTIPOS: + ImGuiKnobs::KnobInt(label, &pots[i], 0, 5, 0.2f, "%d", + ImGuiKnobVariant_Stepped, 0, flags, 6); + break; + } + ImGui::PopID(); + if (++pot_idx >= 3) pot_idx = 0; + } + } + ImGui::EndGroup(); + } + ImGui::PopID(); + ImGui::PopStyleVar(); +} + ImU32 get_bg_color() { if (LCD_DEPTH < 16) { @@ -438,7 +482,7 @@ static void draw_screen() float aspect_ratio = float(LCD_H) / float(LCD_W); ImVec2 size(width, width * aspect_ratio); - SimuScreen(desc, screen_frame_buffer, size, get_bg_color(), + SimuScreen(desc, (ImTextureID)screen_frame_buffer, size, get_bg_color(), (LCD_DEPTH == 16 && !isBacklightEnabled()) ? IM_COL32(0,0,0,127) : IM_COL32_BLACK_TRANS); @@ -479,15 +523,23 @@ static void redraw() bool show_win = true; if (ImGui::Begin("Main window", &show_win, flags)) { + auto flags = ImGuiTableFlags_SizingStretchProp; + ImGui::BeginTable("controls", 3, flags); + + ImGui::TableNextColumn(); draw_switches(); - ImGui::SameLine(); - draw_gimbals(); - ImGui::SameLine(); + ImGui::TableNextColumn(); + draw_gimbals(); + + ImGui::TableNextColumn(); + draw_pots(); + + ImGui::EndTable(); + + ImGui::Spacing(); + ImGui::Spacing(); draw_screen(); - // ImGui::Text("tmr10ms: %u", g_tmr10ms); - // ImGui::Text("rtos time: %u", RTOS_GET_MS()); - // ImGui::Text("Backlight: %s", isBacklightEnabled() ? "on" : "off"); } ImGui::End(); @@ -507,7 +559,7 @@ static void redraw() SDL_RenderClear(renderer); - ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData()); + ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData(), renderer); SDL_RenderPresent(renderer); } @@ -529,7 +581,7 @@ int main(int argc, char** argv) #ifdef SDL_HINT_IME_SHOW_UI SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1"); #endif - + // Create window with SDL_Renderer graphics context SDL_WindowFlags window_flags = (SDL_WindowFlags)(SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); @@ -653,11 +705,19 @@ uint16_t simu_get_analog(uint8_t idx) } } - // idx -= max_sticks; - - // auto max_pots = adcGetMaxInputs(ADC_INPUT_POT); - // if (idx < max_pots) - // return opentxSim->knobs[idx]->getValue(); + idx -= max_sticks; + + auto max_pots = adcGetMaxInputs(ADC_INPUT_FLEX); + if (idx < max_pots) { + switch(getPotType(idx)){ + case FLEX_POT: + case FLEX_POT_CENTER: + case FLEX_SLIDER: + return uint16_t(((uint32_t(pots[idx]) + 100) * 4096) / 200); + case FLEX_MULTIPOS: + return (uint32_t(pots[idx]) * 4096) / 5; + } + } // idx -= max_pots; diff --git a/radio/src/targets/simu/widgets.cpp b/radio/src/targets/simu/widgets.cpp index 3b5e01cb984..ddeed388022 100644 --- a/radio/src/targets/simu/widgets.cpp +++ b/radio/src/targets/simu/widgets.cpp @@ -98,15 +98,11 @@ void SingleGimbal(const char* name, GimbalState& gs) const auto border_thickness = 6.0f; const auto default_dot_radius = 12.0f; - const auto frame_width = ImGui::GetContentRegionAvail().x; - - ImVec2 pos = ImGui::GetCursorScreenPos(); - ImVec2 gimbal_p0(pos.x + (frame_width - gimbal_width) / 2.0f, pos.y); - ImVec2 gimbal_p1(gimbal_p0.x + gimbal_width, gimbal_p0.y + gimbal_width); - ImGui::SetCursorScreenPos(gimbal_p0); - ImGui::InvisibleButton(name, ImVec2(gimbal_width, gimbal_width)); - draw_gimbal_frame(gimbal_p0, gimbal_p1, border_rounding, border_thickness); + + auto p0 = ImGui::GetItemRectMin(); + auto p1 = ImGui::GetItemRectMax(); + draw_gimbal_frame(p0, p1, border_rounding, border_thickness); float dot_radius = default_dot_radius; ImU32 col = ImGui::GetColorU32(ImGuiCol_Button); @@ -122,7 +118,7 @@ void SingleGimbal(const char* name, GimbalState& gs) if (ImGui::IsItemActive()) { const auto& io = ImGui::GetIO(); - ImVec2 mouse_pos(io.MousePos.x - gimbal_p0.x, io.MousePos.y - gimbal_p0.y); + ImVec2 mouse_pos(io.MousePos.x - p0.x, io.MousePos.y - p0.y); gs.pos.x = _clamp((mouse_pos.x - half_width) / eff_width + 0.5); gs.pos.y = _clamp((mouse_pos.y - half_width) / eff_width + 0.5); } else { @@ -130,61 +126,60 @@ void SingleGimbal(const char* name, GimbalState& gs) if (!gs.lock_y) adjust_gimbal(gs.pos.y); } - ImVec2 dot_center(gimbal_p0.x + eff_width * (gs.pos.x - 0.5) + half_width, - gimbal_p0.y + eff_width * (gs.pos.y - 0.5) + half_width); + ImVec2 dot_center(p0.x + eff_width * (gs.pos.x - 0.5) + half_width, + p0.y + eff_width * (gs.pos.y - 0.5) + half_width); // Make color opaque col |= IM_COL32_BLACK; - ImGui::GetWindowDrawList()->AddCircle(dot_center, dot_radius, col, 0, dot_radius - 1.0); + ImGui::GetWindowDrawList()->AddCircle(dot_center, dot_radius, col, 0, dot_radius - 1.0); } void GimbalPair(const char* str_id, GimbalState& left, GimbalState& right) { - if (ImGui::BeginTable(str_id, 2)) { - ImGui::TableNextColumn(); - SingleGimbal("#g-left", left); + ImGui::PushID(str_id); + ImGui::BeginGroup(); - ImGui::TableNextColumn(); - SingleGimbal("#g-right", right); + SingleGimbal("#g-left", left); + ImGui::SameLine(); + SingleGimbal("#g-right", right); - ImGui::EndTable(); - ImGui::Spacing(); - } + ImGui::EndGroup(); + ImGui::PopID(); } void SimuScreen(const ScreenDesc& desc, ImTextureID screen_img, ImVec2 size, ImU32 bg_col, ImU32 overlay_col) { - ImVec2 p0 = ImGui::GetCursorScreenPos(); - ImVec2 p1 = { p0.x + size.x, p0.y + size.y }; + ImGui::InvisibleButton("#simu-screen", size); + if (!ImGui::IsItemVisible()) return; + + ImVec2 p_min = ImGui::GetItemRectMin(); + ImVec2 p_max = ImGui::GetItemRectMax(); ImDrawList* draw_list = ImGui::GetWindowDrawList(); if (bg_col != IM_COL32_BLACK_TRANS) { - draw_list->AddRectFilled(p0, p1, bg_col); + draw_list->AddRectFilled(p_min, p_max, bg_col); } - - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); - ImGui::ImageButton("#simu-screen", screen_img, size); - ImGui::PopStyleVar(); + draw_list->AddImage(screen_img, p_min, p_max); if (desc.is_dot_matrix) { float dx = size.x / float(desc.width); float thickness = dx / 50.0; auto col = bg_col & 0x80FFFFFF; for (int x = 1; x < desc.width; x++) { - ImVec2 p1 = { p0.x + x * size.x / float(desc.width), p0.y }; - ImVec2 p2 = { p1.x, p0.y + size.y - 1 }; + ImVec2 p1 = { p_min.x + x * size.x / float(desc.width), p_min.y }; + ImVec2 p2 = { p1.x, p_min.y + size.y - 1 }; draw_list->AddLine(p1, p2, col, thickness); } for (int y = 1; y < desc.height; y++) { - ImVec2 p1 = { p0.x, p0.y + y * size.y / float(desc.height) }; - ImVec2 p2 = { p0.x + size.x - 1, p1.y }; + ImVec2 p1 = { p_min.x, p_min.y + y * size.y / float(desc.height) }; + ImVec2 p2 = { p_min.x + size.x - 1, p1.y }; draw_list->AddLine(p1, p2, col, thickness); } } if (overlay_col != IM_COL32_BLACK_TRANS) { - draw_list->AddRectFilled(p0, p1, overlay_col); + draw_list->AddRectFilled(p_min, p_max, overlay_col); } } diff --git a/radio/src/targets/taranis/backlight_driver.cpp b/radio/src/targets/taranis/backlight_driver.cpp index 4785e9a61dd..9de11ee8087 100644 --- a/radio/src/targets/taranis/backlight_driver.cpp +++ b/radio/src/targets/taranis/backlight_driver.cpp @@ -31,7 +31,7 @@ void backlightEnable(uint8_t level) {} void backlightFullOn() {} void backlightDisable() {} - uint8_t isBacklightEnabled() {return false;} + bool isBacklightEnabled() { return false; } #elif defined(PCBX9E) void backlightInit() { @@ -65,7 +65,7 @@ void backlightDisable() BACKLIGHT_TIMER->CCR2 = 0; } -uint8_t isBacklightEnabled() +bool isBacklightEnabled() { return (BACKLIGHT_TIMER->CCR1 != 0 || BACKLIGHT_TIMER->CCR2 != 0); } @@ -103,7 +103,7 @@ void backlightDisable() BACKLIGHT_TIMER->CCR2 = 0; } -uint8_t isBacklightEnabled() +bool isBacklightEnabled() { return (BACKLIGHT_TIMER->CCR4 != 0 || BACKLIGHT_TIMER->CCR2 != 0); } @@ -144,7 +144,7 @@ void backlightDisable() BACKLIGHT_COUNTER_REGISTER = 0; } -uint8_t isBacklightEnabled() +bool isBacklightEnabled() { return BACKLIGHT_COUNTER_REGISTER != 0; } @@ -178,7 +178,7 @@ void backlightDisable() BACKLIGHT_TIMER->CCR1 = 0; } -uint8_t isBacklightEnabled() +bool isBacklightEnabled() { return BACKLIGHT_TIMER->CCR1 != 0; } diff --git a/radio/src/targets/taranis/board.h b/radio/src/targets/taranis/board.h index 29b43becf4b..04576b8f9ae 100644 --- a/radio/src/targets/taranis/board.h +++ b/radio/src/targets/taranis/board.h @@ -202,7 +202,7 @@ void pwrResetHandler(); void backlightInit(); void backlightDisable(); void backlightFullOn(); -uint8_t isBacklightEnabled(); +bool isBacklightEnabled(); #if defined(PCBX9E) || defined(PCBX9DP) void backlightEnable(uint8_t level, uint8_t color); From 77ee871fb0f5577269a6f34bb1fdafe4c368e24e Mon Sep 17 00:00:00 2001 From: raphaelcoeffic <1050031+raphaelcoeffic@users.noreply.github.com> Date: Sun, 23 Feb 2025 08:01:17 +0100 Subject: [PATCH 07/14] rotary encoder + page-up / page-down --- radio/src/targets/simu/sdl_simu.cpp | 35 ++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/radio/src/targets/simu/sdl_simu.cpp b/radio/src/targets/simu/sdl_simu.cpp index c4a18f5af3e..139df3113d3 100644 --- a/radio/src/targets/simu/sdl_simu.cpp +++ b/radio/src/targets/simu/sdl_simu.cpp @@ -26,6 +26,11 @@ #include #include +#include "gui_common.h" +#include "hal/adc_driver.h" +#include "hal/rotary_encoder.h" +#include "edgetx_constants.h" + #ifdef __EMSCRIPTEN__ #include #endif @@ -77,6 +82,10 @@ static const unsigned char _icon_png[] = { }; #endif +#if defined(ROTARY_ENCODER_NAVIGATION) +extern volatile rotenc_t rotencValue; +#endif + int pots[MAX_POTS] = {0}; static void _set_pixel(uint8_t* pixel, const SDL_Color& color) @@ -234,17 +243,27 @@ static bool handleKeyEvents(SDL_Event& event) break; case SDLK_UP: +#if defined(ROTARY_ENCODER_NAVIGATION) + rotencValue -= ROTARY_ENCODER_GRANULARITY; + key_handled = true; +#else if (keysGetSupported() & (1 << KEY_UP)) { key = KEY_UP; key_handled = true; } +#endif break; case SDLK_DOWN: +#if defined(ROTARY_ENCODER_NAVIGATION) + rotencValue += ROTARY_ENCODER_GRANULARITY; + key_handled = true; +#else if (keysGetSupported() & (1 << KEY_DOWN)) { key = KEY_DOWN; key_handled = true; } +#endif break; case SDLK_PLUS: @@ -261,6 +280,20 @@ static bool handleKeyEvents(SDL_Event& event) } break; + case SDLK_PAGEUP: + if (keysGetSupported() & (1 << KEY_PAGEUP)) { + key = KEY_PAGEUP; + key_handled = true; + } + break; + + case SDLK_PAGEDOWN: + if (keysGetSupported() & (1 << KEY_PAGEDN)) { + key = KEY_PAGEDN; + key_handled = true; + } + break; + case SDLK_m: if (keysGetSupported() & (1 << KEY_MENU)) { key = KEY_MENU; @@ -640,7 +673,7 @@ int main(int argc, char** argv) return 0; } - // race condition on YAML loaded... + // TODO: race condition on YAML loaded... if (g_eeGeneral.stickMode == 1) { stick_left.pos.y = 1.0f; } else if (g_eeGeneral.stickMode == 0) { From 6fb483ea539fa162965d90d00577aaabe08f4985 Mon Sep 17 00:00:00 2001 From: raphaelcoeffic <1050031+raphaelcoeffic@users.noreply.github.com> Date: Sun, 23 Feb 2025 08:01:49 +0100 Subject: [PATCH 08/14] use import instead of script async --- radio/src/targets/simu/html/simu.html | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/radio/src/targets/simu/html/simu.html b/radio/src/targets/simu/html/simu.html index 6bac46ccb8e..0838a6a8c17 100644 --- a/radio/src/targets/simu/html/simu.html +++ b/radio/src/targets/simu/html/simu.html @@ -22,8 +22,9 @@
- - From ea82afd2fedabd2373128dfd1cc55f43322470c8 Mon Sep 17 00:00:00 2001 From: raphaelcoeffic <1050031+raphaelcoeffic@users.noreply.github.com> Date: Mon, 24 Feb 2025 09:22:32 +0100 Subject: [PATCH 09/14] fix(simu): audio uses signed 16 bit --- radio/src/targets/simu/simuaudio.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/radio/src/targets/simu/simuaudio.cpp b/radio/src/targets/simu/simuaudio.cpp index b22b30adffe..8373d5a73da 100644 --- a/radio/src/targets/simu/simuaudio.cpp +++ b/radio/src/targets/simu/simuaudio.cpp @@ -25,7 +25,7 @@ #include #include -#define SIMU_AUDIO_FMT AUDIO_U16SYS +#define SIMU_AUDIO_FMT AUDIO_S16SYS static SDL_AudioDeviceID _sdl_audio_device = 0; @@ -79,6 +79,7 @@ bool simuAudioInit() .freq = AUDIO_SAMPLE_RATE, .format = SIMU_AUDIO_FMT, .channels = 1, + .silence = 0, .samples = 1024, }; From bf3ee3a4791d4074f9c1250881dc460790a2f3c3 Mon Sep 17 00:00:00 2001 From: raphaelcoeffic <1050031+raphaelcoeffic@users.noreply.github.com> Date: Sat, 10 May 2025 08:46:37 +0200 Subject: [PATCH 10/14] fix(simu): restarting timer_queue --- radio/src/os/task_pthread.cpp | 3 ++- radio/src/os/timer_pthread.cpp | 20 ++++++++++++++++++-- radio/src/os/timer_pthread_impl.h | 3 ++- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/radio/src/os/task_pthread.cpp b/radio/src/os/task_pthread.cpp index bc7d05f1d0b..0b63668ccf4 100644 --- a/radio/src/os/task_pthread.cpp +++ b/radio/src/os/task_pthread.cpp @@ -80,7 +80,8 @@ void task_shutdown_all() pthread_join(task, nullptr); } - timer_queue::instance().stop(); + timer_queue::destroy(); + _stop_tasks = false; } struct run_context { diff --git a/radio/src/os/timer_pthread.cpp b/radio/src/os/timer_pthread.cpp index 3f575535cba..0c62b5e9b1d 100644 --- a/radio/src/os/timer_pthread.cpp +++ b/radio/src/os/timer_pthread.cpp @@ -4,6 +4,9 @@ #include +static timer_queue* _instance = nullptr; +static std::mutex _instance_mut; + bool _timer_cmp(timer_handle_t *lh, timer_handle_t *rh) { return lh->next_trigger < rh->next_trigger; } @@ -22,8 +25,21 @@ void timer_queue::sort_timers() { timer_queue& timer_queue::instance() { - static timer_queue _instance; - return _instance; + std::lock_guard lock(_instance_mut); + if (!_instance) { + _instance = new timer_queue(); + } + return *_instance; +} + +void timer_queue::destroy() +{ + std::lock_guard lock(_instance_mut); + if (_instance) { + _instance->stop(); + delete _instance; + _instance = nullptr; + } } void timer_queue::start() diff --git a/radio/src/os/timer_pthread_impl.h b/radio/src/os/timer_pthread_impl.h index 931d3d9eb0c..9d65df2e5e2 100644 --- a/radio/src/os/timer_pthread_impl.h +++ b/radio/src/os/timer_pthread_impl.h @@ -54,18 +54,19 @@ class timer_queue { void async_calls(); void process_cmds(); void send_cmd(timer_req_t&& req); + void stop(); public: timer_queue(timer_queue const &) = delete; void operator=(timer_queue const &) = delete; static timer_queue &instance(); + static void destroy(); static void create_timer(timer_handle_t *timer, timer_func_t func, const char *name, unsigned period, bool repeat); void start(); - void stop(); void start_timer(timer_handle_t *timer); void stop_timer(timer_handle_t *timer); From 625b76cba2005f7bdee610e295b2f08b18092e7b Mon Sep 17 00:00:00 2001 From: raphaelcoeffic <1050031+raphaelcoeffic@users.noreply.github.com> Date: Sat, 10 May 2025 08:47:48 +0200 Subject: [PATCH 11/14] fix: clean shutdown --- radio/src/targets/simu/sdl_simu.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/radio/src/targets/simu/sdl_simu.cpp b/radio/src/targets/simu/sdl_simu.cpp index 139df3113d3..ca209152efc 100644 --- a/radio/src/targets/simu/sdl_simu.cpp +++ b/radio/src/targets/simu/sdl_simu.cpp @@ -708,6 +708,9 @@ int main(int argc, char** argv) } while(true); #endif + // App cleanup + simuStop(); + // Cleanup ImGui_ImplSDLRenderer2_Shutdown(); ImGui_ImplSDL2_Shutdown(); From c291d76a62bd678b43e7ad909c8a4c3b4cf8490c Mon Sep 17 00:00:00 2001 From: raphaelcoeffic <1050031+raphaelcoeffic@users.noreply.github.com> Date: Sat, 10 May 2025 08:48:26 +0200 Subject: [PATCH 12/14] fix(simu): SD path on restart --- radio/src/targets/simu/simufatfs.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/radio/src/targets/simu/simufatfs.cpp b/radio/src/targets/simu/simufatfs.cpp index 20ee4933f68..1e8dc062ddb 100644 --- a/radio/src/targets/simu/simufatfs.cpp +++ b/radio/src/targets/simu/simufatfs.cpp @@ -89,8 +89,7 @@ void simuFatfsSetPaths(const char * sdPath, const char * settingsPath) { if (sdPath) { simuSdDirectory = removeTrailingPathDelimiter(fixPathDelimiters(sdPath)); - } - else { + } else if (simuSdDirectory.empty()) { char buff[1024]; f_getcwd(buff, sizeof(buff)-1); simuSdDirectory = removeTrailingPathDelimiter(fixPathDelimiters(buff)); From 6050f7a16a99a8af4cf7cee5c5f72ba988030557 Mon Sep 17 00:00:00 2001 From: raphaelcoeffic <1050031+raphaelcoeffic@users.noreply.github.com> Date: Sat, 10 May 2025 08:48:58 +0200 Subject: [PATCH 13/14] chore(simu): remove useless code --- radio/src/targets/simu/simpgmspace.cpp | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/radio/src/targets/simu/simpgmspace.cpp b/radio/src/targets/simu/simpgmspace.cpp index f99b98ce6f7..e3d17b00427 100644 --- a/radio/src/targets/simu/simpgmspace.cpp +++ b/radio/src/targets/simu/simpgmspace.cpp @@ -205,12 +205,6 @@ void simuStart(bool tests, const char * sdPath, const char * settingsPath) } #endif -#if defined(SIMU_EXCEPTIONS) - signal(SIGFPE, sig); - signal(SIGSEGV, sig); - try { -#endif - // Init LCD callbacks lcdInit(); @@ -228,12 +222,6 @@ void simuStart(bool tests, const char * sdPath, const char * settingsPath) #endif simu_running = true; - -#if defined(SIMU_EXCEPTIONS) - } - catch (...) { - } -#endif } extern task_handle_t mixerTaskId; @@ -250,10 +238,6 @@ void simuStop() simu_shutdown = true; task_shutdown_all(); -#if defined(SIMU_AUDIO) - stopAudio(); -#endif - simu_running = false; } From 08301f538494d10dd380bf4c24e047026fcb37f8 Mon Sep 17 00:00:00 2001 From: raphaelcoeffic <1050031+raphaelcoeffic@users.noreply.github.com> Date: Sat, 10 May 2025 08:49:17 +0200 Subject: [PATCH 14/14] feat(simu): reload with "r" key --- radio/src/targets/simu/sdl_simu.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/radio/src/targets/simu/sdl_simu.cpp b/radio/src/targets/simu/sdl_simu.cpp index ca209152efc..f4197899cf7 100644 --- a/radio/src/targets/simu/sdl_simu.cpp +++ b/radio/src/targets/simu/sdl_simu.cpp @@ -326,6 +326,13 @@ static bool handleKeyEvents(SDL_Event& event) ImGui::StyleColorsDark(); break; + case SDLK_r: + if (event.type == SDL_KEYUP) { + simuStop(); + simuStart(); + } + break; + default: key_handled = false; break;