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/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); 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..65b45e8e0d1 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,60 @@ 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} + widgets.cpp + knobs.cpp + 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 00000000000..a8edd8faa2f Binary files /dev/null and b/radio/src/targets/simu/assets/images/gimbal_frame.png differ 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 00000000000..7812d206279 Binary files /dev/null and b/radio/src/targets/simu/assets/images/icon.png differ diff --git a/radio/src/targets/simu/assets/images/stick_dot.png b/radio/src/targets/simu/assets/images/stick_dot.png new file mode 100644 index 00000000000..4a29daf4812 Binary files /dev/null and b/radio/src/targets/simu/assets/images/stick_dot.png differ diff --git a/radio/src/targets/simu/backlight_driver.cpp b/radio/src/targets/simu/backlight_driver.cpp index c50583cb4ce..3b6d82dd27d 100644 --- a/radio/src/targets/simu/backlight_driver.cpp +++ b/radio/src/targets/simu/backlight_driver.cpp @@ -19,11 +19,35 @@ * GNU General Public License for more details. */ +#include "board.h" + bool boardBacklightOn = false; bool isBacklightEnabled() { return boardBacklightOn; } -void backlightFullOn() { boardBacklightOn = true; } void backlightInit() {} -void backlightEnable(unsigned char) {} -void backlightEnable(unsigned char, unsigned char) {} -void backlightDisable() {} + +#if LCD_DEPTH != 16 + +void backlightFullOn() { boardBacklightOn = true; } + +void backlightEnable(unsigned char) +{ + boardBacklightOn = true; +} + +void backlightEnable(unsigned char, unsigned char) +{ + boardBacklightOn = true; +} + +void backlightDisable() +{ + boardBacklightOn = false; +} + +#else + +void backlightFullOn() { backlightEnable(BACKLIGHT_LEVEL_MAX); } +void backlightEnable(uint8_t) {} + +#endif 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; } diff --git a/radio/src/targets/simu/html/simu.html b/radio/src/targets/simu/html/simu.html new file mode 100644 index 00000000000..0838a6a8c17 --- /dev/null +++ b/radio/src/targets/simu/html/simu.html @@ -0,0 +1,52 @@ + + + + +
+ +
+ + + 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 new file mode 100644 index 00000000000..f4197899cf7 --- /dev/null +++ b/radio/src/targets/simu/sdl_simu.cpp @@ -0,0 +1,772 @@ +/* + * 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 +#include + +#include "gui_common.h" +#include "hal/adc_driver.h" +#include "hal/rotary_encoder.h" +#include "edgetx_constants.h" + +#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 "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 "edgetx.h" + +#define TIMER_INTERVAL 10 // 10ms + +static SDL_Window* window; +static SDL_Renderer* renderer; +static SDL_Texture* screen_frame_buffer; + +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" +}; +#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) +{ + 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); + } + } +} + +static 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(); + } +} + +static Uint32 timer_10ms_cb(Uint32 interval, void* name) +{ + per10ms(); + return TIMER_INTERVAL; +} + +static bool handleKeyEvents(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 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: + 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_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; + 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; + + case SDLK_l: + ImGui::StyleColorsLight(); + break; + + case SDLK_d: + ImGui::StyleColorsDark(); + break; + + case SDLK_r: + if (event.type == SDL_KEYUP) { + simuStop(); + simuStart(); + } + break; + + default: + key_handled = false; + break; + } + + if (key_handled) { + simuSetKey(key, key_event.type == SDL_KEYDOWN ? true : false); + } + + return key_handled; + } + + return false; +} + +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; + + 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; +} + +static 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; +} + +static void draw_switches() +{ + 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)) { + 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::PopStyleVar(3); + ImGui::EndGroup(); + } + ImGui::PopID(); +} + +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); +} + +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) { + if (isBacklightEnabled()) { + return IM_COL32(47, 123, 227, 255); + } else { + return IM_COL32(200, 200, 200, 255); + } + } else { + return IM_COL32_BLACK_TRANS; + } +} + +static void draw_screen() +{ + 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, (ImTextureID)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(); + + 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; + + // Use full work area (without menu-bars, task-bars etc.) + 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)) { + + auto flags = ImGuiTableFlags_SizingStretchProp; + ImGui::BeginTable("controls", 3, flags); + + ImGui::TableNextColumn(); + draw_switches(); + + ImGui::TableNextColumn(); + draw_gimbals(); + + ImGui::TableNextColumn(); + draw_pots(); + + ImGui::EndTable(); + + ImGui::Spacing(); + ImGui::Spacing(); + draw_screen(); + } + ImGui::End(); + + // Rendering + ImGui::Render(); + + ImGuiIO& io = ImGui::GetIO(); + SDL_RenderSetScale(renderer, io.DisplayFramebufferScale.x, + io.DisplayFramebufferScale.y); + + 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(), renderer); + SDL_RenderPresent(renderer); +} + +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); + SDL_SetWindowMinimumSize(window, 300, 400); + + 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); + +#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(); + 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); + + // 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; + } + + // TODO: race condition on YAML loaded... + if (g_eeGeneral.stickMode == 1) { + stick_left.pos.y = 1.0f; + } else if (g_eeGeneral.stickMode == 0) { + stick_right.pos.y = 1.0f; + } + + // 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_RESIZED)) { + redraw(); + return 0; + } + return 1; + }, NULL); + +#if defined(__EMSCRIPTEN__) + emscripten_set_main_loop([]() { handleEvents(); }, 0, true); +#else + do { + Uint64 start_ts = SDL_GetPerformanceCounter(); + if (!handleEvents()) 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 + + // App cleanup + simuStop(); + + // Cleanup + ImGui_ImplSDLRenderer2_Shutdown(); + ImGui_ImplSDL2_Shutdown(); + ImGui::DestroyContext(); + + SDL_DestroyTexture(screen_frame_buffer); + SDL_RemoveTimer(timerID_10ms); + SDL_DestroyRenderer(renderer); + SDL_DestroyWindow(window); +#if !defined(__EMSCRIPTEN__) + SDL_FreeSurface(icon); +#endif + 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_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; + } + } + + 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; + + // 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..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; } @@ -269,111 +253,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 +495,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 +511,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..8373d5a73da --- /dev/null +++ b/radio/src/targets/simu/simuaudio.cpp @@ -0,0 +1,103 @@ +/* + * 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_S16SYS + +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, + .silence = 0, + .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/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)); 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 00000000000..e50c17ef032 Binary files /dev/null and b/radio/src/targets/simu/stick_dot.bmp differ diff --git a/radio/src/targets/simu/widgets.cpp b/radio/src/targets/simu/widgets.cpp new file mode 100644 index 00000000000..ddeed388022 --- /dev/null +++ b/radio/src/targets/simu/widgets.cpp @@ -0,0 +1,216 @@ +/* + * 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; + + ImGui::InvisibleButton(name, ImVec2(gimbal_width, gimbal_width)); + + 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); + 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 - 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 { + adjust_gimbal(gs.pos.x); + if (!gs.lock_y) adjust_gimbal(gs.pos.y); + } + + 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); +} + +void GimbalPair(const char* str_id, GimbalState& left, GimbalState& right) +{ + ImGui::PushID(str_id); + ImGui::BeginGroup(); + + SingleGimbal("#g-left", left); + ImGui::SameLine(); + SingleGimbal("#g-right", right); + + ImGui::EndGroup(); + ImGui::PopID(); +} + +void SimuScreen(const ScreenDesc& desc, ImTextureID screen_img, ImVec2 size, + ImU32 bg_col, ImU32 overlay_col) +{ + 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(p_min, p_max, bg_col); + } + 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 = { 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 = { 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(p_min, p_max, overlay_col); + } +} + +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..08efba78865 --- /dev/null +++ b/radio/src/targets/simu/widgets.h @@ -0,0 +1,51 @@ +/* + * 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(const ScreenDesc& desc, ImTextureID screen_img, ImVec2 size, + ImU32 bg_col, ImU32 overlay_col); +bool SimuScreenMouseEvent(const ScreenDesc& desc, ScreenMouseEvent& event); 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);