chore: re-organise source files for color LCD UI (#5358)

This commit is contained in:
philmoz
2024-08-02 08:50:55 +10:00
committed by GitHub
parent 4390e310d0
commit 2eee7cd011
329 changed files with 298 additions and 804 deletions
+1 -1
View File
@@ -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"
+97 -128
View File
@@ -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)
+121
View File
@@ -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()
+1 -1
View File
@@ -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"
+1 -1
View File
@@ -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) :
-4
View File
@@ -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;
@@ -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)
@@ -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"};
@@ -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) :
@@ -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
@@ -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) :
+1 -1
View File
@@ -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"
+1 -1
View File
@@ -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);
}
}
+241
View File
@@ -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);
}
+119
View File
@@ -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); }
+120
View File
@@ -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;
};
+228
View File
@@ -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); });
}
+173
View File
@@ -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();
};
+216
View File
@@ -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;
});
}
+146
View File
@@ -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];
};
@@ -20,7 +20,7 @@
*/
#include "file_browser.h"
#include "libopenui_file.h"
#include "lib_file.h"
#include "fonts.h"
#include <list>
+197
View File
@@ -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);
}
}
+54
View File
@@ -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;
};
+99
View File
@@ -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();
}
+116
View File
@@ -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;
};
@@ -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;
};
+85
View File
@@ -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;
}
+39
View File
@@ -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
@@ -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)
+122
View File
@@ -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);
}
}
+64
View File
@@ -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();
};
+464
View File
@@ -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); }
+94
View File
@@ -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;
};
+291
View File
@@ -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();
}
+119
View File
@@ -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)
+63
View File
@@ -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);
}
}
+37
View File
@@ -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;
};
+159
View File
@@ -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);
}
}
+54
View File
@@ -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;
};
+368
View File
@@ -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());
}
+219
View File
@@ -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:
};
+284
View File
@@ -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);
}
}
+49
View File
@@ -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