chore: re-organise source files for color LCD UI (#5358)
This commit is contained in:
@@ -23,7 +23,7 @@
|
||||
#include <memory.h>
|
||||
|
||||
#include "lcd.h"
|
||||
#include "thirdparty/libopenui/src/bitfield.h"
|
||||
#include "bitfield.h"
|
||||
#include "gui/common/stdlcd/fonts.h"
|
||||
#include "gui/common/stdlcd/utf8.h"
|
||||
|
||||
|
||||
@@ -1,9 +1,21 @@
|
||||
if(HARDWARE_KEYS)
|
||||
add_definitions(-DHARDWARE_KEYS)
|
||||
endif()
|
||||
|
||||
if(HARWARE_TOUCH)
|
||||
add_definitions(-DHARDWARE_TOUCH)
|
||||
endif()
|
||||
|
||||
include_directories(
|
||||
${RADIO_SRC_DIR}/gui/colorlcd
|
||||
${RADIO_SRC_DIR}/gui/colorlcd/layouts
|
||||
${CMAKE_CURRENT_BINARY_DIR}/fonts
|
||||
${RADIO_SRC_DIR}/thirdparty/libopenui/src
|
||||
${RADIO_SRC_DIR}/thirdparty/libopenui/thirdparty
|
||||
${RADIO_SRC_DIR}/gui/colorlcd/libui
|
||||
${RADIO_SRC_DIR}/gui/colorlcd/model
|
||||
${RADIO_SRC_DIR}/gui/colorlcd/radio
|
||||
${RADIO_SRC_DIR}/gui/colorlcd/module
|
||||
${RADIO_SRC_DIR}/gui/colorlcd/mainview
|
||||
${RADIO_SRC_DIR}/gui/colorlcd/themes
|
||||
${RADIO_SRC_DIR}/gui/colorlcd/controls
|
||||
)
|
||||
|
||||
file(GLOB THEMES_SRC RELATIVE ${RADIO_SRC_DIR}/gui/colorlcd
|
||||
@@ -15,103 +27,76 @@ file(GLOB LAYOUTS_SRC RELATIVE ${RADIO_SRC_DIR}/gui/colorlcd
|
||||
file(GLOB WIDGETS_SRC RELATIVE ${RADIO_SRC_DIR}/gui/colorlcd
|
||||
${RADIO_SRC_DIR}/gui/colorlcd/widgets/*.cpp)
|
||||
|
||||
file(GLOB LIBUI_SRC RELATIVE ${RADIO_SRC_DIR}/gui/colorlcd
|
||||
${RADIO_SRC_DIR}/gui/colorlcd/libui/*.cpp)
|
||||
|
||||
file(GLOB MAINVIEW_SRC RELATIVE ${RADIO_SRC_DIR}/gui/colorlcd
|
||||
${RADIO_SRC_DIR}/gui/colorlcd/mainview/*.cpp)
|
||||
|
||||
file(GLOB CONTROLS_SRC RELATIVE ${RADIO_SRC_DIR}/gui/colorlcd
|
||||
${RADIO_SRC_DIR}/gui/colorlcd/controls/*.cpp)
|
||||
|
||||
set(GUI_SRC
|
||||
${GUI_SRC}
|
||||
${LIBUI_SRC}
|
||||
${THEMES_SRC}
|
||||
${LAYOUTS_SRC}
|
||||
${WIDGETS_SRC}
|
||||
${MAINVIEW_SRC}
|
||||
${CONTROLS_SRC}
|
||||
bitmaps.cpp
|
||||
channel_bar.cpp
|
||||
channel_range.cpp
|
||||
color_editor.cpp
|
||||
color_list.cpp
|
||||
color_picker.cpp
|
||||
colors.cpp
|
||||
curve.cpp
|
||||
file_browser.cpp
|
||||
file_preview.cpp
|
||||
fonts.cpp
|
||||
fullscreen_dialog.cpp
|
||||
special_functions.cpp
|
||||
layout.cpp
|
||||
lcd.cpp
|
||||
list_line_button.cpp
|
||||
listbox.cpp
|
||||
LvglWrapper.cpp
|
||||
menu_model.cpp
|
||||
menu_radio.cpp
|
||||
menu_screen.cpp
|
||||
page.cpp
|
||||
popups.cpp
|
||||
screen_setup.cpp
|
||||
screen_user_interface.cpp
|
||||
select_fab_carousel.cpp
|
||||
sourcechoice.cpp
|
||||
startup_shutdown.cpp
|
||||
switch_warn_dialog.cpp
|
||||
switchchoice.cpp
|
||||
tabsgroup.cpp
|
||||
theme_manager.cpp
|
||||
topbar.cpp
|
||||
view_about.cpp
|
||||
view_channels.cpp
|
||||
view_logical_switches.cpp
|
||||
view_main_decoration.cpp
|
||||
view_main_menu.cpp
|
||||
view_main.cpp
|
||||
view_statistics.cpp
|
||||
view_text.cpp
|
||||
widget_settings.cpp
|
||||
widget.cpp
|
||||
widgets_setup.cpp
|
||||
curve_param.cpp
|
||||
curveedit.cpp
|
||||
fm_matrix.cpp
|
||||
gvar_numberedit.cpp
|
||||
input_edit_adv.cpp
|
||||
input_edit.cpp
|
||||
input_source.cpp
|
||||
mixer_edit_adv.cpp
|
||||
mixer_edit.cpp
|
||||
model_curves.cpp
|
||||
model_flightmodes.cpp
|
||||
model_gvars.cpp
|
||||
model_inputs.cpp
|
||||
model_logical_switches.cpp
|
||||
model_mixes.cpp
|
||||
model_outputs.cpp
|
||||
model_select.cpp
|
||||
model_setup.cpp
|
||||
model_telemetry.cpp
|
||||
model_templates.cpp
|
||||
output_edit.cpp
|
||||
preflight_checks.cpp
|
||||
throttle_params.cpp
|
||||
timeedit.cpp
|
||||
timer_setup.cpp
|
||||
trainer_setup.cpp
|
||||
trims_setup.cpp
|
||||
bind_menu_d16.cpp
|
||||
custom_failsafe.cpp
|
||||
hw_extmodule.cpp
|
||||
hw_intmodule.cpp
|
||||
module_setup.cpp
|
||||
ppm_settings.cpp
|
||||
file_carosell.cpp
|
||||
hw_inputs.cpp
|
||||
hw_serial.cpp
|
||||
preview_window.cpp
|
||||
radio_calibration.cpp
|
||||
radio_diaganas.cpp
|
||||
radio_diagkeys.cpp
|
||||
radio_hardware.cpp
|
||||
radio_sdmanager.cpp
|
||||
radio_setup.cpp
|
||||
radio_theme.cpp
|
||||
radio_tools.cpp
|
||||
radio_trainer.cpp
|
||||
radio_version.cpp
|
||||
source_numberedit.cpp
|
||||
|
||||
model/curveedit.cpp
|
||||
model/input_edit_adv.cpp
|
||||
model/input_edit.cpp
|
||||
model/menu_model.cpp
|
||||
model/mixer_edit_adv.cpp
|
||||
model/mixer_edit.cpp
|
||||
model/model_curves.cpp
|
||||
model/model_flightmodes.cpp
|
||||
model/model_gvars.cpp
|
||||
model/model_inputs.cpp
|
||||
model/model_logical_switches.cpp
|
||||
model/model_mixes.cpp
|
||||
model/model_outputs.cpp
|
||||
model/model_select.cpp
|
||||
model/model_setup.cpp
|
||||
model/model_telemetry.cpp
|
||||
model/model_templates.cpp
|
||||
model/output_edit.cpp
|
||||
model/preflight_checks.cpp
|
||||
model/special_functions.cpp
|
||||
model/throttle_params.cpp
|
||||
model/timer_setup.cpp
|
||||
model/trainer_setup.cpp
|
||||
model/trims_setup.cpp
|
||||
|
||||
module/bind_menu_d16.cpp
|
||||
module/custom_failsafe.cpp
|
||||
module/module_setup.cpp
|
||||
module/ppm_settings.cpp
|
||||
|
||||
radio/hw_extmodule.cpp
|
||||
radio/hw_inputs.cpp
|
||||
radio/hw_intmodule.cpp
|
||||
radio/hw_serial.cpp
|
||||
radio/menu_radio.cpp
|
||||
radio/preview_window.cpp
|
||||
radio/radio_calibration.cpp
|
||||
radio/radio_diaganas.cpp
|
||||
radio/radio_diagkeys.cpp
|
||||
radio/radio_hardware.cpp
|
||||
radio/radio_sdmanager.cpp
|
||||
radio/radio_setup.cpp
|
||||
radio/radio_theme.cpp
|
||||
radio/radio_tools.cpp
|
||||
radio/radio_trainer.cpp
|
||||
radio/radio_version.cpp
|
||||
)
|
||||
|
||||
macro(add_gui_src src)
|
||||
@@ -132,86 +117,74 @@ if(LUA)
|
||||
endif()
|
||||
|
||||
if(LUA_MIXER)
|
||||
add_gui_src(model_mixer_scripts.cpp)
|
||||
add_gui_src(model/model_mixer_scripts.cpp)
|
||||
endif()
|
||||
|
||||
if(FUNCTION_SWITCHES)
|
||||
add_gui_src(function_switches.cpp)
|
||||
add_gui_src(radio_diagcustswitches.cpp)
|
||||
add_gui_src(model/function_switches.cpp)
|
||||
add_gui_src(radio/radio_diagcustswitches.cpp)
|
||||
endif()
|
||||
|
||||
if(GVARS)
|
||||
add_gui_src(model_gvars.cpp)
|
||||
add_gui_src(model/model_gvars.cpp)
|
||||
endif()
|
||||
|
||||
if(HELI)
|
||||
add_gui_src(model_heli.cpp)
|
||||
add_gui_src(model/model_heli.cpp)
|
||||
endif()
|
||||
|
||||
if(PXX2 OR MULTIMODULE)
|
||||
add_gui_src(radio_spectrum_analyser.cpp)
|
||||
add_gui_src(radio/radio_spectrum_analyser.cpp)
|
||||
endif()
|
||||
|
||||
if(CROSSFIRE)
|
||||
add_gui_src(crossfire_settings.cpp)
|
||||
add_gui_src(module/crossfire_settings.cpp)
|
||||
endif()
|
||||
|
||||
if(AFHDS2)
|
||||
add_gui_src(afhds2a_settings.cpp)
|
||||
add_gui_src(module/afhds2a_settings.cpp)
|
||||
endif()
|
||||
|
||||
if(AFHDS3)
|
||||
add_gui_src(afhds3_settings.cpp)
|
||||
add_gui_src(afhds3_options.cpp)
|
||||
add_gui_src(module/afhds3_settings.cpp)
|
||||
add_gui_src(module/afhds3_options.cpp)
|
||||
endif()
|
||||
|
||||
if(MULTIMODULE)
|
||||
add_gui_src(mpm_settings.cpp)
|
||||
add_gui_src(module/mpm_settings.cpp)
|
||||
add_gui_src(module/multi_rfprotos.cpp)
|
||||
endif()
|
||||
|
||||
if(INTERNAL_MODULE_PXX1)
|
||||
add_gui_src(pxx1_settings.cpp)
|
||||
add_gui_src(module/pxx1_settings.cpp)
|
||||
endif()
|
||||
|
||||
if(PXX2)
|
||||
add_gui_src(access_settings.cpp)
|
||||
endif()
|
||||
|
||||
if(MULTIMODULE)
|
||||
add_gui_src(multi_rfprotos.cpp)
|
||||
add_gui_src(module/access_settings.cpp)
|
||||
endif()
|
||||
|
||||
if(GHOST)
|
||||
add_gui_src(radio_ghost_module_config.cpp)
|
||||
add_gui_src(radio/radio_ghost_module_config.cpp)
|
||||
endif()
|
||||
|
||||
if(BLUETOOTH)
|
||||
add_gui_src(trainer_bluetooth.cpp)
|
||||
add_gui_src(hw_bluetooth.cpp)
|
||||
add_gui_src(model/trainer_bluetooth.cpp)
|
||||
add_gui_src(radio/hw_bluetooth.cpp)
|
||||
endif()
|
||||
|
||||
if(USBJ_EX)
|
||||
add_gui_src(model_usbjoystick.cpp)
|
||||
add_gui_src(model/model_usbjoystick.cpp)
|
||||
endif()
|
||||
|
||||
set(SRC ${SRC} storage/modelslist.cpp)
|
||||
|
||||
option(WINDOWS_INSPECT_BORDERS "Draw bounding boxes around windows" OFF)
|
||||
if(WINDOWS_INSPECT_BORDERS)
|
||||
add_definitions(-DWINDOWS_INSPECT_BORDERS)
|
||||
endif()
|
||||
|
||||
option(UI_PERF_MONITOR "Draw frame rate and CPU usage" OFF)
|
||||
if(UI_PERF_MONITOR)
|
||||
add_definitions(-DUI_PERF_MONITOR)
|
||||
endif()
|
||||
|
||||
# includes libopenui
|
||||
set(LIBOPENUI_SRC_DIR thirdparty/libopenui)
|
||||
set(LVGL_SRC_DIR ${LIBOPENUI_SRC_DIR}/thirdparty/lvgl/src)
|
||||
|
||||
include(${LIBOPENUI_SRC_DIR}/src/CMakeLists.txt)
|
||||
include(${LIBOPENUI_SRC_DIR}/thirdparty/CMakeLists.txt)
|
||||
# include lvgl
|
||||
include(${RADIO_SRC_DIR}/gui/colorlcd/CMakeListsLVGL.txt)
|
||||
|
||||
list(TRANSFORM LVGL_SOURCES_MINIMAL PREPEND ${RADIO_SRC_DIR}/)
|
||||
list(TRANSFORM LVGL_FONT_SOURCES_MINIMAL PREPEND ${RADIO_SRC_DIR}/)
|
||||
@@ -223,18 +196,14 @@ set(BOOTLOADER_SRC ${BOOTLOADER_SRC}
|
||||
${RADIO_SRC_DIR}/gui/colorlcd/fonts.cpp
|
||||
${RADIO_SRC_DIR}/gui/colorlcd/colors.cpp
|
||||
${RADIO_SRC_DIR}/gui/colorlcd/bitmaps.cpp
|
||||
${RADIO_SRC_DIR}/${LIBOPENUI_SRC_DIR}/thirdparty/lz4/lz4.c
|
||||
${RADIO_SRC_DIR}/${LIBOPENUI_SRC_DIR}/src/bitmapbuffer.cpp
|
||||
${RADIO_SRC_DIR}/gui/colorlcd/libui/bitmapbuffer.cpp
|
||||
${RADIO_SRC_DIR}/thirdparty/lz4/lz4.c
|
||||
)
|
||||
|
||||
set(SRC ${SRC}
|
||||
${LVGL_SOURCES}
|
||||
${LVGL_FONT_SOURCES}
|
||||
${LIBOPENUI_SOURCES}
|
||||
${RADIO_SRC_DIR}/thirdparty/lz4/lz4.c
|
||||
)
|
||||
|
||||
foreach(LVGL_FILE ${LVGL_SOURCES})
|
||||
SET_SOURCE_FILES_PROPERTIES(${LVGL_FILE} PROPERTIES COMPILE_FLAGS -O3)
|
||||
endforeach()
|
||||
|
||||
add_definitions(-DLIBOPENUI)
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
set(LVGL_SOURCES_MINIMAL
|
||||
core/lv_refr.c
|
||||
|
||||
draw/lv_draw_img.c
|
||||
draw/lv_draw_label.c
|
||||
draw/lv_draw_line.c
|
||||
draw/lv_draw_mask.c
|
||||
draw/lv_draw_rect.c
|
||||
draw/lv_draw.c
|
||||
draw/lv_img_buf.c
|
||||
draw/lv_img_cache.c
|
||||
draw/lv_img_decoder.c
|
||||
|
||||
draw/sw/lv_draw_sw_arc.c
|
||||
draw/sw/lv_draw_sw_blend.c
|
||||
draw/sw/lv_draw_sw_img.c
|
||||
draw/sw/lv_draw_sw_letter.c
|
||||
draw/sw/lv_draw_sw_line.c
|
||||
draw/sw/lv_draw_sw_polygon.c
|
||||
draw/sw/lv_draw_sw_rect.c
|
||||
draw/sw/lv_draw_sw.c
|
||||
|
||||
# STM32 specific
|
||||
draw/stm32_dma2d/lv_gpu_stm32_dma2d.c
|
||||
|
||||
hal/lv_hal_disp.c
|
||||
hal/lv_hal_tick.c
|
||||
|
||||
font/lv_font.c
|
||||
font/lv_font_fmt_txt.c
|
||||
|
||||
misc/lv_area.c
|
||||
misc/lv_async.c
|
||||
misc/lv_color.c
|
||||
misc/lv_gc.c
|
||||
misc/lv_ll.c
|
||||
misc/lv_math.c
|
||||
misc/lv_mem.c
|
||||
misc/lv_timer.c
|
||||
misc/lv_txt.c
|
||||
misc/lv_utils.c
|
||||
)
|
||||
|
||||
set(LVGL_SOURCES
|
||||
${LVGL_SOURCES_MINIMAL}
|
||||
|
||||
core/lv_disp.c
|
||||
core/lv_event.c
|
||||
core/lv_group.c
|
||||
core/lv_indev_scroll.c
|
||||
core/lv_indev.c
|
||||
core/lv_obj_class.c
|
||||
core/lv_obj_draw.c
|
||||
core/lv_obj_pos.c
|
||||
core/lv_obj_scroll.c
|
||||
core/lv_obj_style_gen.c
|
||||
core/lv_obj_style.c
|
||||
core/lv_obj_tree.c
|
||||
core/lv_obj.c
|
||||
core/lv_theme.c
|
||||
|
||||
draw/lv_draw_arc.c
|
||||
draw/lv_draw_triangle.c
|
||||
|
||||
draw/sw/lv_draw_sw_gradient.c
|
||||
|
||||
hal/lv_hal_indev.c
|
||||
|
||||
misc/lv_anim.c
|
||||
misc/lv_bidi.c
|
||||
misc/lv_fs.c
|
||||
misc/lv_log.c
|
||||
misc/lv_lru.c
|
||||
misc/lv_printf.c
|
||||
misc/lv_style_gen.c
|
||||
misc/lv_style.c
|
||||
misc/lv_templ.c
|
||||
misc/lv_tlsf.c
|
||||
misc/lv_txt_ap.c
|
||||
|
||||
widgets/lv_arc.c
|
||||
widgets/lv_bar.c
|
||||
widgets/lv_btn.c
|
||||
widgets/lv_btnmatrix.c
|
||||
widgets/lv_canvas.c
|
||||
widgets/lv_checkbox.c
|
||||
widgets/lv_img.c
|
||||
widgets/lv_label.c
|
||||
widgets/lv_line.c
|
||||
widgets/lv_objx_templ.c
|
||||
widgets/lv_slider.c
|
||||
widgets/lv_switch.c
|
||||
widgets/lv_table.c
|
||||
widgets/lv_textarea.c
|
||||
|
||||
extra/lv_extra.c
|
||||
|
||||
extra/layouts/flex/lv_flex.c
|
||||
extra/layouts/grid/lv_grid.c
|
||||
extra/libs/fsdrv/lv_fs_fatfs.c
|
||||
extra/libs/qrcode/lv_qrcode.c
|
||||
extra/libs/qrcode/qrcodegen.c
|
||||
extra/others/snapshot/lv_snapshot.c
|
||||
extra/widgets/imgbtn/lv_imgbtn.c
|
||||
extra/widgets/keyboard/lv_keyboard.c
|
||||
extra/widgets/list/lv_list.c
|
||||
extra/widgets/menu/lv_menu.c
|
||||
extra/widgets/meter/lv_meter.c
|
||||
extra/widgets/tabview/lv_tabview.c
|
||||
extra/widgets/tileview/lv_tileview.c
|
||||
extra/widgets/win/lv_win.c
|
||||
)
|
||||
|
||||
set(LVGL_SRC_DIR thirdparty/lvgl/src)
|
||||
|
||||
list(TRANSFORM LVGL_SOURCES_MINIMAL PREPEND ${LVGL_SRC_DIR}/)
|
||||
list(TRANSFORM LVGL_SOURCES PREPEND ${LVGL_SRC_DIR}/)
|
||||
|
||||
foreach(LVGL_FILE ${LVGL_SOURCES})
|
||||
SET_SOURCE_FILES_PROPERTIES(${LVGL_FILE} PROPERTIES COMPILE_FLAGS -O3)
|
||||
endforeach()
|
||||
@@ -23,7 +23,7 @@
|
||||
#include "hal/rotary_encoder.h"
|
||||
|
||||
#include "LvglWrapper.h"
|
||||
#include "themes/etx_lv_theme.h"
|
||||
#include "etx_lv_theme.h"
|
||||
|
||||
#include "view_main.h"
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
#include "bitmaps.h"
|
||||
|
||||
#include "libopenui.h"
|
||||
#include "libopenui/thirdparty/lz4/lz4.h"
|
||||
#include "lz4/lz4.h"
|
||||
#include "opentx_helpers.h"
|
||||
|
||||
LZ4BitmapBuffer::LZ4BitmapBuffer(uint8_t format, const LZ4Bitmap* lz4Data) :
|
||||
|
||||
@@ -127,10 +127,6 @@ LcdColorIndex indexFromColor(uint32_t lcdFlags);
|
||||
#define COLOR_THEME_DISABLED COLOR(COLOR_THEME_DISABLED_INDEX)
|
||||
#define CUSTOM_COLOR COLOR(CUSTOM_COLOR_INDEX)
|
||||
|
||||
#if defined(WINDOWS_INSPECT_BORDERS)
|
||||
#define WINDOWS_INSPECT_BORDER_COLOR COLOR_THEME_PRIMARY3
|
||||
#endif
|
||||
|
||||
constexpr int MAX_SATURATION = 100;
|
||||
constexpr int MAX_HUE = 360;
|
||||
constexpr int MAX_BRIGHTNESS = 100;
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@
|
||||
#include "channel_bar.h"
|
||||
|
||||
#include "bitmaps.h"
|
||||
#include "themes/etx_lv_theme.h"
|
||||
#include "etx_lv_theme.h"
|
||||
|
||||
#define VIEW_CHANNELS_LIMIT_PCT \
|
||||
(g_model.extendedLimits ? LIMIT_EXT_PERCENT : LIMIT_STD_PERCENT)
|
||||
+1
-1
@@ -21,7 +21,7 @@
|
||||
#include "color_editor.h"
|
||||
|
||||
#include "button.h"
|
||||
#include "themes/etx_lv_theme.h"
|
||||
#include "etx_lv_theme.h"
|
||||
|
||||
static const char* const RGBChars[MAX_BARS] = {"R", "G", "B"};
|
||||
static const char* const HSVChars[MAX_BARS] = {"H", "S", "V"};
|
||||
+1
-1
@@ -22,7 +22,7 @@
|
||||
#include "color_list.h"
|
||||
|
||||
#include "color_editor.h"
|
||||
#include "themes/etx_lv_theme.h"
|
||||
#include "etx_lv_theme.h"
|
||||
|
||||
ColorList::ColorList(Window *parent, const rect_t &rect,
|
||||
std::vector<ColorEntry> colors) :
|
||||
+1
-1
@@ -23,7 +23,7 @@
|
||||
|
||||
#include "color_editor.h"
|
||||
#include "color_list.h"
|
||||
#include "themes/etx_lv_theme.h"
|
||||
#include "etx_lv_theme.h"
|
||||
|
||||
#if !PORTRAIT_LCD
|
||||
// Landscape
|
||||
+1
-1
@@ -22,7 +22,7 @@
|
||||
#include "fm_matrix.h"
|
||||
|
||||
#include "opentx.h"
|
||||
#include "themes/etx_lv_theme.h"
|
||||
#include "etx_lv_theme.h"
|
||||
|
||||
template <class T>
|
||||
FMMatrix<T>::FMMatrix(Window* parent, const rect_t& r, T* input) :
|
||||
@@ -23,7 +23,7 @@
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "fonts.h"
|
||||
#include "libopenui/thirdparty/lz4/lz4.h"
|
||||
#include "lz4/lz4.h"
|
||||
#include "libopenui_defines.h"
|
||||
#include "lz4_fonts.h"
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
#include "bitmapbuffer.h"
|
||||
#include "board.h"
|
||||
#include "dma2d.h"
|
||||
#include "themes/etx_lv_theme.h"
|
||||
#include "etx_lv_theme.h"
|
||||
|
||||
pixel_t LCD_FIRST_FRAME_BUFFER[DISPLAY_BUFFER_SIZE] __SDRAM;
|
||||
pixel_t LCD_SECOND_FRAME_BUFFER[DISPLAY_BUFFER_SIZE] __SDRAM;
|
||||
|
||||
@@ -0,0 +1,565 @@
|
||||
/*
|
||||
* Copyright (C) EdgeTX
|
||||
*
|
||||
* Based on code named
|
||||
* libopenui - https://github.com/opentx/libopenui
|
||||
*
|
||||
* 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 "bitmapbuffer.h"
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "bitmaps.h"
|
||||
#include "board.h"
|
||||
#include "dma2d.h"
|
||||
#include "fonts.h"
|
||||
#include "lvgl/src/draw/sw/lv_draw_sw.h"
|
||||
#include "opentx_helpers.h"
|
||||
#include "strhelpers.h"
|
||||
|
||||
BitmapBuffer::BitmapBuffer(uint8_t format, uint16_t width, uint16_t height) :
|
||||
format(format),
|
||||
_width(width),
|
||||
_height(height),
|
||||
xmax(width),
|
||||
ymax(height),
|
||||
dataAllocated(true)
|
||||
#if defined(DEBUG)
|
||||
,
|
||||
leakReported(false)
|
||||
#endif
|
||||
{
|
||||
data = (uint16_t *)malloc(align32(width * height * sizeof(uint16_t)));
|
||||
data_end = data + (width * height);
|
||||
|
||||
#if !defined(BOOT)
|
||||
// Assume we need a canvas here
|
||||
canvas = lv_canvas_create(nullptr);
|
||||
lv_canvas_set_buffer(canvas, data, width, height, LV_IMG_CF_TRUE_COLOR);
|
||||
#endif
|
||||
}
|
||||
|
||||
BitmapBuffer::BitmapBuffer(uint8_t format, uint16_t width, uint16_t height,
|
||||
uint16_t *data) :
|
||||
format(format),
|
||||
_width(width),
|
||||
_height(height),
|
||||
xmax(width),
|
||||
ymax(height),
|
||||
data(data),
|
||||
data_end(data + (width * height)),
|
||||
dataAllocated(false)
|
||||
#if defined(DEBUG)
|
||||
,
|
||||
leakReported(false)
|
||||
#endif
|
||||
{
|
||||
}
|
||||
|
||||
BitmapBuffer::~BitmapBuffer()
|
||||
{
|
||||
DMAWait();
|
||||
if (dataAllocated) {
|
||||
#if !defined(BOOT)
|
||||
lv_obj_del(canvas);
|
||||
#endif
|
||||
free(data);
|
||||
}
|
||||
}
|
||||
|
||||
void BitmapBuffer::setData(uint16_t *d)
|
||||
{
|
||||
if (!dataAllocated) {
|
||||
data = d;
|
||||
data_end = d + (_width * _height);
|
||||
}
|
||||
}
|
||||
|
||||
void BitmapBuffer::clear(LcdFlags flags)
|
||||
{
|
||||
drawSolidFilledRect(0, 0, _width, _height, flags);
|
||||
}
|
||||
|
||||
void BitmapBuffer::clearClippingRect()
|
||||
{
|
||||
xmin = 0;
|
||||
xmax = _width;
|
||||
ymin = 0;
|
||||
ymax = _height;
|
||||
}
|
||||
|
||||
void BitmapBuffer::setClippingRect(coord_t xmin, coord_t xmax, coord_t ymin,
|
||||
coord_t ymax)
|
||||
{
|
||||
this->xmin = xmin;
|
||||
this->xmax = xmax;
|
||||
this->ymin = ymin;
|
||||
this->ymax = ymax;
|
||||
}
|
||||
|
||||
void BitmapBuffer::getClippingRect(coord_t &xmin, coord_t &xmax, coord_t &ymin,
|
||||
coord_t &ymax)
|
||||
{
|
||||
xmin = this->xmin;
|
||||
xmax = this->xmax;
|
||||
ymin = this->ymin;
|
||||
ymax = this->ymax;
|
||||
}
|
||||
|
||||
bool BitmapBuffer::applyClippingRect(coord_t& x, coord_t& y, coord_t& w,
|
||||
coord_t& h) const
|
||||
{
|
||||
if (h < 0) {
|
||||
y += h;
|
||||
h = -h;
|
||||
}
|
||||
|
||||
if (w < 0) {
|
||||
x += w;
|
||||
w = -w;
|
||||
}
|
||||
|
||||
if (x >= xmax || y >= ymax) return false;
|
||||
|
||||
if (y < ymin) {
|
||||
h += y - ymin;
|
||||
y = ymin;
|
||||
}
|
||||
|
||||
if (x < xmin) {
|
||||
w += x - xmin;
|
||||
x = xmin;
|
||||
}
|
||||
|
||||
if (y + h > ymax) h = ymax - y;
|
||||
|
||||
if (x + w > xmax) w = xmax - x;
|
||||
|
||||
return data && h > 0 && w > 0;
|
||||
}
|
||||
|
||||
void BitmapBuffer::drawBitmap(coord_t x, coord_t y, const BitmapBuffer *bmp,
|
||||
coord_t srcx, coord_t srcy, coord_t srcw,
|
||||
coord_t srch, float scale)
|
||||
{
|
||||
if (!data || !bmp) return;
|
||||
if (x >= xmax || y >= ymax) return;
|
||||
|
||||
coord_t bmpw = bmp->width();
|
||||
coord_t bmph = bmp->height();
|
||||
|
||||
if (srcw == 0) srcw = bmpw;
|
||||
if (srch == 0) srch = bmph;
|
||||
if (srcx + srcw > bmpw) srcw = bmpw - srcx;
|
||||
if (srcy + srch > bmph) srch = bmph - srcy;
|
||||
|
||||
if (scale == 0) {
|
||||
if (x < xmin) {
|
||||
srcw += x - xmin;
|
||||
srcx -= x - xmin;
|
||||
x = xmin;
|
||||
}
|
||||
if (y < ymin) {
|
||||
srch += y - ymin;
|
||||
srcy -= y - ymin;
|
||||
y = ymin;
|
||||
}
|
||||
if (x + srcw > xmax) {
|
||||
srcw = xmax - x;
|
||||
}
|
||||
if (y + srch > ymax) {
|
||||
srch = ymax - y;
|
||||
}
|
||||
} else {
|
||||
if (x < xmin) {
|
||||
srcw += (x - xmin) / scale;
|
||||
srcx -= (x - xmin) / scale;
|
||||
x = xmin;
|
||||
}
|
||||
if (y < ymin) {
|
||||
srch += (y - ymin) / scale;
|
||||
srcy -= (y - ymin) / scale;
|
||||
y = ymin;
|
||||
}
|
||||
if (x + srcw * scale > xmax) {
|
||||
srcw = (xmax - x) / scale;
|
||||
}
|
||||
if (y + srch * scale > ymax) {
|
||||
srch = (ymax - y) / scale;
|
||||
}
|
||||
}
|
||||
|
||||
if (srcw <= 0 || srch <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (scale == 0) {
|
||||
if (bmp->format == BMP_ARGB4444) {
|
||||
DMACopyAlphaBitmap(data, _width, _height, x, y, bmp->getData(), bmpw,
|
||||
bmph, srcx, srcy, srcw, srch);
|
||||
} else {
|
||||
DMACopyBitmap(data, _width, _height, x, y, bmp->getData(), bmpw, bmph,
|
||||
srcx, srcy, srcw, srch);
|
||||
}
|
||||
DMAWait();
|
||||
|
||||
#if __CORTEX_M >= 0x07
|
||||
SCB_CleanInvalidateDCache();
|
||||
#endif
|
||||
} else {
|
||||
int scaledw = srcw * scale;
|
||||
int scaledh = srch * scale;
|
||||
|
||||
if (x + scaledw > _width) scaledw = _width - x;
|
||||
if (y + scaledh > _height) scaledh = _height - y;
|
||||
|
||||
if (format == BMP_ARGB4444) {
|
||||
for (int i = 0; i < scaledh; i++) {
|
||||
pixel_t *p = getPixelPtrAbs(x, y + i);
|
||||
const pixel_t *qstart =
|
||||
bmp->getPixelPtrAbs(srcx, srcy + int(i / scale));
|
||||
|
||||
for (int j = 0; j < scaledw; j++) {
|
||||
const pixel_t *q = qstart;
|
||||
MOVE_PIXEL_RIGHT(q, int(j / scale));
|
||||
|
||||
if (bmp->format == BMP_RGB565) {
|
||||
RGB_SPLIT(*q, r, g, b);
|
||||
drawPixel(p, ARGB_JOIN(0xF, r >> 1, g >> 2, b >> 1));
|
||||
|
||||
} else { // bmp->format == BMP_ARGB4444
|
||||
drawPixel(p, *q);
|
||||
}
|
||||
MOVE_TO_NEXT_RIGHT_PIXEL(p);
|
||||
}
|
||||
}
|
||||
} else { // format == BM_RGB565
|
||||
|
||||
for (int i = 0; i < scaledh; i++) {
|
||||
pixel_t *p = getPixelPtrAbs(x, y + i);
|
||||
const pixel_t *qstart =
|
||||
bmp->getPixelPtrAbs(srcx, srcy + int(i / scale));
|
||||
|
||||
for (int j = 0; j < scaledw; j++) {
|
||||
const pixel_t *q = qstart;
|
||||
MOVE_PIXEL_RIGHT(q, int(j / scale));
|
||||
|
||||
if (bmp->format == BMP_RGB565) {
|
||||
drawPixel(p, *q);
|
||||
} else { // bmp->format == BMP_ARGB4444
|
||||
ARGB_SPLIT(*q, a, r, g, b);
|
||||
drawAlphaPixel(p, a, RGB_JOIN(r << 1, g << 2, b << 1));
|
||||
}
|
||||
MOVE_TO_NEXT_RIGHT_PIXEL(p);
|
||||
} // for j
|
||||
} // for i
|
||||
} // if format
|
||||
} // else (scale != 0) {
|
||||
}
|
||||
|
||||
void BitmapBuffer::drawAlphaPixel(pixel_t *p, uint8_t opacity, uint16_t color)
|
||||
{
|
||||
// TRACE("BitmapBuffer::drawAlphaPixel()");
|
||||
if (opacity == OPACITY_MAX) {
|
||||
drawPixel(p, color);
|
||||
} else if (opacity != 0) {
|
||||
uint8_t bgWeight = OPACITY_MAX - opacity;
|
||||
RGB_SPLIT(color, red, green, blue);
|
||||
RGB_SPLIT(*p, bgRed, bgGreen, bgBlue);
|
||||
uint16_t r = (bgRed * bgWeight + red * opacity) / OPACITY_MAX;
|
||||
uint16_t g = (bgGreen * bgWeight + green * opacity) / OPACITY_MAX;
|
||||
uint16_t b = (bgBlue * bgWeight + blue * opacity) / OPACITY_MAX;
|
||||
drawPixel(p, RGB_JOIN(r, g, b));
|
||||
}
|
||||
}
|
||||
|
||||
void BitmapBuffer::drawHorizontalLine(coord_t x, coord_t y, coord_t w,
|
||||
uint8_t pat, LcdFlags flags,
|
||||
uint8_t opacity)
|
||||
{
|
||||
if (opacity == OPACITY_MAX) return;
|
||||
|
||||
coord_t h = 1;
|
||||
if (!applyClippingRect(x, y, w, h)) return;
|
||||
|
||||
drawHorizontalLineAbs(x, y, w, pat, flags, opacity);
|
||||
}
|
||||
|
||||
void BitmapBuffer::drawHorizontalLineAbs(coord_t x, coord_t y, coord_t w,
|
||||
uint8_t pat, LcdFlags flags,
|
||||
uint8_t opacity)
|
||||
{
|
||||
if (opacity == OPACITY_MAX) return;
|
||||
|
||||
if (draw_ctx) {
|
||||
x += draw_ctx->buf_area->x1;
|
||||
y += draw_ctx->buf_area->y1;
|
||||
}
|
||||
|
||||
lv_draw_line_dsc_t line_dsc;
|
||||
lv_draw_line_dsc_init(&line_dsc);
|
||||
|
||||
line_dsc.width = 1;
|
||||
line_dsc.opa = ((OPACITY_MAX - opacity) * LV_OPA_COVER) / OPACITY_MAX;
|
||||
line_dsc.color = makeLvColor(flags);
|
||||
|
||||
if (pat == DOTTED) {
|
||||
line_dsc.dash_gap = 1;
|
||||
line_dsc.dash_width = 1;
|
||||
}
|
||||
|
||||
lv_point_t p[2];
|
||||
p[0].x = x;
|
||||
p[0].y = y;
|
||||
p[1].x = x + w;
|
||||
p[1].y = y;
|
||||
|
||||
if (draw_ctx) {
|
||||
lv_draw_line(draw_ctx, &line_dsc, &p[0], &p[1]);
|
||||
}
|
||||
#if !defined(BOOT)
|
||||
else if (canvas) {
|
||||
lv_canvas_draw_line(canvas, p, 2, &line_dsc);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void BitmapBuffer::drawVerticalLine(coord_t x, coord_t y, coord_t h,
|
||||
uint8_t pat, LcdFlags flags,
|
||||
uint8_t opacity)
|
||||
{
|
||||
if (opacity == OPACITY_MAX) return;
|
||||
|
||||
coord_t w = 1;
|
||||
if (!applyClippingRect(x, y, w, h)) return;
|
||||
|
||||
if (draw_ctx) {
|
||||
x += draw_ctx->buf_area->x1;
|
||||
y += draw_ctx->buf_area->y1;
|
||||
}
|
||||
|
||||
lv_draw_line_dsc_t line_dsc;
|
||||
lv_draw_line_dsc_init(&line_dsc);
|
||||
|
||||
line_dsc.width = 1;
|
||||
line_dsc.opa = ((OPACITY_MAX - opacity) * LV_OPA_COVER) / OPACITY_MAX;
|
||||
line_dsc.color = makeLvColor(flags);
|
||||
|
||||
if (pat == DOTTED) {
|
||||
line_dsc.dash_gap = 1;
|
||||
line_dsc.dash_width = 1;
|
||||
}
|
||||
|
||||
lv_point_t p[2];
|
||||
p[0].x = x;
|
||||
p[0].y = y;
|
||||
p[1].x = x;
|
||||
p[1].y = y + h;
|
||||
|
||||
if (draw_ctx) {
|
||||
lv_draw_line(draw_ctx, &line_dsc, &p[0], &p[1]);
|
||||
}
|
||||
#if !defined(BOOT)
|
||||
else if (canvas) {
|
||||
lv_canvas_draw_line(canvas, p, 2, &line_dsc);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void BitmapBuffer::drawRect(coord_t x, coord_t y, coord_t w, coord_t h,
|
||||
uint8_t thickness, uint8_t pat, LcdFlags flags,
|
||||
uint8_t opacity)
|
||||
{
|
||||
drawFilledRect(x, y, thickness, h, pat, flags, opacity);
|
||||
drawFilledRect(x + w - thickness, y, thickness, h, pat, flags, opacity);
|
||||
drawFilledRect(x, y, w, thickness, pat, flags, opacity);
|
||||
drawFilledRect(x, y + h - thickness, w, thickness, pat, flags, opacity);
|
||||
}
|
||||
|
||||
void BitmapBuffer::drawSolidRect(coord_t x, coord_t y, coord_t w, coord_t h,
|
||||
uint8_t thickness, LcdFlags flags)
|
||||
{
|
||||
drawRect(x, y, w, h, thickness, SOLID, flags, 0);
|
||||
}
|
||||
|
||||
void BitmapBuffer::drawSolidFilledRect(coord_t x, coord_t y, coord_t w,
|
||||
coord_t h, LcdFlags flags)
|
||||
{
|
||||
drawFilledRect(x, y, w, h, SOLID, flags, 0);
|
||||
}
|
||||
|
||||
void BitmapBuffer::drawFilledRect(coord_t x, coord_t y, coord_t w, coord_t h,
|
||||
uint8_t pat, LcdFlags flags, uint8_t opacity)
|
||||
{
|
||||
if (opacity == OPACITY_MAX) return;
|
||||
|
||||
if (!applyClippingRect(x, y, w, h)) return;
|
||||
|
||||
if (SOLID != pat) {
|
||||
// If we have a pattern, draw line by line
|
||||
for (coord_t i = y; i < y + h; i++) {
|
||||
drawHorizontalLineAbs(x, i, w, pat, flags, opacity);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
lv_draw_rect_dsc_t rect_dsc;
|
||||
lv_draw_rect_dsc_init(&rect_dsc);
|
||||
rect_dsc.bg_opa = ((OPACITY_MAX - opacity) * LV_OPA_COVER) / OPACITY_MAX;
|
||||
rect_dsc.bg_color = makeLvColor(flags);
|
||||
|
||||
if (draw_ctx) {
|
||||
x += draw_ctx->buf_area->x1;
|
||||
y += draw_ctx->buf_area->y1;
|
||||
}
|
||||
|
||||
lv_area_t coords = {
|
||||
(lv_coord_t)x,
|
||||
(lv_coord_t)y,
|
||||
(lv_coord_t)(x + w - 1),
|
||||
(lv_coord_t)(y + h - 1),
|
||||
};
|
||||
|
||||
if (draw_ctx) {
|
||||
lv_draw_rect(draw_ctx, &rect_dsc, &coords);
|
||||
}
|
||||
#if !defined(BOOT)
|
||||
else if (canvas) {
|
||||
lv_canvas_draw_rect(canvas, coords.x1, coords.y1, coords.x2 - coords.x1 + 1,
|
||||
coords.y2 - coords.y1 + 1, &rect_dsc);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
coord_t BitmapBuffer::drawSizedText(coord_t x, coord_t y, const char *s,
|
||||
uint8_t len, LcdFlags flags)
|
||||
{
|
||||
if (!s) return x;
|
||||
|
||||
// LVGL does not handle non-null terminated strings
|
||||
static char buffer[256];
|
||||
strncpy(buffer, s, len);
|
||||
buffer[len] = '\0';
|
||||
|
||||
coord_t pos = x;
|
||||
const coord_t orig_pos = pos;
|
||||
|
||||
lv_draw_label_dsc_t label_draw_dsc;
|
||||
lv_draw_label_dsc_init(&label_draw_dsc);
|
||||
|
||||
const lv_font_t *font = getFont(flags);
|
||||
label_draw_dsc.font = font;
|
||||
|
||||
auto color = COLOR_VAL(flags);
|
||||
label_draw_dsc.color =
|
||||
lv_color_make(GET_RED(color), GET_GREEN(color), GET_BLUE(color));
|
||||
|
||||
if (draw_ctx) {
|
||||
x += draw_ctx->buf_area->x1;
|
||||
y += draw_ctx->buf_area->y1;
|
||||
}
|
||||
|
||||
lv_point_t p;
|
||||
lv_txt_get_size(&p, buffer, font, label_draw_dsc.letter_space,
|
||||
label_draw_dsc.line_space, LV_COORD_MAX, 0);
|
||||
|
||||
lv_coord_t lv_x = (lv_coord_t)x;
|
||||
lv_coord_t lv_y = (lv_coord_t)y;
|
||||
|
||||
lv_area_t coords = {
|
||||
lv_x,
|
||||
lv_y,
|
||||
lv_x,
|
||||
lv_y,
|
||||
};
|
||||
|
||||
coords.x2 += p.x - 1;
|
||||
coords.y2 += p.y - 1;
|
||||
|
||||
if (flags & RIGHT) {
|
||||
label_draw_dsc.align = LV_TEXT_ALIGN_RIGHT;
|
||||
coords.x1 -= p.x;
|
||||
coords.x2 -= p.x;
|
||||
} else if (flags & CENTERED) {
|
||||
label_draw_dsc.align = LV_TEXT_ALIGN_CENTER;
|
||||
coords.x1 -= p.x / 2;
|
||||
coords.x2 -= p.x / 2;
|
||||
}
|
||||
|
||||
if (draw_ctx) {
|
||||
lv_draw_label(draw_ctx, &label_draw_dsc, &coords, buffer, nullptr);
|
||||
}
|
||||
#if !defined(BOOT)
|
||||
else if (canvas) {
|
||||
lv_canvas_draw_text(canvas, coords.x1, coords.y1, coords.x2 - coords.x1 + 1,
|
||||
&label_draw_dsc, buffer);
|
||||
}
|
||||
#endif
|
||||
|
||||
pos += p.x;
|
||||
|
||||
return ((flags & RIGHT) ? orig_pos : pos);
|
||||
}
|
||||
|
||||
// Resize and convert to LVGL image data format with alpha blending to
|
||||
// background color
|
||||
void BitmapBuffer::resizeToLVGL(coord_t w, coord_t h)
|
||||
{
|
||||
// Scale values from ARGB4444 to RGB565 with alpha
|
||||
static uint8_t rbcnv[16] = {0, 2, 4, 6, 8, 10, 12, 14,
|
||||
17, 19, 21, 23, 25, 27, 29, 31};
|
||||
static uint8_t gcnv[16] = {0, 4, 8, 13, 17, 21, 25, 29,
|
||||
34, 38, 42, 46, 50, 55, 59, 63};
|
||||
static uint8_t alpha[16] = {0, 17, 34, 51, 68, 85, 102, 119,
|
||||
136, 153, 170, 187, 204, 221, 238, 255};
|
||||
|
||||
float vscale = float(h) / (float)height();
|
||||
float hscale = float(w) / (float)width();
|
||||
float scale;
|
||||
coord_t scaledw, scaledh;
|
||||
if (vscale < hscale) {
|
||||
scale = vscale;
|
||||
scaledw = width() * scale;
|
||||
scaledh = h;
|
||||
} else {
|
||||
scale = hscale;
|
||||
scaledw = w;
|
||||
scaledh = height() * scale;
|
||||
}
|
||||
|
||||
uint8_t *ndata =
|
||||
(uint8_t *)malloc(align32(scaledw * scaledh * 3));
|
||||
|
||||
if (ndata) {
|
||||
uint8_t *dst = ndata;
|
||||
for (int i = 0; i < scaledh; i += 1) {
|
||||
pixel_t *src = &data[(coord_t)(i / scale) * width()];
|
||||
for (int j = 0; j < scaledw; j += 1) {
|
||||
ARGB_SPLIT(src[(coord_t)(j / scale)], a, r, g, b);
|
||||
auto c = RGB_JOIN(rbcnv[r], gcnv[g], rbcnv[b]);
|
||||
*dst++ = c & 0xFF;
|
||||
*dst++ = c >> 8;
|
||||
*dst++ = alpha[a];
|
||||
}
|
||||
}
|
||||
|
||||
delete data;
|
||||
data = (pixel_t*)ndata;
|
||||
_width = scaledw;
|
||||
_height = scaledh;
|
||||
data_end = data + ((scaledw * scaledh * 3 + 1) / 2);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
/*
|
||||
* Copyright (C) EdgeTX
|
||||
*
|
||||
* Based on code named
|
||||
* libopenui - https://github.com/opentx/libopenui
|
||||
*
|
||||
* 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 "libopenui_defines.h"
|
||||
|
||||
struct MaskBitmap;
|
||||
class TelemetryItem;
|
||||
|
||||
constexpr uint8_t SOLID = 0xFF;
|
||||
constexpr uint8_t DOTTED = 0x55;
|
||||
constexpr uint8_t STASHED = 0x33;
|
||||
|
||||
#define MOVE_PIXEL_RIGHT(p, count) p += count
|
||||
|
||||
#define MOVE_TO_NEXT_RIGHT_PIXEL(p) MOVE_PIXEL_RIGHT(p, 1)
|
||||
|
||||
#define USE_STB
|
||||
|
||||
enum BitmapFormats { BMP_INVALID = -1, BMP_RGB565 = 0, BMP_ARGB4444 };
|
||||
|
||||
class BitmapBuffer
|
||||
{
|
||||
public:
|
||||
BitmapBuffer(uint8_t format, uint16_t width, uint16_t height);
|
||||
BitmapBuffer(uint8_t format, uint16_t width, uint16_t height, uint16_t* data);
|
||||
|
||||
~BitmapBuffer();
|
||||
|
||||
void setDrawCtx(lv_draw_ctx_t* ctx) { draw_ctx = ctx; }
|
||||
|
||||
void setData(uint16_t* d);
|
||||
|
||||
void clear(LcdFlags flags = 0);
|
||||
|
||||
inline const pixel_t* getPixelPtrAbs(coord_t x, coord_t y) const
|
||||
{
|
||||
return &data[y * _width + x];
|
||||
}
|
||||
|
||||
inline pixel_t* getPixelPtrAbs(coord_t x, coord_t y)
|
||||
{
|
||||
return &data[y * _width + x];
|
||||
}
|
||||
|
||||
inline void drawPixel(coord_t x, coord_t y, pixel_t value)
|
||||
{
|
||||
coord_t w = 1, h = 1;
|
||||
if (!applyClippingRect(x, y, w, h)) return;
|
||||
|
||||
drawPixelAbs(x, y, value);
|
||||
}
|
||||
|
||||
void drawAlphaPixel(pixel_t* p, uint8_t opacity, uint16_t color);
|
||||
|
||||
inline void drawAlphaPixel(coord_t x, coord_t y, uint8_t opacity,
|
||||
pixel_t value)
|
||||
{
|
||||
coord_t w = 1, h = 1;
|
||||
if (!applyClippingRect(x, y, w, h)) return;
|
||||
|
||||
pixel_t* p = getPixelPtrAbs(x, y);
|
||||
drawAlphaPixel(p, opacity, value);
|
||||
}
|
||||
|
||||
void drawHorizontalLine(coord_t x, coord_t y, coord_t w, uint8_t pat = SOLID,
|
||||
LcdFlags flags = 0, uint8_t opacity = 0);
|
||||
|
||||
void drawVerticalLine(coord_t x, coord_t y, coord_t h, uint8_t pat = SOLID,
|
||||
LcdFlags flags = 0, uint8_t opacity = 0);
|
||||
|
||||
void drawLine(coord_t x1, coord_t y1, coord_t x2, coord_t y2, uint8_t pat,
|
||||
LcdFlags att);
|
||||
|
||||
void drawRect(coord_t x, coord_t y, coord_t w, coord_t h,
|
||||
uint8_t thickness = 1, uint8_t pat = SOLID, LcdFlags flags = 0,
|
||||
uint8_t opacity = 0);
|
||||
|
||||
void drawSolidRect(coord_t x, coord_t y, coord_t w, coord_t h,
|
||||
uint8_t thickness = 1, LcdFlags flags = 0);
|
||||
|
||||
void drawSolidFilledRect(coord_t x, coord_t y, coord_t w, coord_t h,
|
||||
LcdFlags flags = 0);
|
||||
|
||||
void drawFilledRect(coord_t x, coord_t y, coord_t w, coord_t h,
|
||||
uint8_t pat = SOLID, LcdFlags flags = 0,
|
||||
uint8_t opacity = 0);
|
||||
|
||||
static BitmapBuffer* loadBitmap(const char* filename,
|
||||
BitmapFormats fmt = BMP_INVALID);
|
||||
|
||||
void resizeToLVGL(coord_t w, coord_t h);
|
||||
|
||||
coord_t drawSizedText(coord_t x, coord_t y, const char* s, uint8_t len,
|
||||
LcdFlags flags = 0);
|
||||
|
||||
coord_t drawText(coord_t x, coord_t y, const char* s, LcdFlags flags = 0)
|
||||
{
|
||||
if (!s) return x;
|
||||
return drawSizedText(x, y, s, strlen(s), flags);
|
||||
}
|
||||
|
||||
void drawBitmap(coord_t x, coord_t y, const BitmapBuffer* bmp,
|
||||
coord_t srcx = 0, coord_t srcy = 0, coord_t srcw = 0,
|
||||
coord_t srch = 0, float scale = 0);
|
||||
|
||||
void clearClippingRect();
|
||||
|
||||
inline void reset()
|
||||
{
|
||||
clearClippingRect();
|
||||
}
|
||||
|
||||
inline uint16_t width() const { return _width; }
|
||||
inline uint16_t height() const { return _height; }
|
||||
|
||||
inline pixel_t* getData() const { return data; }
|
||||
|
||||
uint32_t getDataSize() const { return _width * _height * sizeof(pixel_t); }
|
||||
|
||||
// Lua API functions
|
||||
void setClippingRect(coord_t xmin, coord_t xmax, coord_t ymin, coord_t ymax);
|
||||
void getClippingRect(coord_t& xmin, coord_t& xmax, coord_t& ymin,
|
||||
coord_t& ymax);
|
||||
|
||||
MaskBitmap* to8bitMask(size_t* size) const;
|
||||
|
||||
void invertRect(coord_t x, coord_t y, coord_t w, coord_t h,
|
||||
LcdFlags flags = 0);
|
||||
|
||||
void drawFilledTriangle(coord_t x1, coord_t y1, coord_t x2, coord_t y2,
|
||||
coord_t x3, coord_t y3, LcdFlags flags = 0,
|
||||
uint8_t opacity = 0);
|
||||
void drawCircle(coord_t x, coord_t y, coord_t radius, LcdFlags flags = 0, coord_t thickness = 1);
|
||||
void drawFilledCircle(coord_t x, coord_t y, coord_t radius,
|
||||
LcdFlags flags = 0);
|
||||
void drawAnnulusSector(coord_t x, coord_t y, coord_t internalRadius,
|
||||
coord_t externalRadius, int startAngle, int endAngle,
|
||||
LcdFlags flags = 0);
|
||||
|
||||
void drawBitmapPatternPie(coord_t x0, coord_t y0, const MaskBitmap* img,
|
||||
LcdFlags flags, int startAngle, int endAngle);
|
||||
void drawBitmapPattern(coord_t x, coord_t y, const MaskBitmap* bmp,
|
||||
LcdFlags flags, coord_t offset = 0, coord_t width = 0);
|
||||
void drawScaledBitmap(const BitmapBuffer* bitmap, coord_t x, coord_t y,
|
||||
coord_t w, coord_t h);
|
||||
coord_t drawNumber(coord_t x, coord_t y, int32_t val, LcdFlags flags = 0,
|
||||
uint8_t len = 0, const char* prefix = nullptr,
|
||||
const char* suffix = nullptr);
|
||||
void drawTextLines(coord_t left, coord_t top, coord_t width, coord_t height,
|
||||
const char* str, LcdFlags flags);
|
||||
void drawTimer(coord_t x, coord_t y, int32_t tme, LcdFlags flags = 0);
|
||||
void drawSource(coord_t x, coord_t y, mixsrc_t idx, LcdFlags flags = 0);
|
||||
coord_t drawSwitch(coord_t x, coord_t y, int32_t idx, LcdFlags flags = 0);
|
||||
void drawSensorCustomValue(coord_t x, coord_t y, uint8_t sensor,
|
||||
int32_t value, LcdFlags flags = 0);
|
||||
|
||||
protected:
|
||||
void drawValueWithUnit(coord_t x, coord_t y, int val, uint8_t unit,
|
||||
LcdFlags flags = 0);
|
||||
|
||||
coord_t drawGPSCoord(coord_t x, coord_t y, int32_t value,
|
||||
const char* direction, LcdFlags flags,
|
||||
bool seconds = true);
|
||||
void drawGPSPosition(coord_t x, coord_t y, int32_t longitude,
|
||||
int32_t latitude, LcdFlags flags = 0);
|
||||
|
||||
void drawDate(coord_t x, coord_t y, TelemetryItem& telemetryItem,
|
||||
LcdFlags att);
|
||||
|
||||
bool applyClippingRect(coord_t& x, coord_t& y, coord_t& w,
|
||||
coord_t& h) const;
|
||||
|
||||
inline void drawPixel(pixel_t* p, pixel_t value)
|
||||
{
|
||||
if (data && (data <= p && p < data_end)) {
|
||||
*p = value;
|
||||
}
|
||||
#if defined(DEBUG)
|
||||
else if (!leakReported) {
|
||||
leakReported = true;
|
||||
TRACE(
|
||||
"BitmapBuffer(%p).drawPixel(): buffer overrun, data: %p, written at: "
|
||||
"%p",
|
||||
this, data, p);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void drawPixelAbs(coord_t x, coord_t y, pixel_t value)
|
||||
{
|
||||
pixel_t* p = getPixelPtrAbs(x, y);
|
||||
drawPixel(p, value);
|
||||
}
|
||||
|
||||
void drawHorizontalLineAbs(coord_t x, coord_t y, coord_t w, uint8_t pat,
|
||||
LcdFlags flags, uint8_t opacity);
|
||||
|
||||
bool liangBarskyClipper(coord_t& x1, coord_t& y1, coord_t& x2, coord_t& y2);
|
||||
|
||||
protected:
|
||||
uint8_t format;
|
||||
coord_t _width;
|
||||
coord_t _height;
|
||||
coord_t xmin = 0;
|
||||
coord_t xmax;
|
||||
coord_t ymin = 0;
|
||||
coord_t ymax;
|
||||
pixel_t* data;
|
||||
pixel_t* data_end;
|
||||
|
||||
private:
|
||||
bool dataAllocated = false;
|
||||
#if defined(DEBUG)
|
||||
bool leakReported;
|
||||
#endif
|
||||
lv_obj_t* canvas = nullptr;
|
||||
lv_draw_ctx_t* draw_ctx = nullptr;
|
||||
};
|
||||
|
||||
// Back buffer to draw
|
||||
extern BitmapBuffer* lcd;
|
||||
|
||||
// Buffer currently on display
|
||||
extern BitmapBuffer* lcdFront;
|
||||
@@ -0,0 +1,792 @@
|
||||
/*
|
||||
* Copyright (C) EdgeTX
|
||||
*
|
||||
* Based on code named
|
||||
* libopenui - https://github.com/opentx/libopenui
|
||||
*
|
||||
* 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 <math.h>
|
||||
|
||||
#include "bitmapbuffer.h"
|
||||
#include "bitmaps.h"
|
||||
#include "board.h"
|
||||
#include "dma2d.h"
|
||||
#include "opentx.h"
|
||||
#include "strhelpers.h"
|
||||
#include "telemetry/telemetry_sensors.h"
|
||||
#include "translations.h"
|
||||
|
||||
// Drawing functions used by Lua API
|
||||
|
||||
void BitmapBuffer::invertRect(coord_t x, coord_t y, coord_t w, coord_t h,
|
||||
LcdFlags flags)
|
||||
{
|
||||
if (!applyClippingRect(x, y, w, h)) return;
|
||||
|
||||
// No 'opacity' here, only 'color'
|
||||
pixel_t color = COLOR_VAL(flags);
|
||||
RGB_SPLIT(color, red, green, blue);
|
||||
pixel_t bg_color = COLOR_VAL(COLOR_THEME_PRIMARY2);
|
||||
RGB_SPLIT(bg_color, bgRed, bgGreen, bgBlue);
|
||||
|
||||
DMAWait();
|
||||
#if __CORTEX_M >= 0x07
|
||||
SCB_CleanInvalidateDCache();
|
||||
#endif
|
||||
|
||||
for (int i = y; i < y + h; i++) {
|
||||
pixel_t *p = getPixelPtrAbs(x, i);
|
||||
for (int j = 0; j < w; j++) {
|
||||
// TODO ASSERT_IN_DISPLAY(p);
|
||||
RGB_SPLIT(*p, pRed, pGreen, pBlue);
|
||||
drawPixel(p, RGB_JOIN(bgRed + red - pRed, bgGreen + green - pGreen,
|
||||
bgBlue + blue - pBlue));
|
||||
MOVE_TO_NEXT_RIGHT_PIXEL(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BitmapBuffer::drawValueWithUnit(coord_t x, coord_t y, int val,
|
||||
uint8_t unit, LcdFlags flags)
|
||||
{
|
||||
if ((flags & NO_UNIT) || unit == UNIT_RAW) {
|
||||
drawNumber(x, y, val, flags & (~NO_UNIT));
|
||||
} else {
|
||||
drawNumber(x, y, val, flags & (~NO_UNIT), 0, nullptr, STR_VTELEMUNIT[unit]);
|
||||
}
|
||||
}
|
||||
|
||||
void BitmapBuffer::drawTextLines(coord_t left, coord_t top, coord_t width,
|
||||
coord_t height, const char *str,
|
||||
LcdFlags flags)
|
||||
{
|
||||
coord_t x = left;
|
||||
coord_t y = top;
|
||||
coord_t line = getFontHeightCondensed(flags & 0xFFFF);
|
||||
coord_t space = getTextWidth(" ", 1, flags);
|
||||
coord_t word;
|
||||
const char *nxt = str;
|
||||
flags &= ~(VCENTERED | CENTERED | RIGHT);
|
||||
|
||||
while (true) {
|
||||
for (bool done = false; !done; nxt++) {
|
||||
switch (nxt[0]) {
|
||||
case '-':
|
||||
case '/':
|
||||
case ':':
|
||||
case '(':
|
||||
case '{':
|
||||
case '[':
|
||||
nxt++;
|
||||
case ' ':
|
||||
case '\n':
|
||||
case '\0':
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
nxt--;
|
||||
word = getTextWidth(str, nxt - str, flags);
|
||||
if (x + word > left + width && x > left) {
|
||||
x = left;
|
||||
y += line;
|
||||
}
|
||||
if (y + line > top + height) return;
|
||||
drawSizedText(x, y, str, nxt - str, flags);
|
||||
x += word;
|
||||
switch (nxt[0]) {
|
||||
case '\0':
|
||||
return;
|
||||
case '\n':
|
||||
x = left;
|
||||
y += line;
|
||||
nxt++;
|
||||
break;
|
||||
case ' ':
|
||||
x += space;
|
||||
nxt++;
|
||||
}
|
||||
str = nxt;
|
||||
}
|
||||
}
|
||||
|
||||
void BitmapBuffer::drawSource(coord_t x, coord_t y, mixsrc_t idx,
|
||||
LcdFlags flags)
|
||||
{
|
||||
char s[16];
|
||||
getSourceString(s, idx);
|
||||
drawText(x, y, s, flags);
|
||||
}
|
||||
|
||||
coord_t BitmapBuffer::drawSwitch(coord_t x, coord_t y, int32_t idx,
|
||||
LcdFlags flags)
|
||||
{
|
||||
return drawText(x, y, getSwitchPositionName(idx), flags);
|
||||
}
|
||||
|
||||
void BitmapBuffer::drawTimer(coord_t x, coord_t y, int32_t tme, LcdFlags flags)
|
||||
{
|
||||
char str[LEN_TIMER_STRING];
|
||||
TimerOptions timerOptions;
|
||||
timerOptions.options = (flags & TIMEHOUR) != 0 ? SHOW_TIME : SHOW_TIMER;
|
||||
getTimerString(str, tme, timerOptions);
|
||||
drawText(x, y, str, flags);
|
||||
}
|
||||
|
||||
coord_t BitmapBuffer::drawGPSCoord(coord_t x, coord_t y, int32_t value,
|
||||
const char *direction, LcdFlags flags,
|
||||
bool seconds)
|
||||
{
|
||||
std::string s = getGPSCoord(value, direction, seconds);
|
||||
return drawText(x, y, s.c_str(), flags);
|
||||
}
|
||||
|
||||
void BitmapBuffer::drawGPSPosition(coord_t x, coord_t y, int32_t longitude,
|
||||
int32_t latitude, LcdFlags flags)
|
||||
{
|
||||
if (flags & PREC1) {
|
||||
drawGPSCoord(x, y, latitude, "NS", flags, true);
|
||||
drawGPSCoord(x, y + EdgeTxStyles::PAGE_LINE_HEIGHT, longitude, "EW", flags, true);
|
||||
} else {
|
||||
if (flags & RIGHT) {
|
||||
x = drawGPSCoord(x, y, longitude, "EW", flags, true);
|
||||
drawGPSCoord(x - 5, y, latitude, "NS", flags, true);
|
||||
} else {
|
||||
x = drawGPSCoord(x, y, latitude, "NS", flags, true);
|
||||
drawGPSCoord(x + 5, y, longitude, "EW", flags, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BitmapBuffer::drawDate(coord_t x, coord_t y, TelemetryItem &telemetryItem,
|
||||
LcdFlags att)
|
||||
{
|
||||
bool doTwoLines = false;
|
||||
coord_t ox = x;
|
||||
|
||||
if (att & FONT(XL)) {
|
||||
att &= ~FONT_MASK;
|
||||
doTwoLines = true;
|
||||
}
|
||||
|
||||
std::string s = getTelemDate(telemetryItem);
|
||||
x = drawText(x, y, s.c_str(), att);
|
||||
|
||||
if (doTwoLines) {
|
||||
y += EdgeTxStyles::PAGE_LINE_HEIGHT;
|
||||
x = ox;
|
||||
} else {
|
||||
x += 11;
|
||||
}
|
||||
|
||||
s = getTelemTime(telemetryItem);
|
||||
drawText(x, y, s.c_str(), att);
|
||||
}
|
||||
|
||||
void BitmapBuffer::drawSensorCustomValue(coord_t x, coord_t y, uint8_t sensor,
|
||||
int32_t value, LcdFlags flags)
|
||||
{
|
||||
if (sensor >= MAX_TELEMETRY_SENSORS) {
|
||||
// Lua luaLcdDrawChannel() can call us with a bad value
|
||||
return;
|
||||
}
|
||||
|
||||
TelemetryItem &telemetryItem = telemetryItems[sensor];
|
||||
TelemetrySensor &telemetrySensor = g_model.telemetrySensors[sensor];
|
||||
|
||||
if (telemetrySensor.unit == UNIT_DATETIME) {
|
||||
drawDate(x, y, telemetryItem, flags);
|
||||
} else if (telemetrySensor.unit == UNIT_GPS) {
|
||||
drawGPSPosition(x, y, telemetryItem.gps.longitude,
|
||||
telemetryItem.gps.latitude, flags);
|
||||
} else if (telemetrySensor.unit == UNIT_TEXT) {
|
||||
drawSizedText(x, flags & FONT(XL) ? y + 1 : y, telemetryItem.text,
|
||||
sizeof(telemetryItem.text), flags & ~FONT(XL));
|
||||
} else {
|
||||
if (telemetrySensor.prec > 0) {
|
||||
flags |= (telemetrySensor.prec == 1 ? PREC1 : PREC2);
|
||||
}
|
||||
drawValueWithUnit(
|
||||
x, y, value,
|
||||
telemetrySensor.unit == UNIT_CELLS ? UNIT_VOLTS : telemetrySensor.unit,
|
||||
flags);
|
||||
}
|
||||
}
|
||||
|
||||
void BitmapBuffer::drawScaledBitmap(const BitmapBuffer *bitmap, coord_t x, coord_t y,
|
||||
coord_t w, coord_t h)
|
||||
{
|
||||
if (bitmap) {
|
||||
float vscale = float(h) / bitmap->height();
|
||||
float hscale = float(w) / bitmap->width();
|
||||
float scale = vscale < hscale ? vscale : hscale;
|
||||
|
||||
int xshift = (w - (bitmap->width() * scale)) / 2;
|
||||
int yshift = (h - (bitmap->height() * scale)) / 2;
|
||||
// TRACE(" BitmapBuffer::drawScaledBitmap()---- scale %f", scale);
|
||||
drawBitmap(x + xshift, y + yshift, bitmap, 0, 0, 0, 0, scale);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Liang-barsky clipping algo
|
||||
//
|
||||
static float lb_max(float arr[], int n)
|
||||
{
|
||||
float m = 0;
|
||||
for (int i = 0; i < n; ++i)
|
||||
if (m < arr[i]) m = arr[i];
|
||||
return m;
|
||||
}
|
||||
|
||||
// this function gives the minimum
|
||||
static float lb_min(float arr[], int n)
|
||||
{
|
||||
float m = 1;
|
||||
for (int i = 0; i < n; ++i)
|
||||
if (m > arr[i]) m = arr[i];
|
||||
return m;
|
||||
}
|
||||
|
||||
bool BitmapBuffer::liangBarskyClipper(coord_t &x1, coord_t &y1, coord_t &x2,
|
||||
coord_t &y2)
|
||||
{
|
||||
// defining variables
|
||||
float p1 = -(x2 - x1);
|
||||
float p2 = -p1;
|
||||
float p3 = -(y2 - y1);
|
||||
float p4 = -p3;
|
||||
|
||||
float q1 = x1 - xmin;
|
||||
float q2 = xmax - x1;
|
||||
float q3 = y1 - ymin;
|
||||
float q4 = ymax - y1;
|
||||
|
||||
float posarr[5], negarr[5];
|
||||
int posind = 1, negind = 1;
|
||||
posarr[0] = 1;
|
||||
negarr[0] = 0;
|
||||
|
||||
if ((p1 == 0 && q1 < 0) || (p2 == 0 && q2 < 0) || (p3 == 0 && q3 < 0) ||
|
||||
(p4 == 0 && q4 < 0)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (p1 != 0) {
|
||||
float r1 = q1 / p1;
|
||||
float r2 = q2 / p2;
|
||||
if (p1 < 0) {
|
||||
negarr[negind++] = r1; // for negative p1, add it to negative array
|
||||
posarr[posind++] = r2; // and add p2 to positive array
|
||||
} else {
|
||||
negarr[negind++] = r2;
|
||||
posarr[posind++] = r1;
|
||||
}
|
||||
}
|
||||
if (p3 != 0) {
|
||||
float r3 = q3 / p3;
|
||||
float r4 = q4 / p4;
|
||||
if (p3 < 0) {
|
||||
negarr[negind++] = r3;
|
||||
posarr[posind++] = r4;
|
||||
} else {
|
||||
negarr[negind++] = r4;
|
||||
posarr[posind++] = r3;
|
||||
}
|
||||
}
|
||||
|
||||
float xn1, yn1, xn2, yn2;
|
||||
float rn1, rn2;
|
||||
rn1 = lb_max(negarr, negind); // maximum of negative array
|
||||
rn2 = lb_min(posarr, posind); // minimum of positive array
|
||||
|
||||
if (rn1 > rn2) { // reject
|
||||
return false;
|
||||
}
|
||||
|
||||
xn1 = x1 + p2 * rn1;
|
||||
yn1 = y1 + p4 * rn1; // computing new points
|
||||
|
||||
xn2 = x1 + p2 * rn2;
|
||||
yn2 = y1 + p4 * rn2;
|
||||
|
||||
x1 = coord_t(xn1);
|
||||
y1 = coord_t(yn1);
|
||||
x2 = coord_t(xn2);
|
||||
y2 = coord_t(yn2);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void BitmapBuffer::drawLine(coord_t x1, coord_t y1, coord_t x2, coord_t y2,
|
||||
uint8_t pat, LcdFlags flags)
|
||||
{
|
||||
if (!liangBarskyClipper(x1, y1, x2, y2)) return;
|
||||
|
||||
// TODO; Replace with LVGL line draw - currently does not support dotted line drawing
|
||||
// except for vertical and horizontal.
|
||||
|
||||
// No 'opacity' here, only 'color'
|
||||
pixel_t color = COLOR_VAL(flags);
|
||||
|
||||
int dx = x2 - x1; /* the horizontal distance of the line */
|
||||
int dy = y2 - y1; /* the vertical distance of the line */
|
||||
int dxabs = abs(dx);
|
||||
int dyabs = abs(dy);
|
||||
int sdx = sgn(dx);
|
||||
int sdy = sgn(dy);
|
||||
int x = dyabs >> 1;
|
||||
int y = dxabs >> 1;
|
||||
int px = x1;
|
||||
int py = y1;
|
||||
|
||||
DMAWait();
|
||||
#if __CORTEX_M >= 0x07
|
||||
SCB_CleanInvalidateDCache();
|
||||
#endif
|
||||
|
||||
if (dxabs >= dyabs) {
|
||||
/* the line is more horizontal than vertical */
|
||||
for (int i = 0; i <= dxabs; i++) {
|
||||
if ((1 << (px % 8)) & pat) {
|
||||
drawPixelAbs(px, py, color);
|
||||
}
|
||||
y += dyabs;
|
||||
if (y >= dxabs) {
|
||||
y -= dxabs;
|
||||
py += sdy;
|
||||
}
|
||||
px += sdx;
|
||||
}
|
||||
} else {
|
||||
/* the line is more vertical than horizontal */
|
||||
for (int i = 0; i <= dyabs; i++) {
|
||||
if ((1 << (py % 8)) & pat) {
|
||||
drawPixelAbs(px, py, color);
|
||||
}
|
||||
x += dxabs;
|
||||
if (x >= dyabs) {
|
||||
x -= dyabs;
|
||||
px += sdx;
|
||||
}
|
||||
py += sdy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Notice: BitmapBuffer::drawFilledTriangle use the implementation
|
||||
made for the Adafruit GFX library, which is licensed under BSD
|
||||
license.
|
||||
|
||||
Software License Agreement (BSD License)
|
||||
|
||||
Copyright (c) 2012 Adafruit Industries. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
- Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
- Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
void BitmapBuffer::drawFilledTriangle(coord_t x0, coord_t y0, coord_t x1,
|
||||
coord_t y1, coord_t x2, coord_t y2,
|
||||
LcdFlags flags, uint8_t opacity)
|
||||
{
|
||||
coord_t a, b;
|
||||
|
||||
#define SWAP(a, b) \
|
||||
{ \
|
||||
coord_t tmp = b; \
|
||||
b = a; \
|
||||
a = tmp; \
|
||||
}
|
||||
|
||||
if (y0 > y1) {
|
||||
SWAP(y0, y1);
|
||||
SWAP(x0, x1);
|
||||
}
|
||||
if (y1 > y2) {
|
||||
SWAP(y2, y1);
|
||||
SWAP(x2, x1);
|
||||
}
|
||||
if (y0 > y1) {
|
||||
SWAP(y0, y1);
|
||||
SWAP(x0, x1);
|
||||
}
|
||||
|
||||
if (y0 == y2) { // Handle awkward all-on-same-line case as its own thing
|
||||
a = b = x0;
|
||||
if (x1 < a)
|
||||
a = x1;
|
||||
else if (x1 > b)
|
||||
b = x1;
|
||||
if (x2 < a)
|
||||
a = x2;
|
||||
else if (x2 > b)
|
||||
b = x2;
|
||||
drawHorizontalLine(a, y0, b - a + 1, SOLID, flags, opacity);
|
||||
return;
|
||||
}
|
||||
|
||||
coord_t dx01 = x1 - x0, dy01 = y1 - y0, dx02 = x2 - x0, dy02 = y2 - y0,
|
||||
dx12 = x2 - x1, dy12 = y2 - y1;
|
||||
coord_t sa = 0, sb = 0;
|
||||
coord_t last = (y1 == y2) ? y1 : y1 - 1;
|
||||
coord_t y;
|
||||
|
||||
for (y = y0; y <= last; y++) {
|
||||
a = x0 + sa / dy01;
|
||||
b = x0 + sb / dy02;
|
||||
sa += dx01;
|
||||
sb += dx02;
|
||||
if (a > b) SWAP(a, b);
|
||||
drawHorizontalLine(a, y, b - a + 1, SOLID, flags, opacity);
|
||||
}
|
||||
|
||||
sa = dx12 * (y - y1);
|
||||
sb = dx02 * (y - y0);
|
||||
|
||||
for (; y <= y2; y++) {
|
||||
a = x1 + sa / dy12;
|
||||
b = x0 + sb / dy02;
|
||||
sa += dx12;
|
||||
sb += dx02;
|
||||
if (a > b) SWAP(a, b);
|
||||
drawHorizontalLine(a, y, b - a + 1, SOLID, flags, opacity);
|
||||
}
|
||||
}
|
||||
|
||||
void BitmapBuffer::drawFilledCircle(coord_t x, coord_t y, coord_t radius,
|
||||
LcdFlags flags)
|
||||
{
|
||||
lv_draw_rect_dsc_t rect_dsc;
|
||||
lv_draw_rect_dsc_init(&rect_dsc);
|
||||
rect_dsc.bg_opa = LV_OPA_COVER;
|
||||
rect_dsc.bg_color = makeLvColor(flags);
|
||||
rect_dsc.radius = LV_RADIUS_CIRCLE;
|
||||
|
||||
if (draw_ctx) {
|
||||
x += draw_ctx->buf_area->x1;
|
||||
y += draw_ctx->buf_area->y1;
|
||||
}
|
||||
|
||||
lv_area_t coords = {
|
||||
(lv_coord_t)(x - radius),
|
||||
(lv_coord_t)(y - radius),
|
||||
(lv_coord_t)(x + radius),
|
||||
(lv_coord_t)(y + radius),
|
||||
};
|
||||
|
||||
if (draw_ctx) {
|
||||
lv_draw_rect(draw_ctx, &rect_dsc, &coords);
|
||||
}
|
||||
#if !defined(BOOT)
|
||||
else if (canvas) {
|
||||
lv_canvas_draw_rect(canvas, coords.x1, coords.y1, coords.x2 - coords.x1 + 1,
|
||||
coords.y2 - coords.y1 + 1, &rect_dsc);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void BitmapBuffer::drawCircle(coord_t x, coord_t y, coord_t radius,
|
||||
LcdFlags flags, coord_t thickness)
|
||||
{
|
||||
lv_draw_rect_dsc_t rect_dsc;
|
||||
lv_draw_rect_dsc_init(&rect_dsc);
|
||||
rect_dsc.bg_opa = LV_OPA_TRANSP;
|
||||
rect_dsc.border_opa = LV_OPA_COVER;
|
||||
rect_dsc.border_color = makeLvColor(flags);
|
||||
rect_dsc.border_width = thickness;
|
||||
rect_dsc.radius = LV_RADIUS_CIRCLE;
|
||||
|
||||
if (draw_ctx) {
|
||||
x += draw_ctx->buf_area->x1;
|
||||
y += draw_ctx->buf_area->y1;
|
||||
}
|
||||
|
||||
lv_area_t coords = {
|
||||
(lv_coord_t)(x - radius),
|
||||
(lv_coord_t)(y - radius),
|
||||
(lv_coord_t)(x + radius),
|
||||
(lv_coord_t)(y + radius),
|
||||
};
|
||||
|
||||
if (draw_ctx) {
|
||||
lv_draw_rect(draw_ctx, &rect_dsc, &coords);
|
||||
}
|
||||
#if !defined(BOOT)
|
||||
else if (canvas) {
|
||||
lv_canvas_draw_rect(canvas, coords.x1, coords.y1, coords.x2 - coords.x1 + 1,
|
||||
coords.y2 - coords.y1 + 1, &rect_dsc);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
class Slope
|
||||
{
|
||||
public:
|
||||
explicit Slope(int angle)
|
||||
{
|
||||
if (angle < 0) angle += 360;
|
||||
if (angle > 360) angle %= 360;
|
||||
float radians = float(angle) * (M_PI / 180.0f);
|
||||
if (angle == 0) {
|
||||
left = false;
|
||||
value = 100000;
|
||||
} else if (angle == 360) {
|
||||
left = true;
|
||||
value = 100000;
|
||||
} else if (angle >= 180) {
|
||||
left = true;
|
||||
value = -(cosf(radians) * 100 / sinf(radians));
|
||||
} else {
|
||||
left = false;
|
||||
value = (cosf(radians) * 100 / sinf(radians));
|
||||
}
|
||||
}
|
||||
|
||||
Slope(bool left, int value) : left(left), value(value) {}
|
||||
|
||||
bool isBetween(const Slope &start, const Slope &end) const
|
||||
{
|
||||
if (left) {
|
||||
if (start.left) {
|
||||
if (end.left)
|
||||
return end.value > start.value
|
||||
? (value <= end.value && value >= start.value)
|
||||
: (value <= end.value || value >= start.value);
|
||||
else
|
||||
return value >= start.value;
|
||||
} else {
|
||||
if (end.left)
|
||||
return value <= end.value;
|
||||
else
|
||||
return end.value > start.value;
|
||||
}
|
||||
} else {
|
||||
if (start.left) {
|
||||
if (end.left)
|
||||
return start.value > end.value;
|
||||
else
|
||||
return value >= end.value;
|
||||
} else {
|
||||
if (end.left)
|
||||
return value <= start.value;
|
||||
else
|
||||
return end.value < start.value
|
||||
? (value >= end.value && value <= start.value)
|
||||
: (value <= start.value || value >= end.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Slope &invertVertical()
|
||||
{
|
||||
value = -value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Slope &invertHorizontal()
|
||||
{
|
||||
left = !left;
|
||||
return *this;
|
||||
}
|
||||
|
||||
protected:
|
||||
bool left;
|
||||
int value;
|
||||
};
|
||||
|
||||
void BitmapBuffer::drawBitmapPatternPie(coord_t x, coord_t y,
|
||||
const MaskBitmap *img, LcdFlags flags,
|
||||
int startAngle, int endAngle)
|
||||
{
|
||||
// TODO: can this converted to LVGL primitives?
|
||||
|
||||
if (endAngle == startAngle) {
|
||||
endAngle += 1;
|
||||
}
|
||||
|
||||
Slope startSlope(startAngle);
|
||||
Slope endSlope(endAngle);
|
||||
|
||||
pixel_t color = COLOR_VAL(flags);
|
||||
|
||||
coord_t width = img->width;
|
||||
coord_t height = img->height;
|
||||
const uint8_t *q = img->data;
|
||||
|
||||
int w2 = width / 2;
|
||||
int h2 = height / 2;
|
||||
|
||||
DMAWait();
|
||||
#if __CORTEX_M >= 0x07
|
||||
SCB_CleanInvalidateDCache();
|
||||
#endif
|
||||
|
||||
for (int y1 = h2 - 1; y1 >= 0; y1--) {
|
||||
for (int x1 = w2 - 1; x1 >= 0; x1--) {
|
||||
Slope slope(false, x1 == 0 ? 99000 : y1 * 100 / x1);
|
||||
if (slope.isBetween(startSlope, endSlope)) {
|
||||
drawAlphaPixel(x + w2 + x1, y + h2 - y1,
|
||||
q[(h2 - y1) * width + w2 + x1] >> 4, color);
|
||||
}
|
||||
if (slope.invertVertical().isBetween(startSlope, endSlope)) {
|
||||
drawAlphaPixel(x + w2 + x1, y + h2 + y1,
|
||||
q[(h2 + y1) * width + w2 + x1] >> 4, color);
|
||||
}
|
||||
if (slope.invertHorizontal().isBetween(startSlope, endSlope)) {
|
||||
drawAlphaPixel(x + w2 - x1, y + h2 + y1,
|
||||
q[(h2 + y1) * width + w2 - x1] >> 4, color);
|
||||
}
|
||||
if (slope.invertVertical().isBetween(startSlope, endSlope)) {
|
||||
drawAlphaPixel(x + w2 - x1, y + h2 - y1,
|
||||
q[(h2 - y1) * width + w2 - x1] >> 4, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BitmapBuffer::drawAnnulusSector(coord_t x, coord_t y,
|
||||
coord_t internalRadius,
|
||||
coord_t externalRadius, int startAngle,
|
||||
int endAngle, LcdFlags flags)
|
||||
{
|
||||
if (endAngle == startAngle)
|
||||
endAngle += 1;
|
||||
|
||||
startAngle -= 90;
|
||||
if (startAngle < 0) startAngle += 360;
|
||||
endAngle -= 90;
|
||||
if (endAngle < 0) endAngle += 360;
|
||||
|
||||
lv_draw_arc_dsc_t arc_dsc;
|
||||
lv_draw_arc_dsc_init(&arc_dsc);
|
||||
|
||||
arc_dsc.width = externalRadius - internalRadius;
|
||||
arc_dsc.opa = LV_OPA_COVER;
|
||||
arc_dsc.color = makeLvColor(flags);
|
||||
|
||||
if (draw_ctx) {
|
||||
lv_point_t p;
|
||||
p.x = x + draw_ctx->buf_area->x1;
|
||||
p.y = y + draw_ctx->buf_area->y1;
|
||||
lv_draw_arc(draw_ctx, &arc_dsc, &p, externalRadius, startAngle, endAngle);
|
||||
}
|
||||
#if !defined(BOOT)
|
||||
else if (canvas) {
|
||||
lv_canvas_draw_arc(canvas, x, y, externalRadius, startAngle, endAngle, &arc_dsc);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
MaskBitmap *BitmapBuffer::to8bitMask(size_t *size) const
|
||||
{
|
||||
static uint8_t grayScale[16] = {255, 238, 221, 204, 187, 170, 153, 136, 119, 102, 85, 68, 51, 34, 17, 0};
|
||||
|
||||
*size = width() * height() + 4;
|
||||
MaskBitmap *mask = (MaskBitmap*)malloc(*size);
|
||||
mask->width = width();
|
||||
mask->height = height();
|
||||
|
||||
const pixel_t *p = getPixelPtrAbs(0, 0);
|
||||
int ofs = 0;
|
||||
if (format == BMP_ARGB4444) {
|
||||
for (int i = width() * height(); i > 0; i--) {
|
||||
ARGB_SPLIT(*p, a __attribute__((unused)), r, g, b);
|
||||
int v = (r + g + b) / 3;
|
||||
mask->data[ofs++] = grayScale[v];
|
||||
MOVE_TO_NEXT_RIGHT_PIXEL(p);
|
||||
}
|
||||
} else { // BMP_RGB565
|
||||
for (int i = width() * height(); i > 0; i--) {
|
||||
RGB_SPLIT(*p, r, g, b);
|
||||
int v = ((r + (g >> 1) + b) / 3) >> 1;
|
||||
mask->data[ofs++] = grayScale[v];
|
||||
MOVE_TO_NEXT_RIGHT_PIXEL(p);
|
||||
}
|
||||
}
|
||||
|
||||
return mask;
|
||||
}
|
||||
|
||||
// Apply a mask ('bmp') + color ('flags') on top of current pixels:
|
||||
//
|
||||
// drawAlphaPixel(bmp[x][y], pixel(x,y), color)
|
||||
//
|
||||
//
|
||||
//
|
||||
void BitmapBuffer::drawBitmapPattern(coord_t x, coord_t y, const MaskBitmap *bmp,
|
||||
LcdFlags flags, coord_t offset,
|
||||
coord_t width)
|
||||
{
|
||||
coord_t bmpw = bmp->width;
|
||||
coord_t bmph = bmp->height;
|
||||
|
||||
coord_t srcx = offset;
|
||||
coord_t srcy = 0;
|
||||
|
||||
coord_t srcw = (width != 0 ? width : bmpw);
|
||||
coord_t srch = bmph;
|
||||
if (srcx + srcw > bmpw) srcw = bmpw - srcx;
|
||||
if (srcy + srch > bmph) srch = bmph - srcy;
|
||||
|
||||
if (x < xmin) {
|
||||
srcw += x - xmin;
|
||||
srcx -= x - xmin;
|
||||
x = xmin;
|
||||
}
|
||||
if (y < ymin) {
|
||||
srch += y - ymin;
|
||||
srcy -= y - ymin;
|
||||
y = ymin;
|
||||
}
|
||||
if (x + srcw > xmax) {
|
||||
srcw = xmax - x;
|
||||
}
|
||||
if (y + srch > ymax) {
|
||||
srch = ymax - y;
|
||||
}
|
||||
|
||||
if (srcw <= 0 || srch <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
DMACopyAlphaMask(data, _width, _height, x, y, bmp->data, bmpw, bmph, srcx,
|
||||
srcy, srcw, srch, COLOR_VAL(flags));
|
||||
}
|
||||
|
||||
coord_t BitmapBuffer::drawNumber(coord_t x, coord_t y, int32_t val,
|
||||
LcdFlags flags, uint8_t len,
|
||||
const char *prefix, const char *suffix)
|
||||
{
|
||||
char s[49];
|
||||
formatNumberAsString(s, 49, val, flags, len, prefix, suffix);
|
||||
return drawText(x, y, s, flags);
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
/*
|
||||
* Copyright (C) EdgeTX
|
||||
*
|
||||
* Based on code named
|
||||
* libopenui - https://github.com/opentx/libopenui
|
||||
*
|
||||
* 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 GCC optimize("O3")
|
||||
|
||||
#include "bitmapbuffer.h"
|
||||
#include "lib_file.h"
|
||||
#include "opentx_helpers.h"
|
||||
|
||||
FIL imgFile __DMA;
|
||||
|
||||
// #define TRACE_STB_MALLOC
|
||||
|
||||
#if defined(TRACE_STB_MALLOC)
|
||||
#define STBI_MALLOC(sz) stb_malloc(sz)
|
||||
#define STBI_REALLOC_SIZED(p, oldsz, newsz) stb_realloc(p, oldsz, newsz)
|
||||
#define STBI_FREE(p) stb_free(p)
|
||||
|
||||
void *stb_malloc(unsigned int size)
|
||||
{
|
||||
void *res = malloc(size);
|
||||
TRACE("malloc %d = %p", size, res);
|
||||
return res;
|
||||
}
|
||||
|
||||
void stb_free(void *ptr)
|
||||
{
|
||||
TRACE("free %p", ptr);
|
||||
free(ptr);
|
||||
}
|
||||
|
||||
void *stb_realloc(void *ptr, unsigned int oldsz, unsigned int newsz)
|
||||
{
|
||||
void *res = realloc(ptr, newsz);
|
||||
TRACE("realloc %p, %d -> %d = %p", ptr, oldsz, newsz, res);
|
||||
return res;
|
||||
}
|
||||
#endif // #if defined(TRACE_STB_MALLOC)
|
||||
|
||||
#pragma GCC diagnostic ignored "-Wsign-compare"
|
||||
#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
|
||||
#undef __I
|
||||
#define STBI_ONLY_PNG
|
||||
#define STBI_ONLY_BMP
|
||||
#define STBI_ONLY_JPEG
|
||||
#define STBI_NO_STDIO
|
||||
#define STBI_NO_LINEAR
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include "stb/stb_image.h"
|
||||
|
||||
// fill 'data' with 'size' bytes. return number of bytes actually read
|
||||
int stbc_read(void *user, char *data, int size)
|
||||
{
|
||||
FIL *fp = (FIL *)user;
|
||||
UINT br = 0;
|
||||
FRESULT res = f_read(fp, data, size, &br);
|
||||
if (res == FR_OK) {
|
||||
return (int)br;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// skip the next 'n' bytes, or 'unget' the last -n bytes if negative
|
||||
void stbc_skip(void *user, int n)
|
||||
{
|
||||
FIL *fp = (FIL *)user;
|
||||
f_lseek(fp, f_tell(fp) + n);
|
||||
}
|
||||
|
||||
// returns nonzero if we are at end of file/data
|
||||
int stbc_eof(void *user)
|
||||
{
|
||||
FIL *fp = (FIL *)user;
|
||||
int res = f_eof(fp);
|
||||
return res;
|
||||
}
|
||||
|
||||
// callbacks for stb-image
|
||||
const stbi_io_callbacks stbCallbacks = {stbc_read, stbc_skip, stbc_eof};
|
||||
|
||||
BitmapBuffer *BitmapBuffer::loadBitmap(const char *filename, BitmapFormats fmt)
|
||||
{
|
||||
FRESULT result = f_open(&imgFile, filename, FA_OPEN_EXISTING | FA_READ);
|
||||
if (result != FR_OK) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int x, y, nn;
|
||||
stbi_info_from_callbacks(&stbCallbacks, &imgFile, &x, &y, &nn);
|
||||
f_lseek(&imgFile, 0);
|
||||
|
||||
int w, h, n;
|
||||
unsigned char *img =
|
||||
stbi_load_from_callbacks(&stbCallbacks, &imgFile, &w, &h, &n, 4);
|
||||
f_close(&imgFile);
|
||||
|
||||
if (!img) {
|
||||
TRACE("load_stb(%s) failed: %s", filename, stbi_failure_reason());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// convert to RGB565 or ARGB4444 format
|
||||
BitmapFormats dst_fmt = fmt;
|
||||
if (dst_fmt == BMP_INVALID) {
|
||||
dst_fmt = (n == 4 ? BMP_ARGB4444 : BMP_RGB565);
|
||||
}
|
||||
|
||||
BitmapBuffer *bmp = new BitmapBuffer(dst_fmt, w, h);
|
||||
if (bmp == nullptr) {
|
||||
TRACE("convert_stn_bitmap: malloc failed");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
pixel_t *dest = bmp->getPixelPtrAbs(0, 0);
|
||||
const uint8_t *p = img;
|
||||
if (dst_fmt == BMP_ARGB4444) {
|
||||
for (int row = 0; row < h; ++row) {
|
||||
for (int col = 0; col < w; ++col) {
|
||||
*dest = ARGB(p[3], p[0], p[1], p[2]);
|
||||
MOVE_TO_NEXT_RIGHT_PIXEL(dest);
|
||||
p += 4;
|
||||
}
|
||||
}
|
||||
} else { // assume 3 bytes, packed in groups of 4
|
||||
for (int row = 0; row < h; ++row) {
|
||||
for (int col = 0; col < w; ++col) {
|
||||
*dest = RGB(p[0], p[1], p[2]);
|
||||
MOVE_TO_NEXT_RIGHT_PIXEL(dest);
|
||||
p += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stbi_image_free(img);
|
||||
return bmp;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
static lv_res_t decoder_info(struct _lv_img_decoder_t *decoder, const void *src,
|
||||
lv_img_header_t *header)
|
||||
{
|
||||
LV_UNUSED(decoder); /*Unused*/
|
||||
|
||||
lv_img_src_t src_type = lv_img_src_get_type(src); /*Get the source type*/
|
||||
|
||||
/*If it's a file...*/
|
||||
if (src_type == LV_IMG_SRC_FILE) {
|
||||
const char *fn = ((const char *)src) + 1;
|
||||
FIL imgFile;
|
||||
|
||||
FRESULT result = f_open(&imgFile, fn, FA_OPEN_EXISTING | FA_READ);
|
||||
if (result == FR_OK) {
|
||||
int x, y, nn;
|
||||
stbi_info_from_callbacks(&stbCallbacks, &imgFile, &x, &y, &nn);
|
||||
f_close(&imgFile);
|
||||
|
||||
header->always_zero = 0;
|
||||
header->cf =
|
||||
(nn == 4) ? LV_IMG_CF_TRUE_COLOR_ALPHA : LV_IMG_CF_TRUE_COLOR;
|
||||
header->w = x;
|
||||
header->h = y;
|
||||
|
||||
return LV_RES_OK;
|
||||
}
|
||||
}
|
||||
/*If it's a file in a C array...*/
|
||||
else if (src_type == LV_IMG_SRC_VARIABLE) {
|
||||
// Not implemented...
|
||||
}
|
||||
|
||||
return LV_RES_INV;
|
||||
}
|
||||
|
||||
static uint8_t *convert_bitmap(uint8_t *img, int w, int h, int n)
|
||||
{
|
||||
uint8_t *bmp = (uint8_t *)lv_mem_alloc(((n == 4) ? 3 : 2) * w * h);
|
||||
if (bmp == nullptr) {
|
||||
TRACE("convert_bitmap: lv_mem_alloc failed");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const uint8_t *p = img;
|
||||
if (n == 4) {
|
||||
uint8_t *dest = bmp;
|
||||
for (int row = 0; row < h; ++row) {
|
||||
for (int col = 0; col < w; ++col) {
|
||||
uint16_t c = RGB(p[0], p[1], p[2]);
|
||||
*dest++ = c & 0xFF;
|
||||
*dest++ = c >> 8;
|
||||
*dest++ = p[3];
|
||||
p += 4;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
pixel_t *dest = (pixel_t *)bmp;
|
||||
for (int row = 0; row < h; ++row) {
|
||||
for (int col = 0; col < w; ++col) {
|
||||
*dest = RGB(p[0], p[1], p[2]);
|
||||
MOVE_TO_NEXT_RIGHT_PIXEL(dest);
|
||||
p += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bmp;
|
||||
}
|
||||
|
||||
static lv_res_t decoder_open(lv_img_decoder_t *decoder,
|
||||
lv_img_decoder_dsc_t *dsc)
|
||||
{
|
||||
LV_UNUSED(decoder); /*Unused*/
|
||||
|
||||
/*If it's a file...*/
|
||||
if (dsc->src_type == LV_IMG_SRC_FILE) {
|
||||
const char *fn = ((const char *)dsc->src) + 1;
|
||||
FIL imgFile;
|
||||
|
||||
FRESULT result = f_open(&imgFile, fn, FA_OPEN_EXISTING | FA_READ);
|
||||
if (result == FR_OK) {
|
||||
int w, h, n;
|
||||
unsigned char *img =
|
||||
stbi_load_from_callbacks(&stbCallbacks, &imgFile, &w, &h, &n, 4);
|
||||
f_close(&imgFile);
|
||||
|
||||
if (!img) {
|
||||
TRACE("decoder_open(%s) failed: %s", fn, stbi_failure_reason());
|
||||
return LV_RES_INV;
|
||||
}
|
||||
|
||||
dsc->img_data = convert_bitmap(img, w, h, n);
|
||||
stbi_image_free(img);
|
||||
|
||||
return dsc->img_data ? LV_RES_OK : LV_RES_INV;
|
||||
}
|
||||
}
|
||||
/*If it's a file in a C array...*/
|
||||
else if (dsc->src_type == LV_IMG_SRC_VARIABLE) {
|
||||
// Not implemented...
|
||||
}
|
||||
|
||||
return LV_RES_INV;
|
||||
}
|
||||
|
||||
static void decoder_close(lv_img_decoder_t *decoder, lv_img_decoder_dsc_t *dsc)
|
||||
{
|
||||
LV_UNUSED(decoder); /*Unused*/
|
||||
if (dsc->img_data) {
|
||||
lv_mem_free((uint8_t *)dsc->img_data);
|
||||
dsc->img_data = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the STB decoder functions in LVGL
|
||||
*/
|
||||
void lv_stb_init(void)
|
||||
{
|
||||
lv_img_decoder_t *dec = lv_img_decoder_create();
|
||||
lv_img_decoder_set_info_cb(dec, decoder_info);
|
||||
lv_img_decoder_set_open_cb(dec, decoder_open);
|
||||
lv_img_decoder_set_close_cb(dec, decoder_close);
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copyright (C) EdgeTX
|
||||
*
|
||||
* Based on code named
|
||||
* libopenui - https://github.com/opentx/libopenui
|
||||
*
|
||||
* 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 "button.h"
|
||||
|
||||
#include "static.h"
|
||||
#include "etx_lv_theme.h"
|
||||
|
||||
static void button_constructor(const lv_obj_class_t* class_p, lv_obj_t* obj)
|
||||
{
|
||||
etx_btn_style(obj, LV_PART_MAIN);
|
||||
}
|
||||
|
||||
// Must not be static - inherited by menu_button_class
|
||||
const lv_obj_class_t button_class = {
|
||||
.base_class = &lv_btn_class,
|
||||
.constructor_cb = button_constructor,
|
||||
.destructor_cb = nullptr,
|
||||
.user_data = nullptr,
|
||||
.event_cb = nullptr,
|
||||
.width_def = LV_SIZE_CONTENT,
|
||||
.height_def = EdgeTxStyles::UI_ELEMENT_HEIGHT,
|
||||
.editable = LV_OBJ_CLASS_EDITABLE_INHERIT,
|
||||
.group_def = LV_OBJ_CLASS_GROUP_DEF_TRUE,
|
||||
.instance_size = sizeof(lv_btn_t),
|
||||
};
|
||||
|
||||
static lv_obj_t* button_create(lv_obj_t* parent)
|
||||
{
|
||||
return etx_create(&button_class, parent);
|
||||
}
|
||||
|
||||
Button::Button(Window* parent, const rect_t& rect,
|
||||
std::function<uint8_t(void)> pressHandler) :
|
||||
ButtonBase(parent, rect, pressHandler, button_create)
|
||||
{
|
||||
}
|
||||
|
||||
ButtonBase::ButtonBase(Window* parent, const rect_t& rect,
|
||||
std::function<uint8_t(void)> pressHandler,
|
||||
LvglCreate objConstruct) :
|
||||
FormField(parent, rect, objConstruct ? objConstruct : lv_btn_create),
|
||||
pressHandler(std::move(pressHandler))
|
||||
{
|
||||
}
|
||||
|
||||
void ButtonBase::check(bool checked)
|
||||
{
|
||||
if (!_deleted) {
|
||||
if (checked != this->checked()) {
|
||||
if (checked)
|
||||
lv_obj_add_state(lvobj, LV_STATE_CHECKED);
|
||||
else
|
||||
lv_obj_clear_state(lvobj, LV_STATE_CHECKED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ButtonBase::checked() const
|
||||
{
|
||||
return lv_obj_get_state(lvobj) & LV_STATE_CHECKED;
|
||||
}
|
||||
|
||||
void ButtonBase::onPress() { check(pressHandler && pressHandler()); }
|
||||
|
||||
bool ButtonBase::onLongPress()
|
||||
{
|
||||
if (longPressHandler) {
|
||||
check(longPressHandler());
|
||||
lv_obj_clear_state(lvobj, LV_STATE_PRESSED);
|
||||
lv_indev_wait_release(lv_indev_get_act());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ButtonBase::onClicked() { onPress(); }
|
||||
|
||||
void ButtonBase::checkEvents()
|
||||
{
|
||||
Window::checkEvents();
|
||||
if (checkHandler) checkHandler();
|
||||
}
|
||||
|
||||
TextButton::TextButton(Window* parent, const rect_t& rect, std::string text,
|
||||
std::function<uint8_t(void)> pressHandler) :
|
||||
ButtonBase(parent, rect, pressHandler, button_create),
|
||||
text(std::move(text))
|
||||
{
|
||||
label = lv_label_create(lvobj);
|
||||
lv_label_set_text(label, this->text.c_str());
|
||||
lv_obj_center(label);
|
||||
}
|
||||
|
||||
IconButton::IconButton(Window* parent, EdgeTxIcon icon, coord_t x, coord_t y,
|
||||
std::function<uint8_t(void)> pressHandler) :
|
||||
ButtonBase(parent, {x, y, EdgeTxStyles::UI_ELEMENT_HEIGHT, EdgeTxStyles::UI_ELEMENT_HEIGHT}, pressHandler, button_create)
|
||||
{
|
||||
padAll(PAD_ZERO);
|
||||
iconImage = new StaticIcon(this, 0, 0, icon, COLOR_THEME_SECONDARY1_INDEX);
|
||||
iconImage->center(EdgeTxStyles::UI_ELEMENT_HEIGHT - 4, EdgeTxStyles::UI_ELEMENT_HEIGHT - 4);
|
||||
}
|
||||
|
||||
void IconButton::setIcon(EdgeTxIcon icon) { iconImage->setIcon(icon); }
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright (C) EdgeTX
|
||||
*
|
||||
* Based on code named
|
||||
* libopenui - https://github.com/opentx/libopenui
|
||||
*
|
||||
* 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 "form.h"
|
||||
#include "bitmaps.h"
|
||||
|
||||
class StaticIcon;
|
||||
|
||||
// Base button class - default LVGL style unless overridden on create
|
||||
class ButtonBase : public FormField
|
||||
{
|
||||
public:
|
||||
ButtonBase(Window* parent, const rect_t& rect,
|
||||
std::function<uint8_t(void)> pressHandler = nullptr,
|
||||
LvglCreate objConstruct = nullptr);
|
||||
|
||||
#if defined(DEBUG_WINDOWS)
|
||||
std::string getName() const override { return "ButtonBase"; }
|
||||
#endif
|
||||
|
||||
virtual void onPress();
|
||||
bool onLongPress() override;
|
||||
|
||||
void onClicked() override;
|
||||
void checkEvents() override;
|
||||
|
||||
void check(bool checked = true);
|
||||
bool checked() const;
|
||||
|
||||
void setPressHandler(std::function<uint8_t(void)> handler)
|
||||
{
|
||||
pressHandler = std::move(handler);
|
||||
}
|
||||
|
||||
void setLongPressHandler(std::function<uint8_t(void)> handler)
|
||||
{
|
||||
longPressHandler = std::move(handler);
|
||||
}
|
||||
|
||||
void setCheckHandler(std::function<void(void)> handler)
|
||||
{
|
||||
checkHandler = std::move(handler);
|
||||
}
|
||||
|
||||
protected:
|
||||
std::function<uint8_t(void)> pressHandler;
|
||||
std::function<uint8_t(void)> longPressHandler;
|
||||
std::function<void(void)> checkHandler;
|
||||
};
|
||||
|
||||
class Button : public ButtonBase
|
||||
{
|
||||
public:
|
||||
Button(Window* parent, const rect_t& rect,
|
||||
std::function<uint8_t(void)> pressHandler = nullptr);
|
||||
|
||||
#if defined(DEBUG_WINDOWS)
|
||||
std::string getName() const override { return "Button"; }
|
||||
#endif
|
||||
};
|
||||
|
||||
class TextButton : public ButtonBase
|
||||
{
|
||||
public:
|
||||
TextButton(Window* parent, const rect_t& rect, std::string text,
|
||||
std::function<uint8_t(void)> pressHandler = nullptr);
|
||||
|
||||
#if defined(DEBUG_WINDOWS)
|
||||
std::string getName() const override { return "TextButton \"" + text + "\""; }
|
||||
#endif
|
||||
|
||||
void setText(std::string value)
|
||||
{
|
||||
if (value != text) {
|
||||
text = std::move(value);
|
||||
lv_label_set_text(label, text.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void setWrap()
|
||||
{
|
||||
lv_obj_set_width(label, lv_pct(100));
|
||||
etx_obj_add_style(label, styles->text_align_center, LV_PART_MAIN);
|
||||
lv_label_set_long_mode(label, LV_LABEL_LONG_WRAP);
|
||||
}
|
||||
|
||||
protected:
|
||||
lv_obj_t* label = nullptr;
|
||||
|
||||
std::string text;
|
||||
};
|
||||
|
||||
class IconButton : public ButtonBase
|
||||
{
|
||||
public:
|
||||
IconButton(Window* parent, EdgeTxIcon icon, coord_t x, coord_t y,
|
||||
std::function<uint8_t(void)> pressHandler = nullptr);
|
||||
|
||||
void setIcon(EdgeTxIcon icon);
|
||||
|
||||
protected:
|
||||
StaticIcon* iconImage = nullptr;
|
||||
};
|
||||
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* Copyright (C) EdgeTX
|
||||
*
|
||||
* Based on code named
|
||||
* libopenui - https://github.com/opentx/libopenui
|
||||
*
|
||||
* 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 "button_matrix.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "etx_lv_theme.h"
|
||||
|
||||
static void btnmatrix_constructor(const lv_obj_class_t* class_p, lv_obj_t* obj)
|
||||
{
|
||||
etx_obj_add_style(obj, styles->rounded, LV_PART_MAIN);
|
||||
etx_obj_add_style(obj, styles->bg_opacity_20,
|
||||
LV_PART_MAIN | LV_STATE_FOCUSED);
|
||||
|
||||
etx_bg_color(obj, COLOR_THEME_FOCUS_INDEX, LV_PART_MAIN | LV_STATE_FOCUSED);
|
||||
|
||||
etx_std_style(obj, LV_PART_ITEMS, PAD_LARGE);
|
||||
|
||||
etx_obj_add_style(obj, styles->border_color[COLOR_THEME_FOCUS_INDEX],
|
||||
LV_PART_ITEMS | LV_STATE_EDITED);
|
||||
}
|
||||
|
||||
static const lv_obj_class_t btnmatrix_class = {
|
||||
.base_class = &lv_btnmatrix_class,
|
||||
.constructor_cb = btnmatrix_constructor,
|
||||
.destructor_cb = nullptr,
|
||||
.user_data = nullptr,
|
||||
.event_cb = nullptr,
|
||||
.width_def = 0,
|
||||
.height_def = 0,
|
||||
.editable = LV_OBJ_CLASS_EDITABLE_INHERIT,
|
||||
.group_def = LV_OBJ_CLASS_GROUP_DEF_INHERIT,
|
||||
.instance_size = sizeof(lv_btnmatrix_t),
|
||||
};
|
||||
|
||||
static lv_obj_t* btnmatrix_create(lv_obj_t* parent)
|
||||
{
|
||||
return etx_create(&btnmatrix_class, parent);
|
||||
}
|
||||
|
||||
static const char _filler[] = "0";
|
||||
static const char _newline[] = "\n";
|
||||
static const char _map_end[] = "";
|
||||
|
||||
static void btn_matrix_event(lv_event_t* e)
|
||||
{
|
||||
lv_event_code_t code = lv_event_get_code(e);
|
||||
|
||||
if (code == LV_EVENT_VALUE_CHANGED) {
|
||||
lv_obj_t* obj = lv_event_get_target(e);
|
||||
auto btn_id = *((uint8_t*)lv_event_get_param(e));
|
||||
auto btnm = (ButtonMatrix*)lv_event_get_user_data(e);
|
||||
|
||||
bool edited = lv_obj_has_state(obj, LV_STATE_EDITED);
|
||||
bool is_pointer =
|
||||
lv_indev_get_type(lv_indev_get_act()) == LV_INDEV_TYPE_POINTER;
|
||||
if (edited || is_pointer) {
|
||||
btnm->onPress(btn_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ButtonMatrix::ButtonMatrix(Window* parent, const rect_t& r) :
|
||||
FormField(parent, r, btnmatrix_create)
|
||||
{
|
||||
lv_obj_add_flag(lvobj, LV_OBJ_FLAG_SCROLL_ON_FOCUS);
|
||||
lv_obj_clear_flag(lvobj, LV_OBJ_FLAG_CLICK_FOCUSABLE);
|
||||
|
||||
lv_obj_add_event_cb(lvobj, btn_matrix_event, LV_EVENT_ALL, this);
|
||||
}
|
||||
|
||||
ButtonMatrix::~ButtonMatrix() { deallocate(); }
|
||||
|
||||
void ButtonMatrix::deallocate()
|
||||
{
|
||||
if (txt_cnt == 0) return;
|
||||
|
||||
for (uint8_t i = 0; i < txt_cnt; i++) {
|
||||
char* txt = lv_btnm_map[i];
|
||||
if (txt != _filler && txt != _newline && txt != _map_end) free(txt);
|
||||
}
|
||||
|
||||
free(lv_btnm_map);
|
||||
free(txt_index);
|
||||
|
||||
txt_cnt = 0;
|
||||
btn_cnt = 0;
|
||||
}
|
||||
|
||||
void ButtonMatrix::initBtnMap(uint8_t cols, uint8_t btns)
|
||||
{
|
||||
deallocate();
|
||||
|
||||
uint8_t rows = ((btns - 1) / cols) + 1;
|
||||
if (rows == 1) cols = btns;
|
||||
txt_cnt = (cols + 1) * rows;
|
||||
btn_cnt = btns;
|
||||
|
||||
lv_btnm_map = (char**)malloc(sizeof(char*) * txt_cnt);
|
||||
txt_index = (uint8_t*)malloc(sizeof(uint8_t) * cols * rows);
|
||||
|
||||
uint8_t col = 0;
|
||||
uint8_t btn = 0;
|
||||
uint8_t txt_i = 0;
|
||||
|
||||
while (btn < cols * rows) {
|
||||
if (col == cols) {
|
||||
lv_btnm_map[txt_i++] = (char*)_newline;
|
||||
col = 0;
|
||||
}
|
||||
|
||||
txt_index[btn] = txt_i;
|
||||
lv_btnm_map[txt_i++] = (char*)_filler;
|
||||
btn++;
|
||||
col++;
|
||||
}
|
||||
lv_btnm_map[txt_i] = (char*)_map_end;
|
||||
update();
|
||||
}
|
||||
|
||||
void ButtonMatrix::setText(uint8_t btn_id, const char* txt)
|
||||
{
|
||||
if (btn_id < btn_cnt) lv_btnm_map[txt_index[btn_id]] = strdup(txt);
|
||||
}
|
||||
|
||||
void ButtonMatrix::update()
|
||||
{
|
||||
lv_btnmatrix_set_map(lvobj, (const char**)lv_btnm_map);
|
||||
lv_btnmatrix_set_btn_ctrl_all(
|
||||
lvobj, LV_BTNMATRIX_CTRL_CLICK_TRIG | LV_BTNMATRIX_CTRL_NO_REPEAT);
|
||||
int btn = 0;
|
||||
for (int i = 0; lv_btnm_map[i] != _map_end; i += 1) {
|
||||
if (lv_btnm_map[i] == _filler)
|
||||
lv_btnmatrix_set_btn_ctrl(lvobj, btn, LV_BTNMATRIX_CTRL_HIDDEN);
|
||||
else
|
||||
lv_btnmatrix_clear_btn_ctrl(lvobj, btn, LV_BTNMATRIX_CTRL_HIDDEN);
|
||||
if (lv_btnm_map[i] != _newline) btn += 1;
|
||||
}
|
||||
}
|
||||
|
||||
void ButtonMatrix::onClicked()
|
||||
{
|
||||
lv_group_focus_obj(lvobj);
|
||||
setEditMode(true);
|
||||
}
|
||||
|
||||
void ButtonMatrix::setChecked(uint8_t btn_id)
|
||||
{
|
||||
if (isActive(btn_id))
|
||||
lv_btnmatrix_set_btn_ctrl(lvobj, btn_id, LV_BTNMATRIX_CTRL_CHECKED);
|
||||
else
|
||||
lv_btnmatrix_clear_btn_ctrl(lvobj, btn_id, LV_BTNMATRIX_CTRL_CHECKED);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (C) EdgeTX
|
||||
*
|
||||
* Based on code named
|
||||
* libopenui - https://github.com/opentx/libopenui
|
||||
*
|
||||
* 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 "form.h"
|
||||
|
||||
class ButtonMatrix : public FormField
|
||||
{
|
||||
char** lv_btnm_map = nullptr;
|
||||
uint8_t* txt_index = nullptr;
|
||||
uint8_t txt_cnt = 0;
|
||||
uint8_t btn_cnt = 0;
|
||||
|
||||
public:
|
||||
ButtonMatrix(Window* parent, const rect_t& rect);
|
||||
~ButtonMatrix();
|
||||
|
||||
virtual void onPress(uint8_t btn_id) {}
|
||||
virtual bool isActive(uint8_t btn_id) { return false; }
|
||||
|
||||
protected:
|
||||
void deallocate();
|
||||
void initBtnMap(uint8_t cols, uint8_t btns);
|
||||
void setText(uint8_t btn_id, const char* txt);
|
||||
void setChecked(uint8_t btn_id);
|
||||
void update();
|
||||
|
||||
void onClicked() override;
|
||||
};
|
||||
@@ -0,0 +1,228 @@
|
||||
/*
|
||||
* Copyright (C) EdgeTX
|
||||
*
|
||||
* Based on code named
|
||||
* libopenui - https://github.com/opentx/libopenui
|
||||
*
|
||||
* 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 "choice.h"
|
||||
|
||||
#include "menu.h"
|
||||
#include "etx_lv_theme.h"
|
||||
|
||||
// Choice
|
||||
static void choice_constructor(const lv_obj_class_t* class_p, lv_obj_t* obj)
|
||||
{
|
||||
etx_std_style(obj, LV_PART_MAIN, PAD_TINY);
|
||||
lv_obj_set_style_pad_hor(obj, PAD_MEDIUM, LV_PART_MAIN);
|
||||
}
|
||||
|
||||
static const lv_obj_class_t choice_class = {
|
||||
.base_class = &lv_obj_class,
|
||||
.constructor_cb = choice_constructor,
|
||||
.destructor_cb = nullptr,
|
||||
.user_data = nullptr,
|
||||
.event_cb = nullptr,
|
||||
.width_def = LV_SIZE_CONTENT,
|
||||
.height_def = EdgeTxStyles::UI_ELEMENT_HEIGHT,
|
||||
.editable = LV_OBJ_CLASS_EDITABLE_INHERIT,
|
||||
.group_def = LV_OBJ_CLASS_GROUP_DEF_TRUE,
|
||||
.instance_size = sizeof(lv_obj_t),
|
||||
};
|
||||
|
||||
static lv_obj_t* choice_create(lv_obj_t* parent)
|
||||
{
|
||||
return etx_create(&choice_class, parent);
|
||||
}
|
||||
|
||||
void ChoiceBase::changedCB(lv_event_t* e)
|
||||
{
|
||||
auto code = lv_event_get_code(e);
|
||||
|
||||
if (code == LV_EVENT_VALUE_CHANGED) {
|
||||
lv_obj_t* target = lv_event_get_target(e);
|
||||
if (target != nullptr) {
|
||||
ChoiceBase* cb = (ChoiceBase*)lv_obj_get_user_data(target);
|
||||
if (cb)
|
||||
cb->update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ChoiceBase::ChoiceBase(Window* parent, const rect_t& rect,
|
||||
int vmin, int vmax, const char* title,
|
||||
std::function<int()> _getValue,
|
||||
std::function<void(int)> _setValue,
|
||||
ChoiceType type) :
|
||||
FormField(parent, rect, choice_create),
|
||||
vmin(vmin), vmax(vmax), menuTitle(title),
|
||||
_getValue(std::move(_getValue)),
|
||||
_setValue(std::move(_setValue))
|
||||
{
|
||||
// Add image
|
||||
lv_obj_t* img = lv_img_create(lvobj);
|
||||
lv_img_set_src(
|
||||
img, type == CHOICE_TYPE_DROPOWN ? LV_SYMBOL_DOWN : LV_SYMBOL_DIRECTORY);
|
||||
lv_obj_set_pos(img, 0, PAD_TINY);
|
||||
|
||||
// Add label
|
||||
label = lv_label_create(lvobj);
|
||||
lv_obj_set_pos(label, ICON_W, PAD_TINY);
|
||||
|
||||
lv_obj_add_event_cb(lvobj, changedCB, LV_EVENT_VALUE_CHANGED, this);
|
||||
}
|
||||
|
||||
std::string Choice::getLabelText()
|
||||
{
|
||||
std::string text;
|
||||
|
||||
if (_getValue) {
|
||||
int val = _getValue();
|
||||
if (textHandler) {
|
||||
text = textHandler(val);
|
||||
} else {
|
||||
val -= vmin;
|
||||
if (val >= 0 && val < (int)values.size()) {
|
||||
text = values[val];
|
||||
} else {
|
||||
text = std::to_string(val + vmin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
void ChoiceBase::update()
|
||||
{
|
||||
if (!deleted())
|
||||
lv_label_set_text(label, getLabelText().c_str());
|
||||
}
|
||||
|
||||
Choice::Choice(Window* parent, const rect_t& rect, int vmin, int vmax,
|
||||
std::function<int()> getValue,
|
||||
std::function<void(int)> setValue, const char* title, ChoiceType type) :
|
||||
ChoiceBase(parent, rect, vmin, vmax, title, getValue, setValue, type)
|
||||
{
|
||||
update();
|
||||
}
|
||||
|
||||
Choice::Choice(Window* parent, const rect_t& rect, const char* const values[],
|
||||
int vmin, int vmax, std::function<int()> getValue,
|
||||
std::function<void(int)> setValue, const char* title) :
|
||||
ChoiceBase(parent, rect, vmin, vmax, title, getValue, setValue, CHOICE_TYPE_DROPOWN)
|
||||
{
|
||||
setValues(values);
|
||||
update();
|
||||
}
|
||||
|
||||
Choice::Choice(Window* parent, const rect_t& rect,
|
||||
std::vector<std::string> values, int vmin, int vmax,
|
||||
std::function<int()> getValue,
|
||||
std::function<void(int)> setValue, const char* title) :
|
||||
ChoiceBase(parent, rect, vmin, vmax, title, getValue, setValue, CHOICE_TYPE_DROPOWN),
|
||||
values(std::move(values))
|
||||
{
|
||||
update();
|
||||
}
|
||||
|
||||
void Choice::addValue(const char* value)
|
||||
{
|
||||
values.emplace_back(value);
|
||||
vmax += 1;
|
||||
}
|
||||
|
||||
void Choice::setValues(std::vector<std::string> values)
|
||||
{
|
||||
this->values = std::move(values);
|
||||
}
|
||||
|
||||
void Choice::setValues(const char* const values[])
|
||||
{
|
||||
this->values.clear();
|
||||
if (values) {
|
||||
auto value = &values[0];
|
||||
for (int i = vmin; i <= vmax; i++) {
|
||||
this->values.emplace_back(*value++);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Choice::setValue(int val)
|
||||
{
|
||||
if (_setValue) {
|
||||
_setValue(val);
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
void Choice::onClicked()
|
||||
{
|
||||
if (!deleted())
|
||||
openMenu();
|
||||
}
|
||||
|
||||
void Choice::fillMenu(Menu* menu, const FilterFct& filter)
|
||||
{
|
||||
if (menu->count() > 0)
|
||||
menu->removeLines();
|
||||
auto value = getIntValue();
|
||||
|
||||
int count = 0;
|
||||
int selectedIx = -1;
|
||||
selectedIx0 = -1;
|
||||
for (int i = vmin; i <= vmax; ++i) {
|
||||
if (filter && !filter(i)) continue;
|
||||
if (isValueAvailable && !isValueAvailable(inverted ? -i : i)) continue;
|
||||
if (textHandler) {
|
||||
menu->addLineBuffered(textHandler(i), [=]() { setValue(i); });
|
||||
} else if (unsigned(i - vmin) < values.size()) {
|
||||
menu->addLineBuffered(values[i - vmin], [=]() { setValue(i); });
|
||||
} else {
|
||||
menu->addLineBuffered(std::to_string(i), [=]() { setValue(i); });
|
||||
}
|
||||
if (value == i) {
|
||||
selectedIx = count;
|
||||
}
|
||||
if (i == 0) {
|
||||
selectedIx0 = count;
|
||||
}
|
||||
++count;
|
||||
}
|
||||
if (fillMenuHandler) {
|
||||
fillMenuHandler(menu, value, selectedIx);
|
||||
}
|
||||
menu->updateLines();
|
||||
// Force update - in case selected row is first row
|
||||
menu->select(-1);
|
||||
if (selectedIx >= 0)
|
||||
menu->select(selectedIx);
|
||||
else if (selectedIx0 >= 0)
|
||||
menu->select(selectedIx0);
|
||||
else {
|
||||
menu->select(0);
|
||||
}
|
||||
}
|
||||
|
||||
void Choice::openMenu()
|
||||
{
|
||||
setEditMode(true); // this needs to be done first before menu is created.
|
||||
|
||||
auto menu = new Menu(this);
|
||||
if (menuTitle) menu->setTitle(menuTitle);
|
||||
|
||||
fillMenu(menu);
|
||||
|
||||
menu->setCloseHandler([=]() { setEditMode(false); });
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* Copyright (C) EdgeTX
|
||||
*
|
||||
* Based on code named
|
||||
* libopenui - https://github.com/opentx/libopenui
|
||||
*
|
||||
* 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 <vector>
|
||||
|
||||
#include "form.h"
|
||||
|
||||
class Menu;
|
||||
|
||||
enum ChoiceType {
|
||||
CHOICE_TYPE_DROPOWN,
|
||||
CHOICE_TYPE_FOLDER,
|
||||
};
|
||||
|
||||
class ChoiceBase : public FormField
|
||||
{
|
||||
public:
|
||||
ChoiceBase(Window *parent, const rect_t &rect, int vmin, int vmax, const char* title,
|
||||
std::function<int()> _getValue, std::function<void(int)> _setValue,
|
||||
ChoiceType type = CHOICE_TYPE_DROPOWN);
|
||||
|
||||
void update();
|
||||
|
||||
void setTextHandler(std::function<std::string(int)> handler)
|
||||
{
|
||||
textHandler = std::move(handler);
|
||||
update();
|
||||
}
|
||||
|
||||
int getMin() const { return vmin; }
|
||||
void setMin(int value) { vmin = value; }
|
||||
|
||||
int getMax() const { return vmax; }
|
||||
void setMax(int value) { vmax = value; }
|
||||
|
||||
const char* getTitle() const { return menuTitle; }
|
||||
|
||||
static LAYOUT_VAL(ICON_W, 18, 18)
|
||||
|
||||
protected:
|
||||
lv_obj_t *label;
|
||||
int vmin = 0;
|
||||
int vmax = 0;
|
||||
const char *menuTitle = nullptr;
|
||||
|
||||
std::function<int()> _getValue;
|
||||
std::function<void(int)> _setValue;
|
||||
std::function<std::string(int)> textHandler;
|
||||
|
||||
virtual std::string getLabelText() = 0;
|
||||
|
||||
static void changedCB(lv_event_t *e);
|
||||
};
|
||||
|
||||
class Choice : public ChoiceBase
|
||||
{
|
||||
public:
|
||||
Choice(Window *parent, const rect_t &rect, int vmin, int vmax,
|
||||
std::function<int()> _getValue,
|
||||
std::function<void(int)> _setValue = nullptr,
|
||||
const char *title = nullptr, ChoiceType type = CHOICE_TYPE_DROPOWN);
|
||||
Choice(Window *parent, const rect_t &rect, std::vector<std::string> values,
|
||||
int vmin, int vmax, std::function<int()> _getValue,
|
||||
std::function<void(int)> _setValue = nullptr,
|
||||
const char *title = nullptr);
|
||||
Choice(Window *parent, const rect_t &rect, const char *const values[],
|
||||
int vmin, int vmax, std::function<int()> _getValue,
|
||||
std::function<void(int)> _setValue = nullptr,
|
||||
const char *title = nullptr);
|
||||
|
||||
#if defined(DEBUG_WINDOWS)
|
||||
std::string getName() const override { return "Choice"; }
|
||||
#endif
|
||||
|
||||
void addValue(const char *value);
|
||||
|
||||
void setValues(std::vector<std::string> values);
|
||||
void setValues(const char *const values[]);
|
||||
|
||||
void onClicked() override;
|
||||
|
||||
void setFillMenuHandler(std::function<void(Menu *, int, int &)> handler)
|
||||
{
|
||||
fillMenuHandler = std::move(handler);
|
||||
}
|
||||
|
||||
void setSetValueHandler(std::function<void(int)> handler)
|
||||
{
|
||||
_setValue = std::move(handler);
|
||||
}
|
||||
|
||||
void setGetValueHandler(std::function<int()> handler)
|
||||
{
|
||||
_getValue = std::move(handler);
|
||||
}
|
||||
|
||||
void setAvailableHandler(std::function<bool(int)> handler)
|
||||
{
|
||||
isValueAvailable = std::move(handler);
|
||||
}
|
||||
|
||||
unsigned getIndexFromValue(int value) const
|
||||
{
|
||||
if (!isValueAvailable) {
|
||||
return value - vmin;
|
||||
}
|
||||
|
||||
unsigned index = 0;
|
||||
for (int i = vmin; i < value; i++) {
|
||||
if (isValueAvailable(i)) {
|
||||
index++;
|
||||
}
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
int getValueFromIndex(int index) const
|
||||
{
|
||||
if (!isValueAvailable) {
|
||||
return vmin + index;
|
||||
}
|
||||
|
||||
int value = vmin - 1;
|
||||
while (index >= 0) {
|
||||
index--;
|
||||
value++;
|
||||
while (value < vmax && !isValueAvailable(value)) {
|
||||
value++;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
virtual void setValue(int val);
|
||||
virtual int getIntValue() const { return _getValue(); }
|
||||
|
||||
std::string getString(int val) { return values[val]; }
|
||||
|
||||
typedef std::function<bool(int16_t)> FilterFct;
|
||||
|
||||
void fillMenu(Menu *menu, const FilterFct &filter = nullptr);
|
||||
|
||||
protected:
|
||||
friend class MenuToolbar;
|
||||
|
||||
bool inverted = false;
|
||||
int selectedIx0 = 0;
|
||||
|
||||
std::vector<std::string> values;
|
||||
std::function<bool(int)> isValueAvailable;
|
||||
std::function<void(Menu *, int, int &)> fillMenuHandler;
|
||||
|
||||
std::string getLabelText() override;
|
||||
|
||||
virtual void openMenu();
|
||||
};
|
||||
@@ -0,0 +1,216 @@
|
||||
/*
|
||||
* Copyright (C) EdgeTX
|
||||
*
|
||||
* Based on code named
|
||||
* libopenui - https://github.com/opentx/libopenui
|
||||
*
|
||||
* 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 "dialog.h"
|
||||
|
||||
#include "opentx.h"
|
||||
#include "mainwindow.h"
|
||||
#include "progress.h"
|
||||
#include "etx_lv_theme.h"
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
class BaseDialogForm : public Window
|
||||
{
|
||||
public:
|
||||
BaseDialogForm(Window* parent, lv_coord_t width) : Window(parent, rect_t{})
|
||||
{
|
||||
etx_scrollbar(lvobj);
|
||||
padAll(PAD_MEDIUM);
|
||||
setFlexLayout(LV_FLEX_FLOW_COLUMN, PAD_MEDIUM, width, LV_SIZE_CONTENT);
|
||||
}
|
||||
|
||||
protected:
|
||||
void onClicked() override { Keyboard::hide(false); }
|
||||
};
|
||||
|
||||
BaseDialog::BaseDialog(Window* parent, const char* title,
|
||||
bool closeIfClickedOutside, lv_coord_t width,
|
||||
lv_coord_t maxHeight) :
|
||||
ModalWindow(parent, closeIfClickedOutside)
|
||||
{
|
||||
auto content = new Window(this, rect_t{});
|
||||
content->setWindowFlag(OPAQUE);
|
||||
content->padAll(PAD_ZERO);
|
||||
content->setFlexLayout(LV_FLEX_FLOW_COLUMN, PAD_ZERO, width, LV_SIZE_CONTENT);
|
||||
etx_solid_bg(content->getLvObj());
|
||||
lv_obj_center(content->getLvObj());
|
||||
|
||||
header = new StaticText(content, {0, 0, LV_PCT(100), 0}, title ? title : "", COLOR_THEME_PRIMARY2_INDEX);
|
||||
etx_solid_bg(header->getLvObj(), COLOR_THEME_SECONDARY1_INDEX);
|
||||
header->padAll(PAD_SMALL);
|
||||
header->show(title != nullptr);
|
||||
|
||||
form = new BaseDialogForm(content, width);
|
||||
if (maxHeight != LV_SIZE_CONTENT)
|
||||
lv_obj_set_style_max_height(form->getLvObj(), maxHeight - 32, LV_PART_MAIN);
|
||||
}
|
||||
|
||||
void BaseDialog::setTitle(const char* title)
|
||||
{
|
||||
header->setText(title);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
ProgressDialog::ProgressDialog(Window* parent, const char* title,
|
||||
std::function<void()> onClose) :
|
||||
BaseDialog(parent, title, false), onClose(std::move(onClose))
|
||||
{
|
||||
progress = new Progress(form, rect_t{0, 0, LV_PCT(100), 32});
|
||||
updateProgress(0);
|
||||
}
|
||||
|
||||
void ProgressDialog::setTitle(std::string title)
|
||||
{
|
||||
header->setText(title);
|
||||
header->show();
|
||||
}
|
||||
|
||||
void ProgressDialog::updateProgress(int percentage)
|
||||
{
|
||||
progress->setValue(percentage);
|
||||
lv_refr_now(nullptr);
|
||||
}
|
||||
|
||||
void ProgressDialog::closeDialog()
|
||||
{
|
||||
deleteLater();
|
||||
onClose();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
MessageDialog::MessageDialog(Window* parent, const char* title,
|
||||
const char* message, const char* info,
|
||||
LcdFlags messageFlags, LcdFlags infoFlags) :
|
||||
BaseDialog(parent, title, true)
|
||||
{
|
||||
messageWidget = new StaticText(form, {0, 0, LV_PCT(100), LV_SIZE_CONTENT},
|
||||
message, COLOR_THEME_PRIMARY1_INDEX, messageFlags);
|
||||
|
||||
if (info) {
|
||||
infoWidget = new StaticText(form, {0, 0, LV_PCT(100), LV_SIZE_CONTENT},
|
||||
info, COLOR_THEME_PRIMARY1_INDEX, infoFlags);
|
||||
}
|
||||
}
|
||||
|
||||
void MessageDialog::onClicked() { deleteLater(); }
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
DynamicMessageDialog::DynamicMessageDialog(
|
||||
Window* parent, const char* title, std::function<std::string()> textHandler,
|
||||
const char* message, const int lineHeight, LcdColorIndex color, LcdFlags textFlags) :
|
||||
BaseDialog(parent, title, true)
|
||||
{
|
||||
messageWidget = new StaticText(form, {0, 0, LV_PCT(100), LV_SIZE_CONTENT},
|
||||
message, COLOR_THEME_PRIMARY1_INDEX, CENTERED);
|
||||
|
||||
infoWidget = new DynamicText(form, {0, 0, LV_PCT(100), LV_SIZE_CONTENT},
|
||||
textHandler, color, textFlags);
|
||||
}
|
||||
|
||||
void DynamicMessageDialog::onClicked() { deleteLater(); }
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
ConfirmDialog::ConfirmDialog(Window* parent, const char* title,
|
||||
const char* message,
|
||||
std::function<void(void)> confirmHandler,
|
||||
std::function<void(void)> cancelHandler) :
|
||||
BaseDialog(parent, title, false),
|
||||
confirmHandler(std::move(confirmHandler)),
|
||||
cancelHandler(std::move(cancelHandler))
|
||||
{
|
||||
if (message) {
|
||||
new StaticText(form, {0, 0, LV_PCT(100), 0}, message, COLOR_THEME_PRIMARY1_INDEX, CENTERED);
|
||||
}
|
||||
|
||||
auto box = new Window(form, rect_t{});
|
||||
box->padAll(PAD_TINY);
|
||||
box->setFlexLayout(LV_FLEX_FLOW_ROW, 40, LV_PCT(100), LV_SIZE_CONTENT);
|
||||
lv_obj_set_flex_align(box->getLvObj(), LV_FLEX_ALIGN_CENTER,
|
||||
LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_SPACE_BETWEEN);
|
||||
|
||||
new TextButton(box, rect_t{0, 0, 96, 0}, STR_NO, [=]() -> int8_t {
|
||||
onCancel();
|
||||
return 0;
|
||||
});
|
||||
|
||||
new TextButton(box, rect_t{0, 0, 96, 0}, STR_YES, [=]() -> int8_t {
|
||||
this->deleteLater();
|
||||
this->confirmHandler();
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
void ConfirmDialog::onCancel()
|
||||
{
|
||||
deleteLater();
|
||||
if (cancelHandler) cancelHandler();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
LabelDialog::LabelDialog(Window *parent, const char *label, int length, const char* title,
|
||||
std::function<void(std::string)> _saveHandler) :
|
||||
ModalWindow(parent, false), saveHandler(std::move(_saveHandler))
|
||||
{
|
||||
assert(length <= MAX_LABEL_LENGTH);
|
||||
|
||||
strncpy(this->label, label, length);
|
||||
this->label[length] = '\0';
|
||||
|
||||
auto form = new Window(this, rect_t{});
|
||||
form->padAll(PAD_ZERO);
|
||||
form->setFlexLayout(LV_FLEX_FLOW_COLUMN, PAD_ZERO, LCD_W * 0.8,
|
||||
LV_SIZE_CONTENT);
|
||||
etx_solid_bg(form->getLvObj());
|
||||
lv_obj_center(form->getLvObj());
|
||||
|
||||
auto hdr = new StaticText(form, {0, 0, LV_PCT(100), 0}, title,
|
||||
COLOR_THEME_PRIMARY2_INDEX);
|
||||
etx_solid_bg(hdr->getLvObj(), COLOR_THEME_SECONDARY1_INDEX);
|
||||
hdr->padAll(PAD_MEDIUM);
|
||||
|
||||
auto box = new Window(form, rect_t{});
|
||||
box->padAll(PAD_MEDIUM);
|
||||
box->setFlexLayout(LV_FLEX_FLOW_ROW, 40, LV_PCT(100), LV_SIZE_CONTENT);
|
||||
lv_obj_set_flex_align(box->getLvObj(), LV_FLEX_ALIGN_CENTER,
|
||||
LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_SPACE_BETWEEN);
|
||||
|
||||
new TextEdit(box, rect_t{0, 0, LV_PCT(100), 0}, this->label, length);
|
||||
|
||||
box = new Window(form, rect_t{});
|
||||
box->padAll(PAD_MEDIUM);
|
||||
box->setFlexLayout(LV_FLEX_FLOW_ROW, 40, LV_PCT(100), LV_SIZE_CONTENT);
|
||||
lv_obj_set_flex_align(box->getLvObj(), LV_FLEX_ALIGN_CENTER,
|
||||
LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_SPACE_BETWEEN);
|
||||
|
||||
new TextButton(box, rect_t{0, 0, 96, 0}, STR_CANCEL, [=]() {
|
||||
deleteLater();
|
||||
return 0;
|
||||
});
|
||||
|
||||
new TextButton(box, rect_t{0, 0, 96, 0}, STR_SAVE, [=]() {
|
||||
if (saveHandler != nullptr) saveHandler(this->label);
|
||||
deleteLater();
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Copyright (C) EdgeTX
|
||||
*
|
||||
* Based on code named
|
||||
* libopenui - https://github.com/opentx/libopenui
|
||||
*
|
||||
* 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 "form.h"
|
||||
#include "modal_window.h"
|
||||
|
||||
class StaticText;
|
||||
class DynamicText;
|
||||
class Progress;
|
||||
|
||||
#define DIALOG_DEFAULT_WIDTH ((coord_t)(LCD_W * 0.8))
|
||||
#define DIALOG_DEFAULT_HEIGHT ((coord_t)(LCD_H * 0.8))
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
class BaseDialog : public ModalWindow
|
||||
{
|
||||
public:
|
||||
BaseDialog(Window* parent, const char* title, bool closeIfClickedOutside,
|
||||
lv_coord_t width = DIALOG_DEFAULT_WIDTH,
|
||||
lv_coord_t maxHeight = DIALOG_DEFAULT_HEIGHT);
|
||||
|
||||
void setTitle(const char* title);
|
||||
|
||||
protected:
|
||||
Window* form = nullptr;
|
||||
StaticText* header = nullptr;
|
||||
|
||||
void onCancel() override { deleteLater(); }
|
||||
void onEvent(event_t event) override {}
|
||||
};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
class ProgressDialog : public BaseDialog
|
||||
{
|
||||
public:
|
||||
ProgressDialog(Window* parent, const char* title,
|
||||
std::function<void()> onClose);
|
||||
|
||||
void updateProgress(int percentage);
|
||||
void setTitle(std::string title);
|
||||
void closeDialog();
|
||||
|
||||
protected:
|
||||
uint32_t lastUpdate = 0;
|
||||
Progress* progress;
|
||||
|
||||
std::function<void()> onClose;
|
||||
|
||||
// disable keys
|
||||
void onEvent(event_t) override {}
|
||||
};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
class MessageDialog : public BaseDialog
|
||||
{
|
||||
public:
|
||||
MessageDialog(Window* parent, const char* title, const char* message,
|
||||
const char* info = nullptr, LcdFlags messageFlags = CENTERED,
|
||||
LcdFlags infoFlags = CENTERED);
|
||||
|
||||
protected:
|
||||
StaticText* messageWidget;
|
||||
StaticText* infoWidget;
|
||||
|
||||
#if defined(DEBUG_WINDOWS)
|
||||
std::string getName() const override { return "MessageDialog"; }
|
||||
#endif
|
||||
|
||||
void onClicked() override;
|
||||
};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
class DynamicMessageDialog : public BaseDialog
|
||||
{
|
||||
public:
|
||||
DynamicMessageDialog(Window* parent, const char* title,
|
||||
std::function<std::string()> textHandler,
|
||||
const char* message = "",
|
||||
const int lineHeight = EdgeTxStyles::PAGE_LINE_HEIGHT,
|
||||
LcdColorIndex color = COLOR_THEME_PRIMARY1_INDEX, LcdFlags textFlags = CENTERED);
|
||||
// Attn.: FONT(XXL) is not supported by DynamicMessageDialog
|
||||
|
||||
protected:
|
||||
StaticText* messageWidget;
|
||||
DynamicText* infoWidget;
|
||||
|
||||
#if defined(DEBUG_WINDOWS)
|
||||
std::string getName() const override { return "DynamicMessageDialog"; }
|
||||
#endif
|
||||
|
||||
void onClicked() override;
|
||||
};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
class ConfirmDialog : public BaseDialog
|
||||
{
|
||||
public:
|
||||
ConfirmDialog(Window* parent, const char* title, const char* message,
|
||||
std::function<void(void)> confirmHandler,
|
||||
std::function<void(void)> cancelHandler = nullptr);
|
||||
|
||||
protected:
|
||||
std::function<void(void)> confirmHandler;
|
||||
std::function<void(void)> cancelHandler;
|
||||
|
||||
void onCancel() override;
|
||||
};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
class LabelDialog : public ModalWindow
|
||||
{
|
||||
public:
|
||||
LabelDialog(Window *parent, const char *label, int length, const char* title,
|
||||
std::function<void(std::string)> _saveHandler = nullptr);
|
||||
|
||||
static constexpr int MAX_LABEL_LENGTH = 255;
|
||||
|
||||
void onCancel() override { deleteLater(); }
|
||||
|
||||
protected:
|
||||
std::function<void(std::string)> saveHandler;
|
||||
char label[MAX_LABEL_LENGTH + 1];
|
||||
};
|
||||
+1
-1
@@ -20,7 +20,7 @@
|
||||
*/
|
||||
|
||||
#include "file_browser.h"
|
||||
#include "libopenui_file.h"
|
||||
#include "lib_file.h"
|
||||
#include "fonts.h"
|
||||
|
||||
#include <list>
|
||||
@@ -0,0 +1,197 @@
|
||||
/*
|
||||
* Copyright (C) EdgeTX
|
||||
*
|
||||
* Based on code named
|
||||
* libopenui - https://github.com/opentx/libopenui
|
||||
*
|
||||
* 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 "filechoice.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "opentx.h"
|
||||
#include "lib_file.h"
|
||||
#include "menu.h"
|
||||
#include "menutoolbar.h"
|
||||
|
||||
class FileChoiceMenuToolbar : public MenuToolbar
|
||||
{
|
||||
public:
|
||||
FileChoiceMenuToolbar(FileChoice *choice, Menu *menu) :
|
||||
MenuToolbar(choice, menu, FC_COLS)
|
||||
{
|
||||
filterButton(choice, 'a', 'd', "aA-dD");
|
||||
filterButton(choice, 'e', 'h', "eE-hH");
|
||||
filterButton(choice, 'i', 'l', "iI-lL");
|
||||
filterButton(choice, 'm', 'p', "mM-pP");
|
||||
filterButton(choice, 'q', 't', "qQ-tT");
|
||||
filterButton(choice, 'u', 'z', "uU-zZ");
|
||||
filterButton(choice, '0', '9', "0-9");
|
||||
|
||||
bool found = false;
|
||||
for (int i = 0; i <= choice->getMax(); i += 1) {
|
||||
char c = choice->getString(i)[0];
|
||||
if (c && !isdigit(c) && !isalpha(c)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (found) {
|
||||
addButton(
|
||||
"._-", 0, choice->getMax(),
|
||||
[=](int16_t index) {
|
||||
char c = choice->getString(index)[0];
|
||||
return c && !isdigit(c) && !isalpha(c);
|
||||
},
|
||||
STR_MENU_OTHER);
|
||||
}
|
||||
|
||||
addButton(STR_SELECT_MENU_CLR, 0, 0, nullptr, nullptr, true);
|
||||
}
|
||||
|
||||
void filterButton(FileChoice *choice, char from, char to, const char* title)
|
||||
{
|
||||
bool found = false;
|
||||
for (int i = 0; i <= choice->getMax(); i += 1) {
|
||||
char c = choice->getString(i)[0];
|
||||
if (isupper(c)) c += 0x20;
|
||||
if (c >= from && c <= to) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (found) {
|
||||
char s[4];
|
||||
s[0] = from; s[1] = '-'; s[2] = to; s[3] = 0;
|
||||
addButton(
|
||||
s, 0, choice->getMax(),
|
||||
[=](int16_t index) {
|
||||
char c = choice->getString(index)[0];
|
||||
if (isupper(c)) c += 0x20;
|
||||
return (c >= from && c <= to);
|
||||
},
|
||||
title);
|
||||
}
|
||||
}
|
||||
|
||||
static LAYOUT_VAL(FC_COLS, 3, 2)
|
||||
|
||||
protected:
|
||||
};
|
||||
|
||||
FileChoice::FileChoice(Window *parent, const rect_t &rect, std::string folder,
|
||||
const char *extension, int maxlen,
|
||||
std::function<std::string()> getValue,
|
||||
std::function<void(std::string)> setValue,
|
||||
bool stripExtension, const char *title) :
|
||||
Choice(
|
||||
parent, rect, 0, 0, [=]() { return selectedIdx; },
|
||||
[=](int val) { setValue(getString(val)); }, title, CHOICE_TYPE_FOLDER),
|
||||
folder(std::move(folder)),
|
||||
extension(extension),
|
||||
maxlen(maxlen),
|
||||
getValue(std::move(getValue)),
|
||||
setValue(std::move(setValue)),
|
||||
stripExtension(stripExtension)
|
||||
{
|
||||
update();
|
||||
}
|
||||
|
||||
std::string FileChoice::getLabelText() { return getValue(); }
|
||||
|
||||
void FileChoice::loadFiles()
|
||||
{
|
||||
if (loaded) return;
|
||||
|
||||
loaded = true;
|
||||
|
||||
FILINFO fno;
|
||||
DIR dir;
|
||||
std::list<std::string> files;
|
||||
const char *fnExt;
|
||||
uint8_t fnLen, extLen;
|
||||
|
||||
FRESULT res = f_opendir(&dir, folder.c_str()); // Open the directory
|
||||
if (res == FR_OK) {
|
||||
bool firstTime = true;
|
||||
for (;;) {
|
||||
res = sdReadDir(&dir, &fno, firstTime);
|
||||
if (res != FR_OK || fno.fname[0] == 0)
|
||||
break; // break on error or end of dir
|
||||
if (fno.fattrib & (AM_HID | AM_SYS | AM_DIR))
|
||||
continue; // Ignore subfolders, hidden files and system files
|
||||
if (fno.fname[0] == '.' && fno.fname[1] != '.')
|
||||
continue; // Ignore hidden files under UNIX, but not ..
|
||||
|
||||
fnExt = getFileExtension(fno.fname, 0, 0, &fnLen, &extLen);
|
||||
|
||||
if (extension && (!fnExt || !isExtensionMatching(fnExt, extension)))
|
||||
continue; // wrong extension
|
||||
|
||||
if (stripExtension) fnLen -= extLen;
|
||||
|
||||
if (!fnLen || fnLen > maxlen) continue; // wrong size
|
||||
|
||||
// eject duplicates
|
||||
std::string newFile(fno.fname, fnLen);
|
||||
if (std::find(files.begin(), files.end(), newFile) != files.end())
|
||||
continue;
|
||||
|
||||
files.emplace_back(newFile);
|
||||
}
|
||||
|
||||
f_closedir(&dir);
|
||||
}
|
||||
|
||||
if (!files.empty()) {
|
||||
// sort files
|
||||
files.sort(compare_nocase);
|
||||
files.push_front("");
|
||||
|
||||
std::string value = getValue();
|
||||
int idx = 0;
|
||||
for (const auto &file : files) {
|
||||
addValue(file.c_str());
|
||||
if (strcmp(value.c_str(), file.c_str()) == 0) selectedIdx = idx;
|
||||
idx += 1;
|
||||
}
|
||||
|
||||
setMax(files.size() - 1);
|
||||
}
|
||||
|
||||
fileCount = files.size();
|
||||
}
|
||||
|
||||
void FileChoice::openMenu()
|
||||
{
|
||||
loadFiles();
|
||||
|
||||
if (fileCount > 0) {
|
||||
setEditMode(true); // this needs to be done first before menu is created.
|
||||
|
||||
auto menu = new Menu(this);
|
||||
if (menuTitle) menu->setTitle(menuTitle);
|
||||
|
||||
auto tb = new FileChoiceMenuToolbar(this, menu);
|
||||
menu->setToolbar(tb);
|
||||
|
||||
// fillMenu(menu); - called by MenuToolbar
|
||||
|
||||
menu->setCloseHandler([=]() { setEditMode(false); });
|
||||
} else {
|
||||
new MessageDialog(this, STR_SDCARD, STR_NO_FILES_ON_SD);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (C) EdgeTX
|
||||
*
|
||||
* Based on code named
|
||||
* libopenui - https://github.com/opentx/libopenui
|
||||
*
|
||||
* 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 "choice.h"
|
||||
#include <string>
|
||||
|
||||
class FileChoice : public Choice
|
||||
{
|
||||
public:
|
||||
FileChoice(Window* parent, const rect_t& rect, std::string folder,
|
||||
const char* extension, int maxlen,
|
||||
std::function<std::string()> getValue,
|
||||
std::function<void(std::string)> setValue,
|
||||
bool stripExtension = false,
|
||||
const char* title = nullptr);
|
||||
|
||||
#if defined(DEBUG_WINDOWS)
|
||||
std::string getName() const override { return "FileChoice"; }
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool loaded = false;
|
||||
int fileCount = 0;
|
||||
int selectedIdx = -1;
|
||||
Menu* menu = nullptr;
|
||||
std::string getLabelText() override;
|
||||
std::string folder;
|
||||
const char* extension;
|
||||
int maxlen;
|
||||
std::function<std::string()> getValue;
|
||||
std::function<void(std::string)> setValue;
|
||||
bool stripExtension;
|
||||
|
||||
void loadFiles();
|
||||
|
||||
void openMenu() override;
|
||||
};
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright (C) EdgeTX
|
||||
*
|
||||
* Based on code named
|
||||
* libopenui - https://github.com/opentx/libopenui
|
||||
*
|
||||
* 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 "form.h"
|
||||
|
||||
#include "etx_lv_theme.h"
|
||||
|
||||
void FlexGridLayout::apply(Window* w)
|
||||
{
|
||||
w->padAll(padding);
|
||||
|
||||
// layout
|
||||
lv_obj_set_layout(w->getLvObj(), LV_LAYOUT_GRID);
|
||||
if (col_dsc && row_dsc) {
|
||||
lv_obj_set_grid_dsc_array(w->getLvObj(), col_dsc, row_dsc);
|
||||
}
|
||||
}
|
||||
|
||||
void FlexGridLayout::add(Window* w)
|
||||
{
|
||||
if (col_dsc && row_dsc) {
|
||||
lv_obj_set_grid_cell(w->getLvObj(), LV_GRID_ALIGN_START, col_pos, col_span,
|
||||
LV_GRID_ALIGN_CENTER, row_pos, row_span);
|
||||
}
|
||||
}
|
||||
|
||||
FormField::FormField(Window* parent, const rect_t& rect, LvglCreate objConstruct) :
|
||||
Window(parent, rect, objConstruct)
|
||||
{
|
||||
setTextFlag(textFlags);
|
||||
lv_obj_add_flag(lvobj, LV_OBJ_FLAG_SCROLL_ON_FOCUS);
|
||||
}
|
||||
|
||||
void FormField::setEditMode(bool newEditMode)
|
||||
{
|
||||
editMode = newEditMode;
|
||||
|
||||
if (lvobj != nullptr) {
|
||||
lv_group_t* grp = (lv_group_t*)lv_obj_get_group(lvobj);
|
||||
if (grp != nullptr) lv_group_set_editing(grp, editMode);
|
||||
}
|
||||
}
|
||||
|
||||
void FormField::onClicked()
|
||||
{
|
||||
lv_indev_type_t indev_type = lv_indev_get_type(lv_indev_get_act());
|
||||
if (indev_type != LV_INDEV_TYPE_POINTER) {
|
||||
setEditMode(!editMode);
|
||||
}
|
||||
}
|
||||
|
||||
void FormField::onCancel()
|
||||
{
|
||||
if (isEditMode()) {
|
||||
setEditMode(false);
|
||||
} else {
|
||||
Window::onCancel();
|
||||
}
|
||||
}
|
||||
|
||||
void FormField::deleteLater(bool detach, bool trash)
|
||||
{
|
||||
if (isEditMode()) setEditMode(false);
|
||||
Window::deleteLater(detach, trash);
|
||||
}
|
||||
|
||||
FormLine::FormLine(Window* parent, FlexGridLayout& layout) :
|
||||
Window(parent, rect_t{}), layout(layout)
|
||||
{
|
||||
setWindowFlag(NO_FOCUS);
|
||||
|
||||
layout.resetPos();
|
||||
layout.apply(this);
|
||||
|
||||
lv_obj_set_width(lvobj, lv_pct(100));
|
||||
lv_obj_set_height(lvobj, LV_SIZE_CONTENT);
|
||||
}
|
||||
|
||||
void FormLine::addChild(Window* window)
|
||||
{
|
||||
Window::addChild(window);
|
||||
layout.add(window);
|
||||
layout.nextCell();
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright (C) EdgeTX
|
||||
*
|
||||
* Based on code named
|
||||
* libopenui - https://github.com/opentx/libopenui
|
||||
*
|
||||
* 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 "etx_lv_theme.h"
|
||||
#include "window.h"
|
||||
|
||||
class FlexGridLayout
|
||||
{
|
||||
public:
|
||||
FlexGridLayout(const lv_coord_t col_dsc[], const lv_coord_t row_dsc[],
|
||||
PaddingSize padding = PAD_SMALL) :
|
||||
col_dsc(col_dsc), row_dsc(row_dsc), padding(padding)
|
||||
{
|
||||
}
|
||||
|
||||
FlexGridLayout(const FlexGridLayout& g) :
|
||||
col_dsc(g.col_dsc), row_dsc(g.row_dsc), padding(g.padding)
|
||||
{
|
||||
}
|
||||
|
||||
void apply(Window* w);
|
||||
|
||||
void add(Window* w);
|
||||
|
||||
void resetPos()
|
||||
{
|
||||
col_pos = 0;
|
||||
row_pos = 0;
|
||||
}
|
||||
|
||||
void nextColumn() { col_pos += col_span; }
|
||||
void nextRow()
|
||||
{
|
||||
row_pos += row_span;
|
||||
col_pos = 0;
|
||||
}
|
||||
|
||||
void nextCell()
|
||||
{
|
||||
nextColumn();
|
||||
if (col_dsc[col_pos] == LV_GRID_TEMPLATE_LAST) {
|
||||
nextRow();
|
||||
}
|
||||
}
|
||||
|
||||
void setColSpan(uint8_t span) { col_span = span; }
|
||||
void setRowSpan(uint8_t span) { row_span = span; }
|
||||
|
||||
protected:
|
||||
const lv_coord_t* col_dsc = nullptr;
|
||||
const lv_coord_t* row_dsc = nullptr;
|
||||
PaddingSize padding = PAD_ZERO;
|
||||
|
||||
uint8_t col_pos = 0;
|
||||
uint8_t col_span = 1;
|
||||
uint8_t row_pos = 0;
|
||||
uint8_t row_span = 1;
|
||||
};
|
||||
|
||||
class FormField : public Window
|
||||
{
|
||||
public:
|
||||
FormField(Window* parent, const rect_t& rect, LvglCreate objConstruct = nullptr);
|
||||
|
||||
virtual void changeEnd(bool forceChanged = false)
|
||||
{
|
||||
if (changeHandler) {
|
||||
changeHandler();
|
||||
}
|
||||
}
|
||||
|
||||
void setChangeHandler(std::function<void()> handler)
|
||||
{
|
||||
changeHandler = std::move(handler);
|
||||
}
|
||||
|
||||
inline bool isEditMode() const { return editMode; }
|
||||
virtual void setEditMode(bool newEditMode);
|
||||
|
||||
void onClicked() override;
|
||||
void onCancel() override;
|
||||
void deleteLater(bool detach = true, bool trash = true) override;
|
||||
|
||||
protected:
|
||||
bool editMode = false;
|
||||
bool enabled = true;
|
||||
std::function<void()> changeHandler = nullptr;
|
||||
};
|
||||
|
||||
class FormLine : public Window
|
||||
{
|
||||
public:
|
||||
FormLine(Window* parent, FlexGridLayout& layout);
|
||||
|
||||
protected:
|
||||
FlexGridLayout& layout;
|
||||
|
||||
void addChild(Window* window) override;
|
||||
};
|
||||
+1
-1
@@ -25,7 +25,7 @@
|
||||
#include "libopenui.h"
|
||||
#include "mainwindow.h"
|
||||
#include "opentx.h"
|
||||
#include "themes/etx_lv_theme.h"
|
||||
#include "etx_lv_theme.h"
|
||||
#include "view_main.h"
|
||||
#include "hal/watchdog_driver.h"
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
* Copyright (C) EdgeTX
|
||||
*
|
||||
* Based on code named
|
||||
* libopenui - https://github.com/opentx/libopenui
|
||||
*
|
||||
* 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 "keyboard_base.h"
|
||||
|
||||
#include "form.h"
|
||||
#include "mainwindow.h"
|
||||
#include "etx_lv_theme.h"
|
||||
|
||||
static void keyboard_constructor(const lv_obj_class_t* class_p, lv_obj_t* obj)
|
||||
{
|
||||
etx_solid_bg(obj, COLOR_THEME_SECONDARY3_INDEX);
|
||||
etx_obj_add_style(obj, styles->pad_tiny, LV_PART_MAIN);
|
||||
etx_obj_add_style(obj, styles->rounded, LV_PART_MAIN);
|
||||
|
||||
etx_std_style(obj, LV_PART_ITEMS, PAD_SMALL);
|
||||
etx_txt_color(obj, COLOR_THEME_PRIMARY1_INDEX, LV_PART_ITEMS);
|
||||
etx_txt_color(obj, COLOR_THEME_PRIMARY2_INDEX, LV_PART_ITEMS | LV_STATE_FOCUSED);
|
||||
etx_bg_color(obj, COLOR_THEME_FOCUS_INDEX, LV_PART_ITEMS | LV_STATE_EDITED);
|
||||
}
|
||||
|
||||
static const lv_obj_class_t keyboard_class = {
|
||||
.base_class = &lv_keyboard_class,
|
||||
.constructor_cb = keyboard_constructor,
|
||||
.destructor_cb = nullptr,
|
||||
.user_data = nullptr,
|
||||
.event_cb = nullptr,
|
||||
.width_def = 0,
|
||||
.height_def = 0,
|
||||
.editable = LV_OBJ_CLASS_EDITABLE_INHERIT,
|
||||
.group_def = LV_OBJ_CLASS_GROUP_DEF_INHERIT,
|
||||
.instance_size = sizeof(lv_keyboard_t),
|
||||
};
|
||||
|
||||
static lv_obj_t* keyboard_create(lv_obj_t* parent)
|
||||
{
|
||||
return etx_create(&keyboard_class, parent);
|
||||
}
|
||||
|
||||
Keyboard* Keyboard::activeKeyboard = nullptr;
|
||||
|
||||
static void keyboard_event_cb(lv_event_t* e)
|
||||
{
|
||||
auto code = lv_event_get_code(e);
|
||||
if (code == LV_EVENT_READY) {
|
||||
Keyboard::hide(false);
|
||||
} else if (code == LV_EVENT_CANCEL) {
|
||||
Keyboard::hide(true);
|
||||
} else if (code == LV_EVENT_KEY) {
|
||||
int32_t c = *((int32_t*)lv_event_get_param(e));
|
||||
if (c == LV_KEY_ESC) {
|
||||
Keyboard::hide(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void field_focus_leave(lv_event_t* e) { Keyboard::hide(false); }
|
||||
|
||||
static void _assign_lv_group(lv_group_t* g)
|
||||
{
|
||||
// associate it with all input devices
|
||||
lv_indev_t* indev = lv_indev_get_next(NULL);
|
||||
while (indev) {
|
||||
lv_indev_set_group(indev, g);
|
||||
indev = lv_indev_get_next(indev);
|
||||
}
|
||||
}
|
||||
|
||||
Keyboard::Keyboard(coord_t height) :
|
||||
NavWindow(MainWindow::instance(), {0, LCD_H - height, LCD_W, height})
|
||||
{
|
||||
lv_obj_set_parent(lvobj, lv_layer_top()); // the keyboard is always on top
|
||||
|
||||
// use a separate group for the keyboard
|
||||
group = lv_group_create();
|
||||
lv_group_set_editing(group, true);
|
||||
|
||||
auto old_g = lv_group_get_default();
|
||||
lv_group_set_default(group);
|
||||
|
||||
keyboard = keyboard_create(lvobj);
|
||||
lv_group_set_default(old_g);
|
||||
|
||||
lv_obj_add_event_cb(keyboard, keyboard_event_cb, LV_EVENT_ALL, this);
|
||||
|
||||
lv_obj_set_pos(keyboard, 0, 0);
|
||||
lv_obj_set_size(keyboard, LCD_W, height);
|
||||
|
||||
// TODO: really needed ???
|
||||
lv_obj_clear_flag(keyboard, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
|
||||
Keyboard::~Keyboard()
|
||||
{
|
||||
if (group) lv_group_del(group);
|
||||
}
|
||||
|
||||
void Keyboard::clearField(bool wasCancelled)
|
||||
{
|
||||
TRACE("CLEAR FIELD");
|
||||
if (keyboard != nullptr) {
|
||||
lv_obj_add_flag(keyboard, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
|
||||
detach();
|
||||
lv_obj_set_parent(lvobj, lv_layer_top());
|
||||
|
||||
if (fieldContainer) {
|
||||
lv_obj_scroll_to_y(fieldContainer->getLvObj(), scroll_pos, LV_ANIM_OFF);
|
||||
fieldContainer = nullptr;
|
||||
}
|
||||
|
||||
if (field) {
|
||||
// restore field's group
|
||||
auto obj = field->getLvObj();
|
||||
if (obj) {
|
||||
lv_obj_remove_event_cb(obj, field_focus_leave);
|
||||
}
|
||||
|
||||
if (!wasCancelled)
|
||||
field->setEditMode(false);
|
||||
field->changeEnd();
|
||||
field = nullptr;
|
||||
|
||||
if (fieldGroup) {
|
||||
_assign_lv_group(fieldGroup);
|
||||
lv_group_set_editing(fieldGroup, false);
|
||||
fieldGroup = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Keyboard::hide(bool wasCancelled)
|
||||
{
|
||||
if (activeKeyboard) {
|
||||
activeKeyboard->clearField(wasCancelled);
|
||||
lv_obj_add_flag(activeKeyboard->lvobj, LV_OBJ_FLAG_HIDDEN);
|
||||
activeKeyboard = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool Keyboard::attachKeyboard()
|
||||
{
|
||||
if (activeKeyboard) {
|
||||
if (activeKeyboard == this) return false;
|
||||
hide(false);
|
||||
}
|
||||
|
||||
activeKeyboard = this;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Keyboard::setField(FormField* newField)
|
||||
{
|
||||
if (!attachKeyboard()) return;
|
||||
|
||||
lv_obj_t* obj = newField->getLvObj();
|
||||
if (obj) {
|
||||
fieldContainer = newField->getFullScreenWindow();
|
||||
if (fieldContainer) {
|
||||
attach(fieldContainer);
|
||||
|
||||
lv_area_t coords;
|
||||
lv_obj_get_coords(obj, &coords);
|
||||
|
||||
// place keyboard bellow the field with some margin
|
||||
setTop(max(coords.y2 + 21, LCD_H - height()));
|
||||
|
||||
// save scroll position
|
||||
scroll_pos = lv_obj_get_scroll_y(fieldContainer->getLvObj());
|
||||
lv_obj_scroll_to_view(lvobj, LV_ANIM_OFF);
|
||||
|
||||
newField->setEditMode(true);
|
||||
|
||||
lv_keyboard_set_textarea(keyboard, obj);
|
||||
lv_obj_add_event_cb(obj, field_focus_leave, LV_EVENT_DEFOCUSED, nullptr);
|
||||
_assign_lv_group(group);
|
||||
|
||||
field = newField;
|
||||
fieldGroup = (lv_group_t*)lv_obj_get_group(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (C) EdgeTX
|
||||
*
|
||||
* Based on code named
|
||||
* libopenui - https://github.com/opentx/libopenui
|
||||
*
|
||||
* 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 "window.h"
|
||||
|
||||
class FormField;
|
||||
|
||||
class Keyboard : public NavWindow
|
||||
{
|
||||
public:
|
||||
explicit Keyboard(coord_t height);
|
||||
~Keyboard();
|
||||
|
||||
void clearField(bool wasCancelled);
|
||||
static void hide(bool wasCancelled);
|
||||
|
||||
static Keyboard* keyboardWindow() { return activeKeyboard; }
|
||||
|
||||
protected:
|
||||
static Keyboard *activeKeyboard;
|
||||
|
||||
lv_group_t* group = nullptr;
|
||||
lv_obj_t* keyboard = nullptr;
|
||||
|
||||
FormField* field = nullptr;
|
||||
Window* fieldContainer = nullptr;
|
||||
lv_group_t* fieldGroup = nullptr;
|
||||
lv_coord_t scroll_pos = 0;
|
||||
|
||||
void setField(FormField* newField);
|
||||
bool attachKeyboard();
|
||||
};
|
||||
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* Copyright (C) EdgeTX
|
||||
*
|
||||
* Based on code named
|
||||
* libopenui - https://github.com/opentx/libopenui
|
||||
*
|
||||
* 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 "keyboard_number.h"
|
||||
|
||||
#include "numberedit.h"
|
||||
|
||||
constexpr coord_t KEYBOARD_HEIGHT = 90;
|
||||
NumberKeyboard* NumberKeyboard::_instance = nullptr;
|
||||
|
||||
static const char* const number_kb_map[] = {"<<", "-", "+", ">>", "\n",
|
||||
"MIN", "DEF", "+/-", "MAX", ""};
|
||||
|
||||
#define LV_KB_BTN(width) LV_BTNMATRIX_CTRL_POPOVER | width
|
||||
#define LV_KB_CTRL(width) LV_KEYBOARD_CTRL_BTN_FLAGS | width
|
||||
|
||||
static const lv_btnmatrix_ctrl_t number_kb_ctrl_map[] = {
|
||||
LV_KB_BTN(4), LV_KB_BTN(4), LV_KB_BTN(4), LV_KB_BTN(4),
|
||||
LV_KB_BTN(4), LV_KB_BTN(4), LV_KB_CTRL(4), LV_KB_BTN(4)};
|
||||
|
||||
static void on_key(lv_event_t* e)
|
||||
{
|
||||
lv_obj_t* obj = lv_event_get_target(e);
|
||||
NumberKeyboard* edit = (NumberKeyboard*)lv_event_get_user_data(e);
|
||||
if (!obj || !edit) return;
|
||||
|
||||
uint16_t btn_id = lv_btnmatrix_get_selected_btn(obj);
|
||||
if (btn_id == LV_BTNMATRIX_BTN_NONE) return;
|
||||
|
||||
const char* txt =
|
||||
lv_btnmatrix_get_btn_text(obj, lv_btnmatrix_get_selected_btn(obj));
|
||||
if (txt == NULL) return;
|
||||
|
||||
edit->handleEvent(txt);
|
||||
}
|
||||
|
||||
void NumberKeyboard::handleEvent(const char* btn)
|
||||
{
|
||||
if (strcmp(btn, "<<") == 0)
|
||||
decLarge();
|
||||
else if (strcmp(btn, "-") == 0)
|
||||
decSmall();
|
||||
else if (strcmp(btn, "+") == 0)
|
||||
incSmall();
|
||||
else if (strcmp(btn, ">>") == 0)
|
||||
incLarge();
|
||||
else if (strcmp(btn, "MIN") == 0)
|
||||
setMIN();
|
||||
else if (strcmp(btn, "DEF") == 0)
|
||||
setDEF();
|
||||
else if (strcmp(btn, "MAX") == 0)
|
||||
setMAX();
|
||||
else if (strcmp(btn, "+/-") == 0)
|
||||
changeSign();
|
||||
}
|
||||
|
||||
void NumberKeyboard::decLarge()
|
||||
{
|
||||
((NumberEdit*)field)->onEvent(EVT_VIRTUAL_KEY_BACKWARD);
|
||||
}
|
||||
|
||||
void NumberKeyboard::decSmall()
|
||||
{
|
||||
((NumberEdit*)field)->onEvent(EVT_VIRTUAL_KEY_MINUS);
|
||||
}
|
||||
|
||||
void NumberKeyboard::incSmall()
|
||||
{
|
||||
((NumberEdit*)field)->onEvent(EVT_VIRTUAL_KEY_PLUS);
|
||||
}
|
||||
|
||||
void NumberKeyboard::incLarge()
|
||||
{
|
||||
((NumberEdit*)field)->onEvent(EVT_VIRTUAL_KEY_FORWARD);
|
||||
}
|
||||
|
||||
void NumberKeyboard::setMIN()
|
||||
{
|
||||
((NumberEdit*)field)->onEvent(EVT_VIRTUAL_KEY_MIN);
|
||||
}
|
||||
|
||||
void NumberKeyboard::setMAX()
|
||||
{
|
||||
((NumberEdit*)field)->onEvent(EVT_VIRTUAL_KEY_MAX);
|
||||
}
|
||||
|
||||
void NumberKeyboard::setDEF()
|
||||
{
|
||||
((NumberEdit*)field)->onEvent(EVT_VIRTUAL_KEY_DEFAULT);
|
||||
}
|
||||
|
||||
void NumberKeyboard::changeSign()
|
||||
{
|
||||
((NumberEdit*)field)->onEvent(EVT_VIRTUAL_KEY_SIGN);
|
||||
}
|
||||
|
||||
#if defined(HARDWARE_KEYS)
|
||||
|
||||
#if (defined(KEYS_GPIO_REG_PAGEUP) || defined(USE_HATS_AS_KEYS)) && \
|
||||
!defined(PCBX12S)
|
||||
// Radios with both PGUP and PGDN buttons except X12S
|
||||
void NumberKeyboard::onPressSYS() { decLarge(); }
|
||||
void NumberKeyboard::onLongPressSYS() { setMIN(); }
|
||||
void NumberKeyboard::onPressMDL() { incLarge(); }
|
||||
void NumberKeyboard::onLongPressMDL() { setMAX(); }
|
||||
void NumberKeyboard::onPressTELE() { changeSign(); }
|
||||
void NumberKeyboard::onLongPressTELE() { setDEF(); }
|
||||
void NumberKeyboard::onPressPGUP() { decSmall(); }
|
||||
void NumberKeyboard::onPressPGDN() { incSmall(); }
|
||||
void NumberKeyboard::onLongPressPGUP() {}
|
||||
void NumberKeyboard::onLongPressPGDN() {}
|
||||
#else
|
||||
// Radios witb only a single PGUP/DN button or X12S
|
||||
void NumberKeyboard::onPressSYS() { decSmall(); }
|
||||
void NumberKeyboard::onLongPressSYS() { setMIN(); }
|
||||
void NumberKeyboard::onPressMDL() { incLarge(); }
|
||||
void NumberKeyboard::onLongPressMDL() { changeSign(); }
|
||||
void NumberKeyboard::onPressTELE() { incSmall(); }
|
||||
void NumberKeyboard::onLongPressTELE() { setMAX(); }
|
||||
#if defined(PCBX12S)
|
||||
void NumberKeyboard::onPressPGUP() { decLarge(); }
|
||||
#else
|
||||
void NumberKeyboard::onPressPGUP() { setDEF(); }
|
||||
#endif
|
||||
void NumberKeyboard::onPressPGDN() { decLarge(); }
|
||||
void NumberKeyboard::onLongPressPGUP() { setDEF(); }
|
||||
void NumberKeyboard::onLongPressPGDN() { setDEF(); }
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
NumberKeyboard::NumberKeyboard() : Keyboard(KEYBOARD_HEIGHT)
|
||||
{
|
||||
// setup custom keyboard
|
||||
lv_keyboard_set_map(keyboard, LV_KEYBOARD_MODE_USER_1,
|
||||
(const char**)number_kb_map, number_kb_ctrl_map);
|
||||
|
||||
lv_keyboard_set_mode(keyboard, LV_KEYBOARD_MODE_USER_1);
|
||||
}
|
||||
|
||||
NumberKeyboard::~NumberKeyboard() { _instance = nullptr; }
|
||||
|
||||
void NumberKeyboard::show(FormField* field)
|
||||
{
|
||||
if (!_instance) _instance = new NumberKeyboard();
|
||||
|
||||
lv_obj_clear_flag(_instance->lvobj, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_clear_flag(_instance->keyboard, LV_OBJ_FLAG_HIDDEN);
|
||||
|
||||
_instance->setField(field);
|
||||
|
||||
auto kb = _instance->keyboard;
|
||||
lv_keyboard_set_textarea(kb, nullptr);
|
||||
|
||||
lv_obj_remove_event_cb(kb, on_key);
|
||||
lv_obj_add_event_cb(kb, on_key, LV_EVENT_VALUE_CHANGED, _instance);
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (C) EdgeTX
|
||||
*
|
||||
* Based on code named
|
||||
* libopenui - https://github.com/opentx/libopenui
|
||||
*
|
||||
* 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 "keyboard_base.h"
|
||||
|
||||
class NumberKeyboard : public Keyboard
|
||||
{
|
||||
public:
|
||||
NumberKeyboard();
|
||||
~NumberKeyboard() override;
|
||||
|
||||
#if defined(DEBUG_WINDOWS)
|
||||
std::string getName() const override { return "NumberKeyboard"; }
|
||||
#endif
|
||||
|
||||
static void show(FormField* field);
|
||||
|
||||
void handleEvent(const char* btn);
|
||||
|
||||
protected:
|
||||
void decLarge();
|
||||
void decSmall();
|
||||
void incSmall();
|
||||
void incLarge();
|
||||
void setMIN();
|
||||
void setMAX();
|
||||
void setDEF();
|
||||
void changeSign();
|
||||
|
||||
#if defined(HARDWARE_KEYS)
|
||||
void onPressSYS() override;
|
||||
void onLongPressSYS() override;
|
||||
void onPressMDL() override;
|
||||
void onLongPressMDL() override;
|
||||
void onPressTELE() override;
|
||||
void onLongPressTELE() override;
|
||||
void onPressPGUP() override;
|
||||
void onPressPGDN() override;
|
||||
void onLongPressPGUP() override;
|
||||
void onLongPressPGDN() override;
|
||||
#endif
|
||||
|
||||
static NumberKeyboard* _instance;
|
||||
};
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright (C) EdgeTX
|
||||
*
|
||||
* Based on code named
|
||||
* libopenui - https://github.com/opentx/libopenui
|
||||
*
|
||||
* 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 "keyboard_text.h"
|
||||
|
||||
#include "textedit.h"
|
||||
|
||||
constexpr coord_t KEYBOARD_HEIGHT = LCD_H * 2 / 5;
|
||||
TextKeyboard* TextKeyboard::_instance = nullptr;
|
||||
|
||||
TextKeyboard::TextKeyboard() : Keyboard(KEYBOARD_HEIGHT) {}
|
||||
|
||||
TextKeyboard::~TextKeyboard() { _instance = nullptr; }
|
||||
|
||||
#if defined(HARDWARE_KEYS)
|
||||
|
||||
void TextKeyboard::changeMode()
|
||||
{
|
||||
// Change keyboard mode
|
||||
lv_keyboard_mode_t mode = lv_keyboard_get_mode(keyboard);
|
||||
mode = (mode + 1) & 3;
|
||||
lv_keyboard_set_mode(keyboard, mode);
|
||||
}
|
||||
|
||||
void TextKeyboard::backspace()
|
||||
{
|
||||
lv_keyboard_t* kb = (lv_keyboard_t*)keyboard;
|
||||
// Backspace
|
||||
lv_textarea_del_char(kb->ta);
|
||||
}
|
||||
|
||||
void TextKeyboard::toggleCase()
|
||||
{
|
||||
lv_keyboard_t* kb = (lv_keyboard_t*)keyboard;
|
||||
// Toggle case
|
||||
char c = lv_textarea_get_text(kb->ta)[lv_textarea_get_cursor_pos(kb->ta)];
|
||||
if (((c >= 'A') && (c <= 'Z')) || ((c >= 'a') && (c <= 'z'))) {
|
||||
c ^= 0x20;
|
||||
lv_textarea_del_char_forward(kb->ta);
|
||||
lv_textarea_add_char(kb->ta, c);
|
||||
lv_textarea_cursor_left(kb->ta);
|
||||
}
|
||||
}
|
||||
|
||||
void TextKeyboard::deleteChar()
|
||||
{
|
||||
lv_keyboard_t* kb = (lv_keyboard_t*)keyboard;
|
||||
// Delete
|
||||
lv_textarea_del_char_forward(kb->ta);
|
||||
}
|
||||
|
||||
void TextKeyboard::cursorLeft()
|
||||
{
|
||||
lv_keyboard_t* kb = (lv_keyboard_t*)keyboard;
|
||||
// Cursor left
|
||||
lv_textarea_cursor_left(kb->ta);
|
||||
}
|
||||
|
||||
void TextKeyboard::cursorRight()
|
||||
{
|
||||
lv_keyboard_t* kb = (lv_keyboard_t*)keyboard;
|
||||
// Cursor right
|
||||
lv_textarea_cursor_right(kb->ta);
|
||||
}
|
||||
|
||||
void TextKeyboard::cursorStart()
|
||||
{
|
||||
lv_keyboard_t* kb = (lv_keyboard_t*)keyboard;
|
||||
// Cursor to start
|
||||
while (lv_textarea_get_cursor_pos(kb->ta) > 0)
|
||||
lv_textarea_cursor_left(kb->ta);
|
||||
}
|
||||
|
||||
void TextKeyboard::cursorEnd()
|
||||
{
|
||||
lv_keyboard_t* kb = (lv_keyboard_t*)keyboard;
|
||||
// Cursor to end
|
||||
size_t l = strlen(lv_textarea_get_text(kb->ta));
|
||||
while (lv_textarea_get_cursor_pos(kb->ta) < l)
|
||||
lv_textarea_cursor_right(kb->ta);
|
||||
}
|
||||
|
||||
#if (defined(KEYS_GPIO_REG_PAGEUP) || defined(USE_HATS_AS_KEYS)) && \
|
||||
!defined(PCBX12S)
|
||||
// Radios with both PGUP and PGDN buttons except X12S
|
||||
void TextKeyboard::onPressSYS() { changeMode(); }
|
||||
void TextKeyboard::onLongPressSYS() {}
|
||||
void TextKeyboard::onPressMDL() {}
|
||||
void TextKeyboard::onLongPressMDL() { backspace(); }
|
||||
void TextKeyboard::onPressTELE() { toggleCase(); }
|
||||
void TextKeyboard::onLongPressTELE() { deleteChar(); }
|
||||
void TextKeyboard::onPressPGUP() { cursorLeft(); }
|
||||
void TextKeyboard::onPressPGDN() { cursorRight(); }
|
||||
void TextKeyboard::onLongPressPGUP() { cursorStart(); }
|
||||
void TextKeyboard::onLongPressPGDN() { cursorEnd(); }
|
||||
#else
|
||||
// Radios witb only a single PGUP/DN button or X12S
|
||||
void TextKeyboard::onPressSYS() { cursorLeft(); }
|
||||
void TextKeyboard::onLongPressSYS() { cursorStart(); }
|
||||
void TextKeyboard::onPressMDL() { changeMode(); }
|
||||
void TextKeyboard::onLongPressMDL() { backspace(); }
|
||||
void TextKeyboard::onPressTELE() { cursorRight(); }
|
||||
void TextKeyboard::onLongPressTELE() { cursorEnd(); }
|
||||
#if defined(PCBX12S)
|
||||
void TextKeyboard::onPressPGUP() { toggleCase(); }
|
||||
#else
|
||||
void TextKeyboard::onPressPGUP() { deleteChar(); }
|
||||
#endif
|
||||
void TextKeyboard::onPressPGDN() { toggleCase(); }
|
||||
void TextKeyboard::onLongPressPGUP() {}
|
||||
void TextKeyboard::onLongPressPGDN() { deleteChar(); }
|
||||
#endif
|
||||
#endif
|
||||
|
||||
void TextKeyboard::show(FormField* field)
|
||||
{
|
||||
if (!_instance) _instance = new TextKeyboard();
|
||||
|
||||
lv_obj_clear_flag(_instance->lvobj, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_clear_flag(_instance->keyboard, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_keyboard_set_mode(_instance->keyboard, LV_KEYBOARD_MODE_TEXT_LOWER);
|
||||
|
||||
_instance->setField(field);
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright (C) EdgeTX
|
||||
*
|
||||
* Based on code named
|
||||
* libopenui - https://github.com/opentx/libopenui
|
||||
*
|
||||
* 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 "keyboard_base.h"
|
||||
|
||||
#define KEYBOARD_SPACE "\t"
|
||||
#define KEYBOARD_ENTER "\n"
|
||||
#define KEYBOARD_BACKSPACE "\200"
|
||||
#define KEYBOARD_SET_UPPERCASE "\201"
|
||||
#define KEYBOARD_SET_LOWERCASE "\202"
|
||||
#define KEYBOARD_SET_LETTERS "\203"
|
||||
#define KEYBOARD_SET_NUMBERS "\204"
|
||||
|
||||
#define KEYBOARD_OFFSET_Y 15
|
||||
#define KEYBOARD_ROW_HEIGHT 40
|
||||
#define KEYBOARD_CHAR_WIDTH 30
|
||||
#define KEYBOARD_SPACE_WIDTH 135
|
||||
#define KEYBOARD_ENTER_WIDTH 80
|
||||
#define KEYBOARD_BITMAP_WIDTH 45
|
||||
|
||||
class TextKeyboard : public Keyboard
|
||||
{
|
||||
public:
|
||||
TextKeyboard();
|
||||
~TextKeyboard() override;
|
||||
|
||||
#if defined(DEBUG_WINDOWS)
|
||||
std::string getName() const override { return "TextKeyboard"; }
|
||||
#endif
|
||||
|
||||
static void show(FormField* field);
|
||||
|
||||
protected:
|
||||
#if defined(HARDWARE_KEYS)
|
||||
void changeMode();
|
||||
void cursorLeft();
|
||||
void cursorRight();
|
||||
void cursorStart();
|
||||
void cursorEnd();
|
||||
void toggleCase();
|
||||
void deleteChar();
|
||||
void backspace();
|
||||
|
||||
void onPressSYS() override;
|
||||
void onLongPressSYS() override;
|
||||
void onPressMDL() override;
|
||||
void onLongPressMDL() override;
|
||||
void onPressTELE() override;
|
||||
void onLongPressTELE() override;
|
||||
void onPressPGUP() override;
|
||||
void onPressPGDN() override;
|
||||
void onLongPressPGUP() override;
|
||||
void onLongPressPGDN() override;
|
||||
#endif
|
||||
|
||||
static TextKeyboard* _instance;
|
||||
};
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright (C) EdgeTX
|
||||
*
|
||||
* Based on code named
|
||||
* libopenui - https://github.com/opentx/libopenui
|
||||
*
|
||||
* 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 "layer.h"
|
||||
|
||||
std::list<Layer> Layer::stack;
|
||||
|
||||
Layer::Layer(Window* w, lv_group_t* g) : window(w), group(g) {}
|
||||
Layer::~Layer() { lv_group_del(group); }
|
||||
|
||||
static void _assign_lv_group(lv_group_t* g)
|
||||
{
|
||||
lv_group_set_default(g);
|
||||
|
||||
// associate it with all input devices
|
||||
lv_indev_t* indev = lv_indev_get_next(NULL);
|
||||
while (indev) {
|
||||
lv_indev_set_group(indev, g);
|
||||
indev = lv_indev_get_next(indev);
|
||||
}
|
||||
}
|
||||
|
||||
void Layer::push(Window* w)
|
||||
{
|
||||
// create a new group
|
||||
auto g = lv_group_create();
|
||||
_assign_lv_group(g);
|
||||
|
||||
// and store
|
||||
stack.emplace_back(w, g);
|
||||
}
|
||||
|
||||
void Layer::pop(Window* w)
|
||||
{
|
||||
if (stack.empty()) return;
|
||||
|
||||
if (back() == w) {
|
||||
stack.pop_back();
|
||||
} else {
|
||||
for (auto layer = stack.crbegin(); layer != stack.crend(); layer++) {
|
||||
if (layer->window == w) {
|
||||
stack.erase(layer.base());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!stack.empty()) {
|
||||
lv_group_t* g = stack.back().group;
|
||||
_assign_lv_group(g);
|
||||
} else {
|
||||
lv_group_set_default(NULL);
|
||||
}
|
||||
}
|
||||
|
||||
Window* Layer::back()
|
||||
{
|
||||
if (stack.empty()) return nullptr;
|
||||
return stack.back().window;
|
||||
}
|
||||
|
||||
Window* Layer::getFirstOpaque()
|
||||
{
|
||||
for (auto layer = stack.crbegin(); layer != stack.crend(); layer++) {
|
||||
auto w = layer->window;
|
||||
if (w && w->hasWindowFlag(OPAQUE)) return w;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (C) EdgeTX
|
||||
*
|
||||
* Based on code named
|
||||
* libopenui - https://github.com/opentx/libopenui
|
||||
*
|
||||
* 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 "window.h"
|
||||
|
||||
class Layer
|
||||
{
|
||||
static std::list<Layer> stack;
|
||||
|
||||
Window* window;
|
||||
lv_group_t* group;
|
||||
|
||||
public:
|
||||
explicit Layer(Window* w, lv_group_t* g);
|
||||
~Layer();
|
||||
|
||||
static void push(Window* window);
|
||||
static void pop(Window* window);
|
||||
|
||||
static Window* back();
|
||||
static Window* getFirstOpaque();
|
||||
};
|
||||
@@ -23,7 +23,7 @@
|
||||
|
||||
#if !defined(BOOT)
|
||||
#include "libopenui_defines.h"
|
||||
#include "libopenui_file.h"
|
||||
#include "lib_file.h"
|
||||
#include "myeeprom.h"
|
||||
#include "fonts.h"
|
||||
#include "window.h"
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (C) EdgeTX
|
||||
*
|
||||
* Based on code named
|
||||
* libopenui - https://github.com/opentx/libopenui
|
||||
*
|
||||
* 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 "debug.h"
|
||||
#include "hal.h"
|
||||
#include "opentx_types.h"
|
||||
#include "colors.h"
|
||||
#include "keys.h"
|
||||
|
||||
// Used by Lua API
|
||||
#define INVERS 0x01u
|
||||
#define BLINK 0x1000u
|
||||
#define TIMEHOUR 0x2000u
|
||||
|
||||
/* drawText flags */
|
||||
#define LEFT 0x00u /* align left */
|
||||
#define VCENTERED 0x02u /* align center vertically */
|
||||
#define CENTERED 0x04u /* align center */
|
||||
#define RIGHT 0x08u /* align right */
|
||||
#define SHADOWED 0x80u /* black copy at +1 +1 */
|
||||
// 0x1000u used by Lua in api_colorlcd.h
|
||||
// 0x8000u used by Lua in api_colorlcd.h
|
||||
|
||||
/* drawNumber flags */
|
||||
#define LEADING0 0x10u
|
||||
#define PREC1 0x20u
|
||||
#define PREC2 0x30u
|
||||
#define MODE(flags) ((((int8_t)(flags) & 0x30) - 0x10) >> 4)
|
||||
|
||||
/* telemetry flags */
|
||||
#define NO_UNIT 0x40u
|
||||
|
||||
#define LV_OBJ_FLAG_ENCODER_ACCEL LV_OBJ_FLAG_USER_1
|
||||
+1
-1
@@ -24,7 +24,7 @@
|
||||
#include <algorithm>
|
||||
|
||||
#include "opentx.h"
|
||||
#include "themes/etx_lv_theme.h"
|
||||
#include "etx_lv_theme.h"
|
||||
|
||||
static void input_mix_line_constructor(const lv_obj_class_t* class_p,
|
||||
lv_obj_t* obj)
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright (C) EdgeTX
|
||||
*
|
||||
* Based on code named
|
||||
* libopenui - https://github.com/opentx/libopenui
|
||||
*
|
||||
* 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 "mainwindow.h"
|
||||
|
||||
#include "board.h"
|
||||
#include "keyboard_base.h"
|
||||
#include "layout.h"
|
||||
#include "etx_lv_theme.h"
|
||||
#include "sdcard.h"
|
||||
|
||||
// timers_driver.h
|
||||
uint32_t timersGetMsTick();
|
||||
|
||||
MainWindow* MainWindow::_instance = nullptr;
|
||||
|
||||
MainWindow* MainWindow::instance()
|
||||
{
|
||||
if (!_instance) _instance = new MainWindow();
|
||||
return _instance;
|
||||
}
|
||||
|
||||
MainWindow::MainWindow() : Window(nullptr, {0, 0, LCD_W, LCD_H})
|
||||
{
|
||||
setWindowFlag(OPAQUE);
|
||||
|
||||
etx_solid_bg(lvobj);
|
||||
|
||||
background = lv_canvas_create(lvobj);
|
||||
lv_obj_center(background);
|
||||
}
|
||||
|
||||
void MainWindow::emptyTrash()
|
||||
{
|
||||
for (auto window : trash) {
|
||||
delete window;
|
||||
}
|
||||
trash.clear();
|
||||
}
|
||||
|
||||
void MainWindow::run(bool trash)
|
||||
{
|
||||
auto start = timersGetMsTick();
|
||||
|
||||
auto opaque = Layer::getFirstOpaque();
|
||||
if (opaque) {
|
||||
opaque->checkEvents();
|
||||
}
|
||||
|
||||
auto copy = children;
|
||||
for (auto child : copy) {
|
||||
if (!child->deleted() && child->isBubblePopup()) {
|
||||
child->checkEvents();
|
||||
}
|
||||
}
|
||||
|
||||
if (trash) emptyTrash();
|
||||
|
||||
auto delta = timersGetMsTick() - start;
|
||||
if (delta > 10) {
|
||||
TRACE_WINDOWS("MainWindow::run took %dms", delta);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::shutdown()
|
||||
{
|
||||
// Called when USB is connected in SD card mode
|
||||
|
||||
// Delete main view screens
|
||||
LayoutFactory::deleteCustomScreens();
|
||||
|
||||
// clear layer stack first
|
||||
for (Window* w = Layer::back(); w; w = Layer::back()) w->deleteLater();
|
||||
|
||||
children.clear();
|
||||
clear();
|
||||
emptyTrash();
|
||||
|
||||
// Re-add background canvas
|
||||
background = lv_canvas_create(lvobj);
|
||||
lv_obj_center(background);
|
||||
}
|
||||
|
||||
void MainWindow::setBackgroundImage(const char* fileName)
|
||||
{
|
||||
// ensure you delete old bitmap
|
||||
if (backgroundBitmap != nullptr) delete backgroundBitmap;
|
||||
|
||||
if (fileName == nullptr) fileName = "";
|
||||
|
||||
backgroundImageFileName = fileName;
|
||||
|
||||
// Try to load bitmap. If this fails backgroundBitmap will be NULL and default
|
||||
// will be loaded in update() method
|
||||
backgroundBitmap =
|
||||
BitmapBuffer::loadBitmap(backgroundImageFileName.c_str(), BMP_RGB565);
|
||||
|
||||
if (!backgroundBitmap) {
|
||||
backgroundBitmap = BitmapBuffer::loadBitmap(
|
||||
THEMES_PATH "/EdgeTX/background.png", BMP_RGB565);
|
||||
}
|
||||
|
||||
if (backgroundBitmap) {
|
||||
lv_canvas_set_buffer(background, backgroundBitmap->getData(), backgroundBitmap->width(),
|
||||
backgroundBitmap->height(), LV_IMG_CF_TRUE_COLOR);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (C) EdgeTX
|
||||
*
|
||||
* Based on code named
|
||||
* libopenui - https://github.com/opentx/libopenui
|
||||
*
|
||||
* 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 "layer.h"
|
||||
#include "window.h"
|
||||
|
||||
class MainWindow: public Window
|
||||
{
|
||||
protected:
|
||||
// singleton
|
||||
MainWindow();
|
||||
|
||||
public:
|
||||
static MainWindow * instance();
|
||||
|
||||
#if defined(TESTS)
|
||||
static void create()
|
||||
{
|
||||
_instance = new MainWindow();
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(DEBUG_WINDOWS)
|
||||
std::string getName() const override
|
||||
{
|
||||
return "MainWindow";
|
||||
}
|
||||
#endif
|
||||
|
||||
void setActiveScreen() {
|
||||
lv_scr_load(lvobj);
|
||||
}
|
||||
|
||||
void run(bool trash=true);
|
||||
|
||||
void setBackgroundImage(const char* fileName);
|
||||
|
||||
void shutdown();
|
||||
|
||||
protected:
|
||||
lv_obj_t* background = nullptr;
|
||||
std::string backgroundImageFileName;
|
||||
const BitmapBuffer *backgroundBitmap = nullptr;
|
||||
|
||||
static MainWindow * _instance;
|
||||
static void emptyTrash();
|
||||
};
|
||||
@@ -0,0 +1,464 @@
|
||||
/*
|
||||
* Copyright (C) EdgeTX
|
||||
*
|
||||
* Based on code named
|
||||
* libopenui - https://github.com/opentx/libopenui
|
||||
*
|
||||
* 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 "menu.h"
|
||||
|
||||
#include <lvgl/lvgl.h>
|
||||
|
||||
#include "menutoolbar.h"
|
||||
#include "opentx.h"
|
||||
#include "table.h"
|
||||
#include "etx_lv_theme.h"
|
||||
|
||||
constexpr coord_t MENUS_WIDTH = 200;
|
||||
|
||||
static void _force_editing(lv_group_t* g) { lv_group_set_editing(g, true); }
|
||||
|
||||
class MenuBody : public TableField
|
||||
{
|
||||
friend class MenuWindowContent;
|
||||
friend class Menu;
|
||||
|
||||
enum MENU_DIRECTION { DIRECTION_UP = 1, DIRECTION_DOWN = -1 };
|
||||
|
||||
class MenuLine
|
||||
{
|
||||
friend class MenuBody;
|
||||
|
||||
public:
|
||||
MenuLine(const std::string& text, std::function<void()> onPress,
|
||||
std::function<bool()> isChecked, lv_obj_t* icon) :
|
||||
text(text),
|
||||
onPress(std::move(onPress)),
|
||||
isChecked(std::move(isChecked)),
|
||||
icon(icon)
|
||||
{
|
||||
}
|
||||
|
||||
~MenuLine()
|
||||
{
|
||||
if (icon)
|
||||
lv_obj_del(icon);
|
||||
}
|
||||
|
||||
lv_obj_t* getIcon() const { return icon; }
|
||||
|
||||
protected:
|
||||
std::string text;
|
||||
std::function<void()> onPress;
|
||||
std::function<bool()> isChecked;
|
||||
lv_obj_t* icon;
|
||||
};
|
||||
|
||||
public:
|
||||
MenuBody(Window* parent, const rect_t& rect) :
|
||||
TableField(parent, rect)
|
||||
{
|
||||
// Allow encoder acceleration
|
||||
lv_obj_add_flag(lvobj, LV_OBJ_FLAG_ENCODER_ACCEL);
|
||||
// Add scroll bar if needed
|
||||
etx_scrollbar(lvobj);
|
||||
|
||||
setColumnWidth(0, rect.w);
|
||||
|
||||
lv_group_t* g = (lv_group_t*)lv_obj_get_group(lvobj);
|
||||
if (g) {
|
||||
setFocusHandler([=](bool focus) {
|
||||
if (focus) {
|
||||
lv_group_set_focus_cb(g, _force_editing);
|
||||
} else {
|
||||
lv_group_set_focus_cb(g, nullptr);
|
||||
}
|
||||
});
|
||||
lv_group_set_editing(g, true);
|
||||
}
|
||||
}
|
||||
|
||||
~MenuBody()
|
||||
{
|
||||
clearLines();
|
||||
}
|
||||
|
||||
#if defined(DEBUG_WINDOWS)
|
||||
std::string getName() const override { return "MenuBody"; }
|
||||
#endif
|
||||
|
||||
void setIndex(int index)
|
||||
{
|
||||
if (index < (int)lines.size()) {
|
||||
if (index == selectedIndex) return;
|
||||
selectedIndex = index;
|
||||
|
||||
lv_obj_invalidate(lvobj);
|
||||
lv_table_t* table = (lv_table_t*)lvobj;
|
||||
|
||||
if (index < 0) {
|
||||
table->row_act = LV_TABLE_CELL_NONE;
|
||||
table->col_act = LV_TABLE_CELL_NONE;
|
||||
return;
|
||||
}
|
||||
|
||||
table->row_act = index;
|
||||
table->col_act = 0;
|
||||
|
||||
lv_coord_t h_before = 0;
|
||||
for (uint16_t i = 0; i < table->row_act; i++) h_before += table->row_h[i];
|
||||
|
||||
lv_coord_t row_h = table->row_h[table->row_act];
|
||||
lv_coord_t scroll_y = lv_obj_get_scroll_y(lvobj);
|
||||
|
||||
lv_obj_update_layout(lvobj);
|
||||
lv_coord_t h = lv_obj_get_height(lvobj);
|
||||
|
||||
lv_coord_t diff_y = 0;
|
||||
if (h_before < scroll_y) {
|
||||
diff_y = scroll_y - h_before;
|
||||
} else if (scroll_y + h < h_before + row_h) {
|
||||
diff_y = scroll_y + h - h_before - row_h;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
lv_obj_scroll_by_bounded(lvobj, 0, diff_y, LV_ANIM_OFF);
|
||||
}
|
||||
}
|
||||
|
||||
int selection() const { return selectedIndex; }
|
||||
|
||||
int count() const { return lines.size(); }
|
||||
|
||||
void onEvent(event_t event) override
|
||||
{
|
||||
#if defined(HARDWARE_KEYS)
|
||||
if (event == EVT_KEY_BREAK(KEY_EXIT)) {
|
||||
onCancel();
|
||||
} else {
|
||||
TableField::onEvent(event);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void addLine(const uint8_t* icon_mask, const std::string& text,
|
||||
std::function<void()> onPress, std::function<bool()> isChecked,
|
||||
bool update = true)
|
||||
{
|
||||
lv_obj_t* canvas = nullptr;
|
||||
if (icon_mask) {
|
||||
canvas = lv_canvas_create(nullptr);
|
||||
|
||||
lv_coord_t w = *((uint16_t*)icon_mask);
|
||||
lv_coord_t h = *(((uint16_t*)icon_mask) + 1);
|
||||
void* buf = (void*)(icon_mask + 4);
|
||||
lv_canvas_set_buffer(canvas, buf, w, h, LV_IMG_CF_ALPHA_8BIT);
|
||||
}
|
||||
|
||||
auto l = new MenuLine(text, onPress, isChecked, canvas);
|
||||
lines.push_back(l);
|
||||
|
||||
if (update) {
|
||||
auto idx = lines.size() - 1;
|
||||
lv_table_set_cell_value(lvobj, idx, 0, text.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void updateLines()
|
||||
{
|
||||
setRowCount(lines.size());
|
||||
for (unsigned int idx = 0; idx < lines.size(); idx++) {
|
||||
lv_table_set_cell_value(lvobj, idx, 0, lines[idx]->text.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void clearLines()
|
||||
{
|
||||
for (auto itr = lines.begin(); itr != lines.end();) {
|
||||
auto l = *itr;
|
||||
itr = lines.erase(itr);
|
||||
delete l;
|
||||
}
|
||||
lines.clear();
|
||||
}
|
||||
|
||||
void removeLines()
|
||||
{
|
||||
clearLines();
|
||||
setRowCount(0);
|
||||
selectedIndex = 0;
|
||||
|
||||
// reset vertical scroll
|
||||
lv_obj_scroll_to_y(lvobj, 0, LV_ANIM_OFF);
|
||||
}
|
||||
|
||||
void onPress(uint16_t row, uint16_t col) override
|
||||
{
|
||||
if (!isLongPressed) {
|
||||
Menu* menu = getParentMenu();
|
||||
if (row < lines.size()) {
|
||||
if (menu->multiple) {
|
||||
if (selectedIndex == (int)row)
|
||||
lines[row]->onPress();
|
||||
else {
|
||||
setIndex(row);
|
||||
lines[row]->onPress();
|
||||
}
|
||||
} else {
|
||||
// delete menu first to avoid
|
||||
// focus issues with onPress()
|
||||
menu->deleteLater();
|
||||
lines[row]->onPress();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isLongPressed = false;
|
||||
}
|
||||
|
||||
void onDrawBegin(uint16_t row, uint16_t col,
|
||||
lv_obj_draw_part_dsc_t* dsc) override
|
||||
{
|
||||
lv_canvas_t* icon = (lv_canvas_t*)lines[row]->getIcon();
|
||||
if (!icon) return;
|
||||
|
||||
lv_img_t* img = &icon->img;
|
||||
lv_coord_t cell_left = lv_obj_get_style_pad_left(lvobj, LV_PART_ITEMS);
|
||||
dsc->label_dsc->ofs_x = img->w + cell_left;
|
||||
}
|
||||
|
||||
void onDrawEnd(uint16_t row, uint16_t col,
|
||||
lv_obj_draw_part_dsc_t* dsc) override
|
||||
{
|
||||
if (row >= lines.size()) return;
|
||||
|
||||
lv_obj_t* icon = lines[row]->getIcon();
|
||||
if (icon) {
|
||||
lv_draw_img_dsc_t img_dsc;
|
||||
lv_draw_img_dsc_init(&img_dsc);
|
||||
|
||||
lv_img_dsc_t* img = lv_canvas_get_img(icon);
|
||||
lv_area_t coords;
|
||||
|
||||
lv_coord_t area_h = lv_area_get_height(dsc->draw_area);
|
||||
|
||||
lv_coord_t cell_left = lv_obj_get_style_pad_left(lvobj, LV_PART_ITEMS);
|
||||
coords.x1 = dsc->draw_area->x1 + cell_left;
|
||||
coords.x2 = coords.x1 + img->header.w - 1;
|
||||
coords.y1 = dsc->draw_area->y1 + (area_h - img->header.h) / 2;
|
||||
coords.y2 = coords.y1 + img->header.h - 1;
|
||||
|
||||
lv_draw_img(dsc->draw_ctx, &img_dsc, &coords, img);
|
||||
}
|
||||
|
||||
if (lines[row]->isChecked != nullptr && lines[row]->isChecked()) {
|
||||
lv_area_t coords;
|
||||
lv_coord_t area_h = lv_area_get_height(dsc->draw_area);
|
||||
lv_coord_t cell_right = lv_obj_get_style_pad_right(lvobj, LV_PART_ITEMS);
|
||||
lv_coord_t font_h = getFontHeight(FONT(STD));
|
||||
coords.x1 = dsc->draw_area->x2 - cell_right - font_h;
|
||||
coords.x2 = coords.x1 + font_h;
|
||||
coords.y1 = dsc->draw_area->y1 + (area_h - font_h) / 2;
|
||||
coords.y2 = coords.y1 + font_h - 1;
|
||||
lv_draw_label(dsc->draw_ctx, dsc->label_dsc, &coords, LV_SYMBOL_OK,
|
||||
nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void onSelected(uint16_t row, uint16_t col) override { selectedIndex = row; }
|
||||
|
||||
protected:
|
||||
bool isLongPressed = false;
|
||||
|
||||
std::vector<MenuLine*> lines;
|
||||
int selectedIndex = 0;
|
||||
|
||||
Menu* getParentMenu() { return static_cast<Menu*>(getParent()->getParent()); }
|
||||
};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
static void menu_content_constructor(const lv_obj_class_t* class_p,
|
||||
lv_obj_t* obj)
|
||||
{
|
||||
etx_solid_bg(obj);
|
||||
etx_obj_add_style(obj, styles->outline, LV_PART_MAIN);
|
||||
etx_obj_add_style(obj, styles->outline_color_normal, LV_PART_MAIN);
|
||||
}
|
||||
|
||||
static const lv_obj_class_t menu_content_class = {
|
||||
.base_class = &window_base_class,
|
||||
.constructor_cb = menu_content_constructor,
|
||||
.destructor_cb = nullptr,
|
||||
.user_data = nullptr,
|
||||
.event_cb = nullptr,
|
||||
.width_def = 0,
|
||||
.height_def = 0,
|
||||
.editable = LV_OBJ_CLASS_EDITABLE_INHERIT,
|
||||
.group_def = LV_OBJ_CLASS_EDITABLE_INHERIT,
|
||||
.instance_size = sizeof(lv_obj_t)};
|
||||
|
||||
static lv_obj_t* menu_content_create(lv_obj_t* parent)
|
||||
{
|
||||
return etx_create(&menu_content_class, parent);
|
||||
}
|
||||
|
||||
class MenuWindowContent : public Window
|
||||
{
|
||||
public:
|
||||
explicit MenuWindowContent(Menu* parent) :
|
||||
Window(parent, rect_t{}, menu_content_create)
|
||||
{
|
||||
setWindowFlag(OPAQUE);
|
||||
|
||||
lv_obj_center(lvobj);
|
||||
setFlexLayout(LV_FLEX_FLOW_COLUMN, PAD_ZERO, MENUS_WIDTH, LV_SIZE_CONTENT);
|
||||
|
||||
header = new StaticText(this, {0, 0, LV_PCT(100), 0}, "",
|
||||
COLOR_THEME_PRIMARY2_INDEX);
|
||||
etx_solid_bg(header->getLvObj(), COLOR_THEME_SECONDARY1_INDEX);
|
||||
header->padAll(PAD_SMALL);
|
||||
header->hide();
|
||||
|
||||
body = new MenuBody(this, rect_t{0, 0, MENUS_WIDTH, LV_SIZE_CONTENT});
|
||||
lv_obj_set_style_max_height(body->getLvObj(), LCD_H * 0.8, LV_PART_MAIN);
|
||||
}
|
||||
|
||||
virtual void setTitle(const std::string& text)
|
||||
{
|
||||
header->setText(text);
|
||||
header->show();
|
||||
}
|
||||
|
||||
void onClicked() override { Keyboard::hide(false); }
|
||||
|
||||
#if defined(DEBUG_WINDOWS)
|
||||
std::string getName() const override { return "MenuWindowContent"; }
|
||||
#endif
|
||||
|
||||
void updateLines() { body->updateLines(); }
|
||||
void removeLines() { body->removeLines(); }
|
||||
int count() { return body->count(); }
|
||||
int selection() { return body->selection(); }
|
||||
void setIndex(int index) { body->setIndex(index); }
|
||||
|
||||
void addLine(const uint8_t* icon_mask, const std::string& text,
|
||||
std::function<void()> onPress, std::function<bool()> isChecked,
|
||||
bool update = true)
|
||||
{
|
||||
body->addLine(icon_mask, text, onPress, isChecked, update);
|
||||
}
|
||||
|
||||
protected:
|
||||
StaticText* header = nullptr;
|
||||
MenuBody* body = nullptr;
|
||||
};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
Menu::Menu(Window* parent, bool multiple) :
|
||||
ModalWindow(parent, true),
|
||||
multiple(multiple),
|
||||
content(new MenuWindowContent(this))
|
||||
{
|
||||
}
|
||||
|
||||
void Menu::setToolbar(MenuToolbar* window)
|
||||
{
|
||||
toolbar = window;
|
||||
updatePosition();
|
||||
}
|
||||
|
||||
void Menu::updatePosition()
|
||||
{
|
||||
if (toolbar) {
|
||||
coord_t cw = lv_obj_get_width(content->getLvObj());
|
||||
coord_t ch = lv_obj_get_height(content->getLvObj());
|
||||
coord_t tw = lv_obj_get_width(toolbar->getLvObj());
|
||||
coord_t th = lv_obj_get_height(toolbar->getLvObj());
|
||||
|
||||
lv_obj_align(toolbar->getLvObj(), LV_ALIGN_CENTER, -cw / 2, 0);
|
||||
lv_obj_align(content->getLvObj(), LV_ALIGN_CENTER, tw / 2, 0);
|
||||
|
||||
toolbar->setHeight(max(ch, th));
|
||||
content->setHeight(max(ch, th));
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::setTitle(std::string text)
|
||||
{
|
||||
content->setTitle(std::move(text));
|
||||
updatePosition();
|
||||
}
|
||||
|
||||
void Menu::addLine(const uint8_t* icon_mask, const std::string& text,
|
||||
std::function<void()> onPress,
|
||||
std::function<bool()> isChecked)
|
||||
{
|
||||
content->addLine(icon_mask, text, std::move(onPress), std::move(isChecked));
|
||||
updatePosition();
|
||||
}
|
||||
|
||||
void Menu::addLineBuffered(const uint8_t* icon_mask, const std::string& text,
|
||||
std::function<void()> onPress,
|
||||
std::function<bool()> isChecked)
|
||||
{
|
||||
content->addLine(icon_mask, text, std::move(onPress), std::move(isChecked),
|
||||
false);
|
||||
}
|
||||
|
||||
void Menu::updateLines()
|
||||
{
|
||||
content->updateLines();
|
||||
updatePosition();
|
||||
}
|
||||
|
||||
void Menu::removeLines()
|
||||
{
|
||||
content->removeLines();
|
||||
updatePosition();
|
||||
}
|
||||
|
||||
void Menu::onEvent(event_t event)
|
||||
{
|
||||
#if defined(HARDWARE_KEYS)
|
||||
if (toolbar && (event == EVT_KEY_BREAK(KEY_PAGEDN) ||
|
||||
event == EVT_KEY_BREAK(KEY_PAGEUP))) {
|
||||
toolbar->onEvent(event);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void Menu::onCancel()
|
||||
{
|
||||
if (cancelHandler) cancelHandler();
|
||||
deleteLater();
|
||||
}
|
||||
|
||||
void Menu::setCancelHandler(std::function<void()> handler)
|
||||
{
|
||||
cancelHandler = std::move(handler);
|
||||
}
|
||||
|
||||
void Menu::setWaitHandler(std::function<void()> handler)
|
||||
{
|
||||
waitHandler = std::move(handler);
|
||||
}
|
||||
|
||||
unsigned Menu::count() const { return content->count(); }
|
||||
|
||||
int Menu::selection() const { return content->selection(); }
|
||||
|
||||
void Menu::select(int index) { content->setIndex(index); }
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright (C) EdgeTX
|
||||
*
|
||||
* Based on code named
|
||||
* libopenui - https://github.com/opentx/libopenui
|
||||
*
|
||||
* 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 "modal_window.h"
|
||||
|
||||
class Menu;
|
||||
class MenuWindowContent;
|
||||
class MenuToolbar;
|
||||
|
||||
class Menu : public ModalWindow
|
||||
{
|
||||
friend class MenuBody;
|
||||
|
||||
public:
|
||||
explicit Menu(Window *parent, bool multiple = false);
|
||||
|
||||
#if defined(DEBUG_WINDOWS)
|
||||
std::string getName() const override { return "Menu"; }
|
||||
#endif
|
||||
|
||||
void setCancelHandler(std::function<void()> handler);
|
||||
void setWaitHandler(std::function<void()> handler);
|
||||
|
||||
void setToolbar(MenuToolbar *window);
|
||||
|
||||
void setTitle(std::string text);
|
||||
|
||||
void addLine(const uint8_t *icon_mask, const std::string &text,
|
||||
std::function<void()> onPress,
|
||||
std::function<bool()> isChecked = nullptr);
|
||||
|
||||
void addLine(const std::string &text, std::function<void()> onPress,
|
||||
std::function<bool()> isChecked = nullptr)
|
||||
{
|
||||
addLine(nullptr, text, onPress, isChecked);
|
||||
}
|
||||
|
||||
void addLineBuffered(const uint8_t *icon_mask, const std::string &text,
|
||||
std::function<void()> onPress,
|
||||
std::function<bool()> isChecked = nullptr);
|
||||
|
||||
void addLineBuffered(const std::string &text, std::function<void()> onPress,
|
||||
std::function<bool()> isChecked = nullptr)
|
||||
{
|
||||
addLineBuffered(nullptr, text, onPress, isChecked);
|
||||
}
|
||||
|
||||
void updateLines();
|
||||
|
||||
void removeLines();
|
||||
|
||||
unsigned count() const;
|
||||
|
||||
int selection() const;
|
||||
|
||||
void select(int index);
|
||||
|
||||
void onEvent(event_t event) override;
|
||||
void onCancel() override;
|
||||
|
||||
void checkEvents() override
|
||||
{
|
||||
ModalWindow::checkEvents();
|
||||
if (waitHandler) {
|
||||
waitHandler();
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
bool multiple;
|
||||
MenuWindowContent *content;
|
||||
MenuToolbar *toolbar = nullptr;
|
||||
std::function<void()> waitHandler;
|
||||
std::function<void()> cancelHandler;
|
||||
|
||||
void updatePosition();
|
||||
};
|
||||
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* Copyright (C) EdgeTX
|
||||
*
|
||||
* Based on code named
|
||||
* libopenui - https://github.com/opentx/libopenui
|
||||
*
|
||||
* 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 "menutoolbar.h"
|
||||
|
||||
#include "menu.h"
|
||||
#include "etx_lv_theme.h"
|
||||
#include "translations.h"
|
||||
|
||||
static const lv_obj_class_t menu_button_class = {
|
||||
.base_class = &button_class,
|
||||
.constructor_cb = nullptr,
|
||||
.destructor_cb = nullptr,
|
||||
.user_data = nullptr,
|
||||
.event_cb = nullptr,
|
||||
.width_def = 0,
|
||||
.height_def = 0,
|
||||
.editable = LV_OBJ_CLASS_EDITABLE_INHERIT,
|
||||
.group_def = LV_OBJ_CLASS_GROUP_DEF_FALSE,
|
||||
.instance_size = sizeof(lv_btn_t),
|
||||
};
|
||||
|
||||
static lv_obj_t* menu_button_create(lv_obj_t* parent)
|
||||
{
|
||||
return etx_create(&menu_button_class, parent);
|
||||
}
|
||||
|
||||
static void toolbar_btn_defocus(lv_event_t* event)
|
||||
{
|
||||
auto obj = lv_event_get_target(event);
|
||||
auto btn = (MenuToolbarButton*)lv_obj_get_user_data(obj);
|
||||
btn->check(false);
|
||||
}
|
||||
|
||||
MenuToolbarButton::MenuToolbarButton(Window* parent, const rect_t& rect,
|
||||
const char* picto) :
|
||||
ButtonBase(parent, rect, nullptr, menu_button_create)
|
||||
{
|
||||
lv_obj_add_flag(lvobj, LV_OBJ_FLAG_CLICK_FOCUSABLE);
|
||||
lv_obj_add_flag(lvobj, LV_OBJ_FLAG_SCROLL_ON_FOCUS);
|
||||
|
||||
lv_obj_add_event_cb(lvobj, toolbar_btn_defocus, LV_EVENT_DEFOCUSED, nullptr);
|
||||
|
||||
auto label = lv_label_create(lvobj);
|
||||
lv_label_set_text(label, picto);
|
||||
lv_obj_center(label);
|
||||
}
|
||||
|
||||
MenuToolbar::MenuToolbar(Choice* choice, Menu* menu, const int columns) :
|
||||
Window(menu, {0, 0, 0, MENUS_MAX_HEIGHT}),
|
||||
choice(choice),
|
||||
menu(menu),
|
||||
filterColumns(columns),
|
||||
group(lv_group_create())
|
||||
{
|
||||
setWindowFlag(OPAQUE);
|
||||
|
||||
padAll(PAD_SMALL);
|
||||
|
||||
etx_solid_bg(lvobj);
|
||||
etx_obj_add_style(lvobj, styles->outline, LV_PART_MAIN);
|
||||
etx_obj_add_style(lvobj, styles->outline_color_normal, LV_PART_MAIN);
|
||||
|
||||
setWidth((MENUS_TOOLBAR_BUTTON_WIDTH + PAD_SMALL) * columns + PAD_SMALL);
|
||||
|
||||
addButton(STR_SELECT_MENU_ALL, choice->getMin(), choice->getMax(), nullptr,
|
||||
nullptr, true);
|
||||
}
|
||||
|
||||
MenuToolbar::~MenuToolbar() { lv_group_del(group); }
|
||||
|
||||
void MenuToolbar::resetFilter()
|
||||
{
|
||||
if (lv_group_get_focused(group) != lvobj) {
|
||||
lv_group_focus_obj(lvobj);
|
||||
choice->fillMenu(menu);
|
||||
menu->setTitle(choice->getTitle());
|
||||
}
|
||||
}
|
||||
|
||||
void MenuToolbar::onEvent(event_t event)
|
||||
{
|
||||
if (event == EVT_KEY_BREAK(KEY_PAGEDN)) {
|
||||
lv_group_focus_next(group);
|
||||
}
|
||||
else if (event == EVT_KEY_BREAK(KEY_PAGEUP)) {
|
||||
lv_group_focus_prev(group);
|
||||
}
|
||||
|
||||
auto obj = lv_group_get_focused(group);
|
||||
lv_event_send(obj, LV_EVENT_CLICKED, nullptr);
|
||||
}
|
||||
|
||||
rect_t MenuToolbar::getButtonRect(bool wideButton)
|
||||
{
|
||||
if (wideButton && (nxtBtnPos % filterColumns))
|
||||
nxtBtnPos = nxtBtnPos - (nxtBtnPos % filterColumns) + filterColumns;
|
||||
coord_t x =
|
||||
(nxtBtnPos % filterColumns) * (MENUS_TOOLBAR_BUTTON_WIDTH + PAD_SMALL);
|
||||
coord_t y =
|
||||
(nxtBtnPos / filterColumns) * (EdgeTxStyles::UI_ELEMENT_HEIGHT + PAD_SMALL);
|
||||
coord_t w = wideButton ? (MENUS_TOOLBAR_BUTTON_WIDTH + PAD_SMALL) *
|
||||
(filterColumns - 1) +
|
||||
MENUS_TOOLBAR_BUTTON_WIDTH
|
||||
: MENUS_TOOLBAR_BUTTON_WIDTH;
|
||||
nxtBtnPos += wideButton ? filterColumns : 1;
|
||||
return {x, y, w, EdgeTxStyles::UI_ELEMENT_HEIGHT};
|
||||
}
|
||||
|
||||
bool MenuToolbar::filterMenu(MenuToolbarButton* btn, int16_t filtermin,
|
||||
int16_t filtermax,
|
||||
const Choice::FilterFct& filterFunc,
|
||||
const char* title)
|
||||
{
|
||||
btn->check(!btn->checked());
|
||||
|
||||
filter = nullptr;
|
||||
if (btn->checked()) {
|
||||
if (title)
|
||||
menu->setTitle(title);
|
||||
else
|
||||
menu->setTitle(choice->getTitle());
|
||||
filter = [=](int16_t index) {
|
||||
if (filterFunc) return filterFunc(index);
|
||||
return index == 0 || (abs(index) >= filtermin && abs(index) <= filtermax);
|
||||
};
|
||||
lv_group_focus_obj(btn->getLvObj());
|
||||
choice->fillMenu(menu, filter);
|
||||
} else {
|
||||
lv_event_send(allBtn->getLvObj(), LV_EVENT_CLICKED, nullptr);
|
||||
}
|
||||
|
||||
return btn->checked();
|
||||
}
|
||||
|
||||
void MenuToolbar::addButton(const char* picto, int16_t filtermin,
|
||||
int16_t filtermax,
|
||||
const Choice::FilterFct& filterFunc,
|
||||
const char* title, bool wideButton)
|
||||
{
|
||||
int vmin = choice->getMin();
|
||||
int vmax = choice->getMax();
|
||||
|
||||
if (vmin > filtermin || vmax < filtermin) return;
|
||||
|
||||
if (choice->isValueAvailable) {
|
||||
bool found = false;
|
||||
for (int i = filtermin; i <= filtermax; i += 1) {
|
||||
if (choice->isValueAvailable(i)) {
|
||||
if (filterFunc && !filterFunc(i))
|
||||
continue;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) return;
|
||||
}
|
||||
|
||||
rect_t r = getButtonRect(wideButton);
|
||||
auto button = new MenuToolbarButton(this, r, picto);
|
||||
|
||||
button->setPressHandler(std::bind(&MenuToolbar::filterMenu, this, button,
|
||||
filtermin, filtermax, filterFunc, title));
|
||||
|
||||
lv_group_add_obj(group, button->getLvObj());
|
||||
|
||||
if (children.size() == 1) {
|
||||
allBtn = button;
|
||||
lv_event_send(allBtn->getLvObj(), LV_EVENT_CLICKED, nullptr);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright (C) EdgeTX
|
||||
*
|
||||
* Based on code named
|
||||
* libopenui - https://github.com/opentx/libopenui
|
||||
*
|
||||
* 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 "button.h"
|
||||
#include "choice.h"
|
||||
#include "listbox.h"
|
||||
|
||||
class Menu;
|
||||
|
||||
class MenuToolbarButton : public ButtonBase
|
||||
{
|
||||
public:
|
||||
MenuToolbarButton(Window* parent, const rect_t& rect, const char* picto);
|
||||
|
||||
protected:
|
||||
};
|
||||
|
||||
class MenuToolbar : public Window
|
||||
{
|
||||
// friend Menu;
|
||||
|
||||
public:
|
||||
MenuToolbar(Choice* choice, Menu* menu, const int columns);
|
||||
~MenuToolbar();
|
||||
|
||||
void resetFilter();
|
||||
void onEvent(event_t event) override;
|
||||
|
||||
virtual void longPress() {}
|
||||
|
||||
static LAYOUT_VAL(MENUS_TOOLBAR_BUTTON_WIDTH, 36, 36)
|
||||
static LAYOUT_VAL(MENUS_MAX_HEIGHT, ListBox::MENUS_LINE_HEIGHT * 7 + 8, ListBox::MENUS_LINE_HEIGHT * 10)
|
||||
|
||||
protected:
|
||||
Choice* choice;
|
||||
Choice::FilterFct filter;
|
||||
Menu* menu;
|
||||
int nxtBtnPos = 0;
|
||||
int filterColumns = 0;
|
||||
MenuToolbarButton* allBtn = nullptr;
|
||||
|
||||
lv_group_t* group;
|
||||
|
||||
void addButton(const char* picto, int16_t filtermin, int16_t filtermax,
|
||||
const Choice::FilterFct& filterFunc = nullptr,
|
||||
const char* title = nullptr, bool wideButton = false);
|
||||
bool filterMenu(MenuToolbarButton* btn, int16_t filtermin, int16_t filtermax,
|
||||
const Choice::FilterFct& filterFunc, const char* title);
|
||||
|
||||
rect_t getButtonRect(bool wideButton);
|
||||
};
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright (C) EdgeTX
|
||||
*
|
||||
* Based on code named
|
||||
* libopenui - https://github.com/opentx/libopenui
|
||||
*
|
||||
* 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 "modal_window.h"
|
||||
|
||||
#include "layer.h"
|
||||
#include "etx_lv_theme.h"
|
||||
|
||||
// Modal overlay style (for dimming background)
|
||||
void modal_window_constructor(const lv_obj_class_t* class_p, lv_obj_t* obj)
|
||||
{
|
||||
etx_obj_add_style(obj, styles->bg_opacity_50, LV_PART_MAIN);
|
||||
etx_bg_color(obj, COLOR_BLACK_INDEX, LV_PART_MAIN);
|
||||
}
|
||||
|
||||
static const lv_obj_class_t modal_window_class = {
|
||||
.base_class = &window_base_class,
|
||||
.constructor_cb = modal_window_constructor,
|
||||
.destructor_cb = nullptr,
|
||||
.user_data = nullptr,
|
||||
.event_cb = nullptr,
|
||||
.width_def = 0,
|
||||
.height_def = 0,
|
||||
.editable = LV_OBJ_CLASS_EDITABLE_INHERIT,
|
||||
.group_def = LV_OBJ_CLASS_EDITABLE_INHERIT,
|
||||
.instance_size = sizeof(lv_obj_t)};
|
||||
|
||||
static lv_obj_t* modal_create(lv_obj_t* parent)
|
||||
{
|
||||
return etx_create(&modal_window_class, parent);
|
||||
}
|
||||
|
||||
ModalWindow::ModalWindow(Window* parent, bool closeWhenClickOutside) :
|
||||
Window(parent->getFullScreenWindow(), {0, 0, LCD_W, LCD_H}, modal_create),
|
||||
closeWhenClickOutside(closeWhenClickOutside)
|
||||
{
|
||||
Layer::push(this);
|
||||
}
|
||||
|
||||
void ModalWindow::deleteLater(bool detach, bool trash)
|
||||
{
|
||||
if (_deleted) return;
|
||||
Layer::pop(this);
|
||||
Window::deleteLater(detach, trash);
|
||||
}
|
||||
|
||||
void ModalWindow::onClicked()
|
||||
{
|
||||
if (closeWhenClickOutside) {
|
||||
// Call onCancel of first child (if one exists)
|
||||
if (children.size() > 0)
|
||||
children.front()->onCancel();
|
||||
else
|
||||
deleteLater();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (C) EdgeTX
|
||||
*
|
||||
* Based on code named
|
||||
* libopenui - https://github.com/opentx/libopenui
|
||||
*
|
||||
* 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 "window.h"
|
||||
|
||||
class ModalWindow : public Window
|
||||
{
|
||||
public:
|
||||
explicit ModalWindow(Window* parent, bool closeWhenClickOutside = false);
|
||||
|
||||
#if defined(DEBUG_WINDOWS)
|
||||
std::string getName() const override { return "ModalWindow"; }
|
||||
#endif
|
||||
|
||||
void setCloseWhenClickOutside(bool value = true)
|
||||
{
|
||||
closeWhenClickOutside = value;
|
||||
}
|
||||
|
||||
void onClicked() override;
|
||||
void deleteLater(bool detach = true, bool trash = true) override;
|
||||
|
||||
protected:
|
||||
bool closeWhenClickOutside;
|
||||
};
|
||||
@@ -0,0 +1,291 @@
|
||||
/*
|
||||
* Copyright (C) EdgeTX
|
||||
*
|
||||
* Based on code named
|
||||
* libopenui - https://github.com/opentx/libopenui
|
||||
*
|
||||
* 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 "numberedit.h"
|
||||
|
||||
#include "audio.h"
|
||||
#include "keyboard_number.h"
|
||||
#include "strhelpers.h"
|
||||
#include "etx_lv_theme.h"
|
||||
|
||||
class NumberArea : public FormField
|
||||
{
|
||||
public:
|
||||
NumberArea(NumberEdit* parent, const rect_t& rect) :
|
||||
FormField(parent, rect, etx_textarea_create),
|
||||
numEdit(parent)
|
||||
{
|
||||
lv_obj_clear_flag(lvobj, LV_OBJ_FLAG_CLICK_FOCUSABLE);
|
||||
|
||||
if (parent->getTextFlags() & CENTERED)
|
||||
etx_obj_add_style(lvobj, styles->text_align_center, LV_PART_MAIN);
|
||||
else
|
||||
etx_obj_add_style(lvobj, styles->text_align_right, LV_PART_MAIN);
|
||||
|
||||
// Allow encoder acceleration
|
||||
lv_obj_add_flag(lvobj, LV_OBJ_FLAG_ENCODER_ACCEL);
|
||||
|
||||
lv_obj_add_event_cb(lvobj, NumberArea::numberedit_cb, LV_EVENT_KEY, this);
|
||||
|
||||
setFocusHandler([=](bool focus) {
|
||||
if (!focus && editMode) {
|
||||
setEditMode(false);
|
||||
hide();
|
||||
lv_obj_clear_state(parent->getLvObj(), LV_STATE_FOCUSED);
|
||||
}
|
||||
});
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
#if defined(DEBUG_WINDOWS)
|
||||
std::string getName() const override
|
||||
{
|
||||
return "NumberArea(" + std::to_string(getValue()) + ")";
|
||||
}
|
||||
#endif
|
||||
|
||||
void onEvent(event_t event) override
|
||||
{
|
||||
TRACE_WINDOWS("%s received event 0x%X", getWindowDebugString().c_str(),
|
||||
event);
|
||||
|
||||
if (editMode) {
|
||||
int value = numEdit->getValue();
|
||||
switch (event) {
|
||||
#if defined(HARDWARE_KEYS)
|
||||
case EVT_ROTARY_RIGHT: {
|
||||
auto step = numEdit->step;
|
||||
step += (rotaryEncoderGetAccel() * numEdit->accelFactor) / 8;
|
||||
do {
|
||||
#if defined(USE_HATS_AS_KEYS)
|
||||
value -= step;
|
||||
#else
|
||||
value += step;
|
||||
#endif
|
||||
} while (numEdit->isValueAvailable && !numEdit->isValueAvailable(value) &&
|
||||
value <= numEdit->vmax);
|
||||
if (value <= numEdit->vmax) {
|
||||
numEdit->setValue(value);
|
||||
} else {
|
||||
numEdit->setValue(numEdit->vmax);
|
||||
onKeyError();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
case EVT_ROTARY_LEFT: {
|
||||
auto step = numEdit->step;
|
||||
step += (rotaryEncoderGetAccel() * numEdit->accelFactor) / 8;
|
||||
do {
|
||||
#if defined(USE_HATS_AS_KEYS)
|
||||
value += step;
|
||||
#else
|
||||
value -= step;
|
||||
#endif
|
||||
} while (numEdit->isValueAvailable && !numEdit->isValueAvailable(value) &&
|
||||
value >= numEdit->vmin);
|
||||
if (value >= numEdit->vmin) {
|
||||
numEdit->setValue(value);
|
||||
} else {
|
||||
numEdit->setValue(numEdit->vmin);
|
||||
onKeyError();
|
||||
}
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
case EVT_VIRTUAL_KEY_PLUS:
|
||||
numEdit->setValue(value + numEdit->step);
|
||||
break;
|
||||
|
||||
case EVT_VIRTUAL_KEY_MINUS:
|
||||
numEdit->setValue(value - numEdit->step);
|
||||
break;
|
||||
|
||||
case EVT_VIRTUAL_KEY_FORWARD:
|
||||
numEdit->setValue(value + numEdit->fastStep * numEdit->step);
|
||||
break;
|
||||
|
||||
case EVT_VIRTUAL_KEY_BACKWARD:
|
||||
numEdit->setValue(value - numEdit->fastStep * numEdit->step);
|
||||
break;
|
||||
|
||||
case EVT_VIRTUAL_KEY_DEFAULT:
|
||||
numEdit->setValue(numEdit->vdefault);
|
||||
break;
|
||||
|
||||
case EVT_VIRTUAL_KEY_MAX:
|
||||
numEdit->setValue(numEdit->vmax);
|
||||
break;
|
||||
|
||||
case EVT_VIRTUAL_KEY_MIN:
|
||||
numEdit->setValue(numEdit->vmin);
|
||||
break;
|
||||
|
||||
case EVT_VIRTUAL_KEY_SIGN:
|
||||
numEdit->setValue(-value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
FormField::onEvent(event);
|
||||
}
|
||||
|
||||
void onClicked() override
|
||||
{
|
||||
lv_indev_type_t indev_type = lv_indev_get_type(lv_indev_get_act());
|
||||
if (indev_type == LV_INDEV_TYPE_POINTER) {
|
||||
setEditMode(true);
|
||||
} else {
|
||||
FormField::onClicked();
|
||||
if (!editMode) changeEnd();
|
||||
}
|
||||
}
|
||||
|
||||
void openKeyboard() { NumberKeyboard::show(this); }
|
||||
void directEdit() { FormField::onClicked(); }
|
||||
|
||||
void update()
|
||||
{
|
||||
if (lvobj != nullptr)
|
||||
lv_textarea_set_text(lvobj, numEdit->getDisplayVal().c_str());
|
||||
}
|
||||
|
||||
protected:
|
||||
NumberEdit* numEdit = nullptr;
|
||||
|
||||
void onCancel() override
|
||||
{
|
||||
onClicked();
|
||||
}
|
||||
|
||||
static void numberedit_cb(lv_event_t* e)
|
||||
{
|
||||
NumberArea* numEdit = (NumberArea*)lv_event_get_user_data(e);
|
||||
if (!numEdit || numEdit->deleted()) return;
|
||||
|
||||
uint32_t key = lv_event_get_key(e);
|
||||
switch (key) {
|
||||
case LV_KEY_LEFT:
|
||||
numEdit->onEvent(EVT_ROTARY_LEFT);
|
||||
break;
|
||||
case LV_KEY_RIGHT:
|
||||
numEdit->onEvent(EVT_ROTARY_RIGHT);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
The lv_textarea object is slow. To avoid too much overhead on views with multiple
|
||||
edit fields, the text area is initially displayed as a button. When the button
|
||||
is pressed, a text area object is created over the top of the button in order
|
||||
to edit the value.
|
||||
*/
|
||||
NumberEdit::NumberEdit(Window* parent, const rect_t& rect, int vmin, int vmax,
|
||||
std::function<int()> getValue,
|
||||
std::function<void(int)> setValue, LcdFlags textFlags) :
|
||||
TextButton(parent, rect, "",
|
||||
[=]() {
|
||||
openEdit();
|
||||
return 0;
|
||||
}),
|
||||
_getValue(std::move(getValue)),
|
||||
_setValue(std::move(setValue)),
|
||||
vmin(vmin),
|
||||
vmax(vmax)
|
||||
{
|
||||
if (rect.w == 0) setWidth(DEF_W);
|
||||
|
||||
setTextFlag(textFlags);
|
||||
|
||||
lv_obj_set_width(label, width() - PAD_MEDIUM * 2 - 2);
|
||||
if (textFlags & CENTERED)
|
||||
etx_obj_add_style(label, styles->text_align_center, LV_PART_MAIN);
|
||||
else
|
||||
etx_obj_add_style(label, styles->text_align_right, LV_PART_MAIN);
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
void NumberEdit::openEdit()
|
||||
{
|
||||
if (edit == nullptr) {
|
||||
edit = new NumberArea(
|
||||
this,
|
||||
{-(PAD_MEDIUM + 2), -(PAD_TINY + 2),
|
||||
lv_obj_get_width(lvobj), lv_obj_get_height(lvobj)});
|
||||
edit->setChangeHandler([=]() {
|
||||
update();
|
||||
if (edit->hasFocus())
|
||||
lv_group_focus_obj(lvobj);
|
||||
edit->hide();
|
||||
});
|
||||
}
|
||||
edit->update();
|
||||
edit->show();
|
||||
lv_group_focus_obj(edit->getLvObj());
|
||||
lv_indev_type_t indev_type =
|
||||
lv_indev_get_type(lv_indev_get_act());
|
||||
if (indev_type == LV_INDEV_TYPE_POINTER) {
|
||||
edit->openKeyboard();
|
||||
} else {
|
||||
edit->directEdit();
|
||||
}
|
||||
lv_obj_add_state(lvobj, LV_STATE_FOCUSED);
|
||||
}
|
||||
|
||||
void NumberEdit::update()
|
||||
{
|
||||
if (_getValue == nullptr) return;
|
||||
currentValue = _getValue();
|
||||
updateDisplay();
|
||||
}
|
||||
|
||||
std::string NumberEdit::getDisplayVal()
|
||||
{
|
||||
std::string str;
|
||||
if (displayFunction != nullptr) {
|
||||
str = displayFunction(currentValue);
|
||||
} else if (!zeroText.empty() && currentValue == 0) {
|
||||
str = zeroText;
|
||||
} else {
|
||||
str = formatNumberAsString(currentValue, textFlags, 0, prefix.c_str(),
|
||||
suffix.c_str());
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
void NumberEdit::updateDisplay()
|
||||
{
|
||||
setText(getDisplayVal());
|
||||
}
|
||||
|
||||
void NumberEdit::setValue(int value)
|
||||
{
|
||||
auto newValue = limit(vmin, value, vmax);
|
||||
if (newValue != currentValue) {
|
||||
currentValue = newValue;
|
||||
if (_setValue != nullptr) {
|
||||
_setValue(currentValue);
|
||||
}
|
||||
}
|
||||
updateDisplay();
|
||||
if (edit) edit->update();
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copyright (C) EdgeTX
|
||||
*
|
||||
* Based on code named
|
||||
* libopenui - https://github.com/opentx/libopenui
|
||||
*
|
||||
* 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 "form.h"
|
||||
#include "button.h"
|
||||
|
||||
class NumberArea;
|
||||
|
||||
class NumberEdit : public TextButton
|
||||
{
|
||||
public:
|
||||
NumberEdit(Window* parent, const rect_t& rect, int vmin, int vmax,
|
||||
std::function<int()> getValue,
|
||||
std::function<void(int)> setValue = nullptr,
|
||||
LcdFlags textFlags = 0);
|
||||
|
||||
#if defined(DEBUG_WINDOWS)
|
||||
std::string getName() const override
|
||||
{
|
||||
return "NumberEdit";
|
||||
}
|
||||
#endif
|
||||
|
||||
virtual void update();
|
||||
|
||||
int32_t getMax() const { return vmax; }
|
||||
|
||||
void setMin(int value) { vmin = value; }
|
||||
void setMax(int value) { vmax = value; }
|
||||
void setDefault(int value) { vdefault = value; }
|
||||
void setStep(int value) { step = value; }
|
||||
void setFastStep(int value) { fastStep = value; }
|
||||
void setAccelFactor(int value) { accelFactor = value; }
|
||||
void setValue(int value);
|
||||
|
||||
void setPrefix(std::string value)
|
||||
{
|
||||
prefix = std::move(value);
|
||||
update();
|
||||
}
|
||||
|
||||
void setSuffix(std::string value)
|
||||
{
|
||||
suffix = std::move(value);
|
||||
update();
|
||||
}
|
||||
|
||||
void setZeroText(std::string value)
|
||||
{
|
||||
zeroText = std::move(value);
|
||||
update();
|
||||
}
|
||||
|
||||
void setAvailableHandler(std::function<bool(int)> handler)
|
||||
{
|
||||
isValueAvailable = std::move(handler);
|
||||
}
|
||||
|
||||
void setDisplayHandler(std::function<std::string(int value)> function)
|
||||
{
|
||||
displayFunction = std::move(function);
|
||||
update();
|
||||
}
|
||||
|
||||
void setSetValueHandler(std::function<void(int)> handler)
|
||||
{
|
||||
_setValue = std::move(handler);
|
||||
}
|
||||
|
||||
void setGetValueHandler(std::function<int()> handler)
|
||||
{
|
||||
_getValue = std::move(handler);
|
||||
}
|
||||
|
||||
int32_t getValue() const { return _getValue != nullptr ? _getValue() : 0; }
|
||||
|
||||
static LAYOUT_VAL(DEF_W, 100, 100)
|
||||
|
||||
protected:
|
||||
friend class NumberArea;
|
||||
|
||||
NumberArea* edit = nullptr;
|
||||
std::function<int()> _getValue;
|
||||
std::function<void(int)> _setValue;
|
||||
int vdefault = 0;
|
||||
int vmin;
|
||||
int vmax;
|
||||
int step = 1;
|
||||
int fastStep = 10;
|
||||
int accelFactor = 4;
|
||||
int currentValue;
|
||||
std::string prefix;
|
||||
std::string suffix;
|
||||
std::string zeroText;
|
||||
std::function<std::string(int)> displayFunction;
|
||||
std::function<bool(int)> isValueAvailable;
|
||||
|
||||
std::string getDisplayVal();
|
||||
|
||||
void updateDisplay();
|
||||
void openEdit();
|
||||
};
|
||||
@@ -22,7 +22,7 @@
|
||||
#include "page.h"
|
||||
|
||||
#include "theme_manager.h"
|
||||
#include "themes/etx_lv_theme.h"
|
||||
#include "etx_lv_theme.h"
|
||||
#include "view_main.h"
|
||||
|
||||
PageHeader::PageHeader(Window* parent, EdgeTxIcon icon) :
|
||||
@@ -25,7 +25,7 @@
|
||||
#include "opentx.h"
|
||||
#include "pwr.h"
|
||||
#include "hal/watchdog_driver.h"
|
||||
#include "themes/etx_lv_theme.h"
|
||||
#include "etx_lv_theme.h"
|
||||
|
||||
static void _run_popup_dialog(const char* title, const char* msg,
|
||||
const char* info = nullptr)
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (C) EdgeTX
|
||||
*
|
||||
* Based on code named
|
||||
* libopenui - https://github.com/opentx/libopenui
|
||||
*
|
||||
* 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 "progress.h"
|
||||
|
||||
#include "etx_lv_theme.h"
|
||||
|
||||
static void etx_bar_constructor(const lv_obj_class_t* class_p, lv_obj_t* obj)
|
||||
{
|
||||
etx_solid_bg(obj, COLOR_THEME_SECONDARY2_INDEX);
|
||||
etx_obj_add_style(obj, styles->rounded, LV_PART_MAIN);
|
||||
|
||||
etx_solid_bg(obj, COLOR_THEME_SECONDARY1_INDEX, LV_PART_INDICATOR);
|
||||
etx_obj_add_style(obj, styles->rounded, LV_PART_INDICATOR);
|
||||
}
|
||||
|
||||
static const lv_obj_class_t bar_class = {
|
||||
.base_class = &lv_bar_class,
|
||||
.constructor_cb = etx_bar_constructor,
|
||||
.destructor_cb = nullptr,
|
||||
.user_data = nullptr,
|
||||
.event_cb = nullptr,
|
||||
.width_def = LV_PCT(100),
|
||||
.height_def = EdgeTxStyles::UI_ELEMENT_HEIGHT,
|
||||
.editable = LV_OBJ_CLASS_EDITABLE_INHERIT,
|
||||
.group_def = LV_OBJ_CLASS_GROUP_DEF_INHERIT,
|
||||
.instance_size = sizeof(lv_bar_t),
|
||||
};
|
||||
|
||||
static lv_obj_t* bar_create(lv_obj_t* parent)
|
||||
{
|
||||
return etx_create(&bar_class, parent);
|
||||
}
|
||||
|
||||
Progress::Progress(Window* parent, const rect_t& rect) : Window(parent, rect)
|
||||
{
|
||||
bar = bar_create(lvobj);
|
||||
lv_bar_set_range(bar, 0, 100);
|
||||
setValue(0);
|
||||
}
|
||||
|
||||
void Progress::setValue(int newValue)
|
||||
{
|
||||
if (newValue != value) {
|
||||
value = newValue;
|
||||
lv_bar_set_value(bar, value, LV_ANIM_OFF);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (C) EdgeTX
|
||||
*
|
||||
* Based on code named
|
||||
* libopenui - https://github.com/opentx/libopenui
|
||||
*
|
||||
* 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 "window.h"
|
||||
|
||||
class Progress : public Window
|
||||
{
|
||||
public:
|
||||
Progress(Window* parent, const rect_t& rect);
|
||||
|
||||
#if defined(DEBUG_WINDOWS)
|
||||
std::string getName() const override { return "Progress"; }
|
||||
#endif
|
||||
|
||||
void setValue(int newValue);
|
||||
|
||||
protected:
|
||||
int value = -1;
|
||||
lv_obj_t* bar;
|
||||
};
|
||||
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* Copyright (C) EdgeTX
|
||||
*
|
||||
* Based on code named
|
||||
* libopenui - https://github.com/opentx/libopenui
|
||||
*
|
||||
* 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 "slider.h"
|
||||
|
||||
#include "form.h"
|
||||
#include "etx_lv_theme.h"
|
||||
|
||||
// Slider
|
||||
const lv_style_const_prop_t slider_knob_props[] = {
|
||||
LV_STYLE_CONST_PAD_TOP(9), LV_STYLE_CONST_PAD_BOTTOM(9),
|
||||
LV_STYLE_CONST_PAD_LEFT(4), LV_STYLE_CONST_PAD_RIGHT(4),
|
||||
LV_STYLE_CONST_RADIUS(4), LV_STYLE_CONST_BORDER_WIDTH(2),
|
||||
LV_STYLE_PROP_INV,
|
||||
};
|
||||
LV_STYLE_CONST_MULTI_INIT(slider_knob, slider_knob_props);
|
||||
|
||||
static void slider_constructor(const lv_obj_class_t* class_p, lv_obj_t* obj)
|
||||
{
|
||||
etx_solid_bg(obj, COLOR_THEME_SECONDARY1_INDEX);
|
||||
etx_obj_add_style(obj, styles->rounded, LV_PART_MAIN);
|
||||
etx_bg_color(obj, COLOR_THEME_FOCUS_INDEX, LV_PART_MAIN | LV_STATE_FOCUSED);
|
||||
etx_bg_color(obj, COLOR_THEME_EDIT_INDEX,
|
||||
LV_PART_MAIN | LV_STATE_FOCUSED | LV_STATE_EDITED);
|
||||
|
||||
etx_bg_color(obj, COLOR_THEME_FOCUS_INDEX,
|
||||
LV_PART_INDICATOR | LV_STATE_FOCUSED);
|
||||
etx_bg_color(obj, COLOR_THEME_EDIT_INDEX,
|
||||
LV_PART_INDICATOR | LV_STATE_FOCUSED | LV_STATE_EDITED);
|
||||
|
||||
etx_obj_add_style(obj, styles->bg_opacity_cover, LV_PART_KNOB);
|
||||
etx_obj_add_style(obj, slider_knob, LV_PART_KNOB);
|
||||
etx_obj_add_style(obj, styles->border_color[COLOR_THEME_SECONDARY1_INDEX], LV_PART_MAIN);
|
||||
etx_obj_add_style(obj, styles->border_color[COLOR_THEME_FOCUS_INDEX],
|
||||
LV_PART_KNOB | LV_STATE_FOCUSED);
|
||||
}
|
||||
|
||||
static const lv_obj_class_t slider_class = {
|
||||
.base_class = &lv_slider_class,
|
||||
.constructor_cb = slider_constructor,
|
||||
.destructor_cb = nullptr,
|
||||
.user_data = nullptr,
|
||||
.event_cb = nullptr,
|
||||
.width_def = 0,
|
||||
.height_def = 8,
|
||||
.editable = LV_OBJ_CLASS_EDITABLE_INHERIT,
|
||||
.group_def = LV_OBJ_CLASS_GROUP_DEF_INHERIT,
|
||||
.instance_size = sizeof(lv_slider_t),
|
||||
};
|
||||
|
||||
static lv_obj_t* slider_create(lv_obj_t* parent)
|
||||
{
|
||||
return etx_create(&slider_class, parent);
|
||||
}
|
||||
|
||||
static void slider_changed_cb(lv_event_t* e)
|
||||
{
|
||||
if (lv_event_get_code(e) == LV_EVENT_VALUE_CHANGED) {
|
||||
Slider* sl = (Slider*)lv_event_get_user_data(e);
|
||||
if (sl != nullptr) {
|
||||
lv_obj_t* target = lv_event_get_target(e);
|
||||
sl->setValue(lv_slider_get_value(target));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Slider::Slider(Window* parent, coord_t width, int32_t vmin, int32_t vmax,
|
||||
std::function<int()> getValue,
|
||||
std::function<void(int)> setValue) :
|
||||
Window(parent, {0, 0, width, 32}, window_create),
|
||||
vmin(vmin),
|
||||
vmax(vmax),
|
||||
_getValue(std::move(getValue)),
|
||||
_setValue(std::move(setValue))
|
||||
{
|
||||
padTop(9);
|
||||
padLeft(8);
|
||||
padRight(8);
|
||||
|
||||
slider = (new FormField(this, rect_t{}, slider_create))->getLvObj();
|
||||
lv_obj_set_width(slider, lv_pct(100));
|
||||
|
||||
lv_obj_add_event_cb(slider, slider_changed_cb, LV_EVENT_VALUE_CHANGED, this);
|
||||
lv_slider_set_range(slider, vmin, vmax);
|
||||
|
||||
lv_obj_add_event_cb(lvobj, Slider::on_draw, LV_EVENT_DRAW_MAIN_BEGIN,
|
||||
nullptr);
|
||||
|
||||
int range = vmax - vmin;
|
||||
if (range < 10) {
|
||||
tickPts = new lv_obj_t*[range - 1];
|
||||
for (int n = 1; n < range; n += 1) {
|
||||
lv_obj_t* p = lv_obj_create(lvobj);
|
||||
lv_obj_set_size(p, 2, 6);
|
||||
etx_solid_bg(p, COLOR_THEME_PRIMARY2_INDEX);
|
||||
tickPts[n - 1] = p;
|
||||
}
|
||||
}
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
void Slider::on_draw(lv_event_t* e)
|
||||
{
|
||||
auto dlg = (Slider*)lv_obj_get_user_data(lv_event_get_target(e));
|
||||
if (dlg) dlg->delayedInit();
|
||||
}
|
||||
|
||||
void Slider::delayedInit()
|
||||
{
|
||||
if (!loaded) {
|
||||
loaded = true;
|
||||
|
||||
coord_t w = lv_obj_get_width(lvobj) - 16;
|
||||
coord_t x = -1;
|
||||
int range = vmax - vmin;
|
||||
if (range < 10) {
|
||||
for (int n = 1; n < range; n += 1) {
|
||||
lv_obj_set_pos(tickPts[n - 1], x + (w * n) / range, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Slider::update()
|
||||
{
|
||||
if (_getValue != nullptr) {
|
||||
// Fix for lv_slider_set_value not working when using the rotary encoder to
|
||||
// update value
|
||||
auto bar = (lv_bar_t*)slider;
|
||||
bar->cur_value_anim.anim_state = -1;
|
||||
bar->cur_value_anim.anim_end = _getValue();
|
||||
|
||||
lv_slider_set_value(slider, _getValue(), LV_ANIM_OFF);
|
||||
}
|
||||
}
|
||||
|
||||
void Slider::deleteLater(bool detach, bool trash)
|
||||
{
|
||||
if (!deleted()) {
|
||||
if (tickPts) delete tickPts;
|
||||
Window::deleteLater(detach, trash);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (C) EdgeTX
|
||||
*
|
||||
* Based on code named
|
||||
* libopenui - https://github.com/opentx/libopenui
|
||||
*
|
||||
* 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 "window.h"
|
||||
|
||||
class Slider : public Window
|
||||
{
|
||||
public:
|
||||
Slider(Window* parent, coord_t width, int32_t vmin, int32_t vmax,
|
||||
std::function<int()> getValue, std::function<void(int)> setValue);
|
||||
|
||||
#if defined(DEBUG_WINDOWS)
|
||||
std::string getName() const override { return "Slider"; }
|
||||
#endif
|
||||
|
||||
void setValue(int value)
|
||||
{
|
||||
if (_setValue != nullptr) _setValue(limit(vmin, value, vmax));
|
||||
}
|
||||
|
||||
void update();
|
||||
|
||||
protected:
|
||||
int vmin;
|
||||
int vmax;
|
||||
lv_obj_t* slider;
|
||||
lv_obj_t** tickPts = nullptr;
|
||||
bool loaded = false;
|
||||
|
||||
std::function<int()> _getValue;
|
||||
std::function<void(int)> _setValue;
|
||||
|
||||
static void on_draw(lv_event_t* e);
|
||||
void delayedInit();
|
||||
|
||||
void deleteLater(bool detach, bool trash) override;
|
||||
};
|
||||
@@ -0,0 +1,368 @@
|
||||
/*
|
||||
* Copyright (C) EdgeTX
|
||||
*
|
||||
* Based on code named
|
||||
* libopenui - https://github.com/opentx/libopenui
|
||||
*
|
||||
* 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 "static.h"
|
||||
|
||||
#include "bitmaps.h"
|
||||
#include "lz4/lz4.h"
|
||||
#include "sdcard.h"
|
||||
#include "etx_lv_theme.h"
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
StaticText::StaticText(Window* parent, const rect_t& rect, std::string txt,
|
||||
LcdColorIndex color, LcdFlags textFlags) :
|
||||
Window(parent, rect, lv_label_create), text(std::move(txt))
|
||||
{
|
||||
setTextFlag(textFlags);
|
||||
|
||||
lv_obj_clear_flag(lvobj, LV_OBJ_FLAG_CLICK_FOCUSABLE);
|
||||
|
||||
etx_font(lvobj, FONT_INDEX(textFlags));
|
||||
etx_txt_color(lvobj, color);
|
||||
|
||||
if (textFlags & CENTERED)
|
||||
etx_obj_add_style(lvobj, styles->text_align_center, LV_PART_MAIN);
|
||||
else if (textFlags & RIGHT)
|
||||
etx_obj_add_style(lvobj, styles->text_align_right, LV_PART_MAIN);
|
||||
|
||||
lv_obj_set_style_grid_cell_x_align(lvobj, LV_GRID_ALIGN_STRETCH,
|
||||
LV_PART_MAIN);
|
||||
lv_label_set_text(lvobj, text.c_str());
|
||||
if (rect.h == 0) lv_obj_set_height(lvobj, LV_SIZE_CONTENT);
|
||||
}
|
||||
|
||||
#if defined(DEBUG_WINDOWS)
|
||||
std::string StaticText::getName() const
|
||||
{
|
||||
return "StaticText \"" + text.substr(0, 20) + "\"";
|
||||
}
|
||||
#endif
|
||||
|
||||
void StaticText::setText(std::string value)
|
||||
{
|
||||
if (text != value) {
|
||||
text = std::move(value);
|
||||
if (lvobj) lv_label_set_text(lvobj, text.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
const std::string& StaticText::getText() const { return text; }
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
template <>
|
||||
void DynamicNumber<uint32_t>::updateText()
|
||||
{
|
||||
if (lvobj) {
|
||||
const char* p = prefix ? prefix : "";
|
||||
const char* s = suffix ? suffix : "";
|
||||
if ((textFlags & PREC2) == PREC2) {
|
||||
lv_label_set_text_fmt(lvobj, "%s%" PRIu32 ".%02" PRIu32 "%s", p,
|
||||
value / 100, value % 100, s);
|
||||
} else if (textFlags & PREC1) {
|
||||
lv_label_set_text_fmt(lvobj, "%s%" PRIu32 ".%01" PRIu32 "%s", p,
|
||||
value / 10, value % 10, s);
|
||||
} else {
|
||||
lv_label_set_text_fmt(lvobj, "%s%" PRIu32 "%s", p, value, s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void DynamicNumber<int32_t>::updateText()
|
||||
{
|
||||
if (lvobj) {
|
||||
const char* p = prefix ? prefix : "";
|
||||
const char* s = suffix ? suffix : "";
|
||||
if ((textFlags & PREC2) == PREC2) {
|
||||
lv_label_set_text_fmt(lvobj, "%s%" PRId32 ".%02" PRIu32 "%s", p,
|
||||
value / 100, (uint32_t)abs(value % 100), s);
|
||||
} else if (textFlags & PREC1) {
|
||||
lv_label_set_text_fmt(lvobj, "%s%" PRId32 ".%01" PRIu32 "%s", p,
|
||||
value / 10, (uint32_t)abs(value % 10), s);
|
||||
} else {
|
||||
lv_label_set_text_fmt(lvobj, "%s%" PRId32 "%s", p, value, s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void DynamicNumber<uint16_t>::updateText()
|
||||
{
|
||||
if (lvobj) {
|
||||
const char* p = prefix ? prefix : "";
|
||||
const char* s = suffix ? suffix : "";
|
||||
if ((textFlags & PREC2) == PREC2) {
|
||||
lv_label_set_text_fmt(lvobj, "%s%" PRIu16 ".%02" PRIu16 "%s", p,
|
||||
(uint16_t)(value / 100), (uint16_t)(value % 100),
|
||||
s);
|
||||
} else if (textFlags & PREC1) {
|
||||
lv_label_set_text_fmt(lvobj, "%s%" PRIu16 ".%01" PRIu16 "%s", p,
|
||||
(uint16_t)(value / 10), (uint16_t)(value % 10), s);
|
||||
} else {
|
||||
lv_label_set_text_fmt(lvobj, "%s%" PRIu16 "%s", p, value, s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void DynamicNumber<int16_t>::updateText()
|
||||
{
|
||||
if (lvobj) {
|
||||
const char* p = prefix ? prefix : "";
|
||||
const char* s = suffix ? suffix : "";
|
||||
if ((textFlags & PREC2) == PREC2) {
|
||||
lv_label_set_text_fmt(lvobj, "%s%" PRId16 ".%02" PRIu16 "%s", p,
|
||||
(int16_t)(value / 100), (uint16_t)abs(value % 100),
|
||||
s);
|
||||
} else if (textFlags & PREC1) {
|
||||
lv_label_set_text_fmt(lvobj, "%s%" PRId16 ".%01" PRIu16 "%s", p,
|
||||
(int16_t)(value / 10), (uint16_t)abs(value % 10),
|
||||
s);
|
||||
} else {
|
||||
lv_label_set_text_fmt(lvobj, "%s%" PRId16 "%s", p, value, s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
StaticIcon::StaticIcon(Window* parent, coord_t x, coord_t y, EdgeTxIcon icon,
|
||||
LcdColorIndex color) :
|
||||
Window(parent, rect_t{x, y, 0, 0}, lv_canvas_create),
|
||||
currentColor(color)
|
||||
{
|
||||
setWindowFlag(NO_FOCUS);
|
||||
|
||||
lv_obj_clear_flag(lvobj, LV_OBJ_FLAG_CLICKABLE);
|
||||
|
||||
setIcon(icon);
|
||||
|
||||
etx_img_color(lvobj, currentColor, LV_PART_MAIN);
|
||||
}
|
||||
|
||||
StaticIcon::StaticIcon(Window* parent, coord_t x, coord_t y, const char* filename,
|
||||
LcdColorIndex color) :
|
||||
Window(parent, rect_t{x, y, 0, 0}, lv_canvas_create),
|
||||
currentColor(color)
|
||||
{
|
||||
setWindowFlag(NO_FOCUS);
|
||||
|
||||
lv_obj_clear_flag(lvobj, LV_OBJ_FLAG_CLICKABLE);
|
||||
|
||||
auto bm = BitmapBuffer::loadBitmap(filename, BMP_RGB565);
|
||||
if (bm) {
|
||||
size_t size;
|
||||
mask = bm->to8bitMask(&size);
|
||||
if (mask) {
|
||||
setSize(mask->width, mask->height);
|
||||
lv_canvas_set_buffer(lvobj, (void*)mask->data, mask->width, mask->height,
|
||||
LV_IMG_CF_ALPHA_8BIT);
|
||||
}
|
||||
delete bm;
|
||||
}
|
||||
|
||||
etx_img_color(lvobj, currentColor, LV_PART_MAIN);
|
||||
}
|
||||
|
||||
void StaticIcon::deleteLater(bool detach, bool trash)
|
||||
{
|
||||
if (_deleted) return;
|
||||
if (mask) free(mask);
|
||||
mask = nullptr;
|
||||
}
|
||||
|
||||
void StaticIcon::setColor(LcdColorIndex color)
|
||||
{
|
||||
if (currentColor != color) {
|
||||
etx_img_color(lvobj, color, LV_PART_MAIN);
|
||||
currentColor = color;
|
||||
}
|
||||
}
|
||||
|
||||
void StaticIcon::setIcon(EdgeTxIcon icon)
|
||||
{
|
||||
auto newMask = getBuiltinIcon(icon);
|
||||
setSize(newMask->width, newMask->height);
|
||||
lv_canvas_set_buffer(lvobj, (void*)newMask->data, newMask->width, newMask->height,
|
||||
LV_IMG_CF_ALPHA_8BIT);
|
||||
}
|
||||
|
||||
void StaticIcon::center(coord_t w, coord_t h)
|
||||
{
|
||||
setPos((w - width()) / 2, (h - height()) / 2);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// Display image from file system using LVGL with LVGL scaling
|
||||
// - LVGL scaling is slow so don't use this if there are many images
|
||||
StaticImage::StaticImage(Window* parent, const rect_t& rect,
|
||||
const char* filename, bool fillFrame, bool dontEnlarge) :
|
||||
Window(parent, rect), fillFrame(fillFrame), dontEnlarge(dontEnlarge)
|
||||
{
|
||||
setWindowFlag(NO_FOCUS);
|
||||
|
||||
lv_obj_clear_flag(lvobj, LV_OBJ_FLAG_CLICKABLE);
|
||||
|
||||
if (!filename) filename = "";
|
||||
setSource(filename);
|
||||
}
|
||||
|
||||
void StaticImage::setSource(std::string filename)
|
||||
{
|
||||
if (!filename.empty()) {
|
||||
std::string fullpath = std::string("A" PATH_SEPARATOR) + filename;
|
||||
|
||||
if (!image) image = lv_img_create(lvobj);
|
||||
lv_obj_set_pos(image, 0, 0);
|
||||
lv_obj_set_size(image, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
|
||||
lv_obj_center(image);
|
||||
lv_img_set_src(image, fullpath.c_str());
|
||||
if (!hasImage()) {
|
||||
// Failed to load
|
||||
TRACE("could not load image '%s'", filename.c_str());
|
||||
lv_obj_del(image);
|
||||
image = nullptr;
|
||||
}
|
||||
setZoom();
|
||||
} else {
|
||||
clearSource();
|
||||
}
|
||||
}
|
||||
|
||||
void StaticImage::clearSource()
|
||||
{
|
||||
if (image) lv_obj_del(image);
|
||||
image = nullptr;
|
||||
}
|
||||
|
||||
bool StaticImage::hasImage() const
|
||||
{
|
||||
lv_img_t* img = (lv_img_t*)image;
|
||||
return img && img->w && img->h;
|
||||
}
|
||||
|
||||
void StaticImage::setZoom()
|
||||
{
|
||||
lv_img_t* img = (lv_img_t*)image;
|
||||
if (img && img->w && img->h) {
|
||||
uint16_t zw = (width() * 256) / img->w;
|
||||
uint16_t zh = (height() * 256) / img->h;
|
||||
uint16_t z = fillFrame ? max(zw, zh) : min(zw, zh);
|
||||
if (dontEnlarge) z = min(z, (uint16_t)256);
|
||||
lv_img_set_zoom(image, z);
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// Display image from file system with software scaling
|
||||
// - uglier but much faster than LVGL scaling
|
||||
StaticBitmap::StaticBitmap(Window* parent, const rect_t& rect,
|
||||
const char* filename) :
|
||||
Window(parent, rect)
|
||||
{
|
||||
setWindowFlag(NO_FOCUS);
|
||||
lv_obj_clear_flag(lvobj, LV_OBJ_FLAG_CLICKABLE);
|
||||
|
||||
setSource(filename);
|
||||
}
|
||||
|
||||
void StaticBitmap::setSource(const char *filename)
|
||||
{
|
||||
if (filename) {
|
||||
if (img) delete img;
|
||||
img = BitmapBuffer::loadBitmap(filename, BMP_ARGB4444);
|
||||
if (img) {
|
||||
img->resizeToLVGL(width(), height());
|
||||
if (canvas) lv_obj_del(canvas);
|
||||
canvas = lv_canvas_create(lvobj);
|
||||
lv_obj_center(canvas);
|
||||
lv_canvas_set_buffer(canvas, img->getData(), img->width(), img->height(),
|
||||
LV_IMG_CF_TRUE_COLOR_ALPHA);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StaticBitmap::~StaticBitmap()
|
||||
{
|
||||
if (img) delete img;
|
||||
}
|
||||
|
||||
bool StaticBitmap::hasImage() const
|
||||
{
|
||||
return img && canvas;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
StaticLZ4Image::StaticLZ4Image(Window* parent, coord_t x, coord_t y,
|
||||
const LZ4Bitmap* lz4Bitmap) :
|
||||
Window(parent, {x, y, lz4Bitmap->width, lz4Bitmap->height},
|
||||
lv_canvas_create)
|
||||
{
|
||||
setWindowFlag(NO_FOCUS);
|
||||
|
||||
lv_obj_clear_flag(lvobj, LV_OBJ_FLAG_CLICKABLE);
|
||||
|
||||
// Convert ARGB4444 to LV_IMG_CF_TRUE_COLOR_ALPHA
|
||||
uint16_t w = lz4Bitmap->width;
|
||||
uint16_t h = lz4Bitmap->height;
|
||||
|
||||
uint32_t pixels = w * h;
|
||||
uint32_t size = (pixels + 1) & 0xFFFFFFFE;
|
||||
imgData = (uint8_t*)lv_mem_alloc(size * 3);
|
||||
uint16_t* decompData = (uint16_t*)(imgData + size);
|
||||
|
||||
LZ4_decompress_safe((const char*)lz4Bitmap->data, (char*)decompData,
|
||||
lz4Bitmap->compressedSize, pixels * sizeof(uint16_t));
|
||||
|
||||
uint8_t* dest = imgData;
|
||||
for (uint32_t i = 0; i < pixels; i += 1) {
|
||||
uint16_t c = *decompData;
|
||||
ARGB_SPLIT(c, a, r, g, b);
|
||||
c = RGB_JOIN(r * 2, g * 4, b * 2);
|
||||
*dest++ = c & 0xFF;
|
||||
*dest++ = c >> 8;
|
||||
*dest++ = (a * 255) / 15;
|
||||
decompData += 1;
|
||||
}
|
||||
|
||||
lv_canvas_set_buffer(lvobj, imgData, w, h, LV_IMG_CF_TRUE_COLOR_ALPHA);
|
||||
}
|
||||
|
||||
void StaticLZ4Image::deleteLater(bool detach, bool trash)
|
||||
{
|
||||
if (!deleted()) {
|
||||
if (imgData) lv_mem_free(imgData);
|
||||
imgData = nullptr;
|
||||
Window::deleteLater(detach, trash);
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
QRCode::QRCode(Window *parent, coord_t x, coord_t y, coord_t sz, std::string data,
|
||||
LcdFlags color, LcdFlags bgColor) :
|
||||
Window(parent, {x, y, sz, sz})
|
||||
{
|
||||
auto qr = lv_qrcode_create(lvobj, sz, makeLvColor(color), makeLvColor(bgColor));
|
||||
lv_qrcode_update(qr, data.c_str(), data.length());
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
/*
|
||||
* Copyright (C) EdgeTX
|
||||
*
|
||||
* Based on code named
|
||||
* libopenui - https://github.com/opentx/libopenui
|
||||
*
|
||||
* 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 "bitmaps.h"
|
||||
#include "window.h"
|
||||
|
||||
struct LZ4Bitmap;
|
||||
class BitmapBuffer;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
class StaticText : public Window
|
||||
{
|
||||
public:
|
||||
StaticText(Window *parent, const rect_t &rect, std::string text = "",
|
||||
LcdColorIndex color = COLOR_THEME_PRIMARY1_INDEX, LcdFlags textFlags = 0);
|
||||
|
||||
#if defined(DEBUG_WINDOWS)
|
||||
std::string getName() const override;
|
||||
#endif
|
||||
|
||||
void setText(std::string value);
|
||||
const std::string &getText() const;
|
||||
|
||||
protected:
|
||||
std::string text;
|
||||
};
|
||||
|
||||
#define Subtitle(parent, text) \
|
||||
StaticText(parent, rect_t{}, text, COLOR_THEME_PRIMARY1_INDEX, FONT(BOLD))
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
class DynamicText : public StaticText
|
||||
{
|
||||
public:
|
||||
DynamicText(Window *parent, const rect_t &rect,
|
||||
std::function<std::string()> textHandler,
|
||||
LcdColorIndex color = COLOR_THEME_PRIMARY1_INDEX, LcdFlags textFlags = 0) :
|
||||
StaticText(parent, rect, "", color, textFlags),
|
||||
textHandler(std::move(textHandler))
|
||||
{
|
||||
}
|
||||
|
||||
void checkEvents() override
|
||||
{
|
||||
StaticText::checkEvents();
|
||||
setText(textHandler());
|
||||
}
|
||||
|
||||
protected:
|
||||
std::function<std::string()> textHandler;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
class DynamicNumber : public StaticText
|
||||
{
|
||||
public:
|
||||
DynamicNumber(Window *parent, const rect_t &rect,
|
||||
std::function<T()> numberHandler,
|
||||
LcdColorIndex color = COLOR_THEME_PRIMARY1_INDEX, LcdFlags textFlags = 0,
|
||||
const char *prefix = nullptr, const char *suffix = nullptr) :
|
||||
StaticText(parent, rect, "", color, textFlags),
|
||||
numberHandler(std::move(numberHandler)),
|
||||
prefix(prefix),
|
||||
suffix(suffix)
|
||||
{
|
||||
value = this->numberHandler();
|
||||
updateText();
|
||||
}
|
||||
|
||||
void checkEvents() override
|
||||
{
|
||||
T newValue = numberHandler();
|
||||
if (value != newValue) {
|
||||
value = newValue;
|
||||
updateText();
|
||||
}
|
||||
}
|
||||
|
||||
void setPrefix(const char *value)
|
||||
{
|
||||
prefix = value;
|
||||
updateText();
|
||||
}
|
||||
|
||||
void setSuffix(const char *value)
|
||||
{
|
||||
suffix = value;
|
||||
updateText();
|
||||
}
|
||||
|
||||
protected:
|
||||
T value = 0;
|
||||
std::function<T()> numberHandler;
|
||||
const char *prefix;
|
||||
const char *suffix;
|
||||
|
||||
void updateText();
|
||||
};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
class StaticIcon : public Window
|
||||
{
|
||||
public:
|
||||
StaticIcon(Window *parent, coord_t x, coord_t y, EdgeTxIcon icon,
|
||||
LcdColorIndex color);
|
||||
StaticIcon(Window *parent, coord_t x, coord_t y, const char* filename,
|
||||
LcdColorIndex color);
|
||||
|
||||
void deleteLater(bool detach = true, bool trash = true) override;
|
||||
|
||||
#if defined(DEBUG_WINDOWS)
|
||||
std::string getName() const override { return "StaticIcon"; }
|
||||
#endif
|
||||
|
||||
void setColor(LcdColorIndex color);
|
||||
void setIcon(EdgeTxIcon icon);
|
||||
void center(coord_t w, coord_t h);
|
||||
|
||||
protected:
|
||||
LcdColorIndex currentColor;
|
||||
MaskBitmap* mask = nullptr;
|
||||
};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
class StaticImage : public Window
|
||||
{
|
||||
public:
|
||||
StaticImage(Window *parent, const rect_t &rect,
|
||||
const char *filename = nullptr, bool fillFrame = false, bool dontEnlarge = false);
|
||||
|
||||
#if defined(DEBUG_WINDOWS)
|
||||
std::string getName() const override { return "StaticImage"; }
|
||||
#endif
|
||||
|
||||
void setZoom();
|
||||
void setSource(std::string filename);
|
||||
void clearSource();
|
||||
bool hasImage() const;
|
||||
|
||||
protected:
|
||||
bool fillFrame;
|
||||
bool dontEnlarge = false;
|
||||
lv_obj_t *image = nullptr;
|
||||
};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
class StaticBitmap : public Window
|
||||
{
|
||||
public:
|
||||
StaticBitmap(Window *parent, const rect_t &rect,
|
||||
const char *filename = nullptr);
|
||||
~StaticBitmap();
|
||||
|
||||
#if defined(DEBUG_WINDOWS)
|
||||
std::string getName() const override { return "StaticBitmap"; }
|
||||
#endif
|
||||
|
||||
void setSource(const char *filename);
|
||||
bool hasImage() const;
|
||||
|
||||
protected:
|
||||
lv_obj_t *canvas = nullptr;
|
||||
BitmapBuffer *img = nullptr;
|
||||
};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
class StaticLZ4Image : public Window
|
||||
{
|
||||
public:
|
||||
StaticLZ4Image(Window *parent, coord_t x, coord_t y,
|
||||
const LZ4Bitmap *lz4Bitmap);
|
||||
|
||||
#if defined(DEBUG_WINDOWS)
|
||||
std::string getName() const override { return "StaticILZ4mage"; }
|
||||
#endif
|
||||
|
||||
protected:
|
||||
uint8_t *imgData = nullptr;
|
||||
|
||||
void deleteLater(bool detach, bool trash) override;
|
||||
};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
class QRCode : public Window
|
||||
{
|
||||
public:
|
||||
QRCode(Window *parent, coord_t x, coord_t y, coord_t sz, std::string data,
|
||||
LcdFlags color = COLOR_THEME_SECONDARY1, LcdFlags bgColor = COLOR_THEME_SECONDARY3);
|
||||
|
||||
#if defined(DEBUG_WINDOWS)
|
||||
std::string getName() const override { return "QRCode"; }
|
||||
#endif
|
||||
|
||||
protected:
|
||||
};
|
||||
@@ -0,0 +1,284 @@
|
||||
/*
|
||||
* Copyright (C) EdgeTX
|
||||
*
|
||||
* Based on code named
|
||||
* libopenui - https://github.com/opentx/libopenui
|
||||
*
|
||||
* 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 "table.h"
|
||||
|
||||
#include "etx_lv_theme.h"
|
||||
|
||||
static void table_event(const lv_obj_class_t* class_p, lv_event_t* e);
|
||||
|
||||
// Table
|
||||
const lv_style_const_prop_t table_cell_props[] = {
|
||||
LV_STYLE_CONST_BORDER_WIDTH(1),
|
||||
LV_STYLE_CONST_BORDER_SIDE(LV_BORDER_SIDE_TOP | LV_BORDER_SIDE_BOTTOM),
|
||||
LV_STYLE_CONST_PAD_TOP(7), LV_STYLE_CONST_PAD_BOTTOM(7),
|
||||
LV_STYLE_CONST_PAD_LEFT(4), LV_STYLE_CONST_PAD_RIGHT(4),
|
||||
LV_STYLE_PROP_INV,
|
||||
};
|
||||
LV_STYLE_CONST_MULTI_INIT(table_cell, table_cell_props);
|
||||
|
||||
static void table_constructor(const lv_obj_class_t* class_p, lv_obj_t* obj)
|
||||
{
|
||||
etx_obj_add_style(obj, styles->pad_zero, LV_PART_MAIN);
|
||||
|
||||
etx_scrollbar(obj);
|
||||
|
||||
etx_solid_bg(obj, COLOR_THEME_PRIMARY2_INDEX, LV_PART_ITEMS);
|
||||
etx_txt_color(obj, COLOR_THEME_PRIMARY1_INDEX, LV_PART_ITEMS);
|
||||
etx_obj_add_style(obj, table_cell, LV_PART_ITEMS);
|
||||
etx_obj_add_style(obj, styles->border_color[COLOR_THEME_SECONDARY2_INDEX], LV_PART_ITEMS);
|
||||
|
||||
etx_bg_color(obj, COLOR_THEME_FOCUS_INDEX, LV_PART_ITEMS | LV_STATE_EDITED);
|
||||
etx_txt_color(obj, COLOR_THEME_PRIMARY2_INDEX, LV_PART_ITEMS | LV_STATE_EDITED);
|
||||
}
|
||||
|
||||
static const lv_obj_class_t table_class = {
|
||||
.base_class = &lv_table_class,
|
||||
.constructor_cb = table_constructor,
|
||||
.destructor_cb = nullptr,
|
||||
.user_data = nullptr,
|
||||
.event_cb = table_event,
|
||||
.width_def = 0,
|
||||
.height_def = 0,
|
||||
.editable = LV_OBJ_CLASS_EDITABLE_TRUE,
|
||||
.group_def = LV_OBJ_CLASS_GROUP_DEF_TRUE,
|
||||
.instance_size = sizeof(lv_table_t),
|
||||
};
|
||||
|
||||
static lv_obj_t* table_create(lv_obj_t* parent)
|
||||
{
|
||||
return etx_create(&table_class, parent);
|
||||
}
|
||||
|
||||
static void table_event(const lv_obj_class_t* class_p, lv_event_t* e)
|
||||
{
|
||||
lv_obj_t* obj = lv_event_get_target(e);
|
||||
if (obj) {
|
||||
TableField* tf = (TableField*)lv_obj_get_user_data(obj);
|
||||
if (tf) {
|
||||
lv_event_code_t code = lv_event_get_code(e);
|
||||
switch (code) {
|
||||
case LV_EVENT_VALUE_CHANGED: {
|
||||
uint16_t row;
|
||||
uint16_t col;
|
||||
lv_table_get_selected_cell(obj, &row, &col);
|
||||
if (row != LV_TABLE_CELL_NONE && col != LV_TABLE_CELL_NONE) {
|
||||
lv_indev_type_t indev_type = lv_indev_get_type(lv_indev_get_act());
|
||||
if (indev_type == LV_INDEV_TYPE_ENCODER) {
|
||||
// Encoder send VALUE_CHANGED when selection changes
|
||||
tf->adjustScroll();
|
||||
tf->onSelected(row, col);
|
||||
} else {
|
||||
// Otherwise it's a click
|
||||
if (lv_group_get_editing((lv_group_t*)lv_obj_get_group(obj)) ||
|
||||
indev_type == LV_INDEV_TYPE_POINTER) {
|
||||
tf->onPress(row, col);
|
||||
} else {
|
||||
tf->onClicked();
|
||||
}
|
||||
// Note: VALUE_CHANGED is generated on RELEASED
|
||||
//
|
||||
// It must be avoided that CLICKED be generated afterwards
|
||||
// in case the object has been deleted meanwhile and to
|
||||
// avoid onClicked() being called.
|
||||
//
|
||||
lv_indev_reset(lv_indev_get_act(), nullptr);
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case LV_EVENT_DRAW_PART_BEGIN: {
|
||||
lv_obj_draw_part_dsc_t* dsc = lv_event_get_draw_part_dsc(e);
|
||||
if (dsc->part == LV_PART_ITEMS) {
|
||||
uint16_t cols = lv_table_get_col_cnt(obj);
|
||||
if (cols) {
|
||||
uint16_t row = dsc->id / cols;
|
||||
uint16_t col = dsc->id % cols;
|
||||
tf->onDrawBegin(row, col, dsc);
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case LV_EVENT_DRAW_PART_END: {
|
||||
lv_obj_draw_part_dsc_t* dsc = lv_event_get_draw_part_dsc(e);
|
||||
if (dsc->part == LV_PART_ITEMS) {
|
||||
uint16_t cols = lv_table_get_col_cnt(obj);
|
||||
if (cols) {
|
||||
uint16_t row = dsc->id / cols;
|
||||
uint16_t col = dsc->id % cols;
|
||||
tf->onDrawEnd(row, col, dsc);
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case LV_EVENT_DRAW_POST: {
|
||||
bool has_focus = lv_obj_has_state(obj, LV_STATE_FOCUS_KEY);
|
||||
bool is_edited =
|
||||
lv_group_get_editing((lv_group_t*)lv_obj_get_group(obj));
|
||||
|
||||
if (has_focus && !is_edited) {
|
||||
lv_draw_ctx_t* draw_ctx = lv_event_get_draw_ctx(e);
|
||||
|
||||
lv_draw_rect_dsc_t rect_dsc;
|
||||
lv_draw_rect_dsc_init(&rect_dsc);
|
||||
rect_dsc.bg_opa = LV_OPA_TRANSP;
|
||||
rect_dsc.bg_img_opa = LV_OPA_TRANSP;
|
||||
rect_dsc.outline_opa = LV_OPA_TRANSP;
|
||||
rect_dsc.shadow_opa = LV_OPA_TRANSP;
|
||||
|
||||
rect_dsc.border_color = makeLvColor(COLOR_THEME_FOCUS);
|
||||
rect_dsc.border_opa = LV_OPA_100;
|
||||
rect_dsc.border_width = lv_dpx(2);
|
||||
|
||||
lv_area_t coords;
|
||||
lv_area_copy(&coords, &obj->coords);
|
||||
|
||||
lv_draw_rect(draw_ctx, &rect_dsc, &coords);
|
||||
}
|
||||
} break;
|
||||
case LV_EVENT_RELEASED: {
|
||||
lv_table_t* table = (lv_table_t*)obj;
|
||||
/*From lv_table.c: handler for LV_EVENT_RELEASED*/
|
||||
lv_obj_invalidate(obj);
|
||||
lv_indev_t* indev = lv_indev_get_act();
|
||||
lv_obj_t* scroll_obj = lv_indev_get_scroll_obj(indev);
|
||||
if (table->col_act != LV_TABLE_CELL_NONE &&
|
||||
table->row_act != LV_TABLE_CELL_NONE && scroll_obj == NULL) {
|
||||
lv_event_send(obj, LV_EVENT_VALUE_CHANGED, NULL);
|
||||
}
|
||||
return;
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*Call the ancestor's event handler*/
|
||||
lv_obj_event_base(&table_class, e);
|
||||
}
|
||||
|
||||
TableField::TableField(Window* parent, const rect_t& rect) :
|
||||
Window(parent, rect, table_create)
|
||||
{
|
||||
setWindowFlag(OPAQUE);
|
||||
|
||||
etx_scrollbar(lvobj);
|
||||
lv_table_set_col_cnt(lvobj, 1);
|
||||
}
|
||||
|
||||
void TableField::setRowCount(uint16_t rows)
|
||||
{
|
||||
lv_table_set_row_cnt(lvobj, rows);
|
||||
}
|
||||
|
||||
uint16_t TableField::getRowCount() const { return lv_table_get_row_cnt(lvobj); }
|
||||
|
||||
void TableField::setColumnWidth(uint16_t col, coord_t w)
|
||||
{
|
||||
lv_table_set_col_width(lvobj, col, w);
|
||||
}
|
||||
|
||||
void TableField::select(uint16_t row, uint16_t col, bool force)
|
||||
{
|
||||
lv_table_t* table = (lv_table_t*)lvobj;
|
||||
if (!force && table->row_act == row && table->col_act == row) return;
|
||||
|
||||
if (row >= table->row_cnt || col >= table->col_cnt) {
|
||||
table->col_act = LV_TABLE_CELL_NONE;
|
||||
table->row_act = LV_TABLE_CELL_NONE;
|
||||
} else {
|
||||
table->row_act = row;
|
||||
table->col_act = col;
|
||||
}
|
||||
|
||||
lv_obj_invalidate(lvobj);
|
||||
adjustScroll();
|
||||
}
|
||||
|
||||
void TableField::adjustScroll()
|
||||
{
|
||||
lv_table_t* table = (lv_table_t*)lvobj;
|
||||
|
||||
// only vertical scroll for now
|
||||
lv_coord_t h_before = 0;
|
||||
for (uint16_t i = 0; i < table->row_act; i++) h_before += table->row_h[i];
|
||||
|
||||
lv_coord_t row_h = table->row_h[table->row_act];
|
||||
lv_coord_t scroll_y = lv_obj_get_scroll_y(lvobj);
|
||||
|
||||
lv_obj_update_layout(lvobj);
|
||||
lv_coord_t h = lv_obj_get_height(lvobj);
|
||||
|
||||
lv_coord_t diff_y = 0;
|
||||
if (h_before < scroll_y) {
|
||||
diff_y = scroll_y - h_before;
|
||||
} else if (scroll_y + h < h_before + row_h) {
|
||||
diff_y = scroll_y + h - h_before - row_h;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
lv_obj_scroll_by_bounded(lvobj, 0, diff_y, LV_ANIM_OFF);
|
||||
}
|
||||
|
||||
void TableField::selectNext(int16_t dir)
|
||||
{
|
||||
lv_table_t* table = (lv_table_t*)lvobj;
|
||||
|
||||
if (table->col_act == LV_TABLE_CELL_NONE ||
|
||||
table->row_act == LV_TABLE_CELL_NONE) {
|
||||
if (table->col_cnt > 0 && table->row_cnt > 0) {
|
||||
table->col_act = 0;
|
||||
table->row_act = 0;
|
||||
}
|
||||
} else {
|
||||
table->col_act += dir;
|
||||
if (table->col_act >= table->col_cnt) {
|
||||
table->col_act = 0;
|
||||
table->row_act += dir;
|
||||
|
||||
if (table->row_act >= table->row_cnt) {
|
||||
table->col_act = LV_TABLE_CELL_NONE;
|
||||
table->row_act = LV_TABLE_CELL_NONE;
|
||||
|
||||
// wrap around
|
||||
if (table->col_cnt > 0 && table->row_cnt > 0) {
|
||||
if (dir < 0) {
|
||||
table->col_act = table->col_cnt - 1;
|
||||
table->row_act = table->row_cnt - 1;
|
||||
} else {
|
||||
table->col_act = 0;
|
||||
table->row_act = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lv_obj_invalidate(lvobj);
|
||||
adjustScroll();
|
||||
}
|
||||
|
||||
void TableField::onEvent(event_t event)
|
||||
{
|
||||
if (event == EVT_ROTARY_RIGHT) {
|
||||
selectNext(1);
|
||||
} else if (event == EVT_ROTARY_LEFT) {
|
||||
selectNext(-1);
|
||||
} else {
|
||||
Window::onEvent(event);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (C) EdgeTX
|
||||
*
|
||||
* Based on code named
|
||||
* libopenui - https://github.com/opentx/libopenui
|
||||
*
|
||||
* 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 "window.h"
|
||||
|
||||
class TableField : public Window
|
||||
{
|
||||
public:
|
||||
TableField(Window* parent, const rect_t& rect);
|
||||
|
||||
#if defined(DEBUG_WINDOWS)
|
||||
std::string getName() const override { return "Table"; }
|
||||
#endif
|
||||
|
||||
void setRowCount(uint16_t rows);
|
||||
uint16_t getRowCount() const;
|
||||
|
||||
void setColumnWidth(uint16_t col, coord_t w);
|
||||
|
||||
void select(uint16_t row, uint16_t col, bool force = false);
|
||||
virtual void onPress(uint16_t row, uint16_t col) {}
|
||||
|
||||
virtual void onSelected(uint16_t row, uint16_t col) {}
|
||||
virtual void onDrawBegin(uint16_t row, uint16_t col, lv_obj_draw_part_dsc_t* dsc) {}
|
||||
virtual void onDrawEnd(uint16_t row, uint16_t col, lv_obj_draw_part_dsc_t* dsc) {}
|
||||
|
||||
void adjustScroll();
|
||||
void selectNext(int16_t dir);
|
||||
|
||||
protected:
|
||||
void onEvent(event_t event) override;
|
||||
};
|
||||
@@ -22,7 +22,7 @@
|
||||
#include "tabsgroup.h"
|
||||
|
||||
#include "theme_manager.h"
|
||||
#include "themes/etx_lv_theme.h"
|
||||
#include "etx_lv_theme.h"
|
||||
#include "view_main.h"
|
||||
#include "topbar_impl.h"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user