diff --git a/radio/src/thirdparty/libopenui/.clang-format b/radio/src/thirdparty/libopenui/.clang-format
new file mode 100644
index 000000000..913141780
--- /dev/null
+++ b/radio/src/thirdparty/libopenui/.clang-format
@@ -0,0 +1,6 @@
+---
+Language: Cpp
+BasedOnStyle: Google
+BreakBeforeBraces: Linux
+BreakConstructorInitializers: AfterColon
+...
diff --git a/radio/src/thirdparty/libopenui/.gitignore b/radio/src/thirdparty/libopenui/.gitignore
new file mode 100644
index 000000000..e5d594669
--- /dev/null
+++ b/radio/src/thirdparty/libopenui/.gitignore
@@ -0,0 +1 @@
+src/thirdparty/stb/
diff --git a/radio/src/thirdparty/libopenui/.gitmodules b/radio/src/thirdparty/libopenui/.gitmodules
new file mode 100644
index 000000000..825bab72f
--- /dev/null
+++ b/radio/src/thirdparty/libopenui/.gitmodules
@@ -0,0 +1,7 @@
+[submodule "thirdparty/stb"]
+ path = thirdparty/stb
+ url = https://github.com/nothings/stb.git
+[submodule "thirdparty/lvgl"]
+ path = thirdparty/lvgl
+ url = https://github.com/EdgeTX/lvgl.git
+ branch = release/v8.2
diff --git a/radio/src/thirdparty/libopenui/.idea/codeStyles/Project.xml b/radio/src/thirdparty/libopenui/.idea/codeStyles/Project.xml
new file mode 100644
index 000000000..efad3ce27
--- /dev/null
+++ b/radio/src/thirdparty/libopenui/.idea/codeStyles/Project.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/radio/src/thirdparty/libopenui/.idea/codeStyles/codeStyleConfig.xml b/radio/src/thirdparty/libopenui/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 000000000..79ee123c2
--- /dev/null
+++ b/radio/src/thirdparty/libopenui/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/radio/src/thirdparty/libopenui/LICENSE b/radio/src/thirdparty/libopenui/LICENSE
new file mode 100644
index 000000000..0a041280b
--- /dev/null
+++ b/radio/src/thirdparty/libopenui/LICENSE
@@ -0,0 +1,165 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+ This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+ 0. Additional Definitions.
+
+ As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+ "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+ An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+ A "Combined Work" is a work produced by combining or linking an
+Application with the Library. The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+ The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+ The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+ 1. Exception to Section 3 of the GNU GPL.
+
+ You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+ 2. Conveying Modified Versions.
+
+ If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+ a) under this License, provided that you make a good faith effort to
+ ensure that, in the event an Application does not supply the
+ function or data, the facility still operates, and performs
+ whatever part of its purpose remains meaningful, or
+
+ b) under the GNU GPL, with none of the additional permissions of
+ this License applicable to that copy.
+
+ 3. Object Code Incorporating Material from Library Header Files.
+
+ The object code form of an Application may incorporate material from
+a header file that is part of the Library. You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+ a) Give prominent notice with each copy of the object code that the
+ Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the object code with a copy of the GNU GPL and this license
+ document.
+
+ 4. Combined Works.
+
+ You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+ a) Give prominent notice with each copy of the Combined Work that
+ the Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
+ document.
+
+ c) For a Combined Work that displays copyright notices during
+ execution, include the copyright notice for the Library among
+ these notices, as well as a reference directing the user to the
+ copies of the GNU GPL and this license document.
+
+ d) Do one of the following:
+
+ 0) Convey the Minimal Corresponding Source under the terms of this
+ License, and the Corresponding Application Code in a form
+ suitable for, and under terms that permit, the user to
+ recombine or relink the Application with a modified version of
+ the Linked Version to produce a modified Combined Work, in the
+ manner specified by section 6 of the GNU GPL for conveying
+ Corresponding Source.
+
+ 1) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (a) uses at run time
+ a copy of the Library already present on the user's computer
+ system, and (b) will operate properly with a modified version
+ of the Library that is interface-compatible with the Linked
+ Version.
+
+ e) Provide Installation Information, but only if you would otherwise
+ be required to provide such information under section 6 of the
+ GNU GPL, and only to the extent that such information is
+ necessary to install and execute a modified version of the
+ Combined Work produced by recombining or relinking the
+ Application with a modified version of the Linked Version. (If
+ you use option 4d0, the Installation Information must accompany
+ the Minimal Corresponding Source and Corresponding Application
+ Code. If you use option 4d1, you must provide the Installation
+ Information in the manner specified by section 6 of the GNU GPL
+ for conveying Corresponding Source.)
+
+ 5. Combined Libraries.
+
+ You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+ a) Accompany the combined library with a copy of the same work based
+ on the Library, uncombined with any other library facilities,
+ conveyed under the terms of this License.
+
+ b) Give prominent notice with the combined library that part of it
+ is a work based on the Library, and explaining where to find the
+ accompanying uncombined form of the same work.
+
+ 6. Revised Versions of the GNU Lesser General Public License.
+
+ The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+ If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
diff --git a/radio/src/thirdparty/libopenui/README.md b/radio/src/thirdparty/libopenui/README.md
new file mode 100644
index 000000000..6d17b6d92
--- /dev/null
+++ b/radio/src/thirdparty/libopenui/README.md
@@ -0,0 +1,4 @@
+# libopenui
+C++ UI Library primarily used in EdgeTX for Colour LCD targets.
+
+Was forked from [OpenTX/libopenui](https://github.com/opentx/libopenui) and detached so that contributors can easily work on both if they wish to do so.
diff --git a/radio/src/thirdparty/libopenui/src/CMakeLists.txt b/radio/src/thirdparty/libopenui/src/CMakeLists.txt
new file mode 100644
index 000000000..2d4891572
--- /dev/null
+++ b/radio/src/thirdparty/libopenui/src/CMakeLists.txt
@@ -0,0 +1,57 @@
+if(HARDWARE_KEYS)
+ add_definitions(-DHARDWARE_KEYS)
+endif()
+
+if(HARWARE_TOUCH)
+ add_definitions(-DHARDWARE_TOUCH)
+endif()
+
+set(LIBOPENUI_SRC
+ libopenui_file.cpp
+ bitmapbuffer.cpp
+ window.cpp
+ layer.cpp
+ form.cpp
+ button.cpp
+ button_matrix.cpp
+ static.cpp
+ checkbox.cpp
+ carousel.cpp
+ slider.cpp
+ mainwindow.cpp
+ menu.cpp
+ menutoolbar.cpp
+ choice.cpp
+ choiceex.cpp
+ filechoice.cpp
+ numberedit.cpp
+ textedit.cpp
+ progress.cpp
+ table.cpp
+ modal_window.cpp
+ dialog.cpp
+ keyboard_text.cpp
+ keyboard_number.cpp
+ keyboard_base.cpp
+ # LVGL widgets
+ widgets/window_base.c
+ widgets/simple_btn.c
+ widgets/field_edit.c
+ widgets/edgetx_table.c
+ )
+
+foreach(LIBOPENUI_FILE ${LIBOPENUI_SRC})
+ set(LIBOPENUI_SRC_FILES ${LIBOPENUI_SRC_FILES} ${LIBOPENUI_SRC_DIR}/src/${LIBOPENUI_FILE})
+endforeach()
+
+set(SRC ${SRC} ${LIBOPENUI_SRC_FILES}
+ ${LIBOPENUI_SRC_DIR}/thirdparty/lz4/lz4.c
+ )
+
+# add_library(libopenui_native OBJECT ${LIBOPENUI_SRC_FILES})
+# add_library(libopenui_stm32 OBJECT ${LIBOPENUI_SRC_FILES})
+
+# Dirty hack to include FatFs into libopenui
+# TODO: this must go away
+#target_include_directories(libopenui PRIVATE
+# ${CMAKE_CURRENT_SOURCE_DIR}/../../FatFs)
diff --git a/radio/src/thirdparty/libopenui/src/basenumberedit.h b/radio/src/thirdparty/libopenui/src/basenumberedit.h
new file mode 100644
index 000000000..3ddb8fa5a
--- /dev/null
+++ b/radio/src/thirdparty/libopenui/src/basenumberedit.h
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) OpenTX
+ *
+ * Source:
+ * https://github.com/opentx/libopenui
+ *
+ * This file is a part of libopenui library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ */
+
+#pragma once
+
+#include "form.h"
+#include "strhelpers.h"
+
+class BaseNumberEdit: public FormField
+{
+ public:
+ BaseNumberEdit(Window* parent, const rect_t& rect, int vmin, int vmax,
+ std::function getValue,
+ std::function setValue = nullptr,
+ WindowFlags windowFlags = 0, LcdFlags textFlags = 0,
+ LvglCreate objConstruct = nullptr) :
+ FormField(parent, rect, windowFlags, textFlags, objConstruct),
+ vmin(vmin),
+ vmax(vmax),
+ _getValue(std::move(getValue)),
+ _setValue(std::move(setValue))
+ {
+ }
+
+ void setMin(int value)
+ {
+ vmin = value;
+ }
+
+ void setMax(int value)
+ {
+ vmax = value;
+ }
+
+ void setDefault(int value)
+ {
+ vdefault = value;
+ }
+
+ int32_t getMin() const
+ {
+ return vmin;
+ }
+
+ int32_t getMax() const
+ {
+ return vmax;
+ }
+
+ int32_t getDefault() const
+ {
+ return vdefault;
+ }
+
+ void setStep(int value)
+ {
+ step = value;
+ }
+
+ int32_t getStep() const
+ {
+ return step;
+ }
+
+ void setFastStep(int value)
+ {
+ fastStep = value;
+ }
+
+ int32_t getFastStep() const
+ {
+ return fastStep;
+ }
+
+ void setAccelFactor(int value)
+ {
+ accelFactor = value;
+ }
+
+ int32_t getAccelFactor() const
+ {
+ return accelFactor;
+ }
+
+ void setValue(int value)
+ {
+ auto newValue = limit(vmin, value, vmax);
+ if (newValue != currentValue) {
+ currentValue = newValue;
+ if (instantChange && _setValue != nullptr) {
+ _setValue(currentValue);
+ }
+ }
+ if (lvobj != nullptr) {
+ std::string str;
+ if (displayFunction != nullptr) {
+ str = displayFunction(currentValue);
+ } else {
+ str = formatNumberAsString(currentValue, textFlags, 0, prefix.c_str(),
+ suffix.c_str());
+ }
+ lv_textarea_set_text(lvobj, str.c_str());
+ }
+ }
+
+ 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 enableInstantChange(bool value)
+ {
+ instantChange = value;
+ }
+
+ void disableInstantChange()
+ {
+ enableInstantChange(false);
+ }
+
+ void setSetValueHandler(std::function handler)
+ {
+ _setValue = std::move(handler);
+ }
+
+ void setGetValueHandler(std::function handler)
+ {
+ _getValue = std::move(handler);
+ }
+
+ int32_t getValue() const
+ {
+ return (editMode && !instantChange) ? currentValue
+ : _getValue != nullptr ? _getValue()
+ : 0;
+ }
+
+ void setEditMode(bool newEditMode) override
+ {
+ auto previousEditMode = editMode;
+ FormField::setEditMode(newEditMode);
+ if (!instantChange) {
+ if (!previousEditMode && newEditMode) {
+ if (_getValue != nullptr) currentValue = _getValue();
+ }
+ else if (previousEditMode && !newEditMode) {
+ if (_setValue != nullptr) _setValue(currentValue);
+ }
+ }
+ }
+
+ void setDisplayHandler(std::function function)
+ {
+ displayFunction = std::move(function);
+ update();
+ }
+
+ virtual void update()
+ {
+ if (_getValue == nullptr) return;
+ auto newValue = _getValue();
+ if (newValue != currentValue) {
+ currentValue = newValue;
+ }
+ if (lvobj != nullptr) {
+ std::string str;
+ if (displayFunction != nullptr) {
+ str = displayFunction(currentValue);
+ } else {
+ str = formatNumberAsString(currentValue, textFlags, 0, prefix.c_str(),
+ suffix.c_str());
+ }
+ lv_textarea_set_text(lvobj, str.c_str());
+ }
+ }
+
+ virtual void paint(BitmapBuffer* dc) override {};
+
+ protected:
+ int vdefault = 0;
+ int vmin;
+ int vmax;
+ int step = 1;
+ int fastStep = 10;
+ int accelFactor = 4;
+ int currentValue;
+ bool instantChange = true;
+ std::string prefix;
+ std::string suffix;
+ std::string zeroText;
+ std::function _getValue;
+ std::function _setValue;
+ std::function displayFunction;
+};
diff --git a/radio/src/thirdparty/libopenui/src/bitfield.h b/radio/src/thirdparty/libopenui/src/bitfield.h
new file mode 100644
index 000000000..2bcf1029d
--- /dev/null
+++ b/radio/src/thirdparty/libopenui/src/bitfield.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) OpenTX
+ *
+ * Source:
+ * https://github.com/opentx/libopenui
+ *
+ * This file is a part of libopenui library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ */
+
+#pragma once
+
+#include
+
+// A set of bitfield handling macros
+
+template
+inline T bfBit(uint8_t n)
+{
+ return T(1) << n;
+}
+
+#define BFBIT_FLIP(y, mask) ( y ^= (mask) )
+
+//! Create a bitmask of length 'len'.
+template
+inline T bfBitmask(uint8_t len)
+{
+ return bfBit(len) - 1;
+}
+
+//! Create a bitfield mask of length 'len' starting at bit 'start'.
+template
+inline T bfMask(uint8_t start, uint8_t len)
+{
+ return bfBitmask(len) << start;
+}
+
+//! Prepare a bitmask for insertion or combining.
+template
+inline T bfPrep(T x, uint8_t start, uint8_t len)
+{
+ return (x & bfBitmask(len)) << start;
+}
+
+//! Extract a bitfield of length 'len' starting at bit 'start' from 'y'.
+template
+inline T bfGet(T y, uint8_t start, uint8_t len)
+{
+ return ((y)>>(start)) & bfBitmask(len);
+}
+
+//! Insert 'len' bits of 'x 'into 'y', starting at bit 'start' from 'y'.
+template
+inline T bfSet(T to, T from, uint8_t start, uint8_t len)
+{
+ return (to & ~bfMask(start, len)) | bfPrep(from, start, len);
+}
+
+template
+inline T bfBitGet(T y, T mask)
+{
+ return (y & mask);
+}
+
+template
+inline T bfSingleBitGet(T y, uint8_t i)
+{
+ return bfBitGet(y, bfBit(i));
+}
+
diff --git a/radio/src/thirdparty/libopenui/src/bitmapbuffer.cpp b/radio/src/thirdparty/libopenui/src/bitmapbuffer.cpp
new file mode 100644
index 000000000..742f3b5dd
--- /dev/null
+++ b/radio/src/thirdparty/libopenui/src/bitmapbuffer.cpp
@@ -0,0 +1,1825 @@
+/*
+ * Copyright (C) OpenTX
+ *
+ * Source:
+ * https://github.com/opentx/libopenui
+ *
+ * This file is a part of libopenui library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it underresult the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ */
+
+#include
+#include
+
+#include "bitmapbuffer.h"
+#include "libopenui_depends.h"
+#include "libopenui_helpers.h"
+#include "libopenui_file.h"
+#include "font.h"
+
+#include "lvgl/src/draw/sw/lv_draw_sw.h"
+
+void DMAWait();
+
+RLEBitmap::RLEBitmap(uint8_t format, const uint8_t* rle_data) :
+ BitmapBufferBase(format, 0, 0, nullptr)
+{
+ _width = *((uint16_t *)rle_data);
+ _height = *(((uint16_t *)rle_data)+1);
+ uint32_t pixels = _width * _height;
+ data = (uint16_t*)malloc(align32(pixels * sizeof(uint16_t)));
+ decode((uint8_t *)data, pixels * sizeof(uint16_t), rle_data+4);
+ data_end = data + pixels;
+}
+
+RLEBitmap::~RLEBitmap()
+{
+ free(data);
+}
+
+int RLEBitmap::decode(uint8_t *dest, unsigned int destSize, const uint8_t *src)
+{
+ uint8_t prevByte = 0;
+ bool prevByteValid = false;
+
+ const uint8_t *destEnd = dest + destSize;
+ uint8_t *d = dest;
+
+ while (d < destEnd) {
+ uint8_t byte = *src++;
+ *d++ = byte;
+
+ if (prevByteValid && byte == prevByte) {
+ uint8_t count = *src++;
+
+ if (d + count > destEnd) {
+ TRACE("rle_decode_8bit: destination overflow!\n");
+ return -1;
+ }
+
+ memset(d, byte, count);
+ d += count;
+ prevByteValid = false;
+ } else {
+ prevByte = byte;
+ prevByteValid = true;
+ }
+ }
+
+ return d - dest;
+}
+
+BitmapBuffer::BitmapBuffer(uint8_t format, uint16_t width, uint16_t height) :
+ BitmapBufferBase(format, width, height, nullptr),
+ 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) :
+ BitmapBufferBase(format, width, height, data),
+ dataAllocated(false)
+#if defined(DEBUG)
+ , leakReported(false)
+#endif
+{
+}
+
+BitmapBuffer::~BitmapBuffer()
+{
+ if (dataAllocated) {
+#if !defined(BOOT)
+ lv_obj_del(canvas);
+#endif
+ free(data);
+ }
+}
+
+template
+void BitmapBuffer::drawBitmap(coord_t x, coord_t y, const T *bmp, coord_t srcx,
+ coord_t srcy, coord_t srcw, coord_t srch,
+ float scale)
+{
+ if (!data || !bmp) return;
+ APPLY_OFFSET();
+ if (x >= xmax || y >= ymax) return;
+ drawBitmapAbs(x, y, bmp, srcx, srcy, srcw, srch, scale);
+}
+
+template void BitmapBuffer::drawBitmap(
+ coord_t, coord_t, const BitmapBufferBase *, coord_t,
+ coord_t, coord_t, coord_t, float);
+
+template void BitmapBuffer::drawBitmap(coord_t, coord_t, const BitmapBuffer *,
+ coord_t, coord_t, coord_t, coord_t,
+ float);
+
+
+template
+void BitmapBuffer::drawScaledBitmap(const T *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);
+ }
+}
+
+template void BitmapBuffer::drawScaledBitmap(const BitmapBuffer *, coord_t,
+ coord_t, coord_t, coord_t);
+
+template
+void BitmapBuffer::drawBitmapAbs(coord_t x, coord_t y, const T *bmp,
+ coord_t srcx, coord_t srcy,
+ coord_t srcw, coord_t srch,
+ float scale)
+{
+ 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;
+ }
+
+ DMAWait();
+ if (scale == 0) {
+ if (bmp->getFormat() == 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);
+ }
+ } 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->getFormat() == BMP_RGB565) {
+ RGB_SPLIT(*q, r, g, b);
+ drawPixel(p, ARGB_JOIN(0xF, r>>1, g>>2, b>>1));
+
+ } else { // bmp->getFormat() == 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->getFormat() == BMP_RGB565) {
+ drawPixel(p, *q);
+ } else { // bmp->getFormat() == 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) {
+}
+
+template
+void BitmapBuffer::drawBitmapAbs(coord_t, coord_t, const BitmapBuffer *,
+ coord_t srcx, coord_t srcy,
+ coord_t srcw, coord_t srch,
+ float scale);
+
+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)
+{
+ APPLY_OFFSET();
+
+ 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)
+{
+ pixel_t * p = getPixelPtrAbs(x, y);
+ pixel_t color = COLOR_VAL(flags);
+
+ // Opacity needs to be inverted:
+ // 0 : Opaque
+ // 15 : Translucid
+ //
+ opacity = 0x0F - opacity;
+
+ DMAWait();
+ if (pat == SOLID) {
+ while (w--) {
+ drawAlphaPixel(p, opacity, color);
+ MOVE_TO_NEXT_RIGHT_PIXEL(p);
+ }
+ }
+ else {
+ while (w--) {
+ if (pat & 1) {
+ drawAlphaPixel(p, opacity, color);
+ pat = (pat >> 1) | 0x80;
+ }
+ else {
+ pat = pat >> 1;
+ }
+ MOVE_TO_NEXT_RIGHT_PIXEL(p);
+ }
+ }
+}
+
+void BitmapBuffer::drawVerticalLine(coord_t x, coord_t y, coord_t h, uint8_t pat, LcdFlags flags, uint8_t opacity)
+{
+ APPLY_OFFSET();
+
+ coord_t w = 1;
+ if (!applyClippingRect(x, y, w, h))
+ return;
+
+ // Opacity needs to be inverted:
+ // 0 : Opaque
+ // 15 : Translucid
+ //
+ opacity = 0x0F - opacity;
+
+ DMAWait();
+ pixel_t color = COLOR_VAL(flags);
+ if (pat == SOLID) {
+ while (h--) {
+ drawAlphaPixelAbs(x, y, opacity, color);
+ y++;
+ }
+ }
+ else {
+ if (pat==DOTTED && !(y%2)) {
+ pat = ~pat;
+ }
+ while (h--) {
+ if (pat & 1) {
+ drawAlphaPixelAbs(x, y, opacity, color);
+ pat = (pat >> 1) | 0x80;
+ }
+ else {
+ pat = pat >> 1;
+ }
+ y++;
+ }
+ }
+}
+
+//
+// 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)
+{
+ // Offsets
+ x1 += offsetX;
+ y1 += offsetY;
+ x2 += offsetX;
+ y2 += offsetY;
+
+ if (!liangBarskyClipper(x1, y1, x2, y2))
+ return;
+
+ // 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 (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;
+ }
+ }
+}
+
+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)
+{
+ for (unsigned i = 0; i < thickness; i++) {
+ drawVerticalLine(x + i, y, h, pat, flags, opacity);
+ drawVerticalLine(x + w - 1 - i, y, h, pat, flags, opacity);
+ drawHorizontalLine(x, y + h - 1 - i, w, pat, flags, opacity);
+ drawHorizontalLine(x, y + i, w, pat, flags, opacity);
+ }
+}
+
+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)
+{
+ APPLY_OFFSET();
+ 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_sw_blend_dsc_t blend_dsc = {0};
+ blend_dsc.blend_mode = LV_BLEND_MODE_NORMAL;
+
+ if (opacity == OPACITY_MAX) {
+ // we don't draw fully transparent things
+ return;
+ } else if (!opacity) {
+ blend_dsc.opa = LV_OPA_COVER;
+ } else {
+ blend_dsc.opa = ((OPACITY_MAX - opacity) * LV_OPA_COVER) / OPACITY_MAX;
+ }
+
+ auto color = COLOR_VAL(flags);
+ blend_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_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 += w - 1;
+ coords.y2 += h - 1;
+
+ if (draw_ctx) {
+ lv_area_t clipped_coords;
+ if (!_lv_area_intersect(&clipped_coords, &coords, draw_ctx->clip_area))
+ return;
+ blend_dsc.blend_area = &clipped_coords;
+ lv_draw_sw_blend(draw_ctx, &blend_dsc);
+ }
+#if !defined(BOOT)
+ else if (canvas) {
+ lv_draw_rect_dsc_t rect_dsc;
+ lv_draw_rect_dsc_init(&rect_dsc);
+
+ rect_dsc.blend_mode = blend_dsc.blend_mode;
+ rect_dsc.bg_color = blend_dsc.color;
+ rect_dsc.bg_opa = blend_dsc.opa;
+ lv_canvas_draw_rect(canvas, coords.x1, coords.y1, coords.x2 - coords.x1 + 1,
+ coords.y2 - coords.y1 + 1, &rect_dsc);
+ }
+#endif
+}
+
+void BitmapBuffer::invertRect(coord_t x, coord_t y, coord_t w, coord_t h, LcdFlags flags)
+{
+ APPLY_OFFSET();
+
+ if (!applyClippingRect(x, y, w, h))
+ return;
+
+ // No 'opacity' here, only 'color'
+ pixel_t color = COLOR_VAL(flags);
+ RGB_SPLIT(color, red, green, blue);
+
+ DMAWait();
+ 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, bgRed, bgGreen, bgBlue);
+ drawPixel(p, RGB_JOIN(0x1F + red - bgRed, 0x3F + green - bgGreen, 0x1F + blue - bgBlue));
+ MOVE_TO_NEXT_RIGHT_PIXEL(p);
+ }
+ }
+}
+
+/*
+ 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); }
+
+ DMAWait();
+ 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::drawCircle(coord_t x, coord_t y, coord_t radius, LcdFlags flags)
+{
+ int x1 = radius;
+ int y1 = 0;
+ int decisionOver2 = 1 - x1;
+ pixel_t color = COLOR_VAL(flags);
+
+ DMAWait();
+ while (y1 <= x1) {
+ drawPixel(x1 + x, y1 + y, color);
+ drawPixel(y1 + x, x1 + y, color);
+ drawPixel(-x1 + x, y1 + y, color);
+ drawPixel(-y1 + x, x1 + y, color);
+ drawPixel(-x1 + x, -y1 + y, color);
+ drawPixel(-y1 + x, -x1 + y, color);
+ drawPixel(x1 + x, -y1 + y, color);
+ drawPixel(y1 + x, -x1 + y, color);
+ y1++;
+ if (decisionOver2 <= 0) {
+ decisionOver2 += 2 * y1 + 1;
+ }
+ else {
+ x1--;
+ decisionOver2 += 2 * (y1 - x1) + 1;
+ }
+ }
+}
+
+void BitmapBuffer::drawFilledCircle(coord_t x, coord_t y, coord_t radius, LcdFlags flags)
+{
+ coord_t imax = ((coord_t)((coord_t)radius * 707)) / 1000 + 1;
+ coord_t sqmax = (coord_t)radius * (coord_t)radius + (coord_t)radius / 2;
+ coord_t x1 = radius;
+
+ DMAWait();
+ drawSolidHorizontalLine(x - radius, y, radius * 2, flags);
+ for (coord_t i = 1; i <= imax; i++) {
+ if ((i * i + x1 * x1) > sqmax) {
+ // Draw lines from outside
+ if (x1 > imax) {
+ drawSolidHorizontalLine(x - i + 1, y + x1, (i - 1) * 2, flags);
+ drawSolidHorizontalLine(x - i + 1, y - x1, (i - 1) * 2, flags);
+ }
+ x1--;
+ }
+ // Draw lines from inside (center)
+ drawSolidHorizontalLine(x - x1, y + i, x1 * 2, flags);
+ drawSolidHorizontalLine(x - x1, y - i, x1 * 2, flags);
+ }
+}
+
+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 uint8_t * img, LcdFlags flags, int startAngle, int endAngle)
+{
+ if (endAngle == startAngle) {
+ endAngle += 1;
+ }
+
+ Slope startSlope(startAngle);
+ Slope endSlope(endAngle);
+
+ pixel_t color = COLOR_VAL(flags);
+
+ coord_t width = *((uint16_t *)img);
+ coord_t height = *(((uint16_t *)img) + 1);
+ const uint8_t * q = img + 4;
+
+ int w2 = width / 2;
+ int h2 = height / 2;
+
+ DMAWait();
+ 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;
+ }
+
+ Slope startSlope(startAngle);
+ Slope endSlope(endAngle);
+
+ pixel_t color = COLOR_VAL(flags);
+ APPLY_OFFSET();
+
+ coord_t internalDist = internalRadius * internalRadius;
+ coord_t externalDist = externalRadius * externalRadius;
+
+ DMAWait();
+ for (int y1 = 0; y1 <= externalRadius; y1++) {
+ for (int x1 = 0; x1 <= externalRadius; x1++) {
+ auto dist = x1 * x1 + y1 * y1;
+ if (dist >= internalDist && dist <= externalDist) {
+ Slope slope(false, x1 == 0 ? 99000 : y1 * 100 / x1);
+ if (slope.isBetween(startSlope, endSlope))
+ drawPixelAbs(x + x1, y - y1, color);
+ if (slope.invertVertical().isBetween(startSlope, endSlope))
+ drawPixelAbs(x + x1, y + y1, color);
+ if (slope.invertHorizontal().isBetween(startSlope, endSlope))
+ drawPixelAbs(x - x1, y + y1, color);
+ if (slope.invertVertical().isBetween(startSlope, endSlope))
+ drawPixelAbs(x - x1, y - y1, color);
+ }
+ }
+ }
+}
+
+uint8_t * BitmapBuffer::to8bitMask(size_t* size) const {
+ *size = width() * height()+4;
+ uint8_t* mask = (uint8_t*)malloc(*size);
+ ((uint16_t*)mask)[0] = width();
+ ((uint16_t*)mask)[1] = height();
+ const pixel_t * p = getPixelPtrAbs(0, 0);
+ if (getFormat() == BMP_ARGB4444) {
+ auto ofs = 4u;
+ for (int i = width() * height(); i > 0; i--) {
+ ARGB_SPLIT(*p, a __attribute__((unused)), r, g, b);
+ auto v = ((uint32_t)r + (uint32_t)g + (uint32_t)b) / 3;
+ mask[ofs++] = ((OPACITY_MAX - v) << 4);
+ MOVE_TO_NEXT_RIGHT_PIXEL(p);
+ }
+ } else { // BMP_RGB565
+ auto ofs = 4u;
+ for (int i = width() * height(); i > 0; i--) {
+ auto v = ((GET_RED(*p) >> 1) + (GET_GREEN(*p) >> 2) + (GET_BLUE(*p) >> 1)) / 3;
+ mask[ofs++] = ((OPACITY_MAX - v) << 4);
+ MOVE_TO_NEXT_RIGHT_PIXEL(p);
+ }
+ }
+
+ return mask;
+}
+
+void BitmapBuffer::drawMask(coord_t x, coord_t y, const BitmapBuffer * mask, LcdFlags flags, coord_t offsetX, coord_t width)
+{
+ if (!mask)
+ return;
+
+ APPLY_OFFSET();
+
+ coord_t w = mask->width();
+ coord_t height = mask->height();
+
+ if (!width || width > w) {
+ width = w;
+ }
+
+ if (x + width > xmax) {
+ width = xmax - x;
+ }
+
+ if (x < xmin) {
+ width += x - xmin;
+ offsetX -= x - xmin;
+ x = xmin;
+ }
+
+ if (y >= ymax || x >= xmax || width <= 0 || x + width < xmin || y + height < ymin)
+ return;
+
+ // TODO: This should be doable the same way as with
+ // drawBitmapPattern() (just with ARGB as input).
+ //
+ pixel_t color = COLOR_VAL(flags);
+
+ DMAWait();
+ for (coord_t row = 0; row < height; row++) {
+ if (y + row < ymin || y + row >= ymax)
+ continue;
+ pixel_t * p = getPixelPtrAbs(x, y + row);
+ const pixel_t * q = mask->getPixelPtrAbs(offsetX, row);
+ for (coord_t col = 0; col < width; col++) {
+ drawAlphaPixel(p, *((uint8_t *)q), color);
+ MOVE_TO_NEXT_RIGHT_PIXEL(p);
+ MOVE_TO_NEXT_RIGHT_PIXEL(q);
+ }
+ }
+}
+
+void BitmapBuffer::drawMask(coord_t x, coord_t y, const BitmapBuffer * mask, const BitmapBuffer * srcBitmap, coord_t offsetX, coord_t offsetY, coord_t width, coord_t height)
+{
+ if (!mask || !srcBitmap)
+ return;
+
+ APPLY_OFFSET();
+
+ coord_t maskWidth = mask->width();
+ coord_t maskHeight = mask->height();
+
+ if (!width || width > maskWidth) {
+ width = maskWidth;
+ }
+
+ if (!height || height > maskHeight) {
+ height = maskHeight;
+ }
+
+ if (x + width > xmax) {
+ width = xmax - x;
+ }
+
+ if (x < xmin) {
+ width += x - xmin;
+ offsetX -= x - xmin;
+ x = xmin;
+ }
+
+ if (y >= ymax || x >= xmax || width <= 0 || x + width < xmin || y + height < ymin)
+ return;
+
+
+ // TODO: This should be doable the same way as with
+ // drawBitmapPattern() (just with ARGB as input).
+ //
+ DMAWait();
+ for (coord_t row = 0; row < height; row++) {
+ if (y + row < ymin || y + row >= ymax)
+ continue;
+ pixel_t * p = getPixelPtrAbs(x, y + row);
+ const pixel_t * q = mask->getPixelPtrAbs(offsetX, offsetY + row);
+ for (coord_t col = 0; col < width; col++) {
+ drawAlphaPixel(p, *((uint8_t *)q), *srcBitmap->getPixelPtrAbs(row, col));
+ MOVE_TO_NEXT_RIGHT_PIXEL(p);
+ MOVE_TO_NEXT_RIGHT_PIXEL(q);
+ }
+ }
+}
+
+// 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 uint8_t * bmp, LcdFlags flags, coord_t offset, coord_t width)
+{
+ APPLY_OFFSET();
+
+ coord_t bmpw = *((uint16_t *)bmp); // 'w' -> width of the font file
+ coord_t bmph = *(((uint16_t *)bmp)+1); // height of the font file
+
+ // skip header
+ bmp += 4;
+
+ 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, bmpw, bmph,
+ srcx, srcy, srcw, srch, COLOR_VAL(flags));
+}
+
+// TODO: find a better place to define this
+extern lv_color_t makeLvColor(uint32_t colorFlags);
+
+coord_t BitmapBuffer::drawSizedText(coord_t x, coord_t y, const char *s,
+ uint8_t len, LcdFlags flags)
+{
+ if (!s) return x;
+ MOVE_OFFSET();
+
+ // 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) {
+ DMAWait();
+ lv_canvas_draw_text(canvas, coords.x1, coords.y1, coords.x2 - coords.x1 + 1,
+ &label_draw_dsc, buffer);
+ }
+#endif
+
+ RESTORE_OFFSET();
+
+ pos += p.x;
+ return ((flags & RIGHT) ? orig_pos : pos) - offsetX;
+}
+
+void BitmapBuffer::formatNumberAsString(char *buffer, uint8_t buffer_size, int32_t val, LcdFlags flags, uint8_t len, const char * prefix, const char * suffix)
+{
+ if (buffer == nullptr) {
+ return;
+ }
+
+ char str[48+1]; // max=16 for the prefix, 16 chars for the number, 16 chars for the suffix
+ char *s = str + 32;
+ *s = '\0';
+ int idx = 0;
+ int mode = MODE(flags);
+ bool neg = false;
+ if (val < 0) {
+ val = -val;
+ neg = true;
+ }
+ do {
+ *--s = '0' + (val % 10);
+ ++idx;
+ val /= 10;
+ if (mode != 0 && idx == mode) {
+ mode = 0;
+ *--s = '.';
+ if (val == 0)
+ *--s = '0';
+ }
+ } while (val != 0 || mode > 0 || (mode == MODE(LEADING0) && idx < len));
+ if (neg) *--s = '-';
+
+ // TODO needs check on all string lengths ...
+ if (prefix) {
+ int len = strlen(prefix);
+ if (len <= 16) {
+ s -= len;
+ strncpy(s, prefix, len);
+ }
+ }
+ if (suffix) {
+ strncpy(&str[32], suffix, 16);
+ }
+ strncpy(buffer, s, buffer_size);
+}
+
+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);
+}
+
+void drawSolidRect(BitmapBuffer * dc, coord_t x, coord_t y, coord_t w, coord_t h, uint8_t thickness, LcdFlags flags)
+{
+ dc->drawSolidFilledRect(x, y, thickness, h, flags);
+ dc->drawSolidFilledRect(x+w-thickness, y, thickness, h, flags);
+ dc->drawSolidFilledRect(x, y, w, thickness, flags);
+ dc->drawSolidFilledRect(x, y+h-thickness, w, thickness, flags);
+}
+
+//void BitmapBuffer::drawBitmapPie(int x0, int y0, const uint16_t * img, int startAngle, int endAngle)
+//{
+// const uint16_t * q = img;
+// coord_t width = *q++;
+// coord_t height = *q++;
+//
+// int slopes[4];
+// if (!evalSlopes(slopes, startAngle, endAngle))
+// return;
+//
+// int w2 = width/2;
+// int h2 = height/2;
+//
+// for (int y=h2-1; y>=0; y--) {
+// for (int x=w2-1; x>=0; x--) {
+// int slope = (x==0 ? 99000 : y*100/x);
+// if (slope >= slopes[0] && slope < slopes[1]) {
+// *getPixelPtr(x0+w2+x, y0+h2-y) = q[(h2-y)*width + w2+x];
+// }
+// if (-slope >= slopes[0] && -slope < slopes[1]) {
+// *getPixelPtr(x0+w2+x, y0+h2+y) = q[(h2+y)*width + w2+x];
+// }
+// if (slope >= slopes[2] && slope < slopes[3]) {
+// *getPixelPtr(x0+w2-x, y0+h2-y) = q[(h2-y)*width + w2-x];
+// }
+// if (-slope >= slopes[2] && -slope < slopes[3]) {
+// *getPixelPtr(x0+w2-x, y0+h2+y) = q[(h2+y)*width + w2-x];
+// }
+// }
+// }
+//}
+//
+
+BitmapBuffer * BitmapBuffer::loadBitmap(const char * filename, BitmapFormats fmt)
+{
+ //TRACE(" BitmapBuffer::loadBitmap(%s)", filename);
+ const char * ext = getFileExtension(filename);
+ if (ext && !strcmp(ext, ".bmp"))
+ return load_bmp(filename);
+ else
+ return load_stb(filename, fmt);
+}
+
+BitmapBuffer * BitmapBuffer::loadRamBitmap(const uint8_t * buffer, int len)
+{
+ return load_stb_buffer(buffer, len);
+}
+
+BitmapBuffer * BitmapBuffer::loadMask(const char * filename)
+{
+ BitmapBuffer * bitmap = BitmapBuffer::loadBitmap(filename);
+ if (bitmap) {
+ pixel_t * p = bitmap->getPixelPtrAbs(0, 0);
+ if (bitmap->getFormat() == BMP_ARGB4444) {
+ for (int i = bitmap->width() * bitmap->height(); i > 0; i--) {
+ // invert red and use as alpha
+ ARGB_SPLIT(*p, a __attribute__((unused)), r, g, b);
+
+ *((uint8_t *)p) = OPACITY_MAX - (r + g + b) / 3;
+ MOVE_TO_NEXT_RIGHT_PIXEL(p);
+ }
+ } else { // BMP_RGB565
+ for (int i = bitmap->width() * bitmap->height(); i > 0; i--) {
+ // invert msb 4 bits red and use as alpha
+ *((uint8_t *)p) =
+ OPACITY_MAX -
+ ((GET_RED(*p) >> 1) + (GET_GREEN(*p) >> 2) + (GET_BLUE(*p) >> 1)) /
+ 3;
+ MOVE_TO_NEXT_RIGHT_PIXEL(p);
+ }
+ }
+ }
+ return bitmap;
+}
+
+BitmapBuffer* BitmapBuffer::load8bitMask(const uint8_t * lbm)
+{
+ const uint16_t* hdr = (const uint16_t*)lbm;
+ BitmapBuffer * bitmap = new BitmapBuffer(BMP_RGB565,hdr[0],hdr[1]);
+ if (bitmap) {
+ pixel_t * p = bitmap->getPixelPtrAbs(0, 0);
+ const uint8_t * src = lbm + 4;
+ for (int i = bitmap->width() * bitmap->height(); i > 0; i--) {
+ *((uint8_t *)p) = (*(src++) >> 4);
+ MOVE_TO_NEXT_RIGHT_PIXEL(p);
+ }
+ }
+ return bitmap;
+}
+
+BitmapBuffer* BitmapBuffer::load8bitMaskLZ4(const uint8_t* compressed_data)
+{
+ LZ4Mask lz4_m(compressed_data);
+
+ BitmapBuffer *bitmap =
+ new BitmapBuffer(BMP_RGB565, lz4_m.width(), lz4_m.height());
+ if (bitmap) {
+ pixel_t *p = bitmap->getData();
+ const uint8_t *src = lz4_m.getData();
+ for (int i = bitmap->width() * bitmap->height(); i > 0; i--)
+ *((uint8_t *)p++) = (*(src++) >> 4);
+ }
+ return bitmap;
+}
+
+BitmapBuffer * BitmapBuffer::invertMask() const
+{
+ BitmapBuffer * result = new BitmapBuffer(format, width(), height());
+ pixel_t * srcData = data;
+ pixel_t * destData = result->data;
+ for (auto y = 0; y < height(); y++) {
+ for (auto x = 0; x < width(); x++) {
+ destData[x] = OPACITY_MAX - (uint8_t)srcData[x];
+ }
+ srcData += width();
+ destData += width();
+ }
+ return result;
+}
+
+BitmapBuffer * BitmapBuffer::horizontalFlip() const
+{
+ BitmapBuffer * result = new BitmapBuffer(format, width(), height());
+ pixel_t * srcData = data;
+ pixel_t * destData = result->data;
+ for (uint8_t y = 0; y < height(); y++) {
+ for (uint8_t x = 0; x < width(); x++) {
+ destData[x] = srcData[width() - 1 - x];
+ }
+ srcData += width();
+ destData += width();
+ }
+ return result;
+}
+
+BitmapBuffer * BitmapBuffer::verticalFlip() const
+{
+ BitmapBuffer * result = new BitmapBuffer(format, width(), height());
+ for (uint8_t y = 0; y < height(); y++) {
+ for (uint8_t x = 0; x < width(); x++) {
+ result->data[y * width() + x] = data[(height() - 1 - y) * width() + x];
+ }
+ }
+ return result;
+}
+
+BitmapBuffer * BitmapBuffer::loadMaskOnBackground(const char * filename, LcdFlags foreground, LcdFlags background)
+{
+ BitmapBuffer * result = nullptr;
+ BitmapBuffer * mask = BitmapBuffer::loadMask(filename);
+ if (mask) {
+ result = new BitmapBuffer(BMP_RGB565, mask->width(), mask->height());
+ if (result) {
+ result->clear(background);
+ result->drawMask(0, 0, mask, foreground);
+ }
+ delete mask;
+ }
+ return result;
+}
+
+BitmapBuffer * BitmapBuffer::load8bitMaskOnBackground(const uint8_t * lbm, LcdFlags foreground, LcdFlags background)
+{
+ BitmapBuffer * result = nullptr;
+ BitmapBuffer * mask = BitmapBuffer::load8bitMask(lbm);
+ if (mask) {
+ result = new BitmapBuffer(BMP_RGB565, mask->width(), mask->height());
+ if (result) {
+ result->clear(background);
+ result->drawMask(0, 0, mask, foreground);
+ }
+ delete mask;
+ }
+ return result;
+}
+
+FIL imgFile __DMA;
+
+BitmapBuffer * BitmapBuffer::load_bmp(const char * filename)
+{
+ UINT read;
+ uint8_t palette[16];
+ uint8_t bmpBuf[LCD_W]; /* maximum with LCD_W */
+ uint8_t * buf = &bmpBuf[0];
+
+ FRESULT result = f_open(&imgFile, filename, FA_OPEN_EXISTING | FA_READ);
+ if (result != FR_OK) {
+ return nullptr;
+ }
+
+ if (f_size(&imgFile) < 14) {
+ f_close(&imgFile);
+ return nullptr;
+ }
+
+ result = f_read(&imgFile, buf, 14, &read);
+ if (result != FR_OK || read != 14) {
+ f_close(&imgFile);
+ return nullptr;
+ }
+
+ if (buf[0] != 'B' || buf[1] != 'M') {
+ f_close(&imgFile);
+ return nullptr;
+ }
+
+ uint32_t fsize = *((uint32_t *)&buf[2]);
+ uint32_t hsize = *((uint32_t *)&buf[10]); /* header size */
+
+ uint32_t len = limit(4, hsize - 14, 32);
+ result = f_read(&imgFile, buf, len, &read);
+ if (result != FR_OK || read != len) {
+ f_close(&imgFile);
+ return nullptr;
+ }
+
+ uint32_t ihsize = *((uint32_t *)&buf[0]); /* extra header size */
+
+ /* invalid extra header size */
+ if (ihsize + 14 > hsize) {
+ f_close(&imgFile);
+ return nullptr;
+ }
+
+ /* sometimes file size is set to some headers size, set a real size in that case */
+ if (fsize == 14 || fsize == ihsize + 14)
+ fsize = f_size(&imgFile) - 2;
+
+ /* declared file size less than header size */
+ if (fsize <= hsize) {
+ f_close(&imgFile);
+ return nullptr;
+ }
+
+ uint32_t w, h;
+
+ switch (ihsize) {
+ case 40: // windib
+ case 56: // windib v3
+ case 64: // OS/2 v2
+ case 108: // windib v4
+ case 124: // windib v5
+ w = *((uint32_t *)&buf[4]);
+ h = *((uint32_t *)&buf[8]);
+ buf += 12;
+ break;
+ case 12: // OS/2 v1
+ w = *((uint16_t *)&buf[4]);
+ h = *((uint16_t *)&buf[6]);
+ buf += 8;
+ break;
+ default:
+ f_close(&imgFile);
+ return nullptr;
+ }
+ //TRACE(" BitmapBuffer::load_bmp() %dx%d", w, h);
+ if (*((uint16_t *)&buf[0]) != 1) { /* planes */
+ f_close(&imgFile);
+ return nullptr;
+ }
+
+ uint16_t depth = *((uint16_t *)&buf[2]);
+
+ buf = &bmpBuf[0];
+
+ if (depth == 4) {
+ if (f_lseek(&imgFile, hsize - 64) != FR_OK || f_read(&imgFile, buf, 64, &read) != FR_OK || read != 64) {
+ f_close(&imgFile);
+ return nullptr;
+ }
+ for (uint8_t i = 0; i < 16; i++) {
+ palette[i] = buf[4*i];
+ }
+ }
+ else {
+ if (f_lseek(&imgFile, hsize) != FR_OK) {
+ f_close(&imgFile);
+ return nullptr;
+ }
+ }
+
+ BitmapBuffer * bmp = new BitmapBuffer(BMP_RGB565, w, h);
+ if (bmp == nullptr || bmp->getData() == nullptr) {
+ f_close(&imgFile);
+ return nullptr;
+ }
+
+ uint32_t rowSize;
+ bool hasAlpha = false;
+
+ switch (depth) {
+ case 16:
+ for (int i = h - 1; i >= 0; i--) {
+ pixel_t * dst = bmp->getPixelPtrAbs(0, i);
+ for (unsigned int j = 0; j < w; j++) {
+ result = f_read(&imgFile, (uint8_t *)dst, 2, &read);
+ if (result != FR_OK || read != 2) {
+ f_close(&imgFile);
+ delete bmp;
+ return nullptr;
+ }
+ MOVE_TO_NEXT_RIGHT_PIXEL(dst);
+ }
+ }
+ break;
+
+ case 32:
+ for (int i = h - 1; i >= 0; i--) {
+ pixel_t * dst = bmp->getPixelPtrAbs(0, i);
+ for (unsigned int j = 0; j < w; j++) {
+ uint32_t pixel;
+ result = f_read(&imgFile, (uint8_t *)&pixel, 4, &read);
+ if (result != FR_OK || read != 4) {
+ f_close(&imgFile);
+ delete bmp;
+ return nullptr;
+ }
+ if (hasAlpha) {
+ *dst = ARGB((pixel >> 24) & 0xff, (pixel >> 16) & 0xff, (pixel >> 8) & 0xff, pixel & 0xff);
+ }
+ else {
+ if (((pixel >> 24) & 0xff) == 0xff) {
+ *dst = RGB(pixel >> 16, (pixel >> 8) & 0xff, pixel & 0xff);
+ }
+ else {
+ hasAlpha = true;
+ bmp->setFormat(BMP_ARGB4444);
+ for (pixel_t * p = bmp->getPixelPtrAbs(j, i); p != bmp->getPixelPtrAbs(0, h); MOVE_TO_NEXT_RIGHT_PIXEL(p)) {
+ pixel_t tmp = *p;
+ *p = ((tmp >> 1) & 0x0f) + (((tmp >> 7) & 0x0f) << 4) + (((tmp >> 12) & 0x0f) << 8);
+ }
+ *dst = ARGB((pixel >> 24) & 0xff, (pixel >> 16) & 0xff, (pixel >> 8) & 0xff, pixel & 0xff);
+ }
+ }
+ MOVE_TO_NEXT_RIGHT_PIXEL(dst);
+ }
+ }
+ break;
+
+ case 1:
+ break;
+
+ case 4:
+ rowSize = ((4*w+31)/32)*4;
+ for (int32_t i=h-1; i>=0; i--) {
+ result = f_read(&imgFile, buf, rowSize, &read);
+ if (result != FR_OK || read != rowSize) {
+ f_close(&imgFile);
+ delete bmp;
+ return nullptr;
+ }
+ pixel_t * dst = bmp->getPixelPtrAbs(0, i);
+ for (uint32_t j=0; j> ((j & 1) ? 0 : 4)) & 0x0F;
+ uint8_t val = palette[index];
+ *dst = RGB(val, val, val);
+ MOVE_TO_NEXT_RIGHT_PIXEL(dst);
+ }
+ }
+ break;
+
+ default:
+ f_close(&imgFile);
+ delete bmp;
+ return nullptr;
+ }
+
+ f_close(&imgFile);
+ return bmp;
+}
+
+#define STB_IMAGE_IMPLEMENTATION
+#define STBI_ONLY_PNG
+#define STBI_ONLY_JPEG
+#define STBI_ONLY_BMP
+#define STBI_ONLY_GIF
+#define STBI_NO_STDIO
+#define STBI_NO_HDR
+#define STBI_NO_LINEAR
+
+// #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_NO_HDR
+#define STBI_NO_PIC
+#define STBI_NO_GIF
+#define STBI_NO_PNM
+#define STBI_NO_PSD
+#define STBI_NO_TGA
+#define STBI_NO_BMP
+#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::load_stb(const char * filename, BitmapFormats fmt)
+{
+ //TRACE(" BitmapBuffer::load_stb(%s)", filename);
+
+ 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_close(&imgFile);
+ //TRACE(" BitmapBuffer::load_stb()----Info File %s, %d, %d, %d", filename, x, y, nn);
+
+ result = f_open(&imgFile, filename, FA_OPEN_EXISTING | FA_READ);
+ if (result != FR_OK) {
+ return nullptr;
+ }
+
+ 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;
+ }
+
+ //TRACE(" BitmapBuffer::load_stb()----Info File %s, %d, %d, %d/%d", filename, x, y, nn, n);
+ BitmapBuffer * bmp = convert_stb_bitmap(img, w, h, n, fmt);
+ stbi_image_free(img);
+ return bmp;
+}
+
+BitmapBuffer * BitmapBuffer::load_stb_buffer(const uint8_t * buffer, int len)
+{
+ int w, h, n;
+ unsigned char * img = stbi_load_from_memory(buffer, len, &w, &h, &n, 4);
+ if (!img) {
+ TRACE("load_stb_buffer(%p,%d) failed: %s", buffer, len, stbi_failure_reason());
+ return nullptr;
+ }
+
+ BitmapBuffer * bmp = convert_stb_bitmap(img, w, h, n);
+ stbi_image_free(img);
+ return bmp;
+}
+
+BitmapBuffer * BitmapBuffer::convert_stb_bitmap(uint8_t * img, int w, int h, int n,
+ BitmapFormats fmt)
+{
+ // 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;
+ }
+ }
+ }
+
+ return bmp;
+}
+
+uint8_t * BitmapBuffer::loadFont(const uint8_t * lbm, int len, int& w, int& h)
+{
+ int n;
+ unsigned char * font = stbi_load_from_memory(lbm, len, &w, &h, &n, 1);
+ if (!font) {
+ TRACE("load_stb_buffer(%p,%d) failed: %s", font, len, stbi_failure_reason());
+ return nullptr;
+ }
+
+ return font;
+}
+
+#include "../thirdparty/lz4/lz4.h"
+
+LZ4Bitmap::LZ4Bitmap(uint8_t format, const uint8_t* compressed_data) :
+ BitmapBuffer(format, 0, 0, nullptr)
+{
+ const uint16_t* hdr = (const uint16_t*)compressed_data;
+ _width = hdr[0];
+ _height = hdr[1];
+
+ size_t len = *(uint32_t*)&hdr[2];
+
+ // skip 8 bytes header
+ compressed_data += 8;
+
+ uint32_t pixels = _width * _height;
+ data = (uint16_t*)malloc(align32(pixels * sizeof(uint16_t)));
+
+ LZ4_decompress_safe((const char *)compressed_data, (char *)data, len,
+ pixels * sizeof(uint16_t));
+ data_end = data + pixels;
+}
+
+LZ4Bitmap::~LZ4Bitmap()
+{
+ free(data);
+}
+
+LZ4Mask::LZ4Mask(const uint8_t* compressed_data) :
+ BitmapBufferBase(BMP_8BIT, 0, 0, nullptr)
+{
+ const uint16_t* hdr = (const uint16_t*)compressed_data;
+ _width = hdr[0];
+ _height = hdr[1];
+
+ size_t len = *(uint32_t*)&hdr[2];
+
+ // skip 8 bytes header
+ compressed_data += 8;
+
+ uint32_t pixels = _width * _height;
+ data = (uint8_t*)malloc(align32(pixels));
+
+ LZ4_decompress_safe((const char *)compressed_data, (char *)data, len,
+ pixels);
+ data_end = data + pixels;
+}
+
+LZ4Mask::~LZ4Mask()
+{
+ free(data);
+}
diff --git a/radio/src/thirdparty/libopenui/src/bitmapbuffer.h b/radio/src/thirdparty/libopenui/src/bitmapbuffer.h
new file mode 100644
index 000000000..7a0f6bde6
--- /dev/null
+++ b/radio/src/thirdparty/libopenui/src/bitmapbuffer.h
@@ -0,0 +1,475 @@
+/*
+ * Copyright (C) OpenTX
+ *
+ * Source:
+ * https://github.com/opentx/libopenui
+ *
+ * This file is a part of libopenui library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ */
+
+#pragma once
+
+#include
+#include
+#include
+#include "libopenui_types.h"
+#include "libopenui_defines.h"
+#include "libopenui_depends.h"
+#include "libopenui_helpers.h"
+#include "debug.h"
+
+constexpr uint8_t SOLID = 0xFF;
+constexpr uint8_t DOTTED = 0x55;
+constexpr uint8_t STASHED = 0x33;
+
+#define MOVE_OFFSET() coord_t offsetX = this->offsetX; x += offsetX; this->offsetX = 0; coord_t offsetY = this->offsetY; y += offsetY; this->offsetY = 0
+#define APPLY_OFFSET() x += this->offsetX; y += this->offsetY
+#define RESTORE_OFFSET() this->offsetX = offsetX, this->offsetY = offsetY
+
+#if defined(LCD_VERTICAL_INVERT) && 0
+ #define MOVE_PIXEL_RIGHT(p, count) p -= count
+#else
+ #define MOVE_PIXEL_RIGHT(p, count) p += count
+#endif
+
+#define MOVE_TO_NEXT_RIGHT_PIXEL(p) MOVE_PIXEL_RIGHT(p, 1)
+
+#define USE_STB
+
+enum BitmapFormats
+{
+ BMP_INVALID = -1,
+ BMP_8BIT = 0,
+ BMP_RGB565,
+ BMP_ARGB4444
+};
+
+struct _lv_draw_ctx_t;
+typedef _lv_draw_ctx_t lv_draw_ctx_t;
+
+struct _lv_obj_t;
+typedef _lv_obj_t lv_obj_t;
+
+template
+class BitmapBufferBase
+{
+ public:
+ BitmapBufferBase(uint8_t format, uint16_t width, uint16_t height, T * data):
+ format(format),
+ _width(width),
+ _height(height),
+ xmax(width),
+ ymax(height),
+ data(data),
+ data_end(data + (width * height))
+ {
+ }
+
+ BitmapBufferBase(uint8_t format, T * data):
+ format(format),
+ _width(*((uint16_t*)data)),
+ _height(*(((uint16_t*)data) + 1)),
+ xmax(_width),
+ ymax(_height),
+ data(data + 2),
+ data_end(data + 2 + (_width * _height))
+ {
+ }
+
+ inline void clearClippingRect()
+ {
+ xmin = 0;
+ xmax = _width;
+ ymin = 0;
+ ymax = _height;
+ }
+
+ inline void 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;
+ }
+
+ inline void 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;
+ }
+
+ inline void setOffset(coord_t offsetX, coord_t offsetY)
+ {
+ this->offsetX = offsetX;
+ this->offsetY = offsetY;
+ }
+
+ inline void clearOffset()
+ {
+ setOffset(0, 0);
+ }
+
+ inline void reset()
+ {
+ clearOffset();
+ clearClippingRect();
+ }
+
+ coord_t getOffsetX() const
+ {
+ return offsetX;
+ }
+
+ coord_t getOffsetY() const
+ {
+ return offsetY;
+ }
+
+ inline uint8_t getFormat() const
+ {
+ return format;
+ }
+
+ inline uint16_t width() const
+ {
+ return _width;
+ }
+
+ inline uint16_t height() const
+ {
+ return _height;
+ }
+
+ inline T * getData() const
+ {
+ return data;
+ }
+
+ uint32_t getDataSize() const
+ {
+ return _width * _height * sizeof(T);
+ }
+
+ inline const pixel_t * getPixelPtrAbs(coord_t x, coord_t y) const
+ {
+#if defined(LCD_VERTICAL_INVERT) && 0
+ x = _width - x - 1;
+ y = _height - y - 1;
+#endif
+ return &data[y * _width + x];
+ }
+
+ protected:
+ uint8_t format;
+ coord_t _width;
+ coord_t _height;
+ coord_t xmin = 0;
+ coord_t xmax;
+ coord_t ymin = 0;
+ coord_t ymax;
+ coord_t offsetX = 0;
+ coord_t offsetY = 0;
+ T * data;
+ T * data_end;
+};
+
+typedef BitmapBufferBase Bitmap;
+
+class RLEBitmap:
+ public BitmapBufferBase
+{
+ public:
+ RLEBitmap(uint8_t format, const uint8_t* rle_data);
+ ~RLEBitmap();
+
+ static int decode(uint8_t * dest, unsigned int destSize, const uint8_t * src);
+};
+
+class BitmapBuffer: public BitmapBufferBase
+{
+ private:
+ bool dataAllocated;
+#if defined(DEBUG)
+ bool leakReported;
+#endif
+ lv_obj_t* canvas = nullptr;
+ lv_draw_ctx_t* draw_ctx = nullptr;
+
+ 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) {
+ if (!dataAllocated) {
+ data = d;
+ data_end = d + (_width * _height);
+ }
+ }
+
+ inline void setFormat(uint8_t format)
+ {
+ this->format = format;
+ }
+
+ inline void clear(LcdFlags flags=0)
+ {
+ drawSolidFilledRect(0, 0, _width - offsetX, _height - offsetY, flags);
+ }
+
+ inline const pixel_t * getPixelPtr(coord_t x, coord_t y) const
+ {
+ APPLY_OFFSET();
+
+ coord_t w = 1, h = 1;
+ if (!applyClippingRect(x, y, w, h))
+ return nullptr;
+
+ return getPixelPtrAbs(x, y);
+ }
+
+ inline void drawPixel(coord_t x, coord_t y, pixel_t value)
+ {
+ APPLY_OFFSET();
+
+ 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)
+ {
+ APPLY_OFFSET();
+
+ coord_t w = 1, h = 1;
+ if (!applyClippingRect(x, y, w, h))
+ return;
+
+ drawAlphaPixelAbs(x, y, 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);
+
+ inline void drawSolidHorizontalLine(coord_t x, coord_t y, coord_t w, LcdFlags flags)
+ {
+ drawSolidFilledRect(x, y, w, 1, flags);
+ }
+
+ inline void drawSolidVerticalLine(coord_t x, coord_t y, coord_t h, LcdFlags flags)
+ {
+ drawSolidFilledRect(x, y, 1, h, flags);
+ }
+
+ 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);
+
+ inline void drawSolidRect(coord_t x, coord_t y, coord_t w, coord_t h, uint8_t thickness = 1, LcdFlags flags = 0)
+ {
+ drawSolidFilledRect(x, y, thickness, h, flags);
+ drawSolidFilledRect(x+w-thickness, y, thickness, h, flags);
+ drawSolidFilledRect(x, y, w, thickness, flags);
+ drawSolidFilledRect(x, y+h-thickness, w, thickness, flags);
+ }
+
+ 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);
+
+ 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);
+
+ 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 drawBitmapPie(int x0, int y0, const uint16_t * img, int startAngle, int endAngle);
+
+ void drawBitmapPatternPie(coord_t x0, coord_t y0, const uint8_t * img, LcdFlags flags, int startAngle, int endAngle);
+
+ static BitmapBuffer * loadBitmap(const char * filename, BitmapFormats fmt = BMP_INVALID);
+ static BitmapBuffer * loadRamBitmap(const uint8_t * buffer, int len);
+
+ static BitmapBuffer * loadMask(const char * filename);
+ static BitmapBuffer * load8bitMask(const uint8_t * lbm);
+ static BitmapBuffer * load8bitMaskLZ4(const uint8_t * compressed_data);
+
+ static BitmapBuffer * loadMaskOnBackground(const char * filename, LcdFlags foreground, LcdFlags background);
+ static BitmapBuffer * load8bitMaskOnBackground(const uint8_t * lbm, LcdFlags foreground, LcdFlags background);
+
+ static uint8_t * loadFont(const uint8_t * lbm, int len, int& w, int& h);
+
+ uint8_t * to8bitMask(size_t* size) const;
+
+ void drawMask(coord_t x, coord_t y, const BitmapBuffer * mask, LcdFlags flags, coord_t offsetX = 0, coord_t width = 0);
+
+ void drawMask(coord_t x, coord_t y, const BitmapBuffer * mask, const BitmapBuffer * srcBitmap, coord_t offsetX = 0, coord_t offsetY = 0, coord_t width = 0, coord_t height = 0);
+
+ void drawBitmapPattern(coord_t x, coord_t y, const uint8_t * bmp, LcdFlags flags, coord_t offset=0, coord_t width=0);
+
+ 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);
+ }
+
+ coord_t drawTextAtIndex(coord_t x, coord_t y, const char *const *s, uint8_t idx, LcdFlags flags = 0)
+ {
+ return drawText(x, y, s[idx], flags);
+ }
+
+ static void formatNumberAsString(char *buffer, const uint8_t buffer_size, int32_t val, LcdFlags flags = 0, uint8_t len = 0, const char * prefix = nullptr, const char * suffix = nullptr);
+
+ 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);
+
+ template
+ void drawBitmap(coord_t x, coord_t y, const T * bmp, coord_t srcx = 0, coord_t srcy = 0, coord_t srcw = 0, coord_t srch = 0, float scale = 0);
+
+ template
+ void drawScaledBitmap(const T * bitmap, coord_t x, coord_t y, coord_t w, coord_t h);
+
+ BitmapBuffer * horizontalFlip() const;
+
+ BitmapBuffer * verticalFlip() const;
+
+ BitmapBuffer * invertMask() const;
+
+ protected:
+ static BitmapBuffer * load_bmp(const char * filename);
+ static BitmapBuffer * load_stb(const char * filename, BitmapFormats fmt = BMP_INVALID);
+ static BitmapBuffer * load_stb_buffer(const uint8_t * buffer, int len);
+ static BitmapBuffer * convert_stb_bitmap(uint8_t * img, int w, int h, int n,
+ BitmapFormats fmt = BMP_INVALID);
+
+ inline bool 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;
+ }
+
+ template
+ void drawBitmapAbs(coord_t x, coord_t y, const T* bmp, coord_t srcx = 0,
+ coord_t srcy = 0, coord_t srcw = 0, coord_t srch = 0,
+ float scale = 0);
+
+ 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 const pixel_t * getPixelPtrAbs(coord_t x, coord_t y) const
+ {
+#if defined(LCD_VERTICAL_INVERT) && 0
+ x = _width - x - 1;
+ y = _height - y - 1;
+#endif
+ return &data[y * _width + x];
+ }
+
+ inline pixel_t * getPixelPtrAbs(coord_t x, coord_t y)
+ {
+#if defined(LCD_VERTICAL_INVERT) && 0
+ x = _width - x - 1;
+ y = _height - y - 1;
+#endif
+ return &data[y * _width + x];
+ }
+
+ inline void drawPixelAbs(coord_t x, coord_t y, pixel_t value)
+ {
+ pixel_t * p = getPixelPtrAbs(x, y);
+ drawPixel(p, value);
+ }
+
+ inline void drawAlphaPixelAbs(coord_t x, coord_t y, uint8_t opacity, uint16_t color)
+ {
+ pixel_t * p = getPixelPtrAbs(x, y);
+ drawAlphaPixel(p, opacity, color);
+ }
+
+ 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);
+};
+
+class LZ4Bitmap : public BitmapBuffer
+{
+ public:
+ LZ4Bitmap(uint8_t format, const uint8_t* compressed_data);
+ ~LZ4Bitmap();
+};
+
+class LZ4Mask : public BitmapBufferBase
+{
+ public:
+ LZ4Mask(const uint8_t* compressed_data);
+ ~LZ4Mask();
+};
+
+// Back buffer to draw
+extern BitmapBuffer * lcd;
+
+// Buffer currently on display
+extern BitmapBuffer * lcdFront;
+
diff --git a/radio/src/thirdparty/libopenui/src/bufferedwindow.h b/radio/src/thirdparty/libopenui/src/bufferedwindow.h
new file mode 100644
index 000000000..9f9dd89fb
--- /dev/null
+++ b/radio/src/thirdparty/libopenui/src/bufferedwindow.h
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) OpenTX
+ *
+ * Source:
+ * https://github.com/opentx/libopenui
+ *
+ * This file is a part of libopenui library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ */
+
+#pragma once
+
+#include "window.h"
+
+template
+class BufferedWindow: public T
+{
+ public:
+ using T::T;
+
+ ~BufferedWindow() override
+ {
+ delete bitmap;
+ }
+
+ void invalidate(const rect_t & rect) override
+ {
+ paintUpdateNeeded = true;
+ T::invalidate(rect);
+ }
+
+ protected:
+ BitmapBuffer * bitmap = nullptr;
+ bool paintUpdateNeeded = false;
+ virtual void paintUpdate(BitmapBuffer * dc) = 0;
+};
+
+template
+class OpaqueBufferedWindow: public BufferedWindow
+{
+ public:
+ using BufferedWindow::BufferedWindow;
+
+ void paint(BitmapBuffer * dc) override
+ {
+ if (!this->bitmap) {
+ this->bitmap = new BitmapBuffer(BMP_RGB565, T::width(), T::height());
+ this->paintUpdateNeeded = true;
+ }
+
+ if (this->paintUpdateNeeded) {
+ this->paintUpdate(this->bitmap);
+ this->paintUpdateNeeded = false;
+ }
+
+ dc->drawBitmap(0, 0, this->bitmap);
+ }
+};
+
+template
+class TransparentBufferedWindow: public BufferedWindow
+{
+ public:
+ using BufferedWindow::BufferedWindow;
+
+ void paint(BitmapBuffer * dc) override
+ {
+ if (!BufferedWindow::bitmap) {
+ this->bitmap = new BitmapBuffer(BMP_RGB565, T::width(), T::height());
+ this->paintUpdateNeeded = true;
+ }
+
+ if (this->paintUpdateNeeded) {
+ this->paintUpdate(dc);
+ // we can only store the bitmap if fully drawn
+ coord_t xmin, xmax, ymin, ymax;
+ dc->getClippingRect(xmin, xmax, ymin, ymax);
+ if (xmax - xmin >= this->width() && ymax - ymin >= this->height()) {
+ this->bitmap->drawBitmap(0, 0, dc, dc->getOffsetX(), dc->getOffsetY());
+ this->paintUpdateNeeded = false;
+ }
+ }
+ else {
+ dc->drawBitmap(0, 0, this->bitmap);
+ }
+ }
+};
+
+class TransparentBitmapBackground: public TransparentBufferedWindow
+{
+ public:
+ TransparentBitmapBackground(Window * parent, const rect_t & rect, const BitmapBuffer * bitmap):
+ TransparentBufferedWindow(parent, rect, OPAQUE),
+ background(bitmap)
+ {
+ }
+
+#if defined(DEBUG_WINDOWS)
+ std::string getName() const override
+ {
+ return "TransparentBitmapBackground";
+ }
+#endif
+
+ protected:
+ const BitmapBuffer * background;
+
+ void paintUpdate(BitmapBuffer * dc) override
+ {
+ if (background) {
+ dc->drawBitmap((width() - background->width()) / 2, (height() - background->height()) / 2, background);
+ }
+ }
+};
+
diff --git a/radio/src/thirdparty/libopenui/src/button.cpp b/radio/src/thirdparty/libopenui/src/button.cpp
new file mode 100644
index 000000000..438fc1cff
--- /dev/null
+++ b/radio/src/thirdparty/libopenui/src/button.cpp
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) OpenTX
+ *
+ * Source:
+ * https://github.com/opentx/libopenui
+ *
+ * This file is a part of libopenui library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ */
+
+#include "libopenui_config.h"
+#include "button.h"
+#include "font.h"
+#include "theme.h"
+
+#include "widgets/simple_btn.h"
+
+static void update_checked_flag(lv_obj_t* obj, WindowFlags flags)
+{
+ if (!(flags & BUTTON_CHECKED))
+ lv_obj_clear_state(obj, LV_STATE_CHECKED);
+ else
+ lv_obj_add_state(obj, LV_STATE_CHECKED);
+}
+
+Button::Button(Window* parent, const rect_t& rect,
+ std::function pressHandler,
+ WindowFlags windowFlag, LcdFlags textFlags,
+ LvglCreate objConstruct) :
+ FormField(parent, rect, windowFlag, textFlags,
+ objConstruct ? objConstruct : simple_btn_create),
+ pressHandler(std::move(pressHandler))
+{
+ lv_obj_add_event_cb(lvobj, Button::long_pressed, LV_EVENT_LONG_PRESSED, nullptr);
+ update_checked_flag(lvobj, windowFlags);
+}
+
+void Button::check(bool checked)
+{
+ if (_deleted) return;
+ if (checked != bool(windowFlags & BUTTON_CHECKED)) {
+ windowFlags ^= BUTTON_CHECKED;
+ update_checked_flag(lvobj, windowFlags);
+ invalidate();
+ }
+}
+
+bool Button::checked() const
+{
+ if (windowFlags & BUTTON_CHECKED_ON_FOCUS)
+ return hasFocus();
+ else
+ return windowFlags & BUTTON_CHECKED;
+}
+
+void Button::onPress()
+{
+ check(pressHandler && pressHandler());
+}
+
+void Button::onLongPress()
+{
+ if (longPressHandler) {
+ check(longPressHandler());
+ lv_obj_clear_state(lvobj, LV_STATE_PRESSED);
+ lv_indev_wait_release(lv_indev_get_act());
+ }
+}
+
+void Button::onClicked()
+{
+ onPress();
+}
+
+#if defined(HARDWARE_TOUCH)
+// this needs to stay so that viewmain doesnt get the touch event
+bool Button::onTouchEnd(coord_t x, coord_t y) { return true; }
+#endif
+
+void Button::checkEvents()
+{
+ Window::checkEvents();
+ if (checkHandler) checkHandler();
+}
+
+void Button::long_pressed(lv_event_t* e)
+{
+ auto obj = lv_event_get_target(e);
+ auto btn = (Button*)lv_obj_get_user_data(obj);
+ if (obj) btn->onLongPress();
+}
+
+
+TextButton::TextButton(Window* parent, const rect_t& rect, std::string text,
+ std::function pressHandler,
+ WindowFlags windowFlags) :
+ Button(parent, rect, std::move(pressHandler), windowFlags, 0, lv_btn_create),
+ text(std::move(text))
+{
+ update_checked_flag(lvobj, windowFlags);
+
+ label = lv_label_create(lvobj);
+ lv_label_set_text(label, this->text.c_str());
+ lv_obj_center(label);
+}
+
+void IconButton::paint(BitmapBuffer * dc)
+{
+ dc->drawBitmap(0, 0, theme->getIcon(icon, checked() ? STATE_PRESSED : STATE_DEFAULT));
+}
diff --git a/radio/src/thirdparty/libopenui/src/button.h b/radio/src/thirdparty/libopenui/src/button.h
new file mode 100644
index 000000000..89ae019fe
--- /dev/null
+++ b/radio/src/thirdparty/libopenui/src/button.h
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) OpenTX
+ *
+ * Source:
+ * https://github.com/opentx/libopenui
+ *
+ * This file is a part of libopenui library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ */
+
+#pragma once
+
+#include "form.h"
+
+constexpr WindowFlags BUTTON_BACKGROUND = FORM_FLAGS_LAST << 1u;
+constexpr WindowFlags BUTTON_CHECKED = FORM_FLAGS_LAST << 2u;
+constexpr WindowFlags BUTTON_CHECKED_ON_FOCUS = FORM_FLAGS_LAST << 3u;
+
+class Button : public FormField
+{
+ public:
+ Button(Window* parent, const rect_t& rect,
+ std::function pressHandler = nullptr,
+ WindowFlags windowFlags = 0, LcdFlags textFlags = 0,
+ LvglCreate objConstruct = nullptr);
+
+#if defined(DEBUG_WINDOWS)
+ std::string getName() const override { return "Button"; }
+#endif
+
+ virtual void onPress();
+ virtual void onLongPress();
+
+ void onClicked() override;
+ void checkEvents() override;
+
+ void check(bool checked = true);
+ bool checked() const;
+
+ void setPressHandler(std::function handler)
+ {
+ pressHandler = std::move(handler);
+ }
+
+ void setLongPressHandler(std::function handler)
+ {
+ longPressHandler = std::move(handler);
+ }
+
+ void setCheckHandler(std::function handler)
+ {
+ checkHandler = std::move(handler);
+ }
+
+#if defined(HARDWARE_TOUCH)
+ bool onTouchEnd(coord_t x, coord_t y) override;
+#endif
+
+ protected:
+ std::function pressHandler;
+ std::function longPressHandler;
+ std::function checkHandler;
+
+ static void long_pressed(lv_event_t* e);
+};
+
+class TextButton: public Button
+{
+ public:
+ TextButton(Window* parent, const rect_t& rect, std::string text,
+ std::function pressHandler = nullptr,
+ WindowFlags windowFlags = BUTTON_BACKGROUND | OPAQUE);
+
+#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 setBgColorHandler(std::function handler = nullptr)
+ {
+ bgColorHandler = std::move(handler);
+ }
+
+ protected:
+ lv_obj_t * label = nullptr;
+
+ std::string text;
+ std::function bgColorHandler = nullptr;
+};
+
+class IconButton: public Button
+{
+ public:
+ IconButton(Window* parent, const rect_t& rect, uint8_t icon,
+ std::function pressHandler,
+ WindowFlags flags = 0) :
+ Button(parent, rect, std::move(pressHandler), flags), icon(icon)
+ {
+ }
+
+#if defined(DEBUG_WINDOWS)
+ std::string getName() const override
+ {
+ return "IconButton(" + std::to_string(icon) + ")";
+ }
+#endif
+
+ void paint(BitmapBuffer * dc) override;
+
+ protected:
+ uint8_t icon;
+};
diff --git a/radio/src/thirdparty/libopenui/src/button_matrix.cpp b/radio/src/thirdparty/libopenui/src/button_matrix.cpp
new file mode 100644
index 000000000..fd4ea7fa7
--- /dev/null
+++ b/radio/src/thirdparty/libopenui/src/button_matrix.cpp
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) EdgeTX
+ *
+ * Source:
+ * https://github.com/EdgeTX/libopenui
+ *
+ * This file is a part of libopenui library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ */
+
+#include "button_matrix.h"
+
+#include
+#include
+
+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 = (uint32_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((uint8_t)*btn_id);
+ }
+ else if (code == 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) {
+ auto btnm = (ButtonMatrix*)lv_event_get_user_data(e);
+ if (btnm->isActive((uint8_t)dsc->id)) {
+ dsc->rect_dsc->bg_color = makeLvColor(COLOR_THEME_ACTIVE);
+ dsc->label_dsc->color = makeLvColor(COLOR_THEME_PRIMARY2);
+ } else {
+ dsc->rect_dsc->bg_color = makeLvColor(COLOR_THEME_PRIMARY2);
+ dsc->label_dsc->color = makeLvColor(COLOR_THEME_SECONDARY1);
+ }
+ } else if (dsc->part == LV_PART_MAIN) {
+ lv_obj_t* obj = lv_event_get_target(e);
+ if (lv_obj_has_state(obj, LV_STATE_FOCUS_KEY) &&
+ !lv_obj_has_state(obj, LV_STATE_EDITED)) {
+ dsc->rect_dsc->bg_color = makeLvColor(COLOR_THEME_FOCUS);
+ dsc->rect_dsc->bg_opa = LV_OPA_20;
+ }
+ }
+ }
+}
+
+ButtonMatrix::ButtonMatrix(Window* parent, const rect_t& r) :
+ FormField(parent, r, 0, 0, lv_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;
+ txt_cnt = btns + rows;
+ btn_cnt = btns;
+
+ lv_btnm_map = (char**)malloc(sizeof(char*) * txt_cnt);
+ txt_index = (uint8_t*)malloc(sizeof(uint8_t) * btns);
+
+ uint8_t col = 0;
+ uint8_t btn = 0;
+ uint8_t txt_i = 0;
+
+ while (btn < btns) {
+
+ 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;
+}
+
+void ButtonMatrix::setText(uint8_t btn_id, const char* txt)
+{
+ if (btn_id >= btn_cnt) return;
+
+ uint8_t txt_i = txt_index[btn_id];
+ lv_btnm_map[txt_i] = strdup(txt);
+}
+
+void ButtonMatrix::update()
+{
+ lv_btnmatrix_set_map(lvobj, (const char**)lv_btnm_map);
+
+ lv_btnmatrix_ctrl_t ctrl = LV_BTNMATRIX_CTRL_CLICK_TRIG |
+ LV_BTNMATRIX_CTRL_NO_REPEAT;
+
+ lv_btnmatrix_set_btn_ctrl_all(lvobj, ctrl);
+}
+
+void ButtonMatrix::onClicked()
+{
+ lv_group_focus_obj(lvobj);
+ FormField::onClicked();
+}
diff --git a/radio/src/thirdparty/libopenui/src/button_matrix.h b/radio/src/thirdparty/libopenui/src/button_matrix.h
new file mode 100644
index 000000000..2d84ebae7
--- /dev/null
+++ b/radio/src/thirdparty/libopenui/src/button_matrix.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) EdgeTX
+ *
+ * Source:
+ * https://github.com/EdgeTX/libopenui
+ *
+ * This file is a part of libopenui library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser 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 update();
+
+ void onClicked() override;
+};
diff --git a/radio/src/thirdparty/libopenui/src/carousel.cpp b/radio/src/thirdparty/libopenui/src/carousel.cpp
new file mode 100644
index 000000000..2a3731bcb
--- /dev/null
+++ b/radio/src/thirdparty/libopenui/src/carousel.cpp
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) OpenTX
+ *
+ * Source:
+ * https://github.com/opentx/libopenui
+ *
+ * This file is a part of libopenui library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ */
+
+#include "carousel.h"
+
+void CarouselWindow::update()
+{
+ auto first = min(selection - 2, items.size() - count);
+ first = max(0, first);
+ coord_t lastPosition = 0;
+ coord_t spacing = 10;
+ if (!items.empty() && count > 0)
+ spacing = (width() - items[0]->back->width() * (count - 1) - items[0]->front->width()) / (count - 1);
+
+ int index = 0;
+ for (auto & item: items) {
+ Window * window;
+ if (index == selection) {
+ item->back->detach();
+ window = item->front;
+ }
+ else {
+ item->front->detach();
+ window = item->back;
+ }
+ window->attach(this);
+ window->setLeft(lastPosition);
+ window->setTop((height() - window->height()) / 2);
+ lastPosition += window->width() + spacing;
+ index += 1;
+ }
+
+}
+
+#if defined(HARDWARE_KEYS)
+void Carousel::onEvent(event_t event)
+{
+ TRACE_WINDOWS("%s received event 0x%X", getWindowDebugString().c_str(), event);
+
+ if (event == EVT_ROTARY_RIGHT) {
+ if (body->selection < (int)body->items.size() - 1)
+ select(body->selection + 1);
+ }
+ else if (event == EVT_ROTARY_LEFT) {
+ if (body->selection > 0)
+ select(body->selection - 1);
+ }
+ else {
+ Window::onEvent(event);
+ }
+}
+#endif
diff --git a/radio/src/thirdparty/libopenui/src/carousel.h b/radio/src/thirdparty/libopenui/src/carousel.h
new file mode 100644
index 000000000..613548e9f
--- /dev/null
+++ b/radio/src/thirdparty/libopenui/src/carousel.h
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) OpenTX
+ *
+ * Source:
+ * https://github.com/opentx/libopenui
+ *
+ * This file is a part of libopenui library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ */
+
+#pragma once
+
+#include
+#include "button.h"
+
+class CarouselItem
+{
+ public:
+ CarouselItem(Window * front, Window * back):
+ front(front),
+ back(back)
+ {
+ }
+
+ void setSelectHandler(std::function handler)
+ {
+ selectHandler = std::move(handler);
+ }
+
+ virtual ~CarouselItem() = default;
+
+ public:
+ Window * front;
+ Window * back;
+ std::function selectHandler;
+};
+
+class CarouselWindow: public Window
+{
+ friend class Carousel;
+
+ public:
+ CarouselWindow(Window * parent, const rect_t & rect, uint8_t count):
+ Window(parent, rect, NO_SCROLLBAR),
+ count(count)
+ {
+ }
+
+ void deleteLater(bool detach = true, bool trash = true) override // NOLINT(google-default-arguments)
+ {
+ if (_deleted)
+ return;
+
+ clear();
+ Window::deleteLater(detach, trash);
+ }
+
+ void addItem(CarouselItem * item)
+ {
+ items.push_back(item);
+ update();
+ }
+
+ void clear()
+ {
+ // children will be deleted later (front and back)
+ children.clear();
+
+ for (auto & item: items) {
+ item->front->deleteLater();
+ item->back->deleteLater();
+ delete item;
+ }
+ items.clear();
+ }
+
+ void select(int index, bool scroll = true)
+ {
+ selection = index;
+ if (selection >= 0) {
+ auto item = items[selection];
+ if (item->selectHandler) {
+ item->selectHandler();
+ }
+ }
+ update();
+ }
+
+ protected:
+ std::vector items;
+ int selection = 0;
+ unsigned count;
+ void update();
+};
+
+class Carousel: public Window
+{
+ public:
+ Carousel(Window * parent, const rect_t & rect):
+ Window(parent, rect, NO_SCROLLBAR)
+ {
+ }
+
+#if defined(DEBUG_WINDOWS)
+ std::string getName() const override
+ {
+ return "Carousel";
+ }
+#endif
+
+ void addItem(CarouselItem * item)
+ {
+ body->addItem(item);
+ }
+
+ void clear()
+ {
+ body->clear();
+ }
+
+ void select(int index)
+ {
+ body->select(index);
+ previousButton->enable(index > 0);
+ nextButton->enable(index < (int)body->items.size() - 1);
+ }
+
+ int getSelection() const
+ {
+ return body->selection;
+ }
+
+#if defined(HARDWARE_KEYS)
+ void onEvent(event_t event) override;
+#endif
+
+ protected:
+ Button * previousButton = nullptr;
+ Button * nextButton = nullptr;
+ CarouselWindow * body = nullptr;
+};
+
diff --git a/radio/src/thirdparty/libopenui/src/checkbox.cpp b/radio/src/thirdparty/libopenui/src/checkbox.cpp
new file mode 100644
index 000000000..708718c99
--- /dev/null
+++ b/radio/src/thirdparty/libopenui/src/checkbox.cpp
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) OpenTX
+ *
+ * Source:
+ * https://github.com/opentx/libopenui
+ *
+ * This file is a part of libopenui library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ */
+
+#include "checkbox.h"
+#include "theme.h"
+
+
+static void checkbox_event_handler(lv_event_t* e)
+{
+ lv_obj_t* target = lv_event_get_target(e);
+ CheckBox* cb = (CheckBox*)lv_obj_get_user_data(target);
+
+ if (cb) cb->setValue(lv_obj_get_state(target) & LV_STATE_CHECKED);
+}
+
+CheckBox::CheckBox(Window* parent, const rect_t& rect,
+ std::function getValue,
+ std::function setValue, WindowFlags flags) :
+ FormField(parent, rect, flags, 0, lv_switch_create),
+ _getValue(std::move(getValue)),
+ _setValue(std::move(setValue))
+{
+ // TODO: migrate to default theme
+
+ if (height() > 0)
+ lv_obj_set_width(lvobj, (lv_coord_t)(height() * 1.7f));
+
+ // PART_MAIN
+ lv_obj_set_style_bg_color(lvobj, makeLvColor(COLOR_THEME_DISABLED), LV_PART_MAIN);
+ lv_obj_set_style_bg_color(lvobj, makeLvColor(COLOR_THEME_DISABLED), LV_STATE_FOCUSED);
+
+ update();
+
+ lv_obj_add_event_cb(lvobj, checkbox_event_handler,
+ LV_EVENT_VALUE_CHANGED, this);
+}
+
+void CheckBox::update() const
+{
+ if (!_getValue) return;
+ if (_getValue()) lv_obj_add_state(lvobj, LV_STATE_CHECKED);
+ else lv_obj_clear_state(lvobj, LV_STATE_CHECKED);
+}
+
+void CheckBox::onClicked()
+{
+ // prevent FormField::onClicked()
+}
diff --git a/radio/src/thirdparty/libopenui/src/checkbox.h b/radio/src/thirdparty/libopenui/src/checkbox.h
new file mode 100644
index 000000000..7600c7225
--- /dev/null
+++ b/radio/src/thirdparty/libopenui/src/checkbox.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) OpenTX
+ *
+ * Source:
+ * https://github.com/opentx/libopenui
+ *
+ * This file is a part of libopenui library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ */
+
+#pragma once
+
+#include
+#include "form.h"
+
+class CheckBox : public FormField
+{
+ public:
+ CheckBox(Window* parent, const rect_t& rect,
+ std::function getValue,
+ std::function setValue, WindowFlags flags = 0);
+
+#if defined(DEBUG_WINDOWS)
+ std::string getName() const override { return "CheckBox"; }
+#endif
+
+ void onClicked() override;
+
+ const char* getLabel() const { return label.c_str(); }
+ void setLabel(std::string newLabel) { label = std::move(newLabel); }
+
+ uint8_t getValue() const { return _getValue(); }
+ void setValue(uint8_t value) { _setValue(value); }
+
+ void setSetValueHandler(std::function handler)
+ {
+ _setValue = std::move(handler);
+ }
+
+ void setGetValueHandler(std::function handler)
+ {
+ _getValue = std::move(handler);
+ }
+
+ void update() const;
+
+ protected:
+ std::string label;
+ std::function _getValue;
+ std::function _setValue;
+};
diff --git a/radio/src/thirdparty/libopenui/src/choice.cpp b/radio/src/thirdparty/libopenui/src/choice.cpp
new file mode 100644
index 000000000..e5e24d317
--- /dev/null
+++ b/radio/src/thirdparty/libopenui/src/choice.cpp
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) OpenTX
+ *
+ * Source:
+ * https://github.com/opentx/libopenui
+ *
+ * This file is a part of libopenui library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ */
+
+#include "choice.h"
+#include "menu.h"
+#include "theme.h"
+
+void choice_changed_cb(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) {
+ std::string text = cb->getLabelText();
+ lv_label_set_text(cb->label, text.c_str());
+ }
+ }
+ }
+}
+
+ChoiceBase::ChoiceBase(Window* parent, const rect_t& rect, ChoiceType type,
+ WindowFlags windowFlags) :
+ FormField(parent, rect, windowFlags), type(type)
+{
+ lv_obj_set_height(lvobj, LV_SIZE_CONTENT);
+ lv_obj_set_width(lvobj, LV_SIZE_CONTENT);
+ lv_obj_set_layout(lvobj, LV_LAYOUT_FLEX);
+ lv_obj_set_flex_flow(lvobj, LV_FLEX_FLOW_ROW);
+
+ if (height() == 0)
+ {
+ lv_obj_set_style_pad_top(lvobj, 4, LV_PART_MAIN);
+ lv_obj_set_style_pad_bottom(lvobj, 4, LV_PART_MAIN);
+ lv_obj_set_style_radius(lvobj, 4, LV_PART_MAIN);
+ }
+
+ lv_obj_add_event_cb(lvobj, choice_changed_cb, LV_EVENT_VALUE_CHANGED, lvobj);
+ label = lv_label_create(lvobj);
+
+ lv_group_t * def_group = lv_group_get_default();
+ if(def_group) {
+ lv_group_add_obj(def_group, lvobj);
+ }
+
+ // Normal font and background color
+ lv_obj_set_style_bg_color(lvobj, makeLvColor(COLOR_THEME_PRIMARY2), LV_PART_MAIN);
+ lv_obj_set_style_bg_opa(lvobj, LV_OPA_100, LV_PART_MAIN);
+ lv_obj_set_style_pad_right(lvobj, 5, LV_PART_MAIN);
+ lv_obj_set_style_border_width(lvobj, 1, LV_PART_MAIN);
+ lv_obj_set_style_border_color(lvobj, makeLvColor(COLOR_THEME_SECONDARY2), LV_PART_MAIN);
+ lv_obj_set_style_text_color(lvobj, makeLvColor(COLOR_THEME_SECONDARY1), LV_PART_MAIN);
+
+ // focused
+ lv_obj_set_style_bg_color(lvobj, makeLvColor(COLOR_THEME_FOCUS), LV_PART_MAIN | LV_STATE_FOCUSED);
+ lv_obj_set_style_text_color(lvobj, makeLvColor(COLOR_THEME_PRIMARY2), LV_PART_MAIN | LV_STATE_FOCUSED);
+
+ lv_obj_set_style_pad_left(label, FIELD_PADDING_LEFT, LV_PART_MAIN);
+ lv_obj_set_style_pad_top(label, FIELD_PADDING_TOP, LV_PART_MAIN);
+ lv_obj_set_style_pad_right(label, CHOICE_LABEL_MARGIN_RIGHT, LV_PART_MAIN);
+ // lv_obj_set
+
+ // add the 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_align(img, LV_ALIGN_RIGHT_MID);
+}
+
+std::string Choice::getLabelText()
+{
+ std::string text;
+
+ if (textHandler != nullptr) {
+ if (_getValue) { text = textHandler(_getValue()); }
+ } else if (_getValue) {
+ int val = _getValue();
+ val -= vmin;
+ if (val >= 0 && val < (int)values.size()) {
+ text = values[val];
+ }
+ }
+
+ return text;
+}
+
+Choice::Choice(Window* parent, const rect_t& rect, int vmin, int vmax,
+ std::function _getValue,
+ std::function _setValue, WindowFlags windowFlags) :
+ ChoiceBase(parent, rect, CHOICE_TYPE_DROPOWN, windowFlags),
+ vmin(vmin),
+ vmax(vmax),
+ _getValue(std::move(_getValue)),
+ _setValue(std::move(_setValue))
+{
+ lv_event_send(lvobj, LV_EVENT_VALUE_CHANGED, nullptr);
+}
+
+Choice::Choice(Window* parent, const rect_t& rect, const char* const values[],
+ int vmin, int vmax, std::function _getValue,
+ std::function _setValue, WindowFlags windowFlags) :
+ ChoiceBase(parent, rect, CHOICE_TYPE_DROPOWN, windowFlags),
+ vmin(vmin),
+ vmax(vmax),
+ _getValue(std::move(_getValue)),
+ _setValue(std::move(_setValue))
+{
+ setValues(values);
+ lv_event_send(lvobj, LV_EVENT_VALUE_CHANGED, nullptr);
+}
+
+Choice::Choice(Window* parent, const rect_t& rect,
+ std::vector values, int vmin, int vmax,
+ std::function _getValue, std::function _setValue,
+ WindowFlags windowFlags) :
+ ChoiceBase(parent, rect, CHOICE_TYPE_DROPOWN, windowFlags),
+ values(std::move(values)),
+ vmin(vmin),
+ vmax(vmax),
+ _getValue(std::move(_getValue)),
+ _setValue(std::move(_setValue))
+{
+ lv_event_send(lvobj, LV_EVENT_VALUE_CHANGED, nullptr);
+}
+
+Choice::Choice(Window* parent, const rect_t& rect, const char* values, int vmin,
+ int vmax, std::function _getValue,
+ std::function _setValue, WindowFlags windowFlags) :
+ ChoiceBase(parent, rect, CHOICE_TYPE_DROPOWN, windowFlags),
+ vmin(vmin),
+ vmax(vmax),
+ _getValue(std::move(_getValue)),
+ _setValue(std::move(_setValue))
+{
+ if (values) {
+ uint8_t len = values[0];
+ const char* value = &values[1];
+ for (int i = vmin; i <= vmax; i++) {
+ this->values.emplace_back(
+ std::string(value, min(len, strlen(value))));
+ value += len;
+ }
+ }
+ lv_event_send(lvobj, LV_EVENT_VALUE_CHANGED, nullptr);
+}
+
+void Choice::addValue(const char * value)
+{
+ values.emplace_back(value);
+ vmax += 1;
+}
+
+void Choice::addValues(const char * const values[], uint8_t count)
+{
+ this->values.reserve(this->values.size() + count);
+ for (uint8_t i = 0; i < count; i++)
+ this->values.emplace_back(values[i]);
+ vmax += count;
+}
+
+void Choice::setValues(std::vector 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);
+ lv_event_send(lvobj, LV_EVENT_VALUE_CHANGED, nullptr);
+ }
+}
+
+void Choice::onClicked()
+{
+ openMenu();
+}
+
+void Choice::fillMenu(Menu *menu, const FilterFct& filter)
+{
+ menu->removeLines();
+ auto value = _getValue();
+
+ int count = 0;
+ int selectedIx = -1;
+ for (int i = vmin; i <= vmax; ++i) {
+ if (filter && !filter(i)) continue;
+ if (isValueAvailable && !isValueAvailable(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; }
+ ++count;
+ }
+ menu->updateLines();
+ if (selectedIx >= 0) { menu->select(selectedIx); }
+}
+
+void Choice::openMenu()
+{
+ setEditMode(true); // this needs to be done first before menu is created.
+
+ auto menu = new Menu(this);
+ if (!menuTitle.empty()) menu->setTitle(menuTitle);
+
+ fillMenu(menu);
+
+ if (beforeDisplayMenuHandler) {
+ beforeDisplayMenuHandler(menu);
+ }
+
+ menu->setCloseHandler([=]() { setEditMode(false); });
+}
diff --git a/radio/src/thirdparty/libopenui/src/choice.h b/radio/src/thirdparty/libopenui/src/choice.h
new file mode 100644
index 000000000..584f87fba
--- /dev/null
+++ b/radio/src/thirdparty/libopenui/src/choice.h
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) OpenTX
+ *
+ * Source:
+ * https://github.com/opentx/libopenui
+ *
+ * This file is a part of libopenui library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ */
+
+#pragma once
+
+#include
+#include "form.h"
+
+class Menu;
+
+constexpr int CHOICE_LABEL_MARGIN_RIGHT = 10;
+
+enum ChoiceType {
+ CHOICE_TYPE_DROPOWN,
+ CHOICE_TYPE_FOLDER,
+};
+
+class ChoiceBase : public FormField
+{
+ public:
+ ChoiceBase(Window *parent, const rect_t &rect,
+ ChoiceType type = CHOICE_TYPE_DROPOWN,
+ WindowFlags windowFlags = 0);
+
+ inline ChoiceType getType() const { return type; }
+
+ protected:
+ ChoiceType type;
+ lv_obj_t* label;
+
+ friend void choice_changed_cb(lv_event_t *e);
+ virtual std::string getLabelText() { return ""; };
+};
+
+class Choice: public ChoiceBase {
+ public:
+ Choice(Window* parent, const rect_t &rect, int vmin, int vmax,
+ std::function _getValue,
+ std::function _setValue = nullptr,
+ WindowFlags windowFlags = 0);
+ Choice(Window* parent, const rect_t &rect,
+ std::vector values, int vmin, int vmax,
+ std::function _getValue,
+ std::function _setValue = nullptr,
+ WindowFlags windowFlags = 0);
+ Choice(Window* parent, const rect_t &rect, const char *const values[],
+ int vmin, int vmax, std::function _getValue,
+ std::function _setValue = nullptr,
+ WindowFlags windowFlags = 0);
+ Choice(Window* parent, const rect_t &rect, const char *values, int vmin,
+ int vmax, std::function _getValue,
+ std::function _setValue = nullptr,
+ WindowFlags windowFlags = 0);
+
+ void addValue(const char *value);
+
+ void addValues(const char *const values[], uint8_t count);
+
+ void setValues(std::vector values);
+
+ void setValues(const char *const values[]);
+
+#if defined(DEBUG_WINDOWS)
+ std::string getName() const override
+ {
+ return "Choice";
+ }
+#endif
+
+ void onClicked() override;
+
+ void setBeforeDisplayMenuHandler(std::function handler)
+ {
+ beforeDisplayMenuHandler = std::move(handler);
+ }
+
+ void setSetValueHandler(std::function handler)
+ {
+ _setValue = std::move(handler);
+ }
+
+ void setGetValueHandler(std::function handler)
+ {
+ _getValue = std::move(handler);
+ }
+
+ void setAvailableHandler(std::function 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;
+ }
+
+ void setValue(int val);
+ int getIntValue() const { return _getValue(); }
+
+ unsigned getValuesCount() const
+ {
+ return getIndexFromValue(vmax + 1);
+ }
+
+ void setTextHandler(std::function handler)
+ {
+ textHandler = std::move(handler);
+ lv_event_send(lvobj, LV_EVENT_VALUE_CHANGED, nullptr);
+ }
+
+ void setMenuTitle(std::string value)
+ {
+ menuTitle = std::move(value);
+ }
+
+ void setMin(int value)
+ {
+ vmin = value;
+ invalidate();
+ }
+
+ void setMax(int value)
+ {
+ vmax = value;
+ invalidate();
+ }
+
+ protected:
+ friend class MenuToolbar;
+
+ std::string getLabelText() override;
+ std::vector values;
+ int vmin = 0;
+ int vmax = 0;
+ std::string menuTitle;
+ std::function _getValue;
+ std::function _setValue;
+ std::function isValueAvailable;
+ std::function textHandler;
+ std::function beforeDisplayMenuHandler;
+
+ typedef std::function FilterFct;
+ void fillMenu(Menu *menu, const FilterFct& filter = nullptr);
+
+ virtual void openMenu();
+};
diff --git a/radio/src/thirdparty/libopenui/src/choiceex.cpp b/radio/src/thirdparty/libopenui/src/choiceex.cpp
new file mode 100644
index 000000000..bd7d7c113
--- /dev/null
+++ b/radio/src/thirdparty/libopenui/src/choiceex.cpp
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) EdgeTX
+ *
+ * Based on code named
+ * opentx - https://github.com/opentx/opentx
+ * th9x - http://code.google.com/p/th9x
+ * er9x - http://code.google.com/p/er9x
+ * gruvin9x - http://code.google.com/p/gruvin9x
+ *
+ * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+#include "choiceex.h"
+
+static void localLongPressHandler(lv_event_t* e)
+{
+ lv_eventData_t* ld = (lv_eventData_t*)lv_event_get_user_data(e);
+ ld->isLongPressed = true;
+ ld->lv_LongPressHandler(ld->userData);
+}
+
+void ChoiceEx::set_lv_LongPressHandler(lvHandler_t longPressHandler, void* data)
+{
+ TRACE("longPressHandler=%p", longPressHandler);
+
+ if (longPressHandler) {
+ longPressData.userData = data;
+ longPressData.lv_LongPressHandler = longPressHandler;
+ lv_obj_add_event_cb(lvobj, localLongPressHandler, LV_EVENT_LONG_PRESSED,
+ &longPressData);
+ lv_obj_add_event_cb(lvobj, ClickHandler, LV_EVENT_CLICKED, this);
+ }
+}
+
+ChoiceEx::ChoiceEx(Window* parent, const rect_t& rect, int16_t vmin,
+ int16_t vmax, std::function getValue,
+ std::function setValue,
+ WindowFlags windowFlags) :
+ Choice(parent, rect, vmin, vmax, getValue, setValue, windowFlags)
+{
+ longPressData.isLongPressed = false;
+}
diff --git a/radio/src/thirdparty/libopenui/src/choiceex.h b/radio/src/thirdparty/libopenui/src/choiceex.h
new file mode 100644
index 000000000..dcd21b750
--- /dev/null
+++ b/radio/src/thirdparty/libopenui/src/choiceex.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) EdgeTX
+ *
+ * Based on code named
+ * opentx - https://github.com/opentx/opentx
+ * th9x - http://code.google.com/p/th9x
+ * er9x - http://code.google.com/p/er9x
+ * gruvin9x - http://code.google.com/p/gruvin9x
+ *
+ * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+#ifndef _CHOICEEX_H_
+#define _CHOICEEX_H_
+#include "choice.h"
+
+typedef void (*lvHandler_t)(void*);
+typedef struct {
+ void* userData;
+ bool isLongPressed;
+ lvHandler_t lv_LongPressHandler;
+} lv_eventData_t;
+
+class ChoiceEx : public Choice
+{
+ public:
+ ChoiceEx(Window* parent, const rect_t& rect, int16_t vmin, int16_t vmax,
+ std::function getValue,
+ std::function setValue = nullptr,
+ WindowFlags windowFlags = 0);
+
+ void set_lv_LongPressHandler(lvHandler_t longPressHandler, void* data);
+
+ lv_eventData_t longPressData;
+
+ void onClicked() override
+ {
+ if (!longPressData.isLongPressed) Choice::onClicked();
+ }
+
+ protected:
+ std::function longPressHandler = nullptr;
+
+ static void ClickHandler(lv_event_t* e)
+ {
+ ChoiceEx* ch = (ChoiceEx*)lv_event_get_user_data(e);
+ if (ch) ch->longPressData.isLongPressed = false;
+ }
+};
+
+#endif
diff --git a/radio/src/thirdparty/libopenui/src/dialog.cpp b/radio/src/thirdparty/libopenui/src/dialog.cpp
new file mode 100644
index 000000000..5f649a6c9
--- /dev/null
+++ b/radio/src/thirdparty/libopenui/src/dialog.cpp
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) OpenTX
+ *
+ * Source:
+ * https://github.com/opentx/libopenui
+ *
+ * This file is a part of libopenui library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ */
+
+#include "dialog.h"
+#include "mainwindow.h"
+#include "theme.h"
+
+DialogWindowContent::DialogWindowContent(Dialog* parent, const rect_t& rect) :
+ ModalWindowContent(parent, rect),
+ form(this, rect_t{})
+{
+ form.setFlexLayout();
+
+ auto form_obj = form.getLvObj();
+ lv_obj_set_style_pad_all(form_obj, lv_dpx(8), 0);
+
+ lv_coord_t max_height = LCD_H * 0.8;
+ lv_obj_set_style_max_height(form_obj, max_height, 0);
+}
+
+void DialogWindowContent::setTitle(const std::string& text)
+{
+ ModalWindowContent::setTitle(text);
+ if (title) {
+ lv_coord_t title_h = lv_obj_get_height(title);
+ lv_obj_set_y(form.getLvObj(), title_h);
+ }
+}
+
+void DialogWindowContent::deleteLater(bool detach, bool trash)
+{
+ if (_deleted) return;
+ form.deleteLater(true, false);
+ ModalWindowContent::deleteLater(detach, trash);
+}
+
+void DialogWindowContent::updateSize()
+{
+ lv_obj_center(lvobj);
+ ModalWindowContent::updateSize();
+}
+
+#if defined(DEBUG_WINDOWS)
+std::string DialogWindowContent::getName() const
+{
+ return "DialogWindowContent";
+}
+#endif
+
+Dialog::Dialog(Window* parent, std::string title, const rect_t& rect) :
+ ModalWindow(parent), content(new DialogWindowContent(this, rect))
+{
+ lv_obj_set_style_bg_color(content->getLvObj(), makeLvColor(COLOR_THEME_SECONDARY3), 0);
+ lv_obj_set_style_bg_opa(content->getLvObj(), LV_OPA_100, LV_PART_MAIN);
+ bringToTop();
+ if (!title.empty()) content->setTitle(std::move(title));
+}
+
+void Dialog::onCancel()
+{
+ deleteLater();
+}
+
+void Dialog::onEvent(event_t event)
+{
+ // block key events
+ (void)event;
+}
diff --git a/radio/src/thirdparty/libopenui/src/dialog.h b/radio/src/thirdparty/libopenui/src/dialog.h
new file mode 100644
index 000000000..80a67bfe4
--- /dev/null
+++ b/radio/src/thirdparty/libopenui/src/dialog.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) OpenTX
+ *
+ * Source:
+ * https://github.com/opentx/libopenui
+ *
+ * This file is a part of libopenui library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ */
+
+#pragma once
+
+#include "modal_window.h"
+#include "form.h"
+
+class Dialog;
+
+class DialogWindowContent : public ModalWindowContent
+{
+ friend class Dialog;
+
+ public:
+ DialogWindowContent(Dialog* parent, const rect_t& rect);
+
+ void setTitle(const std::string& text) override;
+
+ void deleteLater(bool detach = true, bool trash = true) override;
+ void updateSize() override;
+
+#if defined(DEBUG_WINDOWS)
+ std::string getName() const override;
+#endif
+
+ public:
+ FormGroup form;
+};
+
+class Dialog : public ModalWindow
+{
+ public:
+ Dialog(Window* parent, std::string title, const rect_t& rect);
+
+ protected:
+ DialogWindowContent* content;
+
+ void onCancel() override;
+ void onEvent(event_t event) override;
+};
diff --git a/radio/src/thirdparty/libopenui/src/filechoice.cpp b/radio/src/thirdparty/libopenui/src/filechoice.cpp
new file mode 100644
index 000000000..223ea2614
--- /dev/null
+++ b/radio/src/thirdparty/libopenui/src/filechoice.cpp
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) OpenTX
+ *
+ * Source:
+ * https://github.com/opentx/libopenui
+ *
+ * This file is a part of libopenui library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ */
+
+#include "filechoice.h"
+#include "libopenui_file.h"
+#include "menu.h"
+#include "theme.h"
+#include "message_dialog.h"
+
+#include
+
+#if !defined(STR_SDCARD)
+ #define STR_SDCARD "SD"
+#endif
+
+#if !defined(STR_NO_FILES_ON_SD)
+ #define STR_NO_FILES_ON_SD "No files on SD Card!"
+#endif
+
+FileChoice::FileChoice(Window* parent, const rect_t &rect,
+ std::string folder, const char *extension, int maxlen,
+ std::function getValue,
+ std::function setValue,
+ bool stripExtension) :
+ ChoiceBase(parent, rect, CHOICE_TYPE_FOLDER),
+ folder(std::move(folder)),
+ extension(extension),
+ maxlen(maxlen),
+ getValue(std::move(getValue)),
+ setValue(std::move(setValue)),
+ stripExtension(stripExtension)
+{
+ lv_event_send(lvobj, LV_EVENT_VALUE_CHANGED, nullptr);
+}
+
+std::string FileChoice::getLabelText()
+{
+ return getValue();
+}
+
+
+bool FileChoice::openMenu()
+{
+ FILINFO fno;
+ DIR dir;
+ std::list 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_DIR) continue; // skip subfolders
+ if (fno.fattrib & AM_HID) continue; // skip hidden files
+ if (fno.fattrib & AM_SYS) continue; // skip system files
+
+ 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);
+ }
+
+ if (!files.empty()) {
+ // sort files
+ files.sort(compare_nocase);
+ files.push_front("");
+
+ auto menu = new Menu(this);
+ int count = 0;
+ int current = -1;
+ std::string value = getValue();
+ for (const auto &file : files) {
+ menu->addLineBuffered(file, [=]() {
+ setValue(file);
+ lv_event_send(lvobj, LV_EVENT_VALUE_CHANGED, nullptr);
+ });
+ // TRACE("%s %d %s %d", value.c_str(), value.size(), file.c_str(),
+ // file.size());
+ if (value.compare(file) == 0) {
+ // TRACE("OK");
+ current = count;
+ }
+ ++count;
+ }
+ menu->updateLines();
+
+ if (current >= 0) {
+ menu->select(current);
+ }
+
+ menu->setCloseHandler([=]() {
+ editMode = false;
+ // setFocus(SET_FOCUS_DEFAULT);
+ });
+
+ return true;
+ }
+ }
+
+ new MessageDialog(this, STR_SDCARD, STR_NO_FILES_ON_SD);
+
+ return false;
+}
+
+void FileChoice::onClicked()
+{
+ openMenu();
+}
diff --git a/radio/src/thirdparty/libopenui/src/filechoice.h b/radio/src/thirdparty/libopenui/src/filechoice.h
new file mode 100644
index 000000000..e74527107
--- /dev/null
+++ b/radio/src/thirdparty/libopenui/src/filechoice.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) OpenTX
+ *
+ * Source:
+ * https://github.com/opentx/libopenui
+ *
+ * This file is a part of libopenui library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ */
+
+#pragma once
+
+#include "choice.h"
+#include
+
+class FileChoice : public ChoiceBase
+{
+public:
+ FileChoice(Window* parent, const rect_t& rect, std::string folder,
+ const char* extension, int maxlen,
+ std::function getValue,
+ std::function setValue,
+ bool stripExtension=false);
+
+#if defined(DEBUG_WINDOWS)
+ std::string getName() const override { return "FileChoice"; }
+#endif
+
+ void onClicked() override;
+
+protected:
+ std::string getLabelText() override;
+ std::string folder;
+ const char* extension;
+ int maxlen;
+ std::function getValue;
+ std::function setValue;
+ bool stripExtension;
+
+ bool openMenu();
+};
diff --git a/radio/src/thirdparty/libopenui/src/flexlayout.h b/radio/src/thirdparty/libopenui/src/flexlayout.h
new file mode 100644
index 000000000..0c36c125d
--- /dev/null
+++ b/radio/src/thirdparty/libopenui/src/flexlayout.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) EdgeTX
+ *
+ * Based on code named
+ * opentx - https://github.com/opentx/opentx
+ * th9x - http://code.google.com/p/th9x
+ * er9x - http://code.google.com/p/er9x
+ * gruvin9x - http://code.google.com/p/gruvin9x
+ *
+ * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#pragma once
+
+#include
+
+class FlexGridLayout
+{
+ const lv_coord_t* col_dsc = nullptr;
+ const lv_coord_t* row_dsc = nullptr;
+ lv_coord_t padding = 0;
+
+ uint8_t col_pos = 0;
+ uint8_t col_span = 1;
+ uint8_t row_pos = 0;
+ uint8_t row_span = 1;
+
+ public:
+ FlexGridLayout(const lv_coord_t col_dsc[], const lv_coord_t row_dsc[],
+ lv_coord_t padding = 4) :
+ 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) { apply(w->getLvObj()); }
+
+ void apply(lv_obj_t* obj) {
+
+ // padding
+ lv_obj_set_style_pad_all(obj, padding, LV_PART_MAIN);
+ lv_obj_set_style_pad_row(obj, padding, LV_PART_MAIN);
+ lv_obj_set_style_pad_column(obj, padding, LV_PART_MAIN);
+
+ // layout
+ lv_obj_set_layout(obj, LV_LAYOUT_GRID);
+ if (col_dsc && row_dsc) {
+ lv_obj_set_grid_dsc_array(obj, col_dsc, row_dsc);
+ }
+ }
+
+ void add(Window* w) { add(w->getLvObj()); }
+
+ void add(lv_obj_t* obj) {
+ if (col_dsc && row_dsc) {
+ lv_obj_set_grid_cell(obj, LV_GRID_ALIGN_START, col_pos, col_span,
+ LV_GRID_ALIGN_CENTER, row_pos, row_span);
+ }
+ }
+
+ 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; }
+};
diff --git a/radio/src/thirdparty/libopenui/src/font.h b/radio/src/thirdparty/libopenui/src/font.h
new file mode 100644
index 000000000..ca16383f5
--- /dev/null
+++ b/radio/src/thirdparty/libopenui/src/font.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) OpenTX
+ *
+ * Source:
+ * https://github.com/opentx/libopenui
+ *
+ * This file is a part of libopenui library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ */
+
+#pragma once
+
+#include
+#include "libopenui_types.h"
+
+const lv_font_t* getFont(LcdFlags flags);
+uint8_t getFontHeight(LcdFlags flags);
+uint8_t getFontHeightCondensed(LcdFlags flags);
+int getTextWidth(const char * s, int len = 0, LcdFlags flags = 0);
+
diff --git a/radio/src/thirdparty/libopenui/src/form.cpp b/radio/src/thirdparty/libopenui/src/form.cpp
new file mode 100644
index 000000000..0cc4af75e
--- /dev/null
+++ b/radio/src/thirdparty/libopenui/src/form.cpp
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) OpenTX
+ *
+ * Source:
+ * https://github.com/opentx/libopenui
+ *
+ * This file is a part of libopenui library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ */
+
+#include "form.h"
+#include "bitmapbuffer.h"
+#include "libopenui_config.h"
+
+#include "widgets/window_base.h"
+
+FormField::FormField(Window* parent, const rect_t& rect,
+ WindowFlags windowFlags, LcdFlags textFlags,
+ LvglCreate objConstruct) :
+ Window(parent, rect, windowFlags, textFlags, objConstruct)
+{
+ 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);
+ }
+
+ if (editMode && lvobj != nullptr) {
+ lv_obj_add_state(lvobj, LV_STATE_EDITED);
+ } else if (lvobj != nullptr) {
+ lv_obj_clear_state(lvobj, LV_STATE_EDITED);
+ }
+ invalidate();
+}
+
+void FormField::enable(bool value)
+{
+ if (value) {
+ lv_obj_clear_state(lvobj, LV_STATE_DISABLED);
+ } else {
+ lv_obj_add_state(lvobj, LV_STATE_DISABLED);
+ }
+}
+
+bool FormField::isEnabled() const
+{
+ return !lv_obj_has_state(lvobj, LV_STATE_DISABLED);
+}
+
+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);
+ invalidate();
+ }
+}
+
+void FormField::onCancel()
+{
+ if (isEditMode()) {
+ setEditMode(false);
+ invalidate();
+ } else {
+ Window::onCancel();
+ }
+}
+
+FormGroup::Line::Line(Window* parent, lv_obj_t* obj, FlexGridLayout* layout) :
+ Window(parent, obj), layout(layout)
+{
+ construct();
+}
+
+FormGroup::Line::Line(Window* parent, FlexGridLayout* layout) :
+ Window(parent, rect_t{}), layout(layout)
+{
+ construct();
+}
+
+void FormGroup::Line::construct()
+{
+ // forward scroll and focus
+ windowFlags |= FORWARD_SCROLL | NO_FOCUS | FORM_FORWARD_FOCUS;
+ lv_obj_clear_flag(lvobj, LV_OBJ_FLAG_CLICK_FOCUSABLE);
+
+ if (layout) {
+ layout->apply(lvobj);
+ }
+
+ lv_obj_set_height(lvobj, LV_SIZE_CONTENT);
+ lv_obj_set_width(lvobj, lv_pct(100));
+}
+
+void FormGroup::Line::setLayout(FlexGridLayout *l)
+{
+ layout = l;
+ if (layout) layout->apply(lvobj);
+}
+
+void FormGroup::Line::addChild(Window* window)
+{
+ Window::addChild(window);
+ if (layout) {
+ layout->add(window->getLvObj());
+ layout->nextCell();
+ }
+}
+
+FormGroup::FormGroup(Window* parent, const rect_t& rect,
+ WindowFlags windowflags, LvglCreate objConstruct) :
+ Window(parent, rect, windowflags, 0, objConstruct)
+{
+ lv_obj_set_style_bg_opa(lvobj, LV_OPA_TRANSP, LV_PART_MAIN);
+}
+
+void FormGroup::setFlexLayout(lv_flex_flow_t flow, lv_coord_t padding)
+{
+ lv_obj_set_flex_flow(lvobj, flow);
+ if (_LV_FLEX_COLUMN & flow) {
+ lv_obj_set_style_pad_row(lvobj, padding, LV_PART_MAIN);
+ } else {
+ lv_obj_set_style_pad_column(lvobj, padding, LV_PART_MAIN);
+ }
+ lv_obj_set_width(lvobj, lv_pct(100));
+ lv_obj_set_height(lvobj, LV_SIZE_CONTENT);
+}
+
+FormGroup::Line* FormGroup::newLine(FlexGridLayout* layout, lv_coord_t left_padding)
+{
+ if (layout) layout->resetPos();
+ auto lv_line = window_create(lvobj);
+ if (left_padding) lv_obj_set_style_pad_left(lv_line, left_padding, 0);
+ return new Line(this, lv_line, layout);
+}
diff --git a/radio/src/thirdparty/libopenui/src/form.h b/radio/src/thirdparty/libopenui/src/form.h
new file mode 100644
index 000000000..c6d8fd3af
--- /dev/null
+++ b/radio/src/thirdparty/libopenui/src/form.h
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) OpenTX
+ *
+ * Source:
+ * https://github.com/opentx/libopenui
+ *
+ * This file is a part of libopenui library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ */
+
+#pragma once
+
+#include "window.h"
+#include "flexlayout.h"
+
+constexpr WindowFlags FORM_FORWARD_FOCUS = WINDOW_FLAGS_LAST << 1;
+constexpr WindowFlags FORM_DETACHED = WINDOW_FLAGS_LAST << 2;
+constexpr WindowFlags FORM_BORDER_FOCUS_ONLY = WINDOW_FLAGS_LAST << 3;
+constexpr WindowFlags FORM_NO_BORDER = WINDOW_FLAGS_LAST << 4;
+constexpr WindowFlags FORM_FLAGS_LAST = FORM_NO_BORDER;
+
+class FormField : public Window
+{
+ public:
+ FormField(Window *parent, const rect_t &rect, WindowFlags windowFlags = 0,
+ LcdFlags textFlags = 0, LvglCreate objConstruct = nullptr);
+
+ virtual void changeEnd(bool forceChanged = false)
+ {
+ if (changeHandler) { changeHandler(); }
+ }
+
+ void setChangeHandler(std::function handler)
+ {
+ changeHandler = std::move(handler);
+ }
+
+ void setBackgroundHandler(std::function handler)
+ {
+ backgroundHandler = std::move(handler);
+ }
+
+ inline bool isEditMode() const { return editMode; }
+ virtual void setEditMode(bool newEditMode);
+
+ bool isEnabled() const;
+ void enable(bool value = true);
+ void disable() { enable(false); }
+
+ void onFocusLost() override
+ {
+ setEditMode(false);
+ Window::onFocusLost();
+ }
+
+ void onClicked() override;
+ void onCancel() override;
+
+ protected:
+ bool editMode = false;
+ bool enabled = true;
+ std::function changeHandler = nullptr;
+ std::function backgroundHandler = nullptr;
+};
+
+class FormGroup : public Window
+{
+ public:
+ class Line : public Window
+ {
+ FlexGridLayout *layout;
+ void construct();
+
+ public:
+ Line(Window *parent, lv_obj_t *obj, FlexGridLayout *layout = nullptr);
+ Line(Window *parent, FlexGridLayout *layout = nullptr);
+
+ void setLayout(FlexGridLayout *l);
+
+ protected:
+ void addChild(Window *window) override;
+ };
+
+ FormGroup(Window *parent, const rect_t &rect, WindowFlags windowflags = 0,
+ LvglCreate objConstruct = nullptr);
+
+#if defined(DEBUG_WINDOWS)
+ std::string getName() const override { return "FormGroup"; }
+#endif
+
+ void setFlexLayout(lv_flex_flow_t flow = LV_FLEX_FLOW_COLUMN, lv_coord_t padding = 0);
+ Line* newLine(FlexGridLayout* layout = nullptr, lv_coord_t left_padding = 0);
+};
+
+class FormWindow : public FormGroup
+{
+ public:
+ FormWindow(Window *parent, const rect_t &rect, WindowFlags windowFlags = 0,
+ LvglCreate objConstruct = nullptr) :
+ FormGroup(parent, rect, windowFlags, objConstruct)
+ {
+ }
+
+#if defined(DEBUG_WINDOWS)
+ std::string getName() const override { return "FormWindow"; }
+#endif
+
+ protected:
+ void paint(BitmapBuffer *) override {}
+};
diff --git a/radio/src/thirdparty/libopenui/src/gridlayout.h b/radio/src/thirdparty/libopenui/src/gridlayout.h
new file mode 100644
index 000000000..60e6024bc
--- /dev/null
+++ b/radio/src/thirdparty/libopenui/src/gridlayout.h
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) OpenTX
+ *
+ * Source:
+ * https://github.com/opentx/libopenui
+ *
+ * This file is a part of libopenui library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ */
+
+#pragma once
+
+#include "window.h"
+
+class GridLayout {
+ public:
+ explicit GridLayout(coord_t width, coord_t top = 0):
+ width(width),
+ currentY(top)
+ {
+ }
+
+ explicit GridLayout(Window * window):
+ width(window->width())
+ {
+ }
+
+ rect_t getSlot(uint8_t count = 1, uint8_t index = 0) const
+ {
+ coord_t width = (this->width - (count - 1) * PAGE_LINE_SPACING) / count;
+ coord_t left = (width + PAGE_LINE_SPACING) * index;
+ return {left, currentY, width, PAGE_LINE_HEIGHT};
+ }
+
+ void spacer(coord_t height=PAGE_LINE_SPACING)
+ {
+ currentY += height;
+ }
+
+ void nextLine(coord_t height=PAGE_LINE_HEIGHT)
+ {
+ spacer(height + PAGE_LINE_SPACING);
+ }
+
+ void addWindow(Window * window)
+ {
+ window->adjustHeight();
+ currentY += window->rect.h + PAGE_LINE_SPACING;
+ }
+
+ coord_t getWindowHeight() const
+ {
+ return currentY;
+ }
+
+ protected:
+ coord_t width;
+ coord_t currentY = 0;
+};
+
+class FormGridLayout: public GridLayout {
+ public:
+ explicit FormGridLayout(coord_t width = LCD_W, coord_t top = 0):
+ GridLayout(width, top)
+ {
+ }
+
+ void setLabelWidth(coord_t value)
+ {
+ labelWidth = value;
+ }
+
+ void setMarginLeft(coord_t value)
+ {
+ lineMarginLeft = value;
+ }
+
+ void setMarginRight(coord_t value)
+ {
+ lineMarginRight = value;
+ }
+
+ rect_t getLineSlot()
+ {
+ return { lineMarginLeft, currentY, this->width - lineMarginRight - lineMarginLeft, PAGE_LINE_HEIGHT };
+ }
+
+ rect_t getCenteredSlot(coord_t width = 0 ) const
+ {
+ if (width == 0)
+ width = this->width;
+ return { lineMarginLeft + (this->width - width)/2, currentY, min(width, this->width - lineMarginRight - lineMarginLeft), PAGE_LINE_HEIGHT };
+ }
+
+ rect_t getLabelSlot(bool indent = false) const
+ {
+ coord_t left = indent ? lineMarginLeft + PAGE_INDENT_WIDTH : lineMarginLeft;
+ return { left, currentY, labelWidth - left, PAGE_LINE_HEIGHT };
+ }
+
+ rect_t getFieldSlot(uint8_t count = 1, uint8_t index = 0) const
+ {
+ coord_t width = (this->width - labelWidth - lineMarginRight - (count - 1) * PAGE_LINE_SPACING) / count;
+ coord_t left = labelWidth + (width + PAGE_LINE_SPACING) * index;
+ return {left, currentY, width, PAGE_LINE_HEIGHT};
+ }
+
+ protected:
+ coord_t labelWidth = PAGE_LABEL_WIDTH;
+ coord_t lineMarginLeft = PAGE_PADDING;
+ coord_t lineMarginRight = PAGE_PADDING;
+};
+
diff --git a/radio/src/thirdparty/libopenui/src/keyboard_base.cpp b/radio/src/thirdparty/libopenui/src/keyboard_base.cpp
new file mode 100644
index 000000000..889ddb0d8
--- /dev/null
+++ b/radio/src/thirdparty/libopenui/src/keyboard_base.cpp
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) EdgeTX
+ *
+ * Source:
+ * https://github.com/edgetx/libopenui
+ *
+ * This file is a part of libopenui library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ */
+#include "keyboard_base.h"
+
+#include "form.h"
+#include "mainwindow.h"
+
+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();
+ } else if (code == LV_EVENT_CANCEL) {
+ Keyboard::hide();
+ } else if (code == LV_EVENT_KEY) {
+ int32_t c = *((int32_t *)lv_event_get_param(e));
+ if (c == LV_KEY_ESC) {
+ Keyboard::hide();
+ }
+ }
+}
+
+static void field_focus_leave(lv_event_t* e)
+{
+ Keyboard::hide();
+}
+
+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) :
+ Window(MainWindow::instance(), {0, LCD_H - height, LCD_W, height}, OPAQUE)
+{
+ // set the background of the window and opacity to 100%
+ lv_obj_set_parent(lvobj, lv_layer_top()); // the keyboard is always on top
+ lv_obj_set_style_bg_color(lvobj, lv_color_make(0xE0, 0xE0, 0xE0), LV_PART_MAIN);
+ lv_obj_set_style_bg_opa(lvobj, LV_OPA_100, LV_PART_MAIN);
+
+ // 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 = lv_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()
+{
+ 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);
+ }
+
+ field->setEditMode(false);
+ field->changeEnd();
+ field = nullptr;
+
+ if (fieldGroup) {
+ _assign_lv_group(fieldGroup);
+ lv_group_set_editing(fieldGroup, false);
+ fieldGroup = nullptr;
+ }
+ }
+}
+
+void Keyboard::hide()
+{
+ if (activeKeyboard) {
+ activeKeyboard->clearField();
+ lv_obj_add_flag(activeKeyboard->lvobj, LV_OBJ_FLAG_HIDDEN);
+ activeKeyboard = nullptr;
+ }
+}
+
+bool Keyboard::attachKeyboard()
+{
+ if (activeKeyboard) {
+ if (activeKeyboard == this) return false;
+ hide();
+ }
+
+ 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);
+
+ invalidate();
+ 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);
+ }
+ }
+}
diff --git a/radio/src/thirdparty/libopenui/src/keyboard_base.h b/radio/src/thirdparty/libopenui/src/keyboard_base.h
new file mode 100644
index 000000000..c67689106
--- /dev/null
+++ b/radio/src/thirdparty/libopenui/src/keyboard_base.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) EdgeTX
+ *
+ * Source:
+ * https://github.com/edgetx/libopenui
+ *
+ * This file is a part of libopenui library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ */
+
+#pragma once
+
+#include "window.h"
+
+class FormField;
+
+class Keyboard : public Window
+{
+ public:
+ explicit Keyboard(coord_t height);
+ ~Keyboard();
+
+ void clearField();
+ static void hide();
+
+ 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();
+};
diff --git a/radio/src/thirdparty/libopenui/src/keyboard_number.cpp b/radio/src/thirdparty/libopenui/src/keyboard_number.cpp
new file mode 100644
index 000000000..b23ef7f33
--- /dev/null
+++ b/radio/src/thirdparty/libopenui/src/keyboard_number.cpp
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) OpenTX
+ *
+ * Source:
+ * https://github.com/opentx/libopenui
+ *
+ * This file is a part of libopenui library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser 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* 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);
+ NumberEdit* edit = (NumberEdit*)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;
+
+ if (strcmp(txt, "<<") == 0) {
+ edit->onEvent(EVT_VIRTUAL_KEY_BACKWARD);
+ } else if (strcmp(txt, "-") == 0) {
+ edit->onEvent(EVT_VIRTUAL_KEY_MINUS);
+ } else if (strcmp(txt, "+") == 0) {
+ edit->onEvent(EVT_VIRTUAL_KEY_PLUS);
+ } else if (strcmp(txt, ">>") == 0) {
+ edit->onEvent(EVT_VIRTUAL_KEY_FORWARD);
+ } else if (strcmp(txt, "MIN") == 0) {
+ edit->onEvent(EVT_VIRTUAL_KEY_MIN);
+ } else if (strcmp(txt, "DEF") == 0) {
+ edit->onEvent(EVT_VIRTUAL_KEY_DEFAULT);
+ } else if (strcmp(txt, "MAX") == 0) {
+ edit->onEvent(EVT_VIRTUAL_KEY_MAX);
+ } else if (strcmp(txt, "+/-") == 0) {
+ edit->onEvent(EVT_VIRTUAL_KEY_SIGN);
+ }
+}
+
+NumberKeyboard::NumberKeyboard() :
+ Keyboard(KEYBOARD_HEIGHT)
+{
+ // setup custom keyboard
+ lv_keyboard_set_map(keyboard, LV_KEYBOARD_MODE_USER_1,
+ number_kb_map, number_kb_ctrl_map);
+
+ lv_keyboard_set_mode(keyboard, LV_KEYBOARD_MODE_USER_1);
+}
+
+NumberKeyboard::~NumberKeyboard()
+{
+ _instance = nullptr;
+}
+
+void NumberKeyboard::show(NumberEdit* 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, field);
+}
diff --git a/radio/src/thirdparty/libopenui/src/keyboard_number.h b/radio/src/thirdparty/libopenui/src/keyboard_number.h
new file mode 100644
index 000000000..859cd9f7a
--- /dev/null
+++ b/radio/src/thirdparty/libopenui/src/keyboard_number.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) OpenTX
+ *
+ * Source:
+ * https://github.com/opentx/libopenui
+ *
+ * This file is a part of libopenui library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ */
+
+#pragma once
+
+#include "keyboard_base.h"
+
+class NumberEdit;
+
+class NumberKeyboard : public Keyboard
+{
+ public:
+ NumberKeyboard();
+ ~NumberKeyboard() override;
+
+#if defined(DEBUG_WINDOWS)
+ std::string getName() const override { return "NumberKeyboard"; }
+#endif
+
+ static void show(NumberEdit* field);
+
+ protected:
+ static NumberKeyboard* _instance;
+};
diff --git a/radio/src/thirdparty/libopenui/src/keyboard_text.cpp b/radio/src/thirdparty/libopenui/src/keyboard_text.cpp
new file mode 100644
index 000000000..2c1b2569b
--- /dev/null
+++ b/radio/src/thirdparty/libopenui/src/keyboard_text.cpp
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) OpenTX
+ *
+ * Source:
+ * https://github.com/opentx/libopenui
+ *
+ * This file is a part of libopenui library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ */
+
+#include "keyboard_text.h"
+#include "textedit.h"
+#include "font.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::onEvent(event_t event)
+{
+}
+#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);
+}
diff --git a/radio/src/thirdparty/libopenui/src/keyboard_text.h b/radio/src/thirdparty/libopenui/src/keyboard_text.h
new file mode 100644
index 000000000..375f0efc4
--- /dev/null
+++ b/radio/src/thirdparty/libopenui/src/keyboard_text.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) OpenTX
+ *
+ * Source:
+ * https://github.com/opentx/libopenui
+ *
+ * This file is a part of libopenui library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser 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 onEvent(event_t event) override;
+#endif
+
+ static TextKeyboard* _instance;
+};
diff --git a/radio/src/thirdparty/libopenui/src/layer.cpp b/radio/src/thirdparty/libopenui/src/layer.cpp
new file mode 100644
index 000000000..98c441cac
--- /dev/null
+++ b/radio/src/thirdparty/libopenui/src/layer.cpp
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) OpenTX
+ *
+ * Source:
+ * https://github.com/opentx/libopenui
+ *
+ * This file is a part of libopenui library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ */
+
+#include "layer.h"
+
+std::list 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->getWindowFlags() & OPAQUE)) return w;
+ }
+
+ return nullptr;
+}
diff --git a/radio/src/thirdparty/libopenui/src/layer.h b/radio/src/thirdparty/libopenui/src/layer.h
new file mode 100644
index 000000000..873138855
--- /dev/null
+++ b/radio/src/thirdparty/libopenui/src/layer.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) OpenTX
+ *
+ * Source:
+ * https://github.com/opentx/libopenui
+ *
+ * This file is a part of libopenui library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ */
+
+#pragma once
+
+#include "window.h"
+
+class Layer
+{
+ static std::list 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();
+};
diff --git a/radio/src/thirdparty/libopenui/src/libopenui_compat.h b/radio/src/thirdparty/libopenui/src/libopenui_compat.h
new file mode 100644
index 000000000..7c8006bd8
--- /dev/null
+++ b/radio/src/thirdparty/libopenui/src/libopenui_compat.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) OpenTX
+ *
+ * Source:
+ * https://github.com/opentx/libopenui
+ *
+ * This file is a part of libopenui library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ */
+
+#pragma once
+
diff --git a/radio/src/thirdparty/libopenui/src/libopenui_defines.h b/radio/src/thirdparty/libopenui/src/libopenui_defines.h
new file mode 100644
index 000000000..e81dad983
--- /dev/null
+++ b/radio/src/thirdparty/libopenui/src/libopenui_defines.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) OpenTX
+ *
+ * Source:
+ * https://github.com/opentx/libopenui
+ *
+ * This file is a part of libopenui library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ */
+
+#pragma once
+
+#include "libopenui_types.h"
+#include "libopenui_compat.h"
+
+/* obsolete flags */
+#define BLINK 0
+#define TIMEHOUR 0
+
+/* drawText flags */
+#define LEFT 0x00u /* align left */
+// 0x01u used by Lua in api_colorlcd.h
+#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
+#define SPACING_NUMBERS_CONST 0x2000u
+// 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 FONT_MASK 0x0F00u
+#define FONT_INDEX(flags) (((flags) & FONT_MASK) >> 8u)
+#define FONT(xx) (unsigned(FONT_ ## xx ## _INDEX) << 8u)
+
+#define ARGB_SPLIT(color, a, r, g, b) \
+ uint16_t a = ((color) & 0xF000) >> 12; \
+ uint16_t r = ((color) & 0x0F00) >> 8; \
+ uint16_t g = ((color) & 0x00F0) >> 4; \
+ uint16_t b = ((color) & 0x000F)
+
+#define RGB_SPLIT(color, r, g, b) \
+ uint16_t r = ((color) & 0xF800) >> 11; \
+ uint16_t g = ((color) & 0x07E0) >> 5; \
+ uint16_t b = ((color) & 0x001F)
+
+#define ARGB_JOIN(a, r, g, b) \
+ (((a&0xF) << 12) + ((r&0xF) << 8) + ((g&0xF) << 4) + (b&0xF))
+
+#define RGB_JOIN(r, g, b) \
+ (((r) << 11) + ((g) << 5) + (b))
+
+#define GET_RED(color) \
+ (((color) & 0xF800) >> 8)
+
+#define GET_GREEN(color) \
+ (((color) & 0x07E0) >> 3)
+
+#define GET_BLUE(color) \
+ (((color) & 0x001F) << 3)
+
+#define OPACITY_MAX 0x0Fu
+#define OPACITY(value) ((value) & OPACITY_MAX)
+
+#define RGB(r, g, b) (uint16_t)((((r) & 0xF8) << 8) + (((g) & 0xFC) << 3) + (((b) & 0xF8) >> 3))
+#define ARGB(a, r, g, b) (uint16_t)((((a) & 0xF0) << 8) + (((r) & 0xF0) << 4) + (((g) & 0xF0) << 0) + (((b) & 0xF0) >> 4))
+
+#define COLOR2FLAGS(color) LcdFlags(unsigned(color) << 16u)
+#define COLOR_VAL(flags) ((flags) >> 16u)
+#define COLOR_MASK(flags) ((flags) & 0xFFFF0000u)
+
+#define LV_OBJ_FLAG_ENCODER_ACCEL LV_OBJ_FLAG_USER_1
diff --git a/radio/src/thirdparty/libopenui/src/libopenui_depends.h b/radio/src/thirdparty/libopenui/src/libopenui_depends.h
new file mode 100644
index 000000000..279a4e070
--- /dev/null
+++ b/radio/src/thirdparty/libopenui/src/libopenui_depends.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) OpenTX
+ *
+ * Source:
+ * https://github.com/opentx/libopenui
+ *
+ * This file is a part of libopenui library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ */
+
+#pragma once
+
+#include
+#include "libopenui_config.h"
+
+void lcdNextLayer();
+uint16_t* lcdGetScratchBuffer();
+void DMACopyBitmap(uint16_t * dest, uint16_t destw, uint16_t desth, uint16_t x, uint16_t y, const uint16_t * src, uint16_t srcw, uint16_t srch, uint16_t srcx, uint16_t srcy, uint16_t w, uint16_t h);
+void DMACopyAlphaBitmap(uint16_t * dest, uint16_t destw, uint16_t desth, uint16_t x, uint16_t y, const uint16_t * src, uint16_t srcw, uint16_t srch, uint16_t srcx, uint16_t srcy, uint16_t w, uint16_t h);
+void DMACopyAlphaMask(uint16_t * dest, uint16_t destw, uint16_t desth, uint16_t x, uint16_t y, const uint8_t * src, uint16_t srcw, uint16_t srch, uint16_t srcx, uint16_t srcy, uint16_t w, uint16_t h, uint16_t bg_color);
+void onKeyError();
+void killEvents(event_t event);
+
+// multiplication factor between 0 and 25
+int8_t rotaryEncoderGetAccel();
+
+//
+// These colors MUST be defined
+//
+
+// #define COLOR_THEME_SECONDARY1
+// #define COLOR_THEME_SECONDARY3
+// #define COLOR_THEME_PRIMARY2
+// #define COLOR_THEME_FOCUS
+// #define COLOR_THEME_DISABLED
+// #define COLOR_THEME_ACTIVE
+// #define COLOR_THEME_FOCUS
+// #define COLOR_THEME_PRIMARY3
+// #define COLOR_THEME_SECONDARY1
+// #define COLOR_THEME_PRIMARY2
+// #define COLOR_THEME_SECONDARY1_THEME_SECONDARY1
+// #define COLOR_THEME_SECONDARY2
+// #define COLOR_THEME_PRIMARY2
+// #define COLOR_THEME_FOCUS
+// #define COLOR_THEME_PRIMARY1
+// #define COLOR_THEME_SECONDARY3
+// #define COLOR_THEME_FOCUS
+// #define CUSTOM_COLOR
diff --git a/radio/src/thirdparty/libopenui/src/libopenui_file.cpp b/radio/src/thirdparty/libopenui/src/libopenui_file.cpp
new file mode 100644
index 000000000..2d5360b6d
--- /dev/null
+++ b/radio/src/thirdparty/libopenui/src/libopenui_file.cpp
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) OpenTX
+ *
+ * Source:
+ * https://github.com/opentx/libopenui
+ *
+ * This file is a part of libopenui library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ */
+
+#include
+#include
+#include "libopenui_file.h"
+#include "libopenui_defines.h"
+
+const char * getFileExtension(const char * filename, uint8_t size, uint8_t extMaxLen, uint8_t * fnlen, uint8_t * extlen)
+{
+ int len = size;
+ if (!size) {
+ len = strlen(filename);
+ }
+ if (!extMaxLen) {
+ extMaxLen = LEN_FILE_EXTENSION_MAX;
+ }
+ if (fnlen != nullptr) {
+ *fnlen = (uint8_t)len;
+ }
+ for (int i=len-1; i >= 0 && len-i <= extMaxLen; --i) {
+ if (filename[i] == '.') {
+ if (extlen) {
+ *extlen = len-i;
+ }
+ return &filename[i];
+ }
+ }
+ if (extlen != nullptr) {
+ *extlen = 0;
+ }
+ return nullptr;
+}
+
+/**
+ Check if given extension exists in a list of extensions.
+ @param extension The extension to search for, including leading period.
+ @param pattern One or more file extensions concatenated together, including the periods.
+ The list is searched backwards and the first match, if any, is returned.
+ eg: ".gif.jpg.jpeg.png"
+ @param match Optional container to hold the matched file extension (wide enough to hold LEN_FILE_EXTENSION_MAX + 1).
+ @retval true if a extension was found in the lost, false otherwise.
+*/
+bool isExtensionMatching(const char * extension, const char * pattern, char * match)
+{
+ const char *ext;
+ uint8_t extlen, fnlen;
+ int plen;
+
+ ext = getFileExtension(pattern, 0, 0, &fnlen, &extlen);
+ plen = (int)fnlen;
+ while (plen > 0 && ext) {
+ if (!strncasecmp(extension, ext, extlen)) {
+ if (match != nullptr) strncat(&(match[0]='\0'), ext, extlen);
+ return true;
+ }
+ plen -= extlen;
+ if (plen > 0) {
+ ext = getFileExtension(pattern, plen, 0, nullptr, &extlen);
+ }
+ }
+ return false;
+}
+
+// returns true if current working dir is at the root level
+bool isCwdAtRoot()
+{
+ char path[10];
+ if (f_getcwd(path, sizeof(path)-1) == FR_OK) {
+ return (strcasecmp("/", path) == 0);
+ }
+ return false;
+}
+
+/*
+ Wrapper around the f_readdir() function which
+ also returns ".." entry for sub-dirs. (FatFS 0.12 does
+ not return ".", ".." dirs anymore)
+*/
+FRESULT sdReadDir(DIR * dir, FILINFO * fno, bool & firstTime)
+{
+ FRESULT res;
+ if (firstTime && !isCwdAtRoot()) {
+ // fake parent directory entry
+ strcpy(fno->fname, "..");
+ fno->fattrib = AM_DIR;
+ res = FR_OK;
+ }
+ else {
+ res = f_readdir(dir, fno); /* Read a directory item */
+ }
+ firstTime = false;
+ return res;
+}
diff --git a/radio/src/thirdparty/libopenui/src/libopenui_file.h b/radio/src/thirdparty/libopenui/src/libopenui_file.h
new file mode 100644
index 000000000..62f36bfa8
--- /dev/null
+++ b/radio/src/thirdparty/libopenui/src/libopenui_file.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) OpenTX
+ *
+ * Source:
+ * https://github.com/opentx/libopenui
+ *
+ * This file is a part of libopenui library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ */
+
+#pragma once
+
+#include "libopenui_compat.h" // for strcasecmp / VC++
+#include
+#include
+#include
+#include "ff.h"
+
+constexpr uint8_t LEN_FILE_EXTENSION_MAX = 5; // longest used, including the dot, excluding null term.
+
+const char * getFileExtension(const char * filename, uint8_t size = 0, uint8_t extMaxLen = 0, uint8_t * fnlen = nullptr, uint8_t * extlen = nullptr);
+bool isExtensionMatching(const char * extension, const char * pattern, char * match = nullptr);
+FRESULT sdReadDir(DIR * dir, FILINFO * fno, bool & firstTime);
+
+// comparison, not case sensitive.
+inline bool compare_nocase(const std::string & first, const std::string & second)
+{
+ return strcasecmp(first.c_str(), second.c_str()) < 0;
+}
diff --git a/radio/src/thirdparty/libopenui/src/libopenui_helpers.cpp b/radio/src/thirdparty/libopenui/src/libopenui_helpers.cpp
new file mode 100644
index 000000000..196771132
--- /dev/null
+++ b/radio/src/thirdparty/libopenui/src/libopenui_helpers.cpp
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) OpenTX
+ *
+ * Source:
+ * https://github.com/opentx/libopenui
+ *
+ * This file is a part of libopenui library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ */
+
+#include "libopenui_helpers.h"
+
diff --git a/radio/src/thirdparty/libopenui/src/libopenui_helpers.h b/radio/src/thirdparty/libopenui/src/libopenui_helpers.h
new file mode 100644
index 000000000..035253912
--- /dev/null
+++ b/radio/src/thirdparty/libopenui/src/libopenui_helpers.h
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) OpenTX
+ *
+ * Source:
+ * https://github.com/opentx/libopenui
+ *
+ * This file is a part of libopenui library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ */
+
+#pragma once
+
+#include
+#include
+
+#if defined(min)
+ #undef min
+ #undef max
+#endif
+
+template
+inline T min(T a, T b)
+{
+ return a < b ? a : b;
+}
+
+template
+inline T max(T a, T b)
+{
+ return a > b ? a : b;
+}
+
+template
+inline T limit(T vmin, T x, T vmax)
+{
+ return ::min(::max(vmin, x), vmax);
+}
+
+inline int divRoundClosest(int n, int d)
+{
+ if (d == 0)
+ return 0;
+ else
+ return ((n < 0) ^ (d < 0)) ? ((n - d/2)/d) : ((n + d/2)/d);
+}
+
+inline int multDivRoundClosest(int v, int n, int d)
+{
+ if (n == d)
+ return v;
+ else
+ return divRoundClosest(v * n, d);
+}
+
+inline int mod(int k, int n)
+{
+ return ((k %= n) < 0) ? k + n : k;
+}
+
+inline unsigned align32(unsigned n)
+{
+ unsigned rest = (n & 3u);
+ return rest ? n + 4u - rest : n;
+}
+
+template
+inline T sgn(T a)
+{
+ return a > 0 ? 1 : (a < 0 ? -1 : 0);
+}
+
+// TODO duplicated code
+inline std::string TEXT_AT_INDEX(const char *const *val, uint8_t idx)
+{
+ return std::string(val[idx]);
+}
+
+inline const char * findNextLine(const char * stack)
+{
+ while (true) {
+ const char * pos = strchr(stack, '\n');
+ if (!pos)
+ return nullptr;
+ if (pos == stack || *((uint8_t *)(pos - 1)) < 0xFE)
+ return pos;
+ stack = pos + 1;
+ }
+}
diff --git a/radio/src/thirdparty/libopenui/src/libopenui_types.h b/radio/src/thirdparty/libopenui/src/libopenui_types.h
new file mode 100644
index 000000000..0fec188c8
--- /dev/null
+++ b/radio/src/thirdparty/libopenui/src/libopenui_types.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) OpenTX
+ *
+ * Source:
+ * https://github.com/opentx/libopenui
+ *
+ * This file is a part of libopenui library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ */
+
+#pragma once
+
+#include
+
+typedef int coord_t;
+
+struct point_t
+{
+ coord_t x;
+ coord_t y;
+
+ bool operator != (const point_t & b) const
+ {
+ return x != b.x || y != b.y;
+ }
+};
+
+struct rect_t
+{
+ coord_t x, y, w, h;
+
+ constexpr coord_t left() const
+ {
+ return x;
+ }
+
+ constexpr coord_t right() const
+ {
+ return x + w;
+ }
+
+ constexpr coord_t top() const
+ {
+ return y;
+ }
+
+ constexpr coord_t bottom() const
+ {
+ return y + h;
+ }
+
+ bool contains(coord_t x, coord_t y) const
+ {
+ return (x >= this->x && x < this->x + this->w && y >= this->y && y < this->y + this->h);
+ }
+
+ bool contains(const rect_t & other) const
+ {
+ return left() <= other.left() && right() >= other.right() && top() <= other.top() && bottom() >= other.bottom();
+ }
+};
+
+static const rect_t nullRect = {0, 0, 0, 0};
+
+typedef uint32_t LcdFlags;
+typedef uint16_t event_t;
diff --git a/radio/src/thirdparty/libopenui/src/mainwindow.cpp b/radio/src/thirdparty/libopenui/src/mainwindow.cpp
new file mode 100644
index 000000000..604dac494
--- /dev/null
+++ b/radio/src/thirdparty/libopenui/src/mainwindow.cpp
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) OpenTX
+ *
+ * Source:
+ * https://github.com/opentx/libopenui
+ *
+ * This file is a part of libopenui library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ */
+
+#include "mainwindow.h"
+#include "keyboard_base.h"
+
+#include "lvgl/lvgl.h"
+
+
+
+MainWindow * MainWindow::_instance = nullptr;
+
+MainWindow::MainWindow() :
+ Window(nullptr, {0, 0, LCD_W, LCD_H}, OPAQUE), invalidatedRect(rect)
+{
+}
+
+void MainWindow::emptyTrash()
+{
+ for (auto window : trash) {
+ delete window;
+ }
+ trash.clear();
+}
+
+
+void MainWindow::invalidate(const rect_t& rect)
+{
+ if (invalidatedRect.w) {
+ auto left = limit(0, rect.left(), invalidatedRect.left());
+ auto right = limit(invalidatedRect.right(), rect.right(), LCD_W);
+ auto top = limit(0, rect.top(), invalidatedRect.top());
+ auto bottom =
+ limit(rect.bottom(), invalidatedRect.bottom(), LCD_H);
+ invalidatedRect = {left, top, right - left, bottom - top};
+ } else {
+ invalidatedRect = rect;
+ }
+}
+
+void MainWindow::run(bool trash)
+{
+ auto start = ticksNow();
+
+ auto opaque = Layer::getFirstOpaque();
+ if (opaque) opaque->checkEvents();
+
+ if (trash) emptyTrash();
+
+ auto delta = ticksNow() - start;
+ if (delta > 10 * SYSTEM_TICKS_1MS) {
+ TRACE_WINDOWS("MainWindow::run took %dms",
+ (ticksNow() - start) / SYSTEM_TICKS_1MS);
+ }
+}
diff --git a/radio/src/thirdparty/libopenui/src/mainwindow.h b/radio/src/thirdparty/libopenui/src/mainwindow.h
new file mode 100644
index 000000000..58e0f1fe8
--- /dev/null
+++ b/radio/src/thirdparty/libopenui/src/mainwindow.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) OpenTX
+ *
+ * Source:
+ * https://github.com/opentx/libopenui
+ *
+ * This file is a part of libopenui library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ */
+
+#pragma once
+
+#include
+#include "layer.h"
+#include "bitmapbuffer.h"
+
+class MainWindow: public Window
+{
+ protected:
+ // singleton
+ MainWindow();
+
+ public:
+ ~MainWindow() override
+ {
+ children.clear();
+ }
+
+ static bool isMainWindowCreated()
+ {
+ return _instance != nullptr;
+ }
+
+ static MainWindow * instance()
+ {
+ if (!_instance)
+ _instance = new MainWindow();
+
+ return _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 invalidate()
+ {
+ invalidate({0, 0, rect.w, rect.h});
+ }
+
+ void invalidate(const rect_t & rect) override;
+
+ bool needsRefresh() const
+ {
+ return invalidatedRect.w > 0;
+ }
+
+ bool refresh();
+
+ void run(bool trash=true);
+
+ protected:
+ static MainWindow * _instance;
+ static void emptyTrash();
+ rect_t invalidatedRect;
+ const char * shutdown = nullptr;
+};
diff --git a/radio/src/thirdparty/libopenui/src/menu.cpp b/radio/src/thirdparty/libopenui/src/menu.cpp
new file mode 100644
index 000000000..5a7026b00
--- /dev/null
+++ b/radio/src/thirdparty/libopenui/src/menu.cpp
@@ -0,0 +1,389 @@
+/*
+ * Copyright (C) OpenTX
+ *
+ * Source:
+ * https://github.com/opentx/libopenui
+ *
+ * This file is a part of libopenui library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ */
+
+#include "menu.h"
+#include "font.h"
+#include "theme.h"
+
+#include
+
+void MenuBody::onDrawBegin(uint16_t row, uint16_t col, lv_obj_draw_part_dsc_t* dsc)
+{
+ 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 MenuBody::onDrawEnd(uint16_t row, uint16_t col, lv_obj_draw_part_dsc_t* dsc)
+{
+ 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);
+ }
+}
+
+static void _force_editing(lv_group_t* g)
+{
+ lv_group_set_editing(g, true);
+}
+
+MenuBody::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
+ lv_obj_set_scrollbar_mode(lvobj, LV_SCROLLBAR_MODE_AUTO);
+
+ setColumnCount(1);
+ 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);
+ }
+}
+
+void MenuBody::addLine(const std::string &text, std::function onPress,
+ std::function isChecked, bool update)
+{
+ lines.emplace_back(text, std::move(onPress), std::move(isChecked), nullptr);
+
+ if (update) {
+ auto idx = lines.size() - 1;
+ lv_table_set_cell_value(lvobj, idx, 0, text.c_str());
+ }
+}
+
+void MenuBody::addLine(const uint8_t *icon_mask, const std::string &text,
+ std::function onPress,
+ std::function isChecked,
+ bool update)
+{
+ lv_obj_t* canvas = lv_canvas_create(nullptr);
+ lines.emplace_back(text, std::move(onPress), std::move(isChecked), canvas);
+
+ 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);
+
+ if (update) {
+ auto idx = lines.size() - 1;
+ lv_table_set_cell_value(lvobj, idx, 0, text.c_str());
+ }
+}
+
+void MenuBody::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 MenuBody::removeLines()
+{
+ lines.clear();
+ setRowCount(0);
+ selectedIndex = 0;
+
+ // reset vertical scroll
+ lv_obj_scroll_to_y(lvobj, 0, LV_ANIM_OFF);
+}
+
+coord_t MenuBody::getContentHeight()
+{
+ return lv_obj_get_self_height(lvobj);
+}
+
+void MenuBody::onPress(size_t index)
+{
+ Menu *menu = getParentMenu();
+ if (index < lines.size()) {
+ if (menu->multiple) {
+ if (selectedIndex == (int)index)
+ lines[index].onPress();
+ else {
+ setIndex(index);
+ lines[index].onPress();
+ }
+ } else {
+ // delete menu first to avoid
+ // focus issues with onPress()
+ menu->deleteLater();
+ lines[index].onPress();
+ }
+ }
+}
+
+// ensure index is in range and also handle wrapping index
+int MenuBody::rangeCheck(int index)
+{
+ if (index < 0)
+ index = lines.size() - 1;
+ else if (index > (signed)lines.size() - 1)
+ index = 0;
+
+ return index;
+}
+
+void MenuBody::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);
+ }
+}
+
+void MenuBody::onPress(uint16_t row, uint16_t col)
+{
+ onPress(row);
+}
+
+void MenuBody::selectNext(MENU_DIRECTION direction)
+{
+ // look for the next non separator line
+ int index = selectedIndex + direction;
+ setIndex(rangeCheck(index));
+}
+
+
+void MenuBody::onEvent(event_t event)
+{
+#if defined(HARDWARE_KEYS)
+ if (event == EVT_KEY_BREAK(KEY_EXIT)) {
+ onCancel();
+ }
+ else {
+ TableField::onEvent(event);
+ }
+#endif
+}
+
+void MenuBody::onCancel()
+{
+ if(_onCancel) _onCancel();
+ TableField::onCancel();
+}
+
+MenuWindowContent::MenuWindowContent(Menu* parent) :
+ ModalWindowContent(parent, {(LCD_W - MENUS_WIDTH) / 2,
+ (LCD_H - MENUS_WIDTH) / 2, MENUS_WIDTH, 0}),
+ body(this, {0, 0, width(), height()})
+{
+ // body.setFocus(SET_FOCUS_DEFAULT);
+ lv_obj_set_style_bg_opa(lvobj, LV_OPA_100, LV_PART_MAIN);
+}
+
+coord_t MenuWindowContent::getHeaderHeight() const
+{
+ if (title) return lv_obj_get_height(title);
+ return 0;
+}
+
+void MenuWindowContent::deleteLater(bool detach, bool trash)
+{
+ if (_deleted) return;
+ body.deleteLater(true, false);
+ ModalWindowContent::deleteLater(detach, trash);
+}
+
+Menu::Menu(Window * parent, bool multiple):
+ ModalWindow(parent, true),
+ content(createMenuWindow(this)),
+ multiple(multiple)
+{
+}
+
+void Menu::setToolbar(Window * window)
+{
+ toolbar = window;
+ updatePosition();
+ toolbar->setLeft(content->left() - toolbar->width());
+ toolbar->setTop(content->top());
+ toolbar->setHeight(content->height());
+}
+
+void Menu::setOutline(Window* obj)
+{
+ lv_obj_set_style_outline_width(obj->getLvObj(), 2, LV_PART_MAIN);
+ lv_obj_set_style_outline_color(obj->getLvObj(), makeLvColor(COLOR_THEME_SECONDARY2), LV_PART_MAIN);
+ lv_obj_set_style_outline_opa(obj->getLvObj(), LV_OPA_100, LV_PART_MAIN);
+ lv_obj_set_style_outline_pad(obj->getLvObj(), 1, LV_PART_MAIN);
+
+}
+
+void Menu::updatePosition()
+{
+ coord_t height = content->body.getContentHeight();
+
+ if (toolbar) {
+ content->setLeft((LCD_W - content->width() + toolbar->width())/2);
+ setOutline(toolbar);
+ } else {
+ content->setLeft((LCD_W - content->width())/2);
+ auto headerHeight = content->getHeaderHeight();
+ auto bodyHeight = min(height, MENUS_MAX_HEIGHT - headerHeight);
+ content->setTop((LCD_H - headerHeight - bodyHeight) / 2);
+ content->setHeight(headerHeight + bodyHeight);
+ content->body.setTop(headerHeight);
+ content->body.setHeight(bodyHeight);
+ }
+ setOutline(content);
+}
+
+void Menu::setTitle(std::string text)
+{
+ content->setTitle(std::move(text));
+ updatePosition();
+}
+
+void Menu::addLine(const std::string &text, std::function onPress,
+ std::function isChecked)
+{
+ content->body.addLine(text, std::move(onPress), std::move(isChecked));
+ updatePosition();
+}
+
+void Menu::addLine(const uint8_t *icon_mask, const std::string &text,
+ std::function onPress,
+ std::function isChecked)
+{
+ content->body.addLine(icon_mask, text, std::move(onPress), std::move(isChecked));
+ updatePosition();
+}
+
+void Menu::addLineBuffered(const std::string &text, std::function onPress,
+ std::function isChecked)
+{
+ content->body.addLine(text, std::move(onPress), std::move(isChecked), false);
+}
+
+void Menu::addLineBuffered(const uint8_t *icon_mask, const std::string &text,
+ std::function onPress,
+ std::function isChecked)
+{
+ content->body.addLine(icon_mask, text, std::move(onPress), std::move(isChecked), false);
+}
+
+void Menu::updateLines()
+{
+ content->body.updateLines();
+ updatePosition();
+}
+
+void Menu::removeLines()
+{
+ content->body.removeLines();
+ updatePosition();
+}
+
+void Menu::onEvent(event_t event)
+{
+#if defined(HARDWARE_KEYS)
+ if (toolbar &&
+ (event == EVT_KEY_BREAK(KEY_PGDN) ||
+#if defined(KEYS_GPIO_REG_PGUP)
+ event == EVT_KEY_BREAK(KEY_PGUP)
+#else
+ event == EVT_KEY_LONG(KEY_PGDN)
+#endif
+ )) {
+ toolbar->onEvent(event);
+ }
+#endif
+}
+
+void Menu::onCancel()
+{
+ deleteLater();
+}
diff --git a/radio/src/thirdparty/libopenui/src/menu.h b/radio/src/thirdparty/libopenui/src/menu.h
new file mode 100644
index 000000000..13a4c784a
--- /dev/null
+++ b/radio/src/thirdparty/libopenui/src/menu.h
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) OpenTX
+ *
+ * Source:
+ * https://github.com/opentx/libopenui
+ *
+ * This file is a part of libopenui library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ */
+
+#pragma once
+
+#include
+#include
+#include
+#include
+
+#include "modal_window.h"
+#include "table.h"
+
+class Menu;
+class MenuWindowContent;
+
+struct lvobj_delete {
+ constexpr lvobj_delete() = default;
+ void operator()(lv_obj_t* obj) const {
+ lv_obj_del(obj);
+ }
+};
+
+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 onPress, std::function isChecked,
+ lv_obj_t *icon) :
+ text(text), onPress(std::move(onPress)), isChecked(std::move(isChecked)), icon(icon)
+ {
+ }
+
+ MenuLine(MenuLine &) = delete;
+ MenuLine(MenuLine &&) = default;
+
+ lv_obj_t* getIcon() { return icon.get(); }
+
+ protected:
+ std::string text;
+ std::function onPress;
+ std::function isChecked;
+ std::unique_ptr icon;
+ };
+
+ public:
+ MenuBody(Window * parent, const rect_t & rect);
+
+#if defined(DEBUG_WINDOWS)
+ std::string getName() const override
+ {
+ return "MenuBody";
+ }
+#endif
+
+ void setIndex(int index);
+
+ int selection() const
+ {
+ return selectedIndex;
+ }
+
+ int count() const
+ {
+ return lines.size();
+ }
+
+ void onEvent(event_t event) override;
+ void onCancel() override;
+
+ void addLine(const std::string &text, std::function onPress,
+ std::function isChecked, bool update = true);
+
+ void addLine(const uint8_t *icon_mask, const std::string &text,
+ std::function onPress,
+ std::function isChecked,
+ bool update = true);
+
+ void updateLines();
+
+ void removeLines();
+
+ void setCancelHandler(std::function handler)
+ {
+ _onCancel = std::move(handler);
+ }
+
+ coord_t getContentHeight();
+
+ void onPress(size_t index);
+
+ protected:
+ void onPress(uint16_t row, uint16_t col) override;
+ void onDrawBegin(uint16_t row, uint16_t col, lv_obj_draw_part_dsc_t* dsc) override;
+ void onDrawEnd(uint16_t row, uint16_t col, lv_obj_draw_part_dsc_t* dsc) override;
+
+ void selectNext(MENU_DIRECTION direction);
+ int rangeCheck(int);
+
+ std::vector lines;
+ int selectedIndex = 0;
+ std::function _onCancel;
+
+ inline Menu * getParentMenu();
+};
+
+class MenuWindowContent: public ModalWindowContent
+{
+ friend class Menu;
+
+ public:
+ explicit MenuWindowContent(Menu * parent);
+
+ coord_t getHeaderHeight() const;
+
+ void deleteLater(bool detach = true, bool trash = true) override;
+
+#if defined(DEBUG_WINDOWS)
+ std::string getName() const override
+ {
+ return "MenuWindowContent";
+ }
+#endif
+
+ protected:
+ MenuBody body;
+};
+
+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 handler)
+ {
+ content->body.setCancelHandler(std::move(handler));
+ }
+
+ void setWaitHandler(std::function handler)
+ {
+ waitHandler = std::move(handler);
+ }
+
+ void setToolbar(Window* window);
+
+ void setTitle(std::string text);
+
+ void addLine(const std::string &text, std::function onPress,
+ std::function isChecked = nullptr);
+
+ void addLine(const uint8_t *icon_mask, const std::string &text,
+ std::function onPress,
+ std::function isChecked = nullptr);
+
+ void addLineBuffered(const std::string &text, std::function onPress,
+ std::function isChecked = nullptr);
+
+ void addLineBuffered(const uint8_t *icon_mask, const std::string &text,
+ std::function onPress,
+ std::function isChecked = nullptr);
+
+ void updateLines();
+
+ void addSeparator();
+
+ void removeLines();
+
+ unsigned count() const
+ {
+ return content->body.count();
+ }
+
+ int selection() const
+ {
+ return content->body.selection();
+ }
+
+ void select(int index)
+ {
+ content->body.setIndex(index);
+ }
+
+ void onEvent(event_t event) override;
+ void onCancel() override;
+
+ void checkEvents() override
+ {
+ ModalWindow::checkEvents();
+ if (waitHandler) {
+ waitHandler();
+ }
+ }
+
+ protected:
+ MenuWindowContent* content;
+ bool multiple;
+ Window * toolbar = nullptr;
+ std::function waitHandler;
+ void updatePosition();
+
+ void setOutline(Window* obj);
+};
+
+Menu * MenuBody::getParentMenu()
+{
+ return static_cast