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

This commit is contained in:
philmoz
2024-08-02 08:50:55 +10:00
committed by GitHub
parent 4390e310d0
commit 2eee7cd011
329 changed files with 298 additions and 804 deletions
-6
View File
@@ -1,6 +0,0 @@
---
Language: Cpp
BasedOnStyle: Google
BreakBeforeBraces: Linux
BreakConstructorInitializers: AfterColon
...
@@ -1 +0,0 @@
src/thirdparty/stb/
@@ -1,7 +0,0 @@
[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
-32
View File
@@ -1,32 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<option name="RIGHT_MARGIN" value="160" />
<Objective-C>
<option name="INDENT_C_STRUCT_MEMBERS" value="2" />
<option name="INDENT_VISIBILITY_KEYWORDS" value="2" />
<option name="FUNCTION_BRACE_PLACEMENT" value="2" />
<option name="SUPERCLASS_LIST_BEFORE_COLON" value="0" />
<option name="SPACE_AFTER_POINTER_IN_DECLARATION" value="true" />
<option name="SPACE_AFTER_REFERENCE_IN_DECLARATION" value="true" />
</Objective-C>
<codeStyleSettings language="CMake">
<option name="SPACE_BEFORE_IF_PARENTHESES" value="false" />
<option name="SPACE_BEFORE_WHILE_PARENTHESES" value="false" />
<option name="SPACE_BEFORE_FOR_PARENTHESES" value="false" />
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="8" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="ObjectiveC">
<option name="CLASS_BRACE_STYLE" value="2" />
<option name="ELSE_ON_NEW_LINE" value="true" />
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="8" />
</indentOptions>
</codeStyleSettings>
</code_scheme>
</component>
-5
View File
@@ -1,5 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>
-339
View File
@@ -1,339 +0,0 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the 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 Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
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.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
-4
View File
@@ -1,4 +0,0 @@
# 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.
-42
View File
@@ -1,42 +0,0 @@
if(HARDWARE_KEYS)
add_definitions(-DHARDWARE_KEYS)
endif()
if(HARWARE_TOUCH)
add_definitions(-DHARDWARE_TOUCH)
endif()
set(LIBOPENUI_SOURCES
libopenui_file.cpp
bitmapbuffer.cpp
bitmapbuffer_fileio.cpp
bitmapbuffer_draw_extra.cpp
window.cpp
layer.cpp
form.cpp
button.cpp
button_matrix.cpp
static.cpp
toggleswitch.cpp
slider.cpp
mainwindow.cpp
menu.cpp
menutoolbar.cpp
choice.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
)
list(TRANSFORM LIBOPENUI_SOURCES PREPEND ${LIBOPENUI_SRC_DIR}/src/)
set(LIBOPENUI_SOURCES ${LIBOPENUI_SOURCES}
${LIBOPENUI_SRC_DIR}/thirdparty/lz4/lz4.c
)
-79
View File
@@ -1,79 +0,0 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* libopenui - https://github.com/opentx/libopenui
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#pragma once
#include <inttypes.h>
// A set of bitfield handling macros
template <typename T>
inline T bfBit(uint8_t n)
{
return T(1) << n;
}
#define BFBIT_FLIP(y, mask) ( y ^= (mask) )
//! Create a bitmask of length 'len'.
template <typename T>
inline T bfBitmask(uint8_t len)
{
return bfBit<T>(len) - 1;
}
//! Create a bitfield mask of length 'len' starting at bit 'start'.
template <typename T>
inline T bfMask(uint8_t start, uint8_t len)
{
return bfBitmask<T>(len) << start;
}
//! Prepare a bitmask for insertion or combining.
template <typename T>
inline T bfPrep(T x, uint8_t start, uint8_t len)
{
return (x & bfBitmask<T>(len)) << start;
}
//! Extract a bitfield of length 'len' starting at bit 'start' from 'y'.
template <typename T>
inline T bfGet(T y, uint8_t start, uint8_t len)
{
return ((y)>>(start)) & bfBitmask<T>(len);
}
//! Insert 'len' bits of 'x 'into 'y', starting at bit 'start' from 'y'.
template <class T>
inline T bfSet(T to, T from, uint8_t start, uint8_t len)
{
return (to & ~bfMask<T>(start, len)) | bfPrep<T>(from, start, len);
}
template <class T>
inline T bfBitGet(T y, T mask)
{
return (y & mask);
}
template <class T>
inline T bfSingleBitGet(T y, uint8_t i)
{
return bfBitGet(y, bfBit<T>(i));
}
-565
View File
@@ -1,565 +0,0 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* libopenui - https://github.com/opentx/libopenui
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "bitmapbuffer.h"
#include <math.h>
#include <string>
#include "bitmaps.h"
#include "board.h"
#include "dma2d.h"
#include "fonts.h"
#include "lvgl/src/draw/sw/lv_draw_sw.h"
#include "opentx_helpers.h"
#include "strhelpers.h"
BitmapBuffer::BitmapBuffer(uint8_t format, uint16_t width, uint16_t height) :
format(format),
_width(width),
_height(height),
xmax(width),
ymax(height),
dataAllocated(true)
#if defined(DEBUG)
,
leakReported(false)
#endif
{
data = (uint16_t *)malloc(align32(width * height * sizeof(uint16_t)));
data_end = data + (width * height);
#if !defined(BOOT)
// Assume we need a canvas here
canvas = lv_canvas_create(nullptr);
lv_canvas_set_buffer(canvas, data, width, height, LV_IMG_CF_TRUE_COLOR);
#endif
}
BitmapBuffer::BitmapBuffer(uint8_t format, uint16_t width, uint16_t height,
uint16_t *data) :
format(format),
_width(width),
_height(height),
xmax(width),
ymax(height),
data(data),
data_end(data + (width * height)),
dataAllocated(false)
#if defined(DEBUG)
,
leakReported(false)
#endif
{
}
BitmapBuffer::~BitmapBuffer()
{
DMAWait();
if (dataAllocated) {
#if !defined(BOOT)
lv_obj_del(canvas);
#endif
free(data);
}
}
void BitmapBuffer::setData(uint16_t *d)
{
if (!dataAllocated) {
data = d;
data_end = d + (_width * _height);
}
}
void BitmapBuffer::clear(LcdFlags flags)
{
drawSolidFilledRect(0, 0, _width, _height, flags);
}
void BitmapBuffer::clearClippingRect()
{
xmin = 0;
xmax = _width;
ymin = 0;
ymax = _height;
}
void BitmapBuffer::setClippingRect(coord_t xmin, coord_t xmax, coord_t ymin,
coord_t ymax)
{
this->xmin = xmin;
this->xmax = xmax;
this->ymin = ymin;
this->ymax = ymax;
}
void BitmapBuffer::getClippingRect(coord_t &xmin, coord_t &xmax, coord_t &ymin,
coord_t &ymax)
{
xmin = this->xmin;
xmax = this->xmax;
ymin = this->ymin;
ymax = this->ymax;
}
bool BitmapBuffer::applyClippingRect(coord_t& x, coord_t& y, coord_t& w,
coord_t& h) const
{
if (h < 0) {
y += h;
h = -h;
}
if (w < 0) {
x += w;
w = -w;
}
if (x >= xmax || y >= ymax) return false;
if (y < ymin) {
h += y - ymin;
y = ymin;
}
if (x < xmin) {
w += x - xmin;
x = xmin;
}
if (y + h > ymax) h = ymax - y;
if (x + w > xmax) w = xmax - x;
return data && h > 0 && w > 0;
}
void BitmapBuffer::drawBitmap(coord_t x, coord_t y, const BitmapBuffer *bmp,
coord_t srcx, coord_t srcy, coord_t srcw,
coord_t srch, float scale)
{
if (!data || !bmp) return;
if (x >= xmax || y >= ymax) return;
coord_t bmpw = bmp->width();
coord_t bmph = bmp->height();
if (srcw == 0) srcw = bmpw;
if (srch == 0) srch = bmph;
if (srcx + srcw > bmpw) srcw = bmpw - srcx;
if (srcy + srch > bmph) srch = bmph - srcy;
if (scale == 0) {
if (x < xmin) {
srcw += x - xmin;
srcx -= x - xmin;
x = xmin;
}
if (y < ymin) {
srch += y - ymin;
srcy -= y - ymin;
y = ymin;
}
if (x + srcw > xmax) {
srcw = xmax - x;
}
if (y + srch > ymax) {
srch = ymax - y;
}
} else {
if (x < xmin) {
srcw += (x - xmin) / scale;
srcx -= (x - xmin) / scale;
x = xmin;
}
if (y < ymin) {
srch += (y - ymin) / scale;
srcy -= (y - ymin) / scale;
y = ymin;
}
if (x + srcw * scale > xmax) {
srcw = (xmax - x) / scale;
}
if (y + srch * scale > ymax) {
srch = (ymax - y) / scale;
}
}
if (srcw <= 0 || srch <= 0) {
return;
}
if (scale == 0) {
if (bmp->format == BMP_ARGB4444) {
DMACopyAlphaBitmap(data, _width, _height, x, y, bmp->getData(), bmpw,
bmph, srcx, srcy, srcw, srch);
} else {
DMACopyBitmap(data, _width, _height, x, y, bmp->getData(), bmpw, bmph,
srcx, srcy, srcw, srch);
}
DMAWait();
#if __CORTEX_M >= 0x07
SCB_CleanInvalidateDCache();
#endif
} else {
int scaledw = srcw * scale;
int scaledh = srch * scale;
if (x + scaledw > _width) scaledw = _width - x;
if (y + scaledh > _height) scaledh = _height - y;
if (format == BMP_ARGB4444) {
for (int i = 0; i < scaledh; i++) {
pixel_t *p = getPixelPtrAbs(x, y + i);
const pixel_t *qstart =
bmp->getPixelPtrAbs(srcx, srcy + int(i / scale));
for (int j = 0; j < scaledw; j++) {
const pixel_t *q = qstart;
MOVE_PIXEL_RIGHT(q, int(j / scale));
if (bmp->format == BMP_RGB565) {
RGB_SPLIT(*q, r, g, b);
drawPixel(p, ARGB_JOIN(0xF, r >> 1, g >> 2, b >> 1));
} else { // bmp->format == BMP_ARGB4444
drawPixel(p, *q);
}
MOVE_TO_NEXT_RIGHT_PIXEL(p);
}
}
} else { // format == BM_RGB565
for (int i = 0; i < scaledh; i++) {
pixel_t *p = getPixelPtrAbs(x, y + i);
const pixel_t *qstart =
bmp->getPixelPtrAbs(srcx, srcy + int(i / scale));
for (int j = 0; j < scaledw; j++) {
const pixel_t *q = qstart;
MOVE_PIXEL_RIGHT(q, int(j / scale));
if (bmp->format == BMP_RGB565) {
drawPixel(p, *q);
} else { // bmp->format == BMP_ARGB4444
ARGB_SPLIT(*q, a, r, g, b);
drawAlphaPixel(p, a, RGB_JOIN(r << 1, g << 2, b << 1));
}
MOVE_TO_NEXT_RIGHT_PIXEL(p);
} // for j
} // for i
} // if format
} // else (scale != 0) {
}
void BitmapBuffer::drawAlphaPixel(pixel_t *p, uint8_t opacity, uint16_t color)
{
// TRACE("BitmapBuffer::drawAlphaPixel()");
if (opacity == OPACITY_MAX) {
drawPixel(p, color);
} else if (opacity != 0) {
uint8_t bgWeight = OPACITY_MAX - opacity;
RGB_SPLIT(color, red, green, blue);
RGB_SPLIT(*p, bgRed, bgGreen, bgBlue);
uint16_t r = (bgRed * bgWeight + red * opacity) / OPACITY_MAX;
uint16_t g = (bgGreen * bgWeight + green * opacity) / OPACITY_MAX;
uint16_t b = (bgBlue * bgWeight + blue * opacity) / OPACITY_MAX;
drawPixel(p, RGB_JOIN(r, g, b));
}
}
void BitmapBuffer::drawHorizontalLine(coord_t x, coord_t y, coord_t w,
uint8_t pat, LcdFlags flags,
uint8_t opacity)
{
if (opacity == OPACITY_MAX) return;
coord_t h = 1;
if (!applyClippingRect(x, y, w, h)) return;
drawHorizontalLineAbs(x, y, w, pat, flags, opacity);
}
void BitmapBuffer::drawHorizontalLineAbs(coord_t x, coord_t y, coord_t w,
uint8_t pat, LcdFlags flags,
uint8_t opacity)
{
if (opacity == OPACITY_MAX) return;
if (draw_ctx) {
x += draw_ctx->buf_area->x1;
y += draw_ctx->buf_area->y1;
}
lv_draw_line_dsc_t line_dsc;
lv_draw_line_dsc_init(&line_dsc);
line_dsc.width = 1;
line_dsc.opa = ((OPACITY_MAX - opacity) * LV_OPA_COVER) / OPACITY_MAX;
line_dsc.color = makeLvColor(flags);
if (pat == DOTTED) {
line_dsc.dash_gap = 1;
line_dsc.dash_width = 1;
}
lv_point_t p[2];
p[0].x = x;
p[0].y = y;
p[1].x = x + w;
p[1].y = y;
if (draw_ctx) {
lv_draw_line(draw_ctx, &line_dsc, &p[0], &p[1]);
}
#if !defined(BOOT)
else if (canvas) {
lv_canvas_draw_line(canvas, p, 2, &line_dsc);
}
#endif
}
void BitmapBuffer::drawVerticalLine(coord_t x, coord_t y, coord_t h,
uint8_t pat, LcdFlags flags,
uint8_t opacity)
{
if (opacity == OPACITY_MAX) return;
coord_t w = 1;
if (!applyClippingRect(x, y, w, h)) return;
if (draw_ctx) {
x += draw_ctx->buf_area->x1;
y += draw_ctx->buf_area->y1;
}
lv_draw_line_dsc_t line_dsc;
lv_draw_line_dsc_init(&line_dsc);
line_dsc.width = 1;
line_dsc.opa = ((OPACITY_MAX - opacity) * LV_OPA_COVER) / OPACITY_MAX;
line_dsc.color = makeLvColor(flags);
if (pat == DOTTED) {
line_dsc.dash_gap = 1;
line_dsc.dash_width = 1;
}
lv_point_t p[2];
p[0].x = x;
p[0].y = y;
p[1].x = x;
p[1].y = y + h;
if (draw_ctx) {
lv_draw_line(draw_ctx, &line_dsc, &p[0], &p[1]);
}
#if !defined(BOOT)
else if (canvas) {
lv_canvas_draw_line(canvas, p, 2, &line_dsc);
}
#endif
}
void BitmapBuffer::drawRect(coord_t x, coord_t y, coord_t w, coord_t h,
uint8_t thickness, uint8_t pat, LcdFlags flags,
uint8_t opacity)
{
drawFilledRect(x, y, thickness, h, pat, flags, opacity);
drawFilledRect(x + w - thickness, y, thickness, h, pat, flags, opacity);
drawFilledRect(x, y, w, thickness, pat, flags, opacity);
drawFilledRect(x, y + h - thickness, w, thickness, pat, flags, opacity);
}
void BitmapBuffer::drawSolidRect(coord_t x, coord_t y, coord_t w, coord_t h,
uint8_t thickness, LcdFlags flags)
{
drawRect(x, y, w, h, thickness, SOLID, flags, 0);
}
void BitmapBuffer::drawSolidFilledRect(coord_t x, coord_t y, coord_t w,
coord_t h, LcdFlags flags)
{
drawFilledRect(x, y, w, h, SOLID, flags, 0);
}
void BitmapBuffer::drawFilledRect(coord_t x, coord_t y, coord_t w, coord_t h,
uint8_t pat, LcdFlags flags, uint8_t opacity)
{
if (opacity == OPACITY_MAX) return;
if (!applyClippingRect(x, y, w, h)) return;
if (SOLID != pat) {
// If we have a pattern, draw line by line
for (coord_t i = y; i < y + h; i++) {
drawHorizontalLineAbs(x, i, w, pat, flags, opacity);
}
return;
}
lv_draw_rect_dsc_t rect_dsc;
lv_draw_rect_dsc_init(&rect_dsc);
rect_dsc.bg_opa = ((OPACITY_MAX - opacity) * LV_OPA_COVER) / OPACITY_MAX;
rect_dsc.bg_color = makeLvColor(flags);
if (draw_ctx) {
x += draw_ctx->buf_area->x1;
y += draw_ctx->buf_area->y1;
}
lv_area_t coords = {
(lv_coord_t)x,
(lv_coord_t)y,
(lv_coord_t)(x + w - 1),
(lv_coord_t)(y + h - 1),
};
if (draw_ctx) {
lv_draw_rect(draw_ctx, &rect_dsc, &coords);
}
#if !defined(BOOT)
else if (canvas) {
lv_canvas_draw_rect(canvas, coords.x1, coords.y1, coords.x2 - coords.x1 + 1,
coords.y2 - coords.y1 + 1, &rect_dsc);
}
#endif
}
coord_t BitmapBuffer::drawSizedText(coord_t x, coord_t y, const char *s,
uint8_t len, LcdFlags flags)
{
if (!s) return x;
// LVGL does not handle non-null terminated strings
static char buffer[256];
strncpy(buffer, s, len);
buffer[len] = '\0';
coord_t pos = x;
const coord_t orig_pos = pos;
lv_draw_label_dsc_t label_draw_dsc;
lv_draw_label_dsc_init(&label_draw_dsc);
const lv_font_t *font = getFont(flags);
label_draw_dsc.font = font;
auto color = COLOR_VAL(flags);
label_draw_dsc.color =
lv_color_make(GET_RED(color), GET_GREEN(color), GET_BLUE(color));
if (draw_ctx) {
x += draw_ctx->buf_area->x1;
y += draw_ctx->buf_area->y1;
}
lv_point_t p;
lv_txt_get_size(&p, buffer, font, label_draw_dsc.letter_space,
label_draw_dsc.line_space, LV_COORD_MAX, 0);
lv_coord_t lv_x = (lv_coord_t)x;
lv_coord_t lv_y = (lv_coord_t)y;
lv_area_t coords = {
lv_x,
lv_y,
lv_x,
lv_y,
};
coords.x2 += p.x - 1;
coords.y2 += p.y - 1;
if (flags & RIGHT) {
label_draw_dsc.align = LV_TEXT_ALIGN_RIGHT;
coords.x1 -= p.x;
coords.x2 -= p.x;
} else if (flags & CENTERED) {
label_draw_dsc.align = LV_TEXT_ALIGN_CENTER;
coords.x1 -= p.x / 2;
coords.x2 -= p.x / 2;
}
if (draw_ctx) {
lv_draw_label(draw_ctx, &label_draw_dsc, &coords, buffer, nullptr);
}
#if !defined(BOOT)
else if (canvas) {
lv_canvas_draw_text(canvas, coords.x1, coords.y1, coords.x2 - coords.x1 + 1,
&label_draw_dsc, buffer);
}
#endif
pos += p.x;
return ((flags & RIGHT) ? orig_pos : pos);
}
// Resize and convert to LVGL image data format with alpha blending to
// background color
void BitmapBuffer::resizeToLVGL(coord_t w, coord_t h)
{
// Scale values from ARGB4444 to RGB565 with alpha
static uint8_t rbcnv[16] = {0, 2, 4, 6, 8, 10, 12, 14,
17, 19, 21, 23, 25, 27, 29, 31};
static uint8_t gcnv[16] = {0, 4, 8, 13, 17, 21, 25, 29,
34, 38, 42, 46, 50, 55, 59, 63};
static uint8_t alpha[16] = {0, 17, 34, 51, 68, 85, 102, 119,
136, 153, 170, 187, 204, 221, 238, 255};
float vscale = float(h) / (float)height();
float hscale = float(w) / (float)width();
float scale;
coord_t scaledw, scaledh;
if (vscale < hscale) {
scale = vscale;
scaledw = width() * scale;
scaledh = h;
} else {
scale = hscale;
scaledw = w;
scaledh = height() * scale;
}
uint8_t *ndata =
(uint8_t *)malloc(align32(scaledw * scaledh * 3));
if (ndata) {
uint8_t *dst = ndata;
for (int i = 0; i < scaledh; i += 1) {
pixel_t *src = &data[(coord_t)(i / scale) * width()];
for (int j = 0; j < scaledw; j += 1) {
ARGB_SPLIT(src[(coord_t)(j / scale)], a, r, g, b);
auto c = RGB_JOIN(rbcnv[r], gcnv[g], rbcnv[b]);
*dst++ = c & 0xFF;
*dst++ = c >> 8;
*dst++ = alpha[a];
}
}
delete data;
data = (pixel_t*)ndata;
_width = scaledw;
_height = scaledh;
data_end = data + ((scaledw * scaledh * 3 + 1) / 2);
}
}
-241
View File
@@ -1,241 +0,0 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* libopenui - https://github.com/opentx/libopenui
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#pragma once
#include "libopenui_defines.h"
struct MaskBitmap;
class TelemetryItem;
constexpr uint8_t SOLID = 0xFF;
constexpr uint8_t DOTTED = 0x55;
constexpr uint8_t STASHED = 0x33;
#define MOVE_PIXEL_RIGHT(p, count) p += count
#define MOVE_TO_NEXT_RIGHT_PIXEL(p) MOVE_PIXEL_RIGHT(p, 1)
#define USE_STB
enum BitmapFormats { BMP_INVALID = -1, BMP_RGB565 = 0, BMP_ARGB4444 };
class BitmapBuffer
{
public:
BitmapBuffer(uint8_t format, uint16_t width, uint16_t height);
BitmapBuffer(uint8_t format, uint16_t width, uint16_t height, uint16_t* data);
~BitmapBuffer();
void setDrawCtx(lv_draw_ctx_t* ctx) { draw_ctx = ctx; }
void setData(uint16_t* d);
void clear(LcdFlags flags = 0);
inline const pixel_t* getPixelPtrAbs(coord_t x, coord_t y) const
{
return &data[y * _width + x];
}
inline pixel_t* getPixelPtrAbs(coord_t x, coord_t y)
{
return &data[y * _width + x];
}
inline void drawPixel(coord_t x, coord_t y, pixel_t value)
{
coord_t w = 1, h = 1;
if (!applyClippingRect(x, y, w, h)) return;
drawPixelAbs(x, y, value);
}
void drawAlphaPixel(pixel_t* p, uint8_t opacity, uint16_t color);
inline void drawAlphaPixel(coord_t x, coord_t y, uint8_t opacity,
pixel_t value)
{
coord_t w = 1, h = 1;
if (!applyClippingRect(x, y, w, h)) return;
pixel_t* p = getPixelPtrAbs(x, y);
drawAlphaPixel(p, opacity, value);
}
void drawHorizontalLine(coord_t x, coord_t y, coord_t w, uint8_t pat = SOLID,
LcdFlags flags = 0, uint8_t opacity = 0);
void drawVerticalLine(coord_t x, coord_t y, coord_t h, uint8_t pat = SOLID,
LcdFlags flags = 0, uint8_t opacity = 0);
void drawLine(coord_t x1, coord_t y1, coord_t x2, coord_t y2, uint8_t pat,
LcdFlags att);
void drawRect(coord_t x, coord_t y, coord_t w, coord_t h,
uint8_t thickness = 1, uint8_t pat = SOLID, LcdFlags flags = 0,
uint8_t opacity = 0);
void drawSolidRect(coord_t x, coord_t y, coord_t w, coord_t h,
uint8_t thickness = 1, LcdFlags flags = 0);
void drawSolidFilledRect(coord_t x, coord_t y, coord_t w, coord_t h,
LcdFlags flags = 0);
void drawFilledRect(coord_t x, coord_t y, coord_t w, coord_t h,
uint8_t pat = SOLID, LcdFlags flags = 0,
uint8_t opacity = 0);
static BitmapBuffer* loadBitmap(const char* filename,
BitmapFormats fmt = BMP_INVALID);
void resizeToLVGL(coord_t w, coord_t h);
coord_t drawSizedText(coord_t x, coord_t y, const char* s, uint8_t len,
LcdFlags flags = 0);
coord_t drawText(coord_t x, coord_t y, const char* s, LcdFlags flags = 0)
{
if (!s) return x;
return drawSizedText(x, y, s, strlen(s), flags);
}
void drawBitmap(coord_t x, coord_t y, const BitmapBuffer* bmp,
coord_t srcx = 0, coord_t srcy = 0, coord_t srcw = 0,
coord_t srch = 0, float scale = 0);
void clearClippingRect();
inline void reset()
{
clearClippingRect();
}
inline uint16_t width() const { return _width; }
inline uint16_t height() const { return _height; }
inline pixel_t* getData() const { return data; }
uint32_t getDataSize() const { return _width * _height * sizeof(pixel_t); }
// Lua API functions
void setClippingRect(coord_t xmin, coord_t xmax, coord_t ymin, coord_t ymax);
void getClippingRect(coord_t& xmin, coord_t& xmax, coord_t& ymin,
coord_t& ymax);
MaskBitmap* to8bitMask(size_t* size) const;
void invertRect(coord_t x, coord_t y, coord_t w, coord_t h,
LcdFlags flags = 0);
void drawFilledTriangle(coord_t x1, coord_t y1, coord_t x2, coord_t y2,
coord_t x3, coord_t y3, LcdFlags flags = 0,
uint8_t opacity = 0);
void drawCircle(coord_t x, coord_t y, coord_t radius, LcdFlags flags = 0, coord_t thickness = 1);
void drawFilledCircle(coord_t x, coord_t y, coord_t radius,
LcdFlags flags = 0);
void drawAnnulusSector(coord_t x, coord_t y, coord_t internalRadius,
coord_t externalRadius, int startAngle, int endAngle,
LcdFlags flags = 0);
void drawBitmapPatternPie(coord_t x0, coord_t y0, const MaskBitmap* img,
LcdFlags flags, int startAngle, int endAngle);
void drawBitmapPattern(coord_t x, coord_t y, const MaskBitmap* bmp,
LcdFlags flags, coord_t offset = 0, coord_t width = 0);
void drawScaledBitmap(const BitmapBuffer* bitmap, coord_t x, coord_t y,
coord_t w, coord_t h);
coord_t drawNumber(coord_t x, coord_t y, int32_t val, LcdFlags flags = 0,
uint8_t len = 0, const char* prefix = nullptr,
const char* suffix = nullptr);
void drawTextLines(coord_t left, coord_t top, coord_t width, coord_t height,
const char* str, LcdFlags flags);
void drawTimer(coord_t x, coord_t y, int32_t tme, LcdFlags flags = 0);
void drawSource(coord_t x, coord_t y, mixsrc_t idx, LcdFlags flags = 0);
coord_t drawSwitch(coord_t x, coord_t y, int32_t idx, LcdFlags flags = 0);
void drawSensorCustomValue(coord_t x, coord_t y, uint8_t sensor,
int32_t value, LcdFlags flags = 0);
protected:
void drawValueWithUnit(coord_t x, coord_t y, int val, uint8_t unit,
LcdFlags flags = 0);
coord_t drawGPSCoord(coord_t x, coord_t y, int32_t value,
const char* direction, LcdFlags flags,
bool seconds = true);
void drawGPSPosition(coord_t x, coord_t y, int32_t longitude,
int32_t latitude, LcdFlags flags = 0);
void drawDate(coord_t x, coord_t y, TelemetryItem& telemetryItem,
LcdFlags att);
bool applyClippingRect(coord_t& x, coord_t& y, coord_t& w,
coord_t& h) const;
inline void drawPixel(pixel_t* p, pixel_t value)
{
if (data && (data <= p && p < data_end)) {
*p = value;
}
#if defined(DEBUG)
else if (!leakReported) {
leakReported = true;
TRACE(
"BitmapBuffer(%p).drawPixel(): buffer overrun, data: %p, written at: "
"%p",
this, data, p);
}
#endif
}
inline void drawPixelAbs(coord_t x, coord_t y, pixel_t value)
{
pixel_t* p = getPixelPtrAbs(x, y);
drawPixel(p, value);
}
void drawHorizontalLineAbs(coord_t x, coord_t y, coord_t w, uint8_t pat,
LcdFlags flags, uint8_t opacity);
bool liangBarskyClipper(coord_t& x1, coord_t& y1, coord_t& x2, coord_t& y2);
protected:
uint8_t format;
coord_t _width;
coord_t _height;
coord_t xmin = 0;
coord_t xmax;
coord_t ymin = 0;
coord_t ymax;
pixel_t* data;
pixel_t* data_end;
private:
bool dataAllocated = false;
#if defined(DEBUG)
bool leakReported;
#endif
lv_obj_t* canvas = nullptr;
lv_draw_ctx_t* draw_ctx = nullptr;
};
// Back buffer to draw
extern BitmapBuffer* lcd;
// Buffer currently on display
extern BitmapBuffer* lcdFront;
@@ -1,792 +0,0 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* libopenui - https://github.com/opentx/libopenui
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
// #include <math.h>
#include "bitmapbuffer.h"
#include "bitmaps.h"
#include "board.h"
#include "dma2d.h"
#include "opentx.h"
#include "strhelpers.h"
#include "telemetry/telemetry_sensors.h"
#include "translations.h"
// Drawing functions used by Lua API
void BitmapBuffer::invertRect(coord_t x, coord_t y, coord_t w, coord_t h,
LcdFlags flags)
{
if (!applyClippingRect(x, y, w, h)) return;
// No 'opacity' here, only 'color'
pixel_t color = COLOR_VAL(flags);
RGB_SPLIT(color, red, green, blue);
pixel_t bg_color = COLOR_VAL(COLOR_THEME_PRIMARY2);
RGB_SPLIT(bg_color, bgRed, bgGreen, bgBlue);
DMAWait();
#if __CORTEX_M >= 0x07
SCB_CleanInvalidateDCache();
#endif
for (int i = y; i < y + h; i++) {
pixel_t *p = getPixelPtrAbs(x, i);
for (int j = 0; j < w; j++) {
// TODO ASSERT_IN_DISPLAY(p);
RGB_SPLIT(*p, pRed, pGreen, pBlue);
drawPixel(p, RGB_JOIN(bgRed + red - pRed, bgGreen + green - pGreen,
bgBlue + blue - pBlue));
MOVE_TO_NEXT_RIGHT_PIXEL(p);
}
}
}
void BitmapBuffer::drawValueWithUnit(coord_t x, coord_t y, int val,
uint8_t unit, LcdFlags flags)
{
if ((flags & NO_UNIT) || unit == UNIT_RAW) {
drawNumber(x, y, val, flags & (~NO_UNIT));
} else {
drawNumber(x, y, val, flags & (~NO_UNIT), 0, nullptr, STR_VTELEMUNIT[unit]);
}
}
void BitmapBuffer::drawTextLines(coord_t left, coord_t top, coord_t width,
coord_t height, const char *str,
LcdFlags flags)
{
coord_t x = left;
coord_t y = top;
coord_t line = getFontHeightCondensed(flags & 0xFFFF);
coord_t space = getTextWidth(" ", 1, flags);
coord_t word;
const char *nxt = str;
flags &= ~(VCENTERED | CENTERED | RIGHT);
while (true) {
for (bool done = false; !done; nxt++) {
switch (nxt[0]) {
case '-':
case '/':
case ':':
case '(':
case '{':
case '[':
nxt++;
case ' ':
case '\n':
case '\0':
done = true;
}
}
nxt--;
word = getTextWidth(str, nxt - str, flags);
if (x + word > left + width && x > left) {
x = left;
y += line;
}
if (y + line > top + height) return;
drawSizedText(x, y, str, nxt - str, flags);
x += word;
switch (nxt[0]) {
case '\0':
return;
case '\n':
x = left;
y += line;
nxt++;
break;
case ' ':
x += space;
nxt++;
}
str = nxt;
}
}
void BitmapBuffer::drawSource(coord_t x, coord_t y, mixsrc_t idx,
LcdFlags flags)
{
char s[16];
getSourceString(s, idx);
drawText(x, y, s, flags);
}
coord_t BitmapBuffer::drawSwitch(coord_t x, coord_t y, int32_t idx,
LcdFlags flags)
{
return drawText(x, y, getSwitchPositionName(idx), flags);
}
void BitmapBuffer::drawTimer(coord_t x, coord_t y, int32_t tme, LcdFlags flags)
{
char str[LEN_TIMER_STRING];
TimerOptions timerOptions;
timerOptions.options = (flags & TIMEHOUR) != 0 ? SHOW_TIME : SHOW_TIMER;
getTimerString(str, tme, timerOptions);
drawText(x, y, str, flags);
}
coord_t BitmapBuffer::drawGPSCoord(coord_t x, coord_t y, int32_t value,
const char *direction, LcdFlags flags,
bool seconds)
{
std::string s = getGPSCoord(value, direction, seconds);
return drawText(x, y, s.c_str(), flags);
}
void BitmapBuffer::drawGPSPosition(coord_t x, coord_t y, int32_t longitude,
int32_t latitude, LcdFlags flags)
{
if (flags & PREC1) {
drawGPSCoord(x, y, latitude, "NS", flags, true);
drawGPSCoord(x, y + EdgeTxStyles::PAGE_LINE_HEIGHT, longitude, "EW", flags, true);
} else {
if (flags & RIGHT) {
x = drawGPSCoord(x, y, longitude, "EW", flags, true);
drawGPSCoord(x - 5, y, latitude, "NS", flags, true);
} else {
x = drawGPSCoord(x, y, latitude, "NS", flags, true);
drawGPSCoord(x + 5, y, longitude, "EW", flags, true);
}
}
}
void BitmapBuffer::drawDate(coord_t x, coord_t y, TelemetryItem &telemetryItem,
LcdFlags att)
{
bool doTwoLines = false;
coord_t ox = x;
if (att & FONT(XL)) {
att &= ~FONT_MASK;
doTwoLines = true;
}
std::string s = getTelemDate(telemetryItem);
x = drawText(x, y, s.c_str(), att);
if (doTwoLines) {
y += EdgeTxStyles::PAGE_LINE_HEIGHT;
x = ox;
} else {
x += 11;
}
s = getTelemTime(telemetryItem);
drawText(x, y, s.c_str(), att);
}
void BitmapBuffer::drawSensorCustomValue(coord_t x, coord_t y, uint8_t sensor,
int32_t value, LcdFlags flags)
{
if (sensor >= MAX_TELEMETRY_SENSORS) {
// Lua luaLcdDrawChannel() can call us with a bad value
return;
}
TelemetryItem &telemetryItem = telemetryItems[sensor];
TelemetrySensor &telemetrySensor = g_model.telemetrySensors[sensor];
if (telemetrySensor.unit == UNIT_DATETIME) {
drawDate(x, y, telemetryItem, flags);
} else if (telemetrySensor.unit == UNIT_GPS) {
drawGPSPosition(x, y, telemetryItem.gps.longitude,
telemetryItem.gps.latitude, flags);
} else if (telemetrySensor.unit == UNIT_TEXT) {
drawSizedText(x, flags & FONT(XL) ? y + 1 : y, telemetryItem.text,
sizeof(telemetryItem.text), flags & ~FONT(XL));
} else {
if (telemetrySensor.prec > 0) {
flags |= (telemetrySensor.prec == 1 ? PREC1 : PREC2);
}
drawValueWithUnit(
x, y, value,
telemetrySensor.unit == UNIT_CELLS ? UNIT_VOLTS : telemetrySensor.unit,
flags);
}
}
void BitmapBuffer::drawScaledBitmap(const BitmapBuffer *bitmap, coord_t x, coord_t y,
coord_t w, coord_t h)
{
if (bitmap) {
float vscale = float(h) / bitmap->height();
float hscale = float(w) / bitmap->width();
float scale = vscale < hscale ? vscale : hscale;
int xshift = (w - (bitmap->width() * scale)) / 2;
int yshift = (h - (bitmap->height() * scale)) / 2;
// TRACE(" BitmapBuffer::drawScaledBitmap()---- scale %f", scale);
drawBitmap(x + xshift, y + yshift, bitmap, 0, 0, 0, 0, scale);
}
}
//
// Liang-barsky clipping algo
//
static float lb_max(float arr[], int n)
{
float m = 0;
for (int i = 0; i < n; ++i)
if (m < arr[i]) m = arr[i];
return m;
}
// this function gives the minimum
static float lb_min(float arr[], int n)
{
float m = 1;
for (int i = 0; i < n; ++i)
if (m > arr[i]) m = arr[i];
return m;
}
bool BitmapBuffer::liangBarskyClipper(coord_t &x1, coord_t &y1, coord_t &x2,
coord_t &y2)
{
// defining variables
float p1 = -(x2 - x1);
float p2 = -p1;
float p3 = -(y2 - y1);
float p4 = -p3;
float q1 = x1 - xmin;
float q2 = xmax - x1;
float q3 = y1 - ymin;
float q4 = ymax - y1;
float posarr[5], negarr[5];
int posind = 1, negind = 1;
posarr[0] = 1;
negarr[0] = 0;
if ((p1 == 0 && q1 < 0) || (p2 == 0 && q2 < 0) || (p3 == 0 && q3 < 0) ||
(p4 == 0 && q4 < 0)) {
return false;
}
if (p1 != 0) {
float r1 = q1 / p1;
float r2 = q2 / p2;
if (p1 < 0) {
negarr[negind++] = r1; // for negative p1, add it to negative array
posarr[posind++] = r2; // and add p2 to positive array
} else {
negarr[negind++] = r2;
posarr[posind++] = r1;
}
}
if (p3 != 0) {
float r3 = q3 / p3;
float r4 = q4 / p4;
if (p3 < 0) {
negarr[negind++] = r3;
posarr[posind++] = r4;
} else {
negarr[negind++] = r4;
posarr[posind++] = r3;
}
}
float xn1, yn1, xn2, yn2;
float rn1, rn2;
rn1 = lb_max(negarr, negind); // maximum of negative array
rn2 = lb_min(posarr, posind); // minimum of positive array
if (rn1 > rn2) { // reject
return false;
}
xn1 = x1 + p2 * rn1;
yn1 = y1 + p4 * rn1; // computing new points
xn2 = x1 + p2 * rn2;
yn2 = y1 + p4 * rn2;
x1 = coord_t(xn1);
y1 = coord_t(yn1);
x2 = coord_t(xn2);
y2 = coord_t(yn2);
return true;
}
void BitmapBuffer::drawLine(coord_t x1, coord_t y1, coord_t x2, coord_t y2,
uint8_t pat, LcdFlags flags)
{
if (!liangBarskyClipper(x1, y1, x2, y2)) return;
// TODO; Replace with LVGL line draw - currently does not support dotted line drawing
// except for vertical and horizontal.
// No 'opacity' here, only 'color'
pixel_t color = COLOR_VAL(flags);
int dx = x2 - x1; /* the horizontal distance of the line */
int dy = y2 - y1; /* the vertical distance of the line */
int dxabs = abs(dx);
int dyabs = abs(dy);
int sdx = sgn(dx);
int sdy = sgn(dy);
int x = dyabs >> 1;
int y = dxabs >> 1;
int px = x1;
int py = y1;
DMAWait();
#if __CORTEX_M >= 0x07
SCB_CleanInvalidateDCache();
#endif
if (dxabs >= dyabs) {
/* the line is more horizontal than vertical */
for (int i = 0; i <= dxabs; i++) {
if ((1 << (px % 8)) & pat) {
drawPixelAbs(px, py, color);
}
y += dyabs;
if (y >= dxabs) {
y -= dxabs;
py += sdy;
}
px += sdx;
}
} else {
/* the line is more vertical than horizontal */
for (int i = 0; i <= dyabs; i++) {
if ((1 << (py % 8)) & pat) {
drawPixelAbs(px, py, color);
}
x += dxabs;
if (x >= dyabs) {
x -= dyabs;
px += sdx;
}
py += sdy;
}
}
}
/*
Notice: BitmapBuffer::drawFilledTriangle use the implementation
made for the Adafruit GFX library, which is licensed under BSD
license.
Software License Agreement (BSD License)
Copyright (c) 2012 Adafruit Industries. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
- Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
void BitmapBuffer::drawFilledTriangle(coord_t x0, coord_t y0, coord_t x1,
coord_t y1, coord_t x2, coord_t y2,
LcdFlags flags, uint8_t opacity)
{
coord_t a, b;
#define SWAP(a, b) \
{ \
coord_t tmp = b; \
b = a; \
a = tmp; \
}
if (y0 > y1) {
SWAP(y0, y1);
SWAP(x0, x1);
}
if (y1 > y2) {
SWAP(y2, y1);
SWAP(x2, x1);
}
if (y0 > y1) {
SWAP(y0, y1);
SWAP(x0, x1);
}
if (y0 == y2) { // Handle awkward all-on-same-line case as its own thing
a = b = x0;
if (x1 < a)
a = x1;
else if (x1 > b)
b = x1;
if (x2 < a)
a = x2;
else if (x2 > b)
b = x2;
drawHorizontalLine(a, y0, b - a + 1, SOLID, flags, opacity);
return;
}
coord_t dx01 = x1 - x0, dy01 = y1 - y0, dx02 = x2 - x0, dy02 = y2 - y0,
dx12 = x2 - x1, dy12 = y2 - y1;
coord_t sa = 0, sb = 0;
coord_t last = (y1 == y2) ? y1 : y1 - 1;
coord_t y;
for (y = y0; y <= last; y++) {
a = x0 + sa / dy01;
b = x0 + sb / dy02;
sa += dx01;
sb += dx02;
if (a > b) SWAP(a, b);
drawHorizontalLine(a, y, b - a + 1, SOLID, flags, opacity);
}
sa = dx12 * (y - y1);
sb = dx02 * (y - y0);
for (; y <= y2; y++) {
a = x1 + sa / dy12;
b = x0 + sb / dy02;
sa += dx12;
sb += dx02;
if (a > b) SWAP(a, b);
drawHorizontalLine(a, y, b - a + 1, SOLID, flags, opacity);
}
}
void BitmapBuffer::drawFilledCircle(coord_t x, coord_t y, coord_t radius,
LcdFlags flags)
{
lv_draw_rect_dsc_t rect_dsc;
lv_draw_rect_dsc_init(&rect_dsc);
rect_dsc.bg_opa = LV_OPA_COVER;
rect_dsc.bg_color = makeLvColor(flags);
rect_dsc.radius = LV_RADIUS_CIRCLE;
if (draw_ctx) {
x += draw_ctx->buf_area->x1;
y += draw_ctx->buf_area->y1;
}
lv_area_t coords = {
(lv_coord_t)(x - radius),
(lv_coord_t)(y - radius),
(lv_coord_t)(x + radius),
(lv_coord_t)(y + radius),
};
if (draw_ctx) {
lv_draw_rect(draw_ctx, &rect_dsc, &coords);
}
#if !defined(BOOT)
else if (canvas) {
lv_canvas_draw_rect(canvas, coords.x1, coords.y1, coords.x2 - coords.x1 + 1,
coords.y2 - coords.y1 + 1, &rect_dsc);
}
#endif
}
void BitmapBuffer::drawCircle(coord_t x, coord_t y, coord_t radius,
LcdFlags flags, coord_t thickness)
{
lv_draw_rect_dsc_t rect_dsc;
lv_draw_rect_dsc_init(&rect_dsc);
rect_dsc.bg_opa = LV_OPA_TRANSP;
rect_dsc.border_opa = LV_OPA_COVER;
rect_dsc.border_color = makeLvColor(flags);
rect_dsc.border_width = thickness;
rect_dsc.radius = LV_RADIUS_CIRCLE;
if (draw_ctx) {
x += draw_ctx->buf_area->x1;
y += draw_ctx->buf_area->y1;
}
lv_area_t coords = {
(lv_coord_t)(x - radius),
(lv_coord_t)(y - radius),
(lv_coord_t)(x + radius),
(lv_coord_t)(y + radius),
};
if (draw_ctx) {
lv_draw_rect(draw_ctx, &rect_dsc, &coords);
}
#if !defined(BOOT)
else if (canvas) {
lv_canvas_draw_rect(canvas, coords.x1, coords.y1, coords.x2 - coords.x1 + 1,
coords.y2 - coords.y1 + 1, &rect_dsc);
}
#endif
}
class Slope
{
public:
explicit Slope(int angle)
{
if (angle < 0) angle += 360;
if (angle > 360) angle %= 360;
float radians = float(angle) * (M_PI / 180.0f);
if (angle == 0) {
left = false;
value = 100000;
} else if (angle == 360) {
left = true;
value = 100000;
} else if (angle >= 180) {
left = true;
value = -(cosf(radians) * 100 / sinf(radians));
} else {
left = false;
value = (cosf(radians) * 100 / sinf(radians));
}
}
Slope(bool left, int value) : left(left), value(value) {}
bool isBetween(const Slope &start, const Slope &end) const
{
if (left) {
if (start.left) {
if (end.left)
return end.value > start.value
? (value <= end.value && value >= start.value)
: (value <= end.value || value >= start.value);
else
return value >= start.value;
} else {
if (end.left)
return value <= end.value;
else
return end.value > start.value;
}
} else {
if (start.left) {
if (end.left)
return start.value > end.value;
else
return value >= end.value;
} else {
if (end.left)
return value <= start.value;
else
return end.value < start.value
? (value >= end.value && value <= start.value)
: (value <= start.value || value >= end.value);
}
}
}
Slope &invertVertical()
{
value = -value;
return *this;
}
Slope &invertHorizontal()
{
left = !left;
return *this;
}
protected:
bool left;
int value;
};
void BitmapBuffer::drawBitmapPatternPie(coord_t x, coord_t y,
const MaskBitmap *img, LcdFlags flags,
int startAngle, int endAngle)
{
// TODO: can this converted to LVGL primitives?
if (endAngle == startAngle) {
endAngle += 1;
}
Slope startSlope(startAngle);
Slope endSlope(endAngle);
pixel_t color = COLOR_VAL(flags);
coord_t width = img->width;
coord_t height = img->height;
const uint8_t *q = img->data;
int w2 = width / 2;
int h2 = height / 2;
DMAWait();
#if __CORTEX_M >= 0x07
SCB_CleanInvalidateDCache();
#endif
for (int y1 = h2 - 1; y1 >= 0; y1--) {
for (int x1 = w2 - 1; x1 >= 0; x1--) {
Slope slope(false, x1 == 0 ? 99000 : y1 * 100 / x1);
if (slope.isBetween(startSlope, endSlope)) {
drawAlphaPixel(x + w2 + x1, y + h2 - y1,
q[(h2 - y1) * width + w2 + x1] >> 4, color);
}
if (slope.invertVertical().isBetween(startSlope, endSlope)) {
drawAlphaPixel(x + w2 + x1, y + h2 + y1,
q[(h2 + y1) * width + w2 + x1] >> 4, color);
}
if (slope.invertHorizontal().isBetween(startSlope, endSlope)) {
drawAlphaPixel(x + w2 - x1, y + h2 + y1,
q[(h2 + y1) * width + w2 - x1] >> 4, color);
}
if (slope.invertVertical().isBetween(startSlope, endSlope)) {
drawAlphaPixel(x + w2 - x1, y + h2 - y1,
q[(h2 - y1) * width + w2 - x1] >> 4, color);
}
}
}
}
void BitmapBuffer::drawAnnulusSector(coord_t x, coord_t y,
coord_t internalRadius,
coord_t externalRadius, int startAngle,
int endAngle, LcdFlags flags)
{
if (endAngle == startAngle)
endAngle += 1;
startAngle -= 90;
if (startAngle < 0) startAngle += 360;
endAngle -= 90;
if (endAngle < 0) endAngle += 360;
lv_draw_arc_dsc_t arc_dsc;
lv_draw_arc_dsc_init(&arc_dsc);
arc_dsc.width = externalRadius - internalRadius;
arc_dsc.opa = LV_OPA_COVER;
arc_dsc.color = makeLvColor(flags);
if (draw_ctx) {
lv_point_t p;
p.x = x + draw_ctx->buf_area->x1;
p.y = y + draw_ctx->buf_area->y1;
lv_draw_arc(draw_ctx, &arc_dsc, &p, externalRadius, startAngle, endAngle);
}
#if !defined(BOOT)
else if (canvas) {
lv_canvas_draw_arc(canvas, x, y, externalRadius, startAngle, endAngle, &arc_dsc);
}
#endif
}
MaskBitmap *BitmapBuffer::to8bitMask(size_t *size) const
{
static uint8_t grayScale[16] = {255, 238, 221, 204, 187, 170, 153, 136, 119, 102, 85, 68, 51, 34, 17, 0};
*size = width() * height() + 4;
MaskBitmap *mask = (MaskBitmap*)malloc(*size);
mask->width = width();
mask->height = height();
const pixel_t *p = getPixelPtrAbs(0, 0);
int ofs = 0;
if (format == BMP_ARGB4444) {
for (int i = width() * height(); i > 0; i--) {
ARGB_SPLIT(*p, a __attribute__((unused)), r, g, b);
int v = (r + g + b) / 3;
mask->data[ofs++] = grayScale[v];
MOVE_TO_NEXT_RIGHT_PIXEL(p);
}
} else { // BMP_RGB565
for (int i = width() * height(); i > 0; i--) {
RGB_SPLIT(*p, r, g, b);
int v = ((r + (g >> 1) + b) / 3) >> 1;
mask->data[ofs++] = grayScale[v];
MOVE_TO_NEXT_RIGHT_PIXEL(p);
}
}
return mask;
}
// Apply a mask ('bmp') + color ('flags') on top of current pixels:
//
// drawAlphaPixel(bmp[x][y], pixel(x,y), color)
//
//
//
void BitmapBuffer::drawBitmapPattern(coord_t x, coord_t y, const MaskBitmap *bmp,
LcdFlags flags, coord_t offset,
coord_t width)
{
coord_t bmpw = bmp->width;
coord_t bmph = bmp->height;
coord_t srcx = offset;
coord_t srcy = 0;
coord_t srcw = (width != 0 ? width : bmpw);
coord_t srch = bmph;
if (srcx + srcw > bmpw) srcw = bmpw - srcx;
if (srcy + srch > bmph) srch = bmph - srcy;
if (x < xmin) {
srcw += x - xmin;
srcx -= x - xmin;
x = xmin;
}
if (y < ymin) {
srch += y - ymin;
srcy -= y - ymin;
y = ymin;
}
if (x + srcw > xmax) {
srcw = xmax - x;
}
if (y + srch > ymax) {
srch = ymax - y;
}
if (srcw <= 0 || srch <= 0) {
return;
}
DMACopyAlphaMask(data, _width, _height, x, y, bmp->data, bmpw, bmph, srcx,
srcy, srcw, srch, COLOR_VAL(flags));
}
coord_t BitmapBuffer::drawNumber(coord_t x, coord_t y, int32_t val,
LcdFlags flags, uint8_t len,
const char *prefix, const char *suffix)
{
char s[49];
formatNumberAsString(s, 49, val, flags, len, prefix, suffix);
return drawText(x, y, s, flags);
}
@@ -1,278 +0,0 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* libopenui - https://github.com/opentx/libopenui
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#pragma GCC optimize("O3")
#include "bitmapbuffer.h"
#include "libopenui_file.h"
#include "opentx_helpers.h"
FIL imgFile __DMA;
// #define TRACE_STB_MALLOC
#if defined(TRACE_STB_MALLOC)
#define STBI_MALLOC(sz) stb_malloc(sz)
#define STBI_REALLOC_SIZED(p, oldsz, newsz) stb_realloc(p, oldsz, newsz)
#define STBI_FREE(p) stb_free(p)
void *stb_malloc(unsigned int size)
{
void *res = malloc(size);
TRACE("malloc %d = %p", size, res);
return res;
}
void stb_free(void *ptr)
{
TRACE("free %p", ptr);
free(ptr);
}
void *stb_realloc(void *ptr, unsigned int oldsz, unsigned int newsz)
{
void *res = realloc(ptr, newsz);
TRACE("realloc %p, %d -> %d = %p", ptr, oldsz, newsz, res);
return res;
}
#endif // #if defined(TRACE_STB_MALLOC)
#pragma GCC diagnostic ignored "-Wsign-compare"
#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#undef __I
#define STBI_ONLY_PNG
#define STBI_ONLY_BMP
#define STBI_ONLY_JPEG
#define STBI_NO_STDIO
#define STBI_NO_LINEAR
#define STB_IMAGE_IMPLEMENTATION
#include "stb/stb_image.h"
// fill 'data' with 'size' bytes. return number of bytes actually read
int stbc_read(void *user, char *data, int size)
{
FIL *fp = (FIL *)user;
UINT br = 0;
FRESULT res = f_read(fp, data, size, &br);
if (res == FR_OK) {
return (int)br;
}
return 0;
}
// skip the next 'n' bytes, or 'unget' the last -n bytes if negative
void stbc_skip(void *user, int n)
{
FIL *fp = (FIL *)user;
f_lseek(fp, f_tell(fp) + n);
}
// returns nonzero if we are at end of file/data
int stbc_eof(void *user)
{
FIL *fp = (FIL *)user;
int res = f_eof(fp);
return res;
}
// callbacks for stb-image
const stbi_io_callbacks stbCallbacks = {stbc_read, stbc_skip, stbc_eof};
BitmapBuffer *BitmapBuffer::loadBitmap(const char *filename, BitmapFormats fmt)
{
FRESULT result = f_open(&imgFile, filename, FA_OPEN_EXISTING | FA_READ);
if (result != FR_OK) {
return nullptr;
}
int x, y, nn;
stbi_info_from_callbacks(&stbCallbacks, &imgFile, &x, &y, &nn);
f_lseek(&imgFile, 0);
int w, h, n;
unsigned char *img =
stbi_load_from_callbacks(&stbCallbacks, &imgFile, &w, &h, &n, 4);
f_close(&imgFile);
if (!img) {
TRACE("load_stb(%s) failed: %s", filename, stbi_failure_reason());
return nullptr;
}
// convert to RGB565 or ARGB4444 format
BitmapFormats dst_fmt = fmt;
if (dst_fmt == BMP_INVALID) {
dst_fmt = (n == 4 ? BMP_ARGB4444 : BMP_RGB565);
}
BitmapBuffer *bmp = new BitmapBuffer(dst_fmt, w, h);
if (bmp == nullptr) {
TRACE("convert_stn_bitmap: malloc failed");
return nullptr;
}
pixel_t *dest = bmp->getPixelPtrAbs(0, 0);
const uint8_t *p = img;
if (dst_fmt == BMP_ARGB4444) {
for (int row = 0; row < h; ++row) {
for (int col = 0; col < w; ++col) {
*dest = ARGB(p[3], p[0], p[1], p[2]);
MOVE_TO_NEXT_RIGHT_PIXEL(dest);
p += 4;
}
}
} else { // assume 3 bytes, packed in groups of 4
for (int row = 0; row < h; ++row) {
for (int col = 0; col < w; ++col) {
*dest = RGB(p[0], p[1], p[2]);
MOVE_TO_NEXT_RIGHT_PIXEL(dest);
p += 4;
}
}
}
stbi_image_free(img);
return bmp;
}
//-----------------------------------------------------------------------------
static lv_res_t decoder_info(struct _lv_img_decoder_t *decoder, const void *src,
lv_img_header_t *header)
{
LV_UNUSED(decoder); /*Unused*/
lv_img_src_t src_type = lv_img_src_get_type(src); /*Get the source type*/
/*If it's a file...*/
if (src_type == LV_IMG_SRC_FILE) {
const char *fn = ((const char *)src) + 1;
FIL imgFile;
FRESULT result = f_open(&imgFile, fn, FA_OPEN_EXISTING | FA_READ);
if (result == FR_OK) {
int x, y, nn;
stbi_info_from_callbacks(&stbCallbacks, &imgFile, &x, &y, &nn);
f_close(&imgFile);
header->always_zero = 0;
header->cf =
(nn == 4) ? LV_IMG_CF_TRUE_COLOR_ALPHA : LV_IMG_CF_TRUE_COLOR;
header->w = x;
header->h = y;
return LV_RES_OK;
}
}
/*If it's a file in a C array...*/
else if (src_type == LV_IMG_SRC_VARIABLE) {
// Not implemented...
}
return LV_RES_INV;
}
static uint8_t *convert_bitmap(uint8_t *img, int w, int h, int n)
{
uint8_t *bmp = (uint8_t *)lv_mem_alloc(((n == 4) ? 3 : 2) * w * h);
if (bmp == nullptr) {
TRACE("convert_bitmap: lv_mem_alloc failed");
return nullptr;
}
const uint8_t *p = img;
if (n == 4) {
uint8_t *dest = bmp;
for (int row = 0; row < h; ++row) {
for (int col = 0; col < w; ++col) {
uint16_t c = RGB(p[0], p[1], p[2]);
*dest++ = c & 0xFF;
*dest++ = c >> 8;
*dest++ = p[3];
p += 4;
}
}
} else {
pixel_t *dest = (pixel_t *)bmp;
for (int row = 0; row < h; ++row) {
for (int col = 0; col < w; ++col) {
*dest = RGB(p[0], p[1], p[2]);
MOVE_TO_NEXT_RIGHT_PIXEL(dest);
p += 4;
}
}
}
return bmp;
}
static lv_res_t decoder_open(lv_img_decoder_t *decoder,
lv_img_decoder_dsc_t *dsc)
{
LV_UNUSED(decoder); /*Unused*/
/*If it's a file...*/
if (dsc->src_type == LV_IMG_SRC_FILE) {
const char *fn = ((const char *)dsc->src) + 1;
FIL imgFile;
FRESULT result = f_open(&imgFile, fn, FA_OPEN_EXISTING | FA_READ);
if (result == FR_OK) {
int w, h, n;
unsigned char *img =
stbi_load_from_callbacks(&stbCallbacks, &imgFile, &w, &h, &n, 4);
f_close(&imgFile);
if (!img) {
TRACE("decoder_open(%s) failed: %s", fn, stbi_failure_reason());
return LV_RES_INV;
}
dsc->img_data = convert_bitmap(img, w, h, n);
stbi_image_free(img);
return dsc->img_data ? LV_RES_OK : LV_RES_INV;
}
}
/*If it's a file in a C array...*/
else if (dsc->src_type == LV_IMG_SRC_VARIABLE) {
// Not implemented...
}
return LV_RES_INV;
}
static void decoder_close(lv_img_decoder_t *decoder, lv_img_decoder_dsc_t *dsc)
{
LV_UNUSED(decoder); /*Unused*/
if (dsc->img_data) {
lv_mem_free((uint8_t *)dsc->img_data);
dsc->img_data = NULL;
}
}
/**
* Register the STB decoder functions in LVGL
*/
void lv_stb_init(void)
{
lv_img_decoder_t *dec = lv_img_decoder_create();
lv_img_decoder_set_info_cb(dec, decoder_info);
lv_img_decoder_set_open_cb(dec, decoder_open);
lv_img_decoder_set_close_cb(dec, decoder_close);
}
-119
View File
@@ -1,119 +0,0 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* libopenui - https://github.com/opentx/libopenui
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "button.h"
#include "static.h"
#include "themes/etx_lv_theme.h"
static void button_constructor(const lv_obj_class_t* class_p, lv_obj_t* obj)
{
etx_btn_style(obj, LV_PART_MAIN);
}
// Must not be static - inherited by menu_button_class
const lv_obj_class_t button_class = {
.base_class = &lv_btn_class,
.constructor_cb = button_constructor,
.destructor_cb = nullptr,
.user_data = nullptr,
.event_cb = nullptr,
.width_def = LV_SIZE_CONTENT,
.height_def = EdgeTxStyles::UI_ELEMENT_HEIGHT,
.editable = LV_OBJ_CLASS_EDITABLE_INHERIT,
.group_def = LV_OBJ_CLASS_GROUP_DEF_TRUE,
.instance_size = sizeof(lv_btn_t),
};
static lv_obj_t* button_create(lv_obj_t* parent)
{
return etx_create(&button_class, parent);
}
Button::Button(Window* parent, const rect_t& rect,
std::function<uint8_t(void)> pressHandler) :
ButtonBase(parent, rect, pressHandler, button_create)
{
}
ButtonBase::ButtonBase(Window* parent, const rect_t& rect,
std::function<uint8_t(void)> pressHandler,
LvglCreate objConstruct) :
FormField(parent, rect, objConstruct ? objConstruct : lv_btn_create),
pressHandler(std::move(pressHandler))
{
}
void ButtonBase::check(bool checked)
{
if (!_deleted) {
if (checked != this->checked()) {
if (checked)
lv_obj_add_state(lvobj, LV_STATE_CHECKED);
else
lv_obj_clear_state(lvobj, LV_STATE_CHECKED);
}
}
}
bool ButtonBase::checked() const
{
return lv_obj_get_state(lvobj) & LV_STATE_CHECKED;
}
void ButtonBase::onPress() { check(pressHandler && pressHandler()); }
bool ButtonBase::onLongPress()
{
if (longPressHandler) {
check(longPressHandler());
lv_obj_clear_state(lvobj, LV_STATE_PRESSED);
lv_indev_wait_release(lv_indev_get_act());
return false;
}
return true;
}
void ButtonBase::onClicked() { onPress(); }
void ButtonBase::checkEvents()
{
Window::checkEvents();
if (checkHandler) checkHandler();
}
TextButton::TextButton(Window* parent, const rect_t& rect, std::string text,
std::function<uint8_t(void)> pressHandler) :
ButtonBase(parent, rect, pressHandler, button_create),
text(std::move(text))
{
label = lv_label_create(lvobj);
lv_label_set_text(label, this->text.c_str());
lv_obj_center(label);
}
IconButton::IconButton(Window* parent, EdgeTxIcon icon, coord_t x, coord_t y,
std::function<uint8_t(void)> pressHandler) :
ButtonBase(parent, {x, y, EdgeTxStyles::UI_ELEMENT_HEIGHT, EdgeTxStyles::UI_ELEMENT_HEIGHT}, pressHandler, button_create)
{
padAll(PAD_ZERO);
iconImage = new StaticIcon(this, 0, 0, icon, COLOR_THEME_SECONDARY1_INDEX);
iconImage->center(EdgeTxStyles::UI_ELEMENT_HEIGHT - 4, EdgeTxStyles::UI_ELEMENT_HEIGHT - 4);
}
void IconButton::setIcon(EdgeTxIcon icon) { iconImage->setIcon(icon); }
-120
View File
@@ -1,120 +0,0 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* libopenui - https://github.com/opentx/libopenui
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#pragma once
#include "form.h"
#include "bitmaps.h"
class StaticIcon;
// Base button class - default LVGL style unless overridden on create
class ButtonBase : public FormField
{
public:
ButtonBase(Window* parent, const rect_t& rect,
std::function<uint8_t(void)> pressHandler = nullptr,
LvglCreate objConstruct = nullptr);
#if defined(DEBUG_WINDOWS)
std::string getName() const override { return "ButtonBase"; }
#endif
virtual void onPress();
bool onLongPress() override;
void onClicked() override;
void checkEvents() override;
void check(bool checked = true);
bool checked() const;
void setPressHandler(std::function<uint8_t(void)> handler)
{
pressHandler = std::move(handler);
}
void setLongPressHandler(std::function<uint8_t(void)> handler)
{
longPressHandler = std::move(handler);
}
void setCheckHandler(std::function<void(void)> handler)
{
checkHandler = std::move(handler);
}
protected:
std::function<uint8_t(void)> pressHandler;
std::function<uint8_t(void)> longPressHandler;
std::function<void(void)> checkHandler;
};
class Button : public ButtonBase
{
public:
Button(Window* parent, const rect_t& rect,
std::function<uint8_t(void)> pressHandler = nullptr);
#if defined(DEBUG_WINDOWS)
std::string getName() const override { return "Button"; }
#endif
};
class TextButton : public ButtonBase
{
public:
TextButton(Window* parent, const rect_t& rect, std::string text,
std::function<uint8_t(void)> pressHandler = nullptr);
#if defined(DEBUG_WINDOWS)
std::string getName() const override { return "TextButton \"" + text + "\""; }
#endif
void setText(std::string value)
{
if (value != text) {
text = std::move(value);
lv_label_set_text(label, text.c_str());
}
}
void setWrap()
{
lv_obj_set_width(label, lv_pct(100));
etx_obj_add_style(label, styles->text_align_center, LV_PART_MAIN);
lv_label_set_long_mode(label, LV_LABEL_LONG_WRAP);
}
protected:
lv_obj_t* label = nullptr;
std::string text;
};
class IconButton : public ButtonBase
{
public:
IconButton(Window* parent, EdgeTxIcon icon, coord_t x, coord_t y,
std::function<uint8_t(void)> pressHandler = nullptr);
void setIcon(EdgeTxIcon icon);
protected:
StaticIcon* iconImage = nullptr;
};
-169
View File
@@ -1,169 +0,0 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* libopenui - https://github.com/opentx/libopenui
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "button_matrix.h"
#include <string.h>
#include "themes/etx_lv_theme.h"
static void btnmatrix_constructor(const lv_obj_class_t* class_p, lv_obj_t* obj)
{
etx_obj_add_style(obj, styles->rounded, LV_PART_MAIN);
etx_obj_add_style(obj, styles->bg_opacity_20,
LV_PART_MAIN | LV_STATE_FOCUSED);
etx_bg_color(obj, COLOR_THEME_FOCUS_INDEX, LV_PART_MAIN | LV_STATE_FOCUSED);
etx_std_style(obj, LV_PART_ITEMS, PAD_LARGE);
etx_obj_add_style(obj, styles->border_color[COLOR_THEME_FOCUS_INDEX],
LV_PART_ITEMS | LV_STATE_EDITED);
}
static const lv_obj_class_t btnmatrix_class = {
.base_class = &lv_btnmatrix_class,
.constructor_cb = btnmatrix_constructor,
.destructor_cb = nullptr,
.user_data = nullptr,
.event_cb = nullptr,
.width_def = 0,
.height_def = 0,
.editable = LV_OBJ_CLASS_EDITABLE_INHERIT,
.group_def = LV_OBJ_CLASS_GROUP_DEF_INHERIT,
.instance_size = sizeof(lv_btnmatrix_t),
};
static lv_obj_t* btnmatrix_create(lv_obj_t* parent)
{
return etx_create(&btnmatrix_class, parent);
}
static const char _filler[] = "0";
static const char _newline[] = "\n";
static const char _map_end[] = "";
static void btn_matrix_event(lv_event_t* e)
{
lv_event_code_t code = lv_event_get_code(e);
if (code == LV_EVENT_VALUE_CHANGED) {
lv_obj_t* obj = lv_event_get_target(e);
auto btn_id = *((uint8_t*)lv_event_get_param(e));
auto btnm = (ButtonMatrix*)lv_event_get_user_data(e);
bool edited = lv_obj_has_state(obj, LV_STATE_EDITED);
bool is_pointer =
lv_indev_get_type(lv_indev_get_act()) == LV_INDEV_TYPE_POINTER;
if (edited || is_pointer) {
btnm->onPress(btn_id);
}
}
}
ButtonMatrix::ButtonMatrix(Window* parent, const rect_t& r) :
FormField(parent, r, btnmatrix_create)
{
lv_obj_add_flag(lvobj, LV_OBJ_FLAG_SCROLL_ON_FOCUS);
lv_obj_clear_flag(lvobj, LV_OBJ_FLAG_CLICK_FOCUSABLE);
lv_obj_add_event_cb(lvobj, btn_matrix_event, LV_EVENT_ALL, this);
}
ButtonMatrix::~ButtonMatrix() { deallocate(); }
void ButtonMatrix::deallocate()
{
if (txt_cnt == 0) return;
for (uint8_t i = 0; i < txt_cnt; i++) {
char* txt = lv_btnm_map[i];
if (txt != _filler && txt != _newline && txt != _map_end) free(txt);
}
free(lv_btnm_map);
free(txt_index);
txt_cnt = 0;
btn_cnt = 0;
}
void ButtonMatrix::initBtnMap(uint8_t cols, uint8_t btns)
{
deallocate();
uint8_t rows = ((btns - 1) / cols) + 1;
if (rows == 1) cols = btns;
txt_cnt = (cols + 1) * rows;
btn_cnt = btns;
lv_btnm_map = (char**)malloc(sizeof(char*) * txt_cnt);
txt_index = (uint8_t*)malloc(sizeof(uint8_t) * cols * rows);
uint8_t col = 0;
uint8_t btn = 0;
uint8_t txt_i = 0;
while (btn < cols * rows) {
if (col == cols) {
lv_btnm_map[txt_i++] = (char*)_newline;
col = 0;
}
txt_index[btn] = txt_i;
lv_btnm_map[txt_i++] = (char*)_filler;
btn++;
col++;
}
lv_btnm_map[txt_i] = (char*)_map_end;
update();
}
void ButtonMatrix::setText(uint8_t btn_id, const char* txt)
{
if (btn_id < btn_cnt) lv_btnm_map[txt_index[btn_id]] = strdup(txt);
}
void ButtonMatrix::update()
{
lv_btnmatrix_set_map(lvobj, (const char**)lv_btnm_map);
lv_btnmatrix_set_btn_ctrl_all(
lvobj, LV_BTNMATRIX_CTRL_CLICK_TRIG | LV_BTNMATRIX_CTRL_NO_REPEAT);
int btn = 0;
for (int i = 0; lv_btnm_map[i] != _map_end; i += 1) {
if (lv_btnm_map[i] == _filler)
lv_btnmatrix_set_btn_ctrl(lvobj, btn, LV_BTNMATRIX_CTRL_HIDDEN);
else
lv_btnmatrix_clear_btn_ctrl(lvobj, btn, LV_BTNMATRIX_CTRL_HIDDEN);
if (lv_btnm_map[i] != _newline) btn += 1;
}
}
void ButtonMatrix::onClicked()
{
lv_group_focus_obj(lvobj);
setEditMode(true);
}
void ButtonMatrix::setChecked(uint8_t btn_id)
{
if (isActive(btn_id))
lv_btnmatrix_set_btn_ctrl(lvobj, btn_id, LV_BTNMATRIX_CTRL_CHECKED);
else
lv_btnmatrix_clear_btn_ctrl(lvobj, btn_id, LV_BTNMATRIX_CTRL_CHECKED);
}
-45
View File
@@ -1,45 +0,0 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* libopenui - https://github.com/opentx/libopenui
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#pragma once
#include "form.h"
class ButtonMatrix : public FormField
{
char** lv_btnm_map = nullptr;
uint8_t* txt_index = nullptr;
uint8_t txt_cnt = 0;
uint8_t btn_cnt = 0;
public:
ButtonMatrix(Window* parent, const rect_t& rect);
~ButtonMatrix();
virtual void onPress(uint8_t btn_id) {}
virtual bool isActive(uint8_t btn_id) { return false; }
protected:
void deallocate();
void initBtnMap(uint8_t cols, uint8_t btns);
void setText(uint8_t btn_id, const char* txt);
void setChecked(uint8_t btn_id);
void update();
void onClicked() override;
};
-228
View File
@@ -1,228 +0,0 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* libopenui - https://github.com/opentx/libopenui
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "choice.h"
#include "menu.h"
#include "themes/etx_lv_theme.h"
// Choice
static void choice_constructor(const lv_obj_class_t* class_p, lv_obj_t* obj)
{
etx_std_style(obj, LV_PART_MAIN, PAD_TINY);
lv_obj_set_style_pad_hor(obj, PAD_MEDIUM, LV_PART_MAIN);
}
static const lv_obj_class_t choice_class = {
.base_class = &lv_obj_class,
.constructor_cb = choice_constructor,
.destructor_cb = nullptr,
.user_data = nullptr,
.event_cb = nullptr,
.width_def = LV_SIZE_CONTENT,
.height_def = EdgeTxStyles::UI_ELEMENT_HEIGHT,
.editable = LV_OBJ_CLASS_EDITABLE_INHERIT,
.group_def = LV_OBJ_CLASS_GROUP_DEF_TRUE,
.instance_size = sizeof(lv_obj_t),
};
static lv_obj_t* choice_create(lv_obj_t* parent)
{
return etx_create(&choice_class, parent);
}
void ChoiceBase::changedCB(lv_event_t* e)
{
auto code = lv_event_get_code(e);
if (code == LV_EVENT_VALUE_CHANGED) {
lv_obj_t* target = lv_event_get_target(e);
if (target != nullptr) {
ChoiceBase* cb = (ChoiceBase*)lv_obj_get_user_data(target);
if (cb)
cb->update();
}
}
}
ChoiceBase::ChoiceBase(Window* parent, const rect_t& rect,
int vmin, int vmax, const char* title,
std::function<int()> _getValue,
std::function<void(int)> _setValue,
ChoiceType type) :
FormField(parent, rect, choice_create),
vmin(vmin), vmax(vmax), menuTitle(title),
_getValue(std::move(_getValue)),
_setValue(std::move(_setValue))
{
// Add image
lv_obj_t* img = lv_img_create(lvobj);
lv_img_set_src(
img, type == CHOICE_TYPE_DROPOWN ? LV_SYMBOL_DOWN : LV_SYMBOL_DIRECTORY);
lv_obj_set_pos(img, 0, PAD_TINY);
// Add label
label = lv_label_create(lvobj);
lv_obj_set_pos(label, ICON_W, PAD_TINY);
lv_obj_add_event_cb(lvobj, changedCB, LV_EVENT_VALUE_CHANGED, this);
}
std::string Choice::getLabelText()
{
std::string text;
if (_getValue) {
int val = _getValue();
if (textHandler) {
text = textHandler(val);
} else {
val -= vmin;
if (val >= 0 && val < (int)values.size()) {
text = values[val];
} else {
text = std::to_string(val + vmin);
}
}
}
return text;
}
void ChoiceBase::update()
{
if (!deleted())
lv_label_set_text(label, getLabelText().c_str());
}
Choice::Choice(Window* parent, const rect_t& rect, int vmin, int vmax,
std::function<int()> getValue,
std::function<void(int)> setValue, const char* title, ChoiceType type) :
ChoiceBase(parent, rect, vmin, vmax, title, getValue, setValue, type)
{
update();
}
Choice::Choice(Window* parent, const rect_t& rect, const char* const values[],
int vmin, int vmax, std::function<int()> getValue,
std::function<void(int)> setValue, const char* title) :
ChoiceBase(parent, rect, vmin, vmax, title, getValue, setValue, CHOICE_TYPE_DROPOWN)
{
setValues(values);
update();
}
Choice::Choice(Window* parent, const rect_t& rect,
std::vector<std::string> values, int vmin, int vmax,
std::function<int()> getValue,
std::function<void(int)> setValue, const char* title) :
ChoiceBase(parent, rect, vmin, vmax, title, getValue, setValue, CHOICE_TYPE_DROPOWN),
values(std::move(values))
{
update();
}
void Choice::addValue(const char* value)
{
values.emplace_back(value);
vmax += 1;
}
void Choice::setValues(std::vector<std::string> values)
{
this->values = std::move(values);
}
void Choice::setValues(const char* const values[])
{
this->values.clear();
if (values) {
auto value = &values[0];
for (int i = vmin; i <= vmax; i++) {
this->values.emplace_back(*value++);
}
}
}
void Choice::setValue(int val)
{
if (_setValue) {
_setValue(val);
update();
}
}
void Choice::onClicked()
{
if (!deleted())
openMenu();
}
void Choice::fillMenu(Menu* menu, const FilterFct& filter)
{
if (menu->count() > 0)
menu->removeLines();
auto value = getIntValue();
int count = 0;
int selectedIx = -1;
selectedIx0 = -1;
for (int i = vmin; i <= vmax; ++i) {
if (filter && !filter(i)) continue;
if (isValueAvailable && !isValueAvailable(inverted ? -i : i)) continue;
if (textHandler) {
menu->addLineBuffered(textHandler(i), [=]() { setValue(i); });
} else if (unsigned(i - vmin) < values.size()) {
menu->addLineBuffered(values[i - vmin], [=]() { setValue(i); });
} else {
menu->addLineBuffered(std::to_string(i), [=]() { setValue(i); });
}
if (value == i) {
selectedIx = count;
}
if (i == 0) {
selectedIx0 = count;
}
++count;
}
if (fillMenuHandler) {
fillMenuHandler(menu, value, selectedIx);
}
menu->updateLines();
// Force update - in case selected row is first row
menu->select(-1);
if (selectedIx >= 0)
menu->select(selectedIx);
else if (selectedIx0 >= 0)
menu->select(selectedIx0);
else {
menu->select(0);
}
}
void Choice::openMenu()
{
setEditMode(true); // this needs to be done first before menu is created.
auto menu = new Menu(this);
if (menuTitle) menu->setTitle(menuTitle);
fillMenu(menu);
menu->setCloseHandler([=]() { setEditMode(false); });
}
-173
View File
@@ -1,173 +0,0 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* libopenui - https://github.com/opentx/libopenui
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#pragma once
#include <vector>
#include "form.h"
class Menu;
enum ChoiceType {
CHOICE_TYPE_DROPOWN,
CHOICE_TYPE_FOLDER,
};
class ChoiceBase : public FormField
{
public:
ChoiceBase(Window *parent, const rect_t &rect, int vmin, int vmax, const char* title,
std::function<int()> _getValue, std::function<void(int)> _setValue,
ChoiceType type = CHOICE_TYPE_DROPOWN);
void update();
void setTextHandler(std::function<std::string(int)> handler)
{
textHandler = std::move(handler);
update();
}
int getMin() const { return vmin; }
void setMin(int value) { vmin = value; }
int getMax() const { return vmax; }
void setMax(int value) { vmax = value; }
const char* getTitle() const { return menuTitle; }
static LAYOUT_VAL(ICON_W, 18, 18)
protected:
lv_obj_t *label;
int vmin = 0;
int vmax = 0;
const char *menuTitle = nullptr;
std::function<int()> _getValue;
std::function<void(int)> _setValue;
std::function<std::string(int)> textHandler;
virtual std::string getLabelText() = 0;
static void changedCB(lv_event_t *e);
};
class Choice : public ChoiceBase
{
public:
Choice(Window *parent, const rect_t &rect, int vmin, int vmax,
std::function<int()> _getValue,
std::function<void(int)> _setValue = nullptr,
const char *title = nullptr, ChoiceType type = CHOICE_TYPE_DROPOWN);
Choice(Window *parent, const rect_t &rect, std::vector<std::string> values,
int vmin, int vmax, std::function<int()> _getValue,
std::function<void(int)> _setValue = nullptr,
const char *title = nullptr);
Choice(Window *parent, const rect_t &rect, const char *const values[],
int vmin, int vmax, std::function<int()> _getValue,
std::function<void(int)> _setValue = nullptr,
const char *title = nullptr);
#if defined(DEBUG_WINDOWS)
std::string getName() const override { return "Choice"; }
#endif
void addValue(const char *value);
void setValues(std::vector<std::string> values);
void setValues(const char *const values[]);
void onClicked() override;
void setFillMenuHandler(std::function<void(Menu *, int, int &)> handler)
{
fillMenuHandler = std::move(handler);
}
void setSetValueHandler(std::function<void(int)> handler)
{
_setValue = std::move(handler);
}
void setGetValueHandler(std::function<int()> handler)
{
_getValue = std::move(handler);
}
void setAvailableHandler(std::function<bool(int)> handler)
{
isValueAvailable = std::move(handler);
}
unsigned getIndexFromValue(int value) const
{
if (!isValueAvailable) {
return value - vmin;
}
unsigned index = 0;
for (int i = vmin; i < value; i++) {
if (isValueAvailable(i)) {
index++;
}
}
return index;
}
int getValueFromIndex(int index) const
{
if (!isValueAvailable) {
return vmin + index;
}
int value = vmin - 1;
while (index >= 0) {
index--;
value++;
while (value < vmax && !isValueAvailable(value)) {
value++;
}
}
return value;
}
virtual void setValue(int val);
virtual int getIntValue() const { return _getValue(); }
std::string getString(int val) { return values[val]; }
typedef std::function<bool(int16_t)> FilterFct;
void fillMenu(Menu *menu, const FilterFct &filter = nullptr);
protected:
friend class MenuToolbar;
bool inverted = false;
int selectedIx0 = 0;
std::vector<std::string> values;
std::function<bool(int)> isValueAvailable;
std::function<void(Menu *, int, int &)> fillMenuHandler;
std::string getLabelText() override;
virtual void openMenu();
};
-216
View File
@@ -1,216 +0,0 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* libopenui - https://github.com/opentx/libopenui
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "dialog.h"
#include "opentx.h"
#include "mainwindow.h"
#include "progress.h"
#include "themes/etx_lv_theme.h"
//-----------------------------------------------------------------------------
class BaseDialogForm : public Window
{
public:
BaseDialogForm(Window* parent, lv_coord_t width) : Window(parent, rect_t{})
{
etx_scrollbar(lvobj);
padAll(PAD_MEDIUM);
setFlexLayout(LV_FLEX_FLOW_COLUMN, PAD_MEDIUM, width, LV_SIZE_CONTENT);
}
protected:
void onClicked() override { Keyboard::hide(false); }
};
BaseDialog::BaseDialog(Window* parent, const char* title,
bool closeIfClickedOutside, lv_coord_t width,
lv_coord_t maxHeight) :
ModalWindow(parent, closeIfClickedOutside)
{
auto content = new Window(this, rect_t{});
content->setWindowFlag(OPAQUE);
content->padAll(PAD_ZERO);
content->setFlexLayout(LV_FLEX_FLOW_COLUMN, PAD_ZERO, width, LV_SIZE_CONTENT);
etx_solid_bg(content->getLvObj());
lv_obj_center(content->getLvObj());
header = new StaticText(content, {0, 0, LV_PCT(100), 0}, title ? title : "", COLOR_THEME_PRIMARY2_INDEX);
etx_solid_bg(header->getLvObj(), COLOR_THEME_SECONDARY1_INDEX);
header->padAll(PAD_SMALL);
header->show(title != nullptr);
form = new BaseDialogForm(content, width);
if (maxHeight != LV_SIZE_CONTENT)
lv_obj_set_style_max_height(form->getLvObj(), maxHeight - 32, LV_PART_MAIN);
}
void BaseDialog::setTitle(const char* title)
{
header->setText(title);
}
//-----------------------------------------------------------------------------
ProgressDialog::ProgressDialog(Window* parent, const char* title,
std::function<void()> onClose) :
BaseDialog(parent, title, false), onClose(std::move(onClose))
{
progress = new Progress(form, rect_t{0, 0, LV_PCT(100), 32});
updateProgress(0);
}
void ProgressDialog::setTitle(std::string title)
{
header->setText(title);
header->show();
}
void ProgressDialog::updateProgress(int percentage)
{
progress->setValue(percentage);
lv_refr_now(nullptr);
}
void ProgressDialog::closeDialog()
{
deleteLater();
onClose();
}
//-----------------------------------------------------------------------------
MessageDialog::MessageDialog(Window* parent, const char* title,
const char* message, const char* info,
LcdFlags messageFlags, LcdFlags infoFlags) :
BaseDialog(parent, title, true)
{
messageWidget = new StaticText(form, {0, 0, LV_PCT(100), LV_SIZE_CONTENT},
message, COLOR_THEME_PRIMARY1_INDEX, messageFlags);
if (info) {
infoWidget = new StaticText(form, {0, 0, LV_PCT(100), LV_SIZE_CONTENT},
info, COLOR_THEME_PRIMARY1_INDEX, infoFlags);
}
}
void MessageDialog::onClicked() { deleteLater(); }
//-----------------------------------------------------------------------------
DynamicMessageDialog::DynamicMessageDialog(
Window* parent, const char* title, std::function<std::string()> textHandler,
const char* message, const int lineHeight, LcdColorIndex color, LcdFlags textFlags) :
BaseDialog(parent, title, true)
{
messageWidget = new StaticText(form, {0, 0, LV_PCT(100), LV_SIZE_CONTENT},
message, COLOR_THEME_PRIMARY1_INDEX, CENTERED);
infoWidget = new DynamicText(form, {0, 0, LV_PCT(100), LV_SIZE_CONTENT},
textHandler, color, textFlags);
}
void DynamicMessageDialog::onClicked() { deleteLater(); }
//-----------------------------------------------------------------------------
ConfirmDialog::ConfirmDialog(Window* parent, const char* title,
const char* message,
std::function<void(void)> confirmHandler,
std::function<void(void)> cancelHandler) :
BaseDialog(parent, title, false),
confirmHandler(std::move(confirmHandler)),
cancelHandler(std::move(cancelHandler))
{
if (message) {
new StaticText(form, {0, 0, LV_PCT(100), 0}, message, COLOR_THEME_PRIMARY1_INDEX, CENTERED);
}
auto box = new Window(form, rect_t{});
box->padAll(PAD_TINY);
box->setFlexLayout(LV_FLEX_FLOW_ROW, 40, LV_PCT(100), LV_SIZE_CONTENT);
lv_obj_set_flex_align(box->getLvObj(), LV_FLEX_ALIGN_CENTER,
LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_SPACE_BETWEEN);
new TextButton(box, rect_t{0, 0, 96, 0}, STR_NO, [=]() -> int8_t {
onCancel();
return 0;
});
new TextButton(box, rect_t{0, 0, 96, 0}, STR_YES, [=]() -> int8_t {
this->deleteLater();
this->confirmHandler();
return 0;
});
}
void ConfirmDialog::onCancel()
{
deleteLater();
if (cancelHandler) cancelHandler();
}
//-----------------------------------------------------------------------------
LabelDialog::LabelDialog(Window *parent, const char *label, int length, const char* title,
std::function<void(std::string)> _saveHandler) :
ModalWindow(parent, false), saveHandler(std::move(_saveHandler))
{
assert(length <= MAX_LABEL_LENGTH);
strncpy(this->label, label, length);
this->label[length] = '\0';
auto form = new Window(this, rect_t{});
form->padAll(PAD_ZERO);
form->setFlexLayout(LV_FLEX_FLOW_COLUMN, PAD_ZERO, LCD_W * 0.8,
LV_SIZE_CONTENT);
etx_solid_bg(form->getLvObj());
lv_obj_center(form->getLvObj());
auto hdr = new StaticText(form, {0, 0, LV_PCT(100), 0}, title,
COLOR_THEME_PRIMARY2_INDEX);
etx_solid_bg(hdr->getLvObj(), COLOR_THEME_SECONDARY1_INDEX);
hdr->padAll(PAD_MEDIUM);
auto box = new Window(form, rect_t{});
box->padAll(PAD_MEDIUM);
box->setFlexLayout(LV_FLEX_FLOW_ROW, 40, LV_PCT(100), LV_SIZE_CONTENT);
lv_obj_set_flex_align(box->getLvObj(), LV_FLEX_ALIGN_CENTER,
LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_SPACE_BETWEEN);
new TextEdit(box, rect_t{0, 0, LV_PCT(100), 0}, this->label, length);
box = new Window(form, rect_t{});
box->padAll(PAD_MEDIUM);
box->setFlexLayout(LV_FLEX_FLOW_ROW, 40, LV_PCT(100), LV_SIZE_CONTENT);
lv_obj_set_flex_align(box->getLvObj(), LV_FLEX_ALIGN_CENTER,
LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_SPACE_BETWEEN);
new TextButton(box, rect_t{0, 0, 96, 0}, STR_CANCEL, [=]() {
deleteLater();
return 0;
});
new TextButton(box, rect_t{0, 0, 96, 0}, STR_SAVE, [=]() {
if (saveHandler != nullptr) saveHandler(this->label);
deleteLater();
return 0;
});
}
-146
View File
@@ -1,146 +0,0 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* libopenui - https://github.com/opentx/libopenui
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#pragma once
#include "form.h"
#include "modal_window.h"
class StaticText;
class DynamicText;
class Progress;
#define DIALOG_DEFAULT_WIDTH ((coord_t)(LCD_W * 0.8))
#define DIALOG_DEFAULT_HEIGHT ((coord_t)(LCD_H * 0.8))
//-----------------------------------------------------------------------------
class BaseDialog : public ModalWindow
{
public:
BaseDialog(Window* parent, const char* title, bool closeIfClickedOutside,
lv_coord_t width = DIALOG_DEFAULT_WIDTH,
lv_coord_t maxHeight = DIALOG_DEFAULT_HEIGHT);
void setTitle(const char* title);
protected:
Window* form = nullptr;
StaticText* header = nullptr;
void onCancel() override { deleteLater(); }
void onEvent(event_t event) override {}
};
//-----------------------------------------------------------------------------
class ProgressDialog : public BaseDialog
{
public:
ProgressDialog(Window* parent, const char* title,
std::function<void()> onClose);
void updateProgress(int percentage);
void setTitle(std::string title);
void closeDialog();
protected:
uint32_t lastUpdate = 0;
Progress* progress;
std::function<void()> onClose;
// disable keys
void onEvent(event_t) override {}
};
//-----------------------------------------------------------------------------
class MessageDialog : public BaseDialog
{
public:
MessageDialog(Window* parent, const char* title, const char* message,
const char* info = nullptr, LcdFlags messageFlags = CENTERED,
LcdFlags infoFlags = CENTERED);
protected:
StaticText* messageWidget;
StaticText* infoWidget;
#if defined(DEBUG_WINDOWS)
std::string getName() const override { return "MessageDialog"; }
#endif
void onClicked() override;
};
//-----------------------------------------------------------------------------
class DynamicMessageDialog : public BaseDialog
{
public:
DynamicMessageDialog(Window* parent, const char* title,
std::function<std::string()> textHandler,
const char* message = "",
const int lineHeight = EdgeTxStyles::PAGE_LINE_HEIGHT,
LcdColorIndex color = COLOR_THEME_PRIMARY1_INDEX, LcdFlags textFlags = CENTERED);
// Attn.: FONT(XXL) is not supported by DynamicMessageDialog
protected:
StaticText* messageWidget;
DynamicText* infoWidget;
#if defined(DEBUG_WINDOWS)
std::string getName() const override { return "DynamicMessageDialog"; }
#endif
void onClicked() override;
};
//-----------------------------------------------------------------------------
class ConfirmDialog : public BaseDialog
{
public:
ConfirmDialog(Window* parent, const char* title, const char* message,
std::function<void(void)> confirmHandler,
std::function<void(void)> cancelHandler = nullptr);
protected:
std::function<void(void)> confirmHandler;
std::function<void(void)> cancelHandler;
void onCancel() override;
};
//-----------------------------------------------------------------------------
class LabelDialog : public ModalWindow
{
public:
LabelDialog(Window *parent, const char *label, int length, const char* title,
std::function<void(std::string)> _saveHandler = nullptr);
static constexpr int MAX_LABEL_LENGTH = 255;
void onCancel() override { deleteLater(); }
protected:
std::function<void(std::string)> saveHandler;
char label[MAX_LABEL_LENGTH + 1];
};
-197
View File
@@ -1,197 +0,0 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* libopenui - https://github.com/opentx/libopenui
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "filechoice.h"
#include <algorithm>
#include "opentx.h"
#include "libopenui_file.h"
#include "menu.h"
#include "menutoolbar.h"
class FileChoiceMenuToolbar : public MenuToolbar
{
public:
FileChoiceMenuToolbar(FileChoice *choice, Menu *menu) :
MenuToolbar(choice, menu, FC_COLS)
{
filterButton(choice, 'a', 'd', "aA-dD");
filterButton(choice, 'e', 'h', "eE-hH");
filterButton(choice, 'i', 'l', "iI-lL");
filterButton(choice, 'm', 'p', "mM-pP");
filterButton(choice, 'q', 't', "qQ-tT");
filterButton(choice, 'u', 'z', "uU-zZ");
filterButton(choice, '0', '9', "0-9");
bool found = false;
for (int i = 0; i <= choice->getMax(); i += 1) {
char c = choice->getString(i)[0];
if (c && !isdigit(c) && !isalpha(c)) {
found = true;
break;
}
}
if (found) {
addButton(
"._-", 0, choice->getMax(),
[=](int16_t index) {
char c = choice->getString(index)[0];
return c && !isdigit(c) && !isalpha(c);
},
STR_MENU_OTHER);
}
addButton(STR_SELECT_MENU_CLR, 0, 0, nullptr, nullptr, true);
}
void filterButton(FileChoice *choice, char from, char to, const char* title)
{
bool found = false;
for (int i = 0; i <= choice->getMax(); i += 1) {
char c = choice->getString(i)[0];
if (isupper(c)) c += 0x20;
if (c >= from && c <= to) {
found = true;
break;
}
}
if (found) {
char s[4];
s[0] = from; s[1] = '-'; s[2] = to; s[3] = 0;
addButton(
s, 0, choice->getMax(),
[=](int16_t index) {
char c = choice->getString(index)[0];
if (isupper(c)) c += 0x20;
return (c >= from && c <= to);
},
title);
}
}
static LAYOUT_VAL(FC_COLS, 3, 2)
protected:
};
FileChoice::FileChoice(Window *parent, const rect_t &rect, std::string folder,
const char *extension, int maxlen,
std::function<std::string()> getValue,
std::function<void(std::string)> setValue,
bool stripExtension, const char *title) :
Choice(
parent, rect, 0, 0, [=]() { return selectedIdx; },
[=](int val) { setValue(getString(val)); }, title, CHOICE_TYPE_FOLDER),
folder(std::move(folder)),
extension(extension),
maxlen(maxlen),
getValue(std::move(getValue)),
setValue(std::move(setValue)),
stripExtension(stripExtension)
{
update();
}
std::string FileChoice::getLabelText() { return getValue(); }
void FileChoice::loadFiles()
{
if (loaded) return;
loaded = true;
FILINFO fno;
DIR dir;
std::list<std::string> files;
const char *fnExt;
uint8_t fnLen, extLen;
FRESULT res = f_opendir(&dir, folder.c_str()); // Open the directory
if (res == FR_OK) {
bool firstTime = true;
for (;;) {
res = sdReadDir(&dir, &fno, firstTime);
if (res != FR_OK || fno.fname[0] == 0)
break; // break on error or end of dir
if (fno.fattrib & (AM_HID | AM_SYS | AM_DIR))
continue; // Ignore subfolders, hidden files and system files
if (fno.fname[0] == '.' && fno.fname[1] != '.')
continue; // Ignore hidden files under UNIX, but not ..
fnExt = getFileExtension(fno.fname, 0, 0, &fnLen, &extLen);
if (extension && (!fnExt || !isExtensionMatching(fnExt, extension)))
continue; // wrong extension
if (stripExtension) fnLen -= extLen;
if (!fnLen || fnLen > maxlen) continue; // wrong size
// eject duplicates
std::string newFile(fno.fname, fnLen);
if (std::find(files.begin(), files.end(), newFile) != files.end())
continue;
files.emplace_back(newFile);
}
f_closedir(&dir);
}
if (!files.empty()) {
// sort files
files.sort(compare_nocase);
files.push_front("");
std::string value = getValue();
int idx = 0;
for (const auto &file : files) {
addValue(file.c_str());
if (strcmp(value.c_str(), file.c_str()) == 0) selectedIdx = idx;
idx += 1;
}
setMax(files.size() - 1);
}
fileCount = files.size();
}
void FileChoice::openMenu()
{
loadFiles();
if (fileCount > 0) {
setEditMode(true); // this needs to be done first before menu is created.
auto menu = new Menu(this);
if (menuTitle) menu->setTitle(menuTitle);
auto tb = new FileChoiceMenuToolbar(this, menu);
menu->setToolbar(tb);
// fillMenu(menu); - called by MenuToolbar
menu->setCloseHandler([=]() { setEditMode(false); });
} else {
new MessageDialog(this, STR_SDCARD, STR_NO_FILES_ON_SD);
}
}
-54
View File
@@ -1,54 +0,0 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* libopenui - https://github.com/opentx/libopenui
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#pragma once
#include "choice.h"
#include <string>
class FileChoice : public Choice
{
public:
FileChoice(Window* parent, const rect_t& rect, std::string folder,
const char* extension, int maxlen,
std::function<std::string()> getValue,
std::function<void(std::string)> setValue,
bool stripExtension = false,
const char* title = nullptr);
#if defined(DEBUG_WINDOWS)
std::string getName() const override { return "FileChoice"; }
#endif
protected:
bool loaded = false;
int fileCount = 0;
int selectedIdx = -1;
Menu* menu = nullptr;
std::string getLabelText() override;
std::string folder;
const char* extension;
int maxlen;
std::function<std::string()> getValue;
std::function<void(std::string)> setValue;
bool stripExtension;
void loadFiles();
void openMenu() override;
};
-99
View File
@@ -1,99 +0,0 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* libopenui - https://github.com/opentx/libopenui
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "form.h"
#include "themes/etx_lv_theme.h"
void FlexGridLayout::apply(Window* w)
{
w->padAll(padding);
// layout
lv_obj_set_layout(w->getLvObj(), LV_LAYOUT_GRID);
if (col_dsc && row_dsc) {
lv_obj_set_grid_dsc_array(w->getLvObj(), col_dsc, row_dsc);
}
}
void FlexGridLayout::add(Window* w)
{
if (col_dsc && row_dsc) {
lv_obj_set_grid_cell(w->getLvObj(), LV_GRID_ALIGN_START, col_pos, col_span,
LV_GRID_ALIGN_CENTER, row_pos, row_span);
}
}
FormField::FormField(Window* parent, const rect_t& rect, LvglCreate objConstruct) :
Window(parent, rect, objConstruct)
{
setTextFlag(textFlags);
lv_obj_add_flag(lvobj, LV_OBJ_FLAG_SCROLL_ON_FOCUS);
}
void FormField::setEditMode(bool newEditMode)
{
editMode = newEditMode;
if (lvobj != nullptr) {
lv_group_t* grp = (lv_group_t*)lv_obj_get_group(lvobj);
if (grp != nullptr) lv_group_set_editing(grp, editMode);
}
}
void FormField::onClicked()
{
lv_indev_type_t indev_type = lv_indev_get_type(lv_indev_get_act());
if (indev_type != LV_INDEV_TYPE_POINTER) {
setEditMode(!editMode);
}
}
void FormField::onCancel()
{
if (isEditMode()) {
setEditMode(false);
} else {
Window::onCancel();
}
}
void FormField::deleteLater(bool detach, bool trash)
{
if (isEditMode()) setEditMode(false);
Window::deleteLater(detach, trash);
}
FormLine::FormLine(Window* parent, FlexGridLayout& layout) :
Window(parent, rect_t{}), layout(layout)
{
setWindowFlag(NO_FOCUS);
layout.resetPos();
layout.apply(this);
lv_obj_set_width(lvobj, lv_pct(100));
lv_obj_set_height(lvobj, LV_SIZE_CONTENT);
}
void FormLine::addChild(Window* window)
{
Window::addChild(window);
layout.add(window);
layout.nextCell();
}
-116
View File
@@ -1,116 +0,0 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* libopenui - https://github.com/opentx/libopenui
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#pragma once
#include "themes/etx_lv_theme.h"
#include "window.h"
class FlexGridLayout
{
public:
FlexGridLayout(const lv_coord_t col_dsc[], const lv_coord_t row_dsc[],
PaddingSize padding = PAD_SMALL) :
col_dsc(col_dsc), row_dsc(row_dsc), padding(padding)
{
}
FlexGridLayout(const FlexGridLayout& g) :
col_dsc(g.col_dsc), row_dsc(g.row_dsc), padding(g.padding)
{
}
void apply(Window* w);
void add(Window* w);
void resetPos()
{
col_pos = 0;
row_pos = 0;
}
void nextColumn() { col_pos += col_span; }
void nextRow()
{
row_pos += row_span;
col_pos = 0;
}
void nextCell()
{
nextColumn();
if (col_dsc[col_pos] == LV_GRID_TEMPLATE_LAST) {
nextRow();
}
}
void setColSpan(uint8_t span) { col_span = span; }
void setRowSpan(uint8_t span) { row_span = span; }
protected:
const lv_coord_t* col_dsc = nullptr;
const lv_coord_t* row_dsc = nullptr;
PaddingSize padding = PAD_ZERO;
uint8_t col_pos = 0;
uint8_t col_span = 1;
uint8_t row_pos = 0;
uint8_t row_span = 1;
};
class FormField : public Window
{
public:
FormField(Window* parent, const rect_t& rect, LvglCreate objConstruct = nullptr);
virtual void changeEnd(bool forceChanged = false)
{
if (changeHandler) {
changeHandler();
}
}
void setChangeHandler(std::function<void()> handler)
{
changeHandler = std::move(handler);
}
inline bool isEditMode() const { return editMode; }
virtual void setEditMode(bool newEditMode);
void onClicked() override;
void onCancel() override;
void deleteLater(bool detach = true, bool trash = true) override;
protected:
bool editMode = false;
bool enabled = true;
std::function<void()> changeHandler = nullptr;
};
class FormLine : public Window
{
public:
FormLine(Window* parent, FlexGridLayout& layout);
protected:
FlexGridLayout& layout;
void addChild(Window* window) override;
};
-198
View File
@@ -1,198 +0,0 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* libopenui - https://github.com/opentx/libopenui
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "keyboard_base.h"
#include "form.h"
#include "mainwindow.h"
#include "themes/etx_lv_theme.h"
static void keyboard_constructor(const lv_obj_class_t* class_p, lv_obj_t* obj)
{
etx_solid_bg(obj, COLOR_THEME_SECONDARY3_INDEX);
etx_obj_add_style(obj, styles->pad_tiny, LV_PART_MAIN);
etx_obj_add_style(obj, styles->rounded, LV_PART_MAIN);
etx_std_style(obj, LV_PART_ITEMS, PAD_SMALL);
etx_txt_color(obj, COLOR_THEME_PRIMARY1_INDEX, LV_PART_ITEMS);
etx_txt_color(obj, COLOR_THEME_PRIMARY2_INDEX, LV_PART_ITEMS | LV_STATE_FOCUSED);
etx_bg_color(obj, COLOR_THEME_FOCUS_INDEX, LV_PART_ITEMS | LV_STATE_EDITED);
}
static const lv_obj_class_t keyboard_class = {
.base_class = &lv_keyboard_class,
.constructor_cb = keyboard_constructor,
.destructor_cb = nullptr,
.user_data = nullptr,
.event_cb = nullptr,
.width_def = 0,
.height_def = 0,
.editable = LV_OBJ_CLASS_EDITABLE_INHERIT,
.group_def = LV_OBJ_CLASS_GROUP_DEF_INHERIT,
.instance_size = sizeof(lv_keyboard_t),
};
static lv_obj_t* keyboard_create(lv_obj_t* parent)
{
return etx_create(&keyboard_class, parent);
}
Keyboard* Keyboard::activeKeyboard = nullptr;
static void keyboard_event_cb(lv_event_t* e)
{
auto code = lv_event_get_code(e);
if (code == LV_EVENT_READY) {
Keyboard::hide(false);
} else if (code == LV_EVENT_CANCEL) {
Keyboard::hide(true);
} else if (code == LV_EVENT_KEY) {
int32_t c = *((int32_t*)lv_event_get_param(e));
if (c == LV_KEY_ESC) {
Keyboard::hide(false);
}
}
}
static void field_focus_leave(lv_event_t* e) { Keyboard::hide(false); }
static void _assign_lv_group(lv_group_t* g)
{
// associate it with all input devices
lv_indev_t* indev = lv_indev_get_next(NULL);
while (indev) {
lv_indev_set_group(indev, g);
indev = lv_indev_get_next(indev);
}
}
Keyboard::Keyboard(coord_t height) :
NavWindow(MainWindow::instance(), {0, LCD_H - height, LCD_W, height})
{
lv_obj_set_parent(lvobj, lv_layer_top()); // the keyboard is always on top
// use a separate group for the keyboard
group = lv_group_create();
lv_group_set_editing(group, true);
auto old_g = lv_group_get_default();
lv_group_set_default(group);
keyboard = keyboard_create(lvobj);
lv_group_set_default(old_g);
lv_obj_add_event_cb(keyboard, keyboard_event_cb, LV_EVENT_ALL, this);
lv_obj_set_pos(keyboard, 0, 0);
lv_obj_set_size(keyboard, LCD_W, height);
// TODO: really needed ???
lv_obj_clear_flag(keyboard, LV_OBJ_FLAG_HIDDEN);
}
Keyboard::~Keyboard()
{
if (group) lv_group_del(group);
}
void Keyboard::clearField(bool wasCancelled)
{
TRACE("CLEAR FIELD");
if (keyboard != nullptr) {
lv_obj_add_flag(keyboard, LV_OBJ_FLAG_HIDDEN);
}
detach();
lv_obj_set_parent(lvobj, lv_layer_top());
if (fieldContainer) {
lv_obj_scroll_to_y(fieldContainer->getLvObj(), scroll_pos, LV_ANIM_OFF);
fieldContainer = nullptr;
}
if (field) {
// restore field's group
auto obj = field->getLvObj();
if (obj) {
lv_obj_remove_event_cb(obj, field_focus_leave);
}
if (!wasCancelled)
field->setEditMode(false);
field->changeEnd();
field = nullptr;
if (fieldGroup) {
_assign_lv_group(fieldGroup);
lv_group_set_editing(fieldGroup, false);
fieldGroup = nullptr;
}
}
}
void Keyboard::hide(bool wasCancelled)
{
if (activeKeyboard) {
activeKeyboard->clearField(wasCancelled);
lv_obj_add_flag(activeKeyboard->lvobj, LV_OBJ_FLAG_HIDDEN);
activeKeyboard = nullptr;
}
}
bool Keyboard::attachKeyboard()
{
if (activeKeyboard) {
if (activeKeyboard == this) return false;
hide(false);
}
activeKeyboard = this;
return true;
}
void Keyboard::setField(FormField* newField)
{
if (!attachKeyboard()) return;
lv_obj_t* obj = newField->getLvObj();
if (obj) {
fieldContainer = newField->getFullScreenWindow();
if (fieldContainer) {
attach(fieldContainer);
lv_area_t coords;
lv_obj_get_coords(obj, &coords);
// place keyboard bellow the field with some margin
setTop(max(coords.y2 + 21, LCD_H - height()));
// save scroll position
scroll_pos = lv_obj_get_scroll_y(fieldContainer->getLvObj());
lv_obj_scroll_to_view(lvobj, LV_ANIM_OFF);
newField->setEditMode(true);
lv_keyboard_set_textarea(keyboard, obj);
lv_obj_add_event_cb(obj, field_focus_leave, LV_EVENT_DEFOCUSED, nullptr);
_assign_lv_group(group);
field = newField;
fieldGroup = (lv_group_t*)lv_obj_get_group(obj);
}
}
}
-49
View File
@@ -1,49 +0,0 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* libopenui - https://github.com/opentx/libopenui
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#pragma once
#include "window.h"
class FormField;
class Keyboard : public NavWindow
{
public:
explicit Keyboard(coord_t height);
~Keyboard();
void clearField(bool wasCancelled);
static void hide(bool wasCancelled);
static Keyboard* keyboardWindow() { return activeKeyboard; }
protected:
static Keyboard *activeKeyboard;
lv_group_t* group = nullptr;
lv_obj_t* keyboard = nullptr;
FormField* field = nullptr;
Window* fieldContainer = nullptr;
lv_group_t* fieldGroup = nullptr;
lv_coord_t scroll_pos = 0;
void setField(FormField* newField);
bool attachKeyboard();
};
-172
View File
@@ -1,172 +0,0 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* libopenui - https://github.com/opentx/libopenui
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "keyboard_number.h"
#include "numberedit.h"
constexpr coord_t KEYBOARD_HEIGHT = 90;
NumberKeyboard* NumberKeyboard::_instance = nullptr;
static const char* const number_kb_map[] = {"<<", "-", "+", ">>", "\n",
"MIN", "DEF", "+/-", "MAX", ""};
#define LV_KB_BTN(width) LV_BTNMATRIX_CTRL_POPOVER | width
#define LV_KB_CTRL(width) LV_KEYBOARD_CTRL_BTN_FLAGS | width
static const lv_btnmatrix_ctrl_t number_kb_ctrl_map[] = {
LV_KB_BTN(4), LV_KB_BTN(4), LV_KB_BTN(4), LV_KB_BTN(4),
LV_KB_BTN(4), LV_KB_BTN(4), LV_KB_CTRL(4), LV_KB_BTN(4)};
static void on_key(lv_event_t* e)
{
lv_obj_t* obj = lv_event_get_target(e);
NumberKeyboard* edit = (NumberKeyboard*)lv_event_get_user_data(e);
if (!obj || !edit) return;
uint16_t btn_id = lv_btnmatrix_get_selected_btn(obj);
if (btn_id == LV_BTNMATRIX_BTN_NONE) return;
const char* txt =
lv_btnmatrix_get_btn_text(obj, lv_btnmatrix_get_selected_btn(obj));
if (txt == NULL) return;
edit->handleEvent(txt);
}
void NumberKeyboard::handleEvent(const char* btn)
{
if (strcmp(btn, "<<") == 0)
decLarge();
else if (strcmp(btn, "-") == 0)
decSmall();
else if (strcmp(btn, "+") == 0)
incSmall();
else if (strcmp(btn, ">>") == 0)
incLarge();
else if (strcmp(btn, "MIN") == 0)
setMIN();
else if (strcmp(btn, "DEF") == 0)
setDEF();
else if (strcmp(btn, "MAX") == 0)
setMAX();
else if (strcmp(btn, "+/-") == 0)
changeSign();
}
void NumberKeyboard::decLarge()
{
((NumberEdit*)field)->onEvent(EVT_VIRTUAL_KEY_BACKWARD);
}
void NumberKeyboard::decSmall()
{
((NumberEdit*)field)->onEvent(EVT_VIRTUAL_KEY_MINUS);
}
void NumberKeyboard::incSmall()
{
((NumberEdit*)field)->onEvent(EVT_VIRTUAL_KEY_PLUS);
}
void NumberKeyboard::incLarge()
{
((NumberEdit*)field)->onEvent(EVT_VIRTUAL_KEY_FORWARD);
}
void NumberKeyboard::setMIN()
{
((NumberEdit*)field)->onEvent(EVT_VIRTUAL_KEY_MIN);
}
void NumberKeyboard::setMAX()
{
((NumberEdit*)field)->onEvent(EVT_VIRTUAL_KEY_MAX);
}
void NumberKeyboard::setDEF()
{
((NumberEdit*)field)->onEvent(EVT_VIRTUAL_KEY_DEFAULT);
}
void NumberKeyboard::changeSign()
{
((NumberEdit*)field)->onEvent(EVT_VIRTUAL_KEY_SIGN);
}
#if defined(HARDWARE_KEYS)
#if (defined(KEYS_GPIO_REG_PAGEUP) || defined(USE_HATS_AS_KEYS)) && \
!defined(PCBX12S)
// Radios with both PGUP and PGDN buttons except X12S
void NumberKeyboard::onPressSYS() { decLarge(); }
void NumberKeyboard::onLongPressSYS() { setMIN(); }
void NumberKeyboard::onPressMDL() { incLarge(); }
void NumberKeyboard::onLongPressMDL() { setMAX(); }
void NumberKeyboard::onPressTELE() { changeSign(); }
void NumberKeyboard::onLongPressTELE() { setDEF(); }
void NumberKeyboard::onPressPGUP() { decSmall(); }
void NumberKeyboard::onPressPGDN() { incSmall(); }
void NumberKeyboard::onLongPressPGUP() {}
void NumberKeyboard::onLongPressPGDN() {}
#else
// Radios witb only a single PGUP/DN button or X12S
void NumberKeyboard::onPressSYS() { decSmall(); }
void NumberKeyboard::onLongPressSYS() { setMIN(); }
void NumberKeyboard::onPressMDL() { incLarge(); }
void NumberKeyboard::onLongPressMDL() { changeSign(); }
void NumberKeyboard::onPressTELE() { incSmall(); }
void NumberKeyboard::onLongPressTELE() { setMAX(); }
#if defined(PCBX12S)
void NumberKeyboard::onPressPGUP() { decLarge(); }
#else
void NumberKeyboard::onPressPGUP() { setDEF(); }
#endif
void NumberKeyboard::onPressPGDN() { decLarge(); }
void NumberKeyboard::onLongPressPGUP() { setDEF(); }
void NumberKeyboard::onLongPressPGDN() { setDEF(); }
#endif
#endif
NumberKeyboard::NumberKeyboard() : Keyboard(KEYBOARD_HEIGHT)
{
// setup custom keyboard
lv_keyboard_set_map(keyboard, LV_KEYBOARD_MODE_USER_1,
(const char**)number_kb_map, number_kb_ctrl_map);
lv_keyboard_set_mode(keyboard, LV_KEYBOARD_MODE_USER_1);
}
NumberKeyboard::~NumberKeyboard() { _instance = nullptr; }
void NumberKeyboard::show(FormField* field)
{
if (!_instance) _instance = new NumberKeyboard();
lv_obj_clear_flag(_instance->lvobj, LV_OBJ_FLAG_HIDDEN);
lv_obj_clear_flag(_instance->keyboard, LV_OBJ_FLAG_HIDDEN);
_instance->setField(field);
auto kb = _instance->keyboard;
lv_keyboard_set_textarea(kb, nullptr);
lv_obj_remove_event_cb(kb, on_key);
lv_obj_add_event_cb(kb, on_key, LV_EVENT_VALUE_CHANGED, _instance);
}
-61
View File
@@ -1,61 +0,0 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* libopenui - https://github.com/opentx/libopenui
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#pragma once
#include "keyboard_base.h"
class NumberKeyboard : public Keyboard
{
public:
NumberKeyboard();
~NumberKeyboard() override;
#if defined(DEBUG_WINDOWS)
std::string getName() const override { return "NumberKeyboard"; }
#endif
static void show(FormField* field);
void handleEvent(const char* btn);
protected:
void decLarge();
void decSmall();
void incSmall();
void incLarge();
void setMIN();
void setMAX();
void setDEF();
void changeSign();
#if defined(HARDWARE_KEYS)
void onPressSYS() override;
void onLongPressSYS() override;
void onPressMDL() override;
void onLongPressMDL() override;
void onPressTELE() override;
void onLongPressTELE() override;
void onPressPGUP() override;
void onPressPGDN() override;
void onLongPressPGUP() override;
void onLongPressPGDN() override;
#endif
static NumberKeyboard* _instance;
};
-139
View File
@@ -1,139 +0,0 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* libopenui - https://github.com/opentx/libopenui
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "keyboard_text.h"
#include "textedit.h"
constexpr coord_t KEYBOARD_HEIGHT = LCD_H * 2 / 5;
TextKeyboard* TextKeyboard::_instance = nullptr;
TextKeyboard::TextKeyboard() : Keyboard(KEYBOARD_HEIGHT) {}
TextKeyboard::~TextKeyboard() { _instance = nullptr; }
#if defined(HARDWARE_KEYS)
void TextKeyboard::changeMode()
{
// Change keyboard mode
lv_keyboard_mode_t mode = lv_keyboard_get_mode(keyboard);
mode = (mode + 1) & 3;
lv_keyboard_set_mode(keyboard, mode);
}
void TextKeyboard::backspace()
{
lv_keyboard_t* kb = (lv_keyboard_t*)keyboard;
// Backspace
lv_textarea_del_char(kb->ta);
}
void TextKeyboard::toggleCase()
{
lv_keyboard_t* kb = (lv_keyboard_t*)keyboard;
// Toggle case
char c = lv_textarea_get_text(kb->ta)[lv_textarea_get_cursor_pos(kb->ta)];
if (((c >= 'A') && (c <= 'Z')) || ((c >= 'a') && (c <= 'z'))) {
c ^= 0x20;
lv_textarea_del_char_forward(kb->ta);
lv_textarea_add_char(kb->ta, c);
lv_textarea_cursor_left(kb->ta);
}
}
void TextKeyboard::deleteChar()
{
lv_keyboard_t* kb = (lv_keyboard_t*)keyboard;
// Delete
lv_textarea_del_char_forward(kb->ta);
}
void TextKeyboard::cursorLeft()
{
lv_keyboard_t* kb = (lv_keyboard_t*)keyboard;
// Cursor left
lv_textarea_cursor_left(kb->ta);
}
void TextKeyboard::cursorRight()
{
lv_keyboard_t* kb = (lv_keyboard_t*)keyboard;
// Cursor right
lv_textarea_cursor_right(kb->ta);
}
void TextKeyboard::cursorStart()
{
lv_keyboard_t* kb = (lv_keyboard_t*)keyboard;
// Cursor to start
while (lv_textarea_get_cursor_pos(kb->ta) > 0)
lv_textarea_cursor_left(kb->ta);
}
void TextKeyboard::cursorEnd()
{
lv_keyboard_t* kb = (lv_keyboard_t*)keyboard;
// Cursor to end
size_t l = strlen(lv_textarea_get_text(kb->ta));
while (lv_textarea_get_cursor_pos(kb->ta) < l)
lv_textarea_cursor_right(kb->ta);
}
#if (defined(KEYS_GPIO_REG_PAGEUP) || defined(USE_HATS_AS_KEYS)) && \
!defined(PCBX12S)
// Radios with both PGUP and PGDN buttons except X12S
void TextKeyboard::onPressSYS() { changeMode(); }
void TextKeyboard::onLongPressSYS() {}
void TextKeyboard::onPressMDL() {}
void TextKeyboard::onLongPressMDL() { backspace(); }
void TextKeyboard::onPressTELE() { toggleCase(); }
void TextKeyboard::onLongPressTELE() { deleteChar(); }
void TextKeyboard::onPressPGUP() { cursorLeft(); }
void TextKeyboard::onPressPGDN() { cursorRight(); }
void TextKeyboard::onLongPressPGUP() { cursorStart(); }
void TextKeyboard::onLongPressPGDN() { cursorEnd(); }
#else
// Radios witb only a single PGUP/DN button or X12S
void TextKeyboard::onPressSYS() { cursorLeft(); }
void TextKeyboard::onLongPressSYS() { cursorStart(); }
void TextKeyboard::onPressMDL() { changeMode(); }
void TextKeyboard::onLongPressMDL() { backspace(); }
void TextKeyboard::onPressTELE() { cursorRight(); }
void TextKeyboard::onLongPressTELE() { cursorEnd(); }
#if defined(PCBX12S)
void TextKeyboard::onPressPGUP() { toggleCase(); }
#else
void TextKeyboard::onPressPGUP() { deleteChar(); }
#endif
void TextKeyboard::onPressPGDN() { toggleCase(); }
void TextKeyboard::onLongPressPGUP() {}
void TextKeyboard::onLongPressPGDN() { deleteChar(); }
#endif
#endif
void TextKeyboard::show(FormField* field)
{
if (!_instance) _instance = new TextKeyboard();
lv_obj_clear_flag(_instance->lvobj, LV_OBJ_FLAG_HIDDEN);
lv_obj_clear_flag(_instance->keyboard, LV_OBJ_FLAG_HIDDEN);
lv_keyboard_set_mode(_instance->keyboard, LV_KEYBOARD_MODE_TEXT_LOWER);
_instance->setField(field);
}
-74
View File
@@ -1,74 +0,0 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* libopenui - https://github.com/opentx/libopenui
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#pragma once
#include "keyboard_base.h"
#define KEYBOARD_SPACE "\t"
#define KEYBOARD_ENTER "\n"
#define KEYBOARD_BACKSPACE "\200"
#define KEYBOARD_SET_UPPERCASE "\201"
#define KEYBOARD_SET_LOWERCASE "\202"
#define KEYBOARD_SET_LETTERS "\203"
#define KEYBOARD_SET_NUMBERS "\204"
#define KEYBOARD_OFFSET_Y 15
#define KEYBOARD_ROW_HEIGHT 40
#define KEYBOARD_CHAR_WIDTH 30
#define KEYBOARD_SPACE_WIDTH 135
#define KEYBOARD_ENTER_WIDTH 80
#define KEYBOARD_BITMAP_WIDTH 45
class TextKeyboard : public Keyboard
{
public:
TextKeyboard();
~TextKeyboard() override;
#if defined(DEBUG_WINDOWS)
std::string getName() const override { return "TextKeyboard"; }
#endif
static void show(FormField* field);
protected:
#if defined(HARDWARE_KEYS)
void changeMode();
void cursorLeft();
void cursorRight();
void cursorStart();
void cursorEnd();
void toggleCase();
void deleteChar();
void backspace();
void onPressSYS() override;
void onLongPressSYS() override;
void onPressMDL() override;
void onLongPressMDL() override;
void onPressTELE() override;
void onLongPressTELE() override;
void onPressPGUP() override;
void onPressPGDN() override;
void onLongPressPGUP() override;
void onLongPressPGDN() override;
#endif
static TextKeyboard* _instance;
};
-85
View File
@@ -1,85 +0,0 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* libopenui - https://github.com/opentx/libopenui
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "layer.h"
std::list<Layer> Layer::stack;
Layer::Layer(Window* w, lv_group_t* g) : window(w), group(g) {}
Layer::~Layer() { lv_group_del(group); }
static void _assign_lv_group(lv_group_t* g)
{
lv_group_set_default(g);
// associate it with all input devices
lv_indev_t* indev = lv_indev_get_next(NULL);
while (indev) {
lv_indev_set_group(indev, g);
indev = lv_indev_get_next(indev);
}
}
void Layer::push(Window* w)
{
// create a new group
auto g = lv_group_create();
_assign_lv_group(g);
// and store
stack.emplace_back(w, g);
}
void Layer::pop(Window* w)
{
if (stack.empty()) return;
if (back() == w) {
stack.pop_back();
} else {
for (auto layer = stack.crbegin(); layer != stack.crend(); layer++) {
if (layer->window == w) {
stack.erase(layer.base());
return;
}
}
}
if (!stack.empty()) {
lv_group_t* g = stack.back().group;
_assign_lv_group(g);
} else {
lv_group_set_default(NULL);
}
}
Window* Layer::back()
{
if (stack.empty()) return nullptr;
return stack.back().window;
}
Window* Layer::getFirstOpaque()
{
for (auto layer = stack.crbegin(); layer != stack.crend(); layer++) {
auto w = layer->window;
if (w && w->hasWindowFlag(OPAQUE)) return w;
}
return nullptr;
}
-39
View File
@@ -1,39 +0,0 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* libopenui - https://github.com/opentx/libopenui
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#pragma once
#include "window.h"
class Layer
{
static std::list<Layer> stack;
Window* window;
lv_group_t* group;
public:
explicit Layer(Window* w, lv_group_t* g);
~Layer();
static void push(Window* window);
static void pop(Window* window);
static Window* back();
static Window* getFirstOpaque();
};
-50
View File
@@ -1,50 +0,0 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* libopenui - https://github.com/opentx/libopenui
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#pragma once
#include "debug.h"
#include "hal.h"
#include "opentx_types.h"
#include "colors.h"
#include "keys.h"
// Used by Lua API
#define INVERS 0x01u
#define BLINK 0x1000u
#define TIMEHOUR 0x2000u
/* drawText flags */
#define LEFT 0x00u /* align left */
#define VCENTERED 0x02u /* align center vertically */
#define CENTERED 0x04u /* align center */
#define RIGHT 0x08u /* align right */
#define SHADOWED 0x80u /* black copy at +1 +1 */
// 0x1000u used by Lua in api_colorlcd.h
// 0x8000u used by Lua in api_colorlcd.h
/* drawNumber flags */
#define LEADING0 0x10u
#define PREC1 0x20u
#define PREC2 0x30u
#define MODE(flags) ((((int8_t)(flags) & 0x30) - 0x10) >> 4)
/* telemetry flags */
#define NO_UNIT 0x40u
#define LV_OBJ_FLAG_ENCODER_ACCEL LV_OBJ_FLAG_USER_1
-109
View File
@@ -1,109 +0,0 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* libopenui - https://github.com/opentx/libopenui
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "libopenui_file.h"
#include <inttypes.h>
#include <cinttypes>
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;
}
-35
View File
@@ -1,35 +0,0 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* libopenui - https://github.com/opentx/libopenui
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#pragma once
#include <string>
#include <cstring>
#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;
}
-122
View File
@@ -1,122 +0,0 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* libopenui - https://github.com/opentx/libopenui
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "mainwindow.h"
#include "board.h"
#include "keyboard_base.h"
#include "layout.h"
#include "themes/etx_lv_theme.h"
#include "sdcard.h"
// timers_driver.h
uint32_t timersGetMsTick();
MainWindow* MainWindow::_instance = nullptr;
MainWindow* MainWindow::instance()
{
if (!_instance) _instance = new MainWindow();
return _instance;
}
MainWindow::MainWindow() : Window(nullptr, {0, 0, LCD_W, LCD_H})
{
setWindowFlag(OPAQUE);
etx_solid_bg(lvobj);
background = lv_canvas_create(lvobj);
lv_obj_center(background);
}
void MainWindow::emptyTrash()
{
for (auto window : trash) {
delete window;
}
trash.clear();
}
void MainWindow::run(bool trash)
{
auto start = timersGetMsTick();
auto opaque = Layer::getFirstOpaque();
if (opaque) {
opaque->checkEvents();
}
auto copy = children;
for (auto child : copy) {
if (!child->deleted() && child->isBubblePopup()) {
child->checkEvents();
}
}
if (trash) emptyTrash();
auto delta = timersGetMsTick() - start;
if (delta > 10) {
TRACE_WINDOWS("MainWindow::run took %dms", delta);
}
}
void MainWindow::shutdown()
{
// Called when USB is connected in SD card mode
// Delete main view screens
LayoutFactory::deleteCustomScreens();
// clear layer stack first
for (Window* w = Layer::back(); w; w = Layer::back()) w->deleteLater();
children.clear();
clear();
emptyTrash();
// Re-add background canvas
background = lv_canvas_create(lvobj);
lv_obj_center(background);
}
void MainWindow::setBackgroundImage(const char* fileName)
{
// ensure you delete old bitmap
if (backgroundBitmap != nullptr) delete backgroundBitmap;
if (fileName == nullptr) fileName = "";
backgroundImageFileName = fileName;
// Try to load bitmap. If this fails backgroundBitmap will be NULL and default
// will be loaded in update() method
backgroundBitmap =
BitmapBuffer::loadBitmap(backgroundImageFileName.c_str(), BMP_RGB565);
if (!backgroundBitmap) {
backgroundBitmap = BitmapBuffer::loadBitmap(
THEMES_PATH "/EdgeTX/background.png", BMP_RGB565);
}
if (backgroundBitmap) {
lv_canvas_set_buffer(background, backgroundBitmap->getData(), backgroundBitmap->width(),
backgroundBitmap->height(), LV_IMG_CF_TRUE_COLOR);
}
}
-64
View File
@@ -1,64 +0,0 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* libopenui - https://github.com/opentx/libopenui
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#pragma once
#include "layer.h"
#include "window.h"
class MainWindow: public Window
{
protected:
// singleton
MainWindow();
public:
static MainWindow * instance();
#if defined(TESTS)
static void create()
{
_instance = new MainWindow();
}
#endif
#if defined(DEBUG_WINDOWS)
std::string getName() const override
{
return "MainWindow";
}
#endif
void setActiveScreen() {
lv_scr_load(lvobj);
}
void run(bool trash=true);
void setBackgroundImage(const char* fileName);
void shutdown();
protected:
lv_obj_t* background = nullptr;
std::string backgroundImageFileName;
const BitmapBuffer *backgroundBitmap = nullptr;
static MainWindow * _instance;
static void emptyTrash();
};
-464
View File
@@ -1,464 +0,0 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* libopenui - https://github.com/opentx/libopenui
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "menu.h"
#include <lvgl/lvgl.h>
#include "menutoolbar.h"
#include "opentx.h"
#include "table.h"
#include "themes/etx_lv_theme.h"
constexpr coord_t MENUS_WIDTH = 200;
static void _force_editing(lv_group_t* g) { lv_group_set_editing(g, true); }
class MenuBody : public TableField
{
friend class MenuWindowContent;
friend class Menu;
enum MENU_DIRECTION { DIRECTION_UP = 1, DIRECTION_DOWN = -1 };
class MenuLine
{
friend class MenuBody;
public:
MenuLine(const std::string& text, std::function<void()> onPress,
std::function<bool()> isChecked, lv_obj_t* icon) :
text(text),
onPress(std::move(onPress)),
isChecked(std::move(isChecked)),
icon(icon)
{
}
~MenuLine()
{
if (icon)
lv_obj_del(icon);
}
lv_obj_t* getIcon() const { return icon; }
protected:
std::string text;
std::function<void()> onPress;
std::function<bool()> isChecked;
lv_obj_t* icon;
};
public:
MenuBody(Window* parent, const rect_t& rect) :
TableField(parent, rect)
{
// Allow encoder acceleration
lv_obj_add_flag(lvobj, LV_OBJ_FLAG_ENCODER_ACCEL);
// Add scroll bar if needed
etx_scrollbar(lvobj);
setColumnWidth(0, rect.w);
lv_group_t* g = (lv_group_t*)lv_obj_get_group(lvobj);
if (g) {
setFocusHandler([=](bool focus) {
if (focus) {
lv_group_set_focus_cb(g, _force_editing);
} else {
lv_group_set_focus_cb(g, nullptr);
}
});
lv_group_set_editing(g, true);
}
}
~MenuBody()
{
clearLines();
}
#if defined(DEBUG_WINDOWS)
std::string getName() const override { return "MenuBody"; }
#endif
void setIndex(int index)
{
if (index < (int)lines.size()) {
if (index == selectedIndex) return;
selectedIndex = index;
lv_obj_invalidate(lvobj);
lv_table_t* table = (lv_table_t*)lvobj;
if (index < 0) {
table->row_act = LV_TABLE_CELL_NONE;
table->col_act = LV_TABLE_CELL_NONE;
return;
}
table->row_act = index;
table->col_act = 0;
lv_coord_t h_before = 0;
for (uint16_t i = 0; i < table->row_act; i++) h_before += table->row_h[i];
lv_coord_t row_h = table->row_h[table->row_act];
lv_coord_t scroll_y = lv_obj_get_scroll_y(lvobj);
lv_obj_update_layout(lvobj);
lv_coord_t h = lv_obj_get_height(lvobj);
lv_coord_t diff_y = 0;
if (h_before < scroll_y) {
diff_y = scroll_y - h_before;
} else if (scroll_y + h < h_before + row_h) {
diff_y = scroll_y + h - h_before - row_h;
} else {
return;
}
lv_obj_scroll_by_bounded(lvobj, 0, diff_y, LV_ANIM_OFF);
}
}
int selection() const { return selectedIndex; }
int count() const { return lines.size(); }
void onEvent(event_t event) override
{
#if defined(HARDWARE_KEYS)
if (event == EVT_KEY_BREAK(KEY_EXIT)) {
onCancel();
} else {
TableField::onEvent(event);
}
#endif
}
void addLine(const uint8_t* icon_mask, const std::string& text,
std::function<void()> onPress, std::function<bool()> isChecked,
bool update = true)
{
lv_obj_t* canvas = nullptr;
if (icon_mask) {
canvas = lv_canvas_create(nullptr);
lv_coord_t w = *((uint16_t*)icon_mask);
lv_coord_t h = *(((uint16_t*)icon_mask) + 1);
void* buf = (void*)(icon_mask + 4);
lv_canvas_set_buffer(canvas, buf, w, h, LV_IMG_CF_ALPHA_8BIT);
}
auto l = new MenuLine(text, onPress, isChecked, canvas);
lines.push_back(l);
if (update) {
auto idx = lines.size() - 1;
lv_table_set_cell_value(lvobj, idx, 0, text.c_str());
}
}
void updateLines()
{
setRowCount(lines.size());
for (unsigned int idx = 0; idx < lines.size(); idx++) {
lv_table_set_cell_value(lvobj, idx, 0, lines[idx]->text.c_str());
}
}
void clearLines()
{
for (auto itr = lines.begin(); itr != lines.end();) {
auto l = *itr;
itr = lines.erase(itr);
delete l;
}
lines.clear();
}
void removeLines()
{
clearLines();
setRowCount(0);
selectedIndex = 0;
// reset vertical scroll
lv_obj_scroll_to_y(lvobj, 0, LV_ANIM_OFF);
}
void onPress(uint16_t row, uint16_t col) override
{
if (!isLongPressed) {
Menu* menu = getParentMenu();
if (row < lines.size()) {
if (menu->multiple) {
if (selectedIndex == (int)row)
lines[row]->onPress();
else {
setIndex(row);
lines[row]->onPress();
}
} else {
// delete menu first to avoid
// focus issues with onPress()
menu->deleteLater();
lines[row]->onPress();
}
}
}
isLongPressed = false;
}
void onDrawBegin(uint16_t row, uint16_t col,
lv_obj_draw_part_dsc_t* dsc) override
{
lv_canvas_t* icon = (lv_canvas_t*)lines[row]->getIcon();
if (!icon) return;
lv_img_t* img = &icon->img;
lv_coord_t cell_left = lv_obj_get_style_pad_left(lvobj, LV_PART_ITEMS);
dsc->label_dsc->ofs_x = img->w + cell_left;
}
void onDrawEnd(uint16_t row, uint16_t col,
lv_obj_draw_part_dsc_t* dsc) override
{
if (row >= lines.size()) return;
lv_obj_t* icon = lines[row]->getIcon();
if (icon) {
lv_draw_img_dsc_t img_dsc;
lv_draw_img_dsc_init(&img_dsc);
lv_img_dsc_t* img = lv_canvas_get_img(icon);
lv_area_t coords;
lv_coord_t area_h = lv_area_get_height(dsc->draw_area);
lv_coord_t cell_left = lv_obj_get_style_pad_left(lvobj, LV_PART_ITEMS);
coords.x1 = dsc->draw_area->x1 + cell_left;
coords.x2 = coords.x1 + img->header.w - 1;
coords.y1 = dsc->draw_area->y1 + (area_h - img->header.h) / 2;
coords.y2 = coords.y1 + img->header.h - 1;
lv_draw_img(dsc->draw_ctx, &img_dsc, &coords, img);
}
if (lines[row]->isChecked != nullptr && lines[row]->isChecked()) {
lv_area_t coords;
lv_coord_t area_h = lv_area_get_height(dsc->draw_area);
lv_coord_t cell_right = lv_obj_get_style_pad_right(lvobj, LV_PART_ITEMS);
lv_coord_t font_h = getFontHeight(FONT(STD));
coords.x1 = dsc->draw_area->x2 - cell_right - font_h;
coords.x2 = coords.x1 + font_h;
coords.y1 = dsc->draw_area->y1 + (area_h - font_h) / 2;
coords.y2 = coords.y1 + font_h - 1;
lv_draw_label(dsc->draw_ctx, dsc->label_dsc, &coords, LV_SYMBOL_OK,
nullptr);
}
}
void onSelected(uint16_t row, uint16_t col) override { selectedIndex = row; }
protected:
bool isLongPressed = false;
std::vector<MenuLine*> lines;
int selectedIndex = 0;
Menu* getParentMenu() { return static_cast<Menu*>(getParent()->getParent()); }
};
//-----------------------------------------------------------------------------
static void menu_content_constructor(const lv_obj_class_t* class_p,
lv_obj_t* obj)
{
etx_solid_bg(obj);
etx_obj_add_style(obj, styles->outline, LV_PART_MAIN);
etx_obj_add_style(obj, styles->outline_color_normal, LV_PART_MAIN);
}
static const lv_obj_class_t menu_content_class = {
.base_class = &window_base_class,
.constructor_cb = menu_content_constructor,
.destructor_cb = nullptr,
.user_data = nullptr,
.event_cb = nullptr,
.width_def = 0,
.height_def = 0,
.editable = LV_OBJ_CLASS_EDITABLE_INHERIT,
.group_def = LV_OBJ_CLASS_EDITABLE_INHERIT,
.instance_size = sizeof(lv_obj_t)};
static lv_obj_t* menu_content_create(lv_obj_t* parent)
{
return etx_create(&menu_content_class, parent);
}
class MenuWindowContent : public Window
{
public:
explicit MenuWindowContent(Menu* parent) :
Window(parent, rect_t{}, menu_content_create)
{
setWindowFlag(OPAQUE);
lv_obj_center(lvobj);
setFlexLayout(LV_FLEX_FLOW_COLUMN, PAD_ZERO, MENUS_WIDTH, LV_SIZE_CONTENT);
header = new StaticText(this, {0, 0, LV_PCT(100), 0}, "",
COLOR_THEME_PRIMARY2_INDEX);
etx_solid_bg(header->getLvObj(), COLOR_THEME_SECONDARY1_INDEX);
header->padAll(PAD_SMALL);
header->hide();
body = new MenuBody(this, rect_t{0, 0, MENUS_WIDTH, LV_SIZE_CONTENT});
lv_obj_set_style_max_height(body->getLvObj(), LCD_H * 0.8, LV_PART_MAIN);
}
virtual void setTitle(const std::string& text)
{
header->setText(text);
header->show();
}
void onClicked() override { Keyboard::hide(false); }
#if defined(DEBUG_WINDOWS)
std::string getName() const override { return "MenuWindowContent"; }
#endif
void updateLines() { body->updateLines(); }
void removeLines() { body->removeLines(); }
int count() { return body->count(); }
int selection() { return body->selection(); }
void setIndex(int index) { body->setIndex(index); }
void addLine(const uint8_t* icon_mask, const std::string& text,
std::function<void()> onPress, std::function<bool()> isChecked,
bool update = true)
{
body->addLine(icon_mask, text, onPress, isChecked, update);
}
protected:
StaticText* header = nullptr;
MenuBody* body = nullptr;
};
//-----------------------------------------------------------------------------
Menu::Menu(Window* parent, bool multiple) :
ModalWindow(parent, true),
multiple(multiple),
content(new MenuWindowContent(this))
{
}
void Menu::setToolbar(MenuToolbar* window)
{
toolbar = window;
updatePosition();
}
void Menu::updatePosition()
{
if (toolbar) {
coord_t cw = lv_obj_get_width(content->getLvObj());
coord_t ch = lv_obj_get_height(content->getLvObj());
coord_t tw = lv_obj_get_width(toolbar->getLvObj());
coord_t th = lv_obj_get_height(toolbar->getLvObj());
lv_obj_align(toolbar->getLvObj(), LV_ALIGN_CENTER, -cw / 2, 0);
lv_obj_align(content->getLvObj(), LV_ALIGN_CENTER, tw / 2, 0);
toolbar->setHeight(max(ch, th));
content->setHeight(max(ch, th));
}
}
void Menu::setTitle(std::string text)
{
content->setTitle(std::move(text));
updatePosition();
}
void Menu::addLine(const uint8_t* icon_mask, const std::string& text,
std::function<void()> onPress,
std::function<bool()> isChecked)
{
content->addLine(icon_mask, text, std::move(onPress), std::move(isChecked));
updatePosition();
}
void Menu::addLineBuffered(const uint8_t* icon_mask, const std::string& text,
std::function<void()> onPress,
std::function<bool()> isChecked)
{
content->addLine(icon_mask, text, std::move(onPress), std::move(isChecked),
false);
}
void Menu::updateLines()
{
content->updateLines();
updatePosition();
}
void Menu::removeLines()
{
content->removeLines();
updatePosition();
}
void Menu::onEvent(event_t event)
{
#if defined(HARDWARE_KEYS)
if (toolbar && (event == EVT_KEY_BREAK(KEY_PAGEDN) ||
event == EVT_KEY_BREAK(KEY_PAGEUP))) {
toolbar->onEvent(event);
}
#endif
}
void Menu::onCancel()
{
if (cancelHandler) cancelHandler();
deleteLater();
}
void Menu::setCancelHandler(std::function<void()> handler)
{
cancelHandler = std::move(handler);
}
void Menu::setWaitHandler(std::function<void()> handler)
{
waitHandler = std::move(handler);
}
unsigned Menu::count() const { return content->count(); }
int Menu::selection() const { return content->selection(); }
void Menu::select(int index) { content->setIndex(index); }
-94
View File
@@ -1,94 +0,0 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* libopenui - https://github.com/opentx/libopenui
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#pragma once
#include "modal_window.h"
class Menu;
class MenuWindowContent;
class MenuToolbar;
class Menu : public ModalWindow
{
friend class MenuBody;
public:
explicit Menu(Window *parent, bool multiple = false);
#if defined(DEBUG_WINDOWS)
std::string getName() const override { return "Menu"; }
#endif
void setCancelHandler(std::function<void()> handler);
void setWaitHandler(std::function<void()> handler);
void setToolbar(MenuToolbar *window);
void setTitle(std::string text);
void addLine(const uint8_t *icon_mask, const std::string &text,
std::function<void()> onPress,
std::function<bool()> isChecked = nullptr);
void addLine(const std::string &text, std::function<void()> onPress,
std::function<bool()> isChecked = nullptr)
{
addLine(nullptr, text, onPress, isChecked);
}
void addLineBuffered(const uint8_t *icon_mask, const std::string &text,
std::function<void()> onPress,
std::function<bool()> isChecked = nullptr);
void addLineBuffered(const std::string &text, std::function<void()> onPress,
std::function<bool()> isChecked = nullptr)
{
addLineBuffered(nullptr, text, onPress, isChecked);
}
void updateLines();
void removeLines();
unsigned count() const;
int selection() const;
void select(int index);
void onEvent(event_t event) override;
void onCancel() override;
void checkEvents() override
{
ModalWindow::checkEvents();
if (waitHandler) {
waitHandler();
}
}
protected:
bool multiple;
MenuWindowContent *content;
MenuToolbar *toolbar = nullptr;
std::function<void()> waitHandler;
std::function<void()> cancelHandler;
void updatePosition();
};
-186
View File
@@ -1,186 +0,0 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* libopenui - https://github.com/opentx/libopenui
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "menutoolbar.h"
#include "menu.h"
#include "themes/etx_lv_theme.h"
#include "translations.h"
static const lv_obj_class_t menu_button_class = {
.base_class = &button_class,
.constructor_cb = nullptr,
.destructor_cb = nullptr,
.user_data = nullptr,
.event_cb = nullptr,
.width_def = 0,
.height_def = 0,
.editable = LV_OBJ_CLASS_EDITABLE_INHERIT,
.group_def = LV_OBJ_CLASS_GROUP_DEF_FALSE,
.instance_size = sizeof(lv_btn_t),
};
static lv_obj_t* menu_button_create(lv_obj_t* parent)
{
return etx_create(&menu_button_class, parent);
}
static void toolbar_btn_defocus(lv_event_t* event)
{
auto obj = lv_event_get_target(event);
auto btn = (MenuToolbarButton*)lv_obj_get_user_data(obj);
btn->check(false);
}
MenuToolbarButton::MenuToolbarButton(Window* parent, const rect_t& rect,
const char* picto) :
ButtonBase(parent, rect, nullptr, menu_button_create)
{
lv_obj_add_flag(lvobj, LV_OBJ_FLAG_CLICK_FOCUSABLE);
lv_obj_add_flag(lvobj, LV_OBJ_FLAG_SCROLL_ON_FOCUS);
lv_obj_add_event_cb(lvobj, toolbar_btn_defocus, LV_EVENT_DEFOCUSED, nullptr);
auto label = lv_label_create(lvobj);
lv_label_set_text(label, picto);
lv_obj_center(label);
}
MenuToolbar::MenuToolbar(Choice* choice, Menu* menu, const int columns) :
Window(menu, {0, 0, 0, MENUS_MAX_HEIGHT}),
choice(choice),
menu(menu),
filterColumns(columns),
group(lv_group_create())
{
setWindowFlag(OPAQUE);
padAll(PAD_SMALL);
etx_solid_bg(lvobj);
etx_obj_add_style(lvobj, styles->outline, LV_PART_MAIN);
etx_obj_add_style(lvobj, styles->outline_color_normal, LV_PART_MAIN);
setWidth((MENUS_TOOLBAR_BUTTON_WIDTH + PAD_SMALL) * columns + PAD_SMALL);
addButton(STR_SELECT_MENU_ALL, choice->getMin(), choice->getMax(), nullptr,
nullptr, true);
}
MenuToolbar::~MenuToolbar() { lv_group_del(group); }
void MenuToolbar::resetFilter()
{
if (lv_group_get_focused(group) != lvobj) {
lv_group_focus_obj(lvobj);
choice->fillMenu(menu);
menu->setTitle(choice->getTitle());
}
}
void MenuToolbar::onEvent(event_t event)
{
if (event == EVT_KEY_BREAK(KEY_PAGEDN)) {
lv_group_focus_next(group);
}
else if (event == EVT_KEY_BREAK(KEY_PAGEUP)) {
lv_group_focus_prev(group);
}
auto obj = lv_group_get_focused(group);
lv_event_send(obj, LV_EVENT_CLICKED, nullptr);
}
rect_t MenuToolbar::getButtonRect(bool wideButton)
{
if (wideButton && (nxtBtnPos % filterColumns))
nxtBtnPos = nxtBtnPos - (nxtBtnPos % filterColumns) + filterColumns;
coord_t x =
(nxtBtnPos % filterColumns) * (MENUS_TOOLBAR_BUTTON_WIDTH + PAD_SMALL);
coord_t y =
(nxtBtnPos / filterColumns) * (EdgeTxStyles::UI_ELEMENT_HEIGHT + PAD_SMALL);
coord_t w = wideButton ? (MENUS_TOOLBAR_BUTTON_WIDTH + PAD_SMALL) *
(filterColumns - 1) +
MENUS_TOOLBAR_BUTTON_WIDTH
: MENUS_TOOLBAR_BUTTON_WIDTH;
nxtBtnPos += wideButton ? filterColumns : 1;
return {x, y, w, EdgeTxStyles::UI_ELEMENT_HEIGHT};
}
bool MenuToolbar::filterMenu(MenuToolbarButton* btn, int16_t filtermin,
int16_t filtermax,
const Choice::FilterFct& filterFunc,
const char* title)
{
btn->check(!btn->checked());
filter = nullptr;
if (btn->checked()) {
if (title)
menu->setTitle(title);
else
menu->setTitle(choice->getTitle());
filter = [=](int16_t index) {
if (filterFunc) return filterFunc(index);
return index == 0 || (abs(index) >= filtermin && abs(index) <= filtermax);
};
lv_group_focus_obj(btn->getLvObj());
choice->fillMenu(menu, filter);
} else {
lv_event_send(allBtn->getLvObj(), LV_EVENT_CLICKED, nullptr);
}
return btn->checked();
}
void MenuToolbar::addButton(const char* picto, int16_t filtermin,
int16_t filtermax,
const Choice::FilterFct& filterFunc,
const char* title, bool wideButton)
{
int vmin = choice->getMin();
int vmax = choice->getMax();
if (vmin > filtermin || vmax < filtermin) return;
if (choice->isValueAvailable) {
bool found = false;
for (int i = filtermin; i <= filtermax; i += 1) {
if (choice->isValueAvailable(i)) {
if (filterFunc && !filterFunc(i))
continue;
found = true;
break;
}
}
if (!found) return;
}
rect_t r = getButtonRect(wideButton);
auto button = new MenuToolbarButton(this, r, picto);
button->setPressHandler(std::bind(&MenuToolbar::filterMenu, this, button,
filtermin, filtermax, filterFunc, title));
lv_group_add_obj(group, button->getLvObj());
if (children.size() == 1) {
allBtn = button;
lv_event_send(allBtn->getLvObj(), LV_EVENT_CLICKED, nullptr);
}
}
-68
View File
@@ -1,68 +0,0 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* libopenui - https://github.com/opentx/libopenui
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#pragma once
#include "button.h"
#include "choice.h"
#include "listbox.h"
class Menu;
class MenuToolbarButton : public ButtonBase
{
public:
MenuToolbarButton(Window* parent, const rect_t& rect, const char* picto);
protected:
};
class MenuToolbar : public Window
{
// friend Menu;
public:
MenuToolbar(Choice* choice, Menu* menu, const int columns);
~MenuToolbar();
void resetFilter();
void onEvent(event_t event) override;
virtual void longPress() {}
static LAYOUT_VAL(MENUS_TOOLBAR_BUTTON_WIDTH, 36, 36)
static LAYOUT_VAL(MENUS_MAX_HEIGHT, ListBox::MENUS_LINE_HEIGHT * 7 + 8, ListBox::MENUS_LINE_HEIGHT * 10)
protected:
Choice* choice;
Choice::FilterFct filter;
Menu* menu;
int nxtBtnPos = 0;
int filterColumns = 0;
MenuToolbarButton* allBtn = nullptr;
lv_group_t* group;
void addButton(const char* picto, int16_t filtermin, int16_t filtermax,
const Choice::FilterFct& filterFunc = nullptr,
const char* title = nullptr, bool wideButton = false);
bool filterMenu(MenuToolbarButton* btn, int16_t filtermin, int16_t filtermax,
const Choice::FilterFct& filterFunc, const char* title);
rect_t getButtonRect(bool wideButton);
};
-71
View File
@@ -1,71 +0,0 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* libopenui - https://github.com/opentx/libopenui
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "modal_window.h"
#include "layer.h"
#include "themes/etx_lv_theme.h"
// Modal overlay style (for dimming background)
void modal_window_constructor(const lv_obj_class_t* class_p, lv_obj_t* obj)
{
etx_obj_add_style(obj, styles->bg_opacity_50, LV_PART_MAIN);
etx_bg_color(obj, COLOR_BLACK_INDEX, LV_PART_MAIN);
}
static const lv_obj_class_t modal_window_class = {
.base_class = &window_base_class,
.constructor_cb = modal_window_constructor,
.destructor_cb = nullptr,
.user_data = nullptr,
.event_cb = nullptr,
.width_def = 0,
.height_def = 0,
.editable = LV_OBJ_CLASS_EDITABLE_INHERIT,
.group_def = LV_OBJ_CLASS_EDITABLE_INHERIT,
.instance_size = sizeof(lv_obj_t)};
static lv_obj_t* modal_create(lv_obj_t* parent)
{
return etx_create(&modal_window_class, parent);
}
ModalWindow::ModalWindow(Window* parent, bool closeWhenClickOutside) :
Window(parent->getFullScreenWindow(), {0, 0, LCD_W, LCD_H}, modal_create),
closeWhenClickOutside(closeWhenClickOutside)
{
Layer::push(this);
}
void ModalWindow::deleteLater(bool detach, bool trash)
{
if (_deleted) return;
Layer::pop(this);
Window::deleteLater(detach, trash);
}
void ModalWindow::onClicked()
{
if (closeWhenClickOutside) {
// Call onCancel of first child (if one exists)
if (children.size() > 0)
children.front()->onCancel();
else
deleteLater();
}
}
-42
View File
@@ -1,42 +0,0 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* libopenui - https://github.com/opentx/libopenui
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#pragma once
#include "window.h"
class ModalWindow : public Window
{
public:
explicit ModalWindow(Window* parent, bool closeWhenClickOutside = false);
#if defined(DEBUG_WINDOWS)
std::string getName() const override { return "ModalWindow"; }
#endif
void setCloseWhenClickOutside(bool value = true)
{
closeWhenClickOutside = value;
}
void onClicked() override;
void deleteLater(bool detach = true, bool trash = true) override;
protected:
bool closeWhenClickOutside;
};
-291
View File
@@ -1,291 +0,0 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* libopenui - https://github.com/opentx/libopenui
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "numberedit.h"
#include "audio.h"
#include "keyboard_number.h"
#include "strhelpers.h"
#include "themes/etx_lv_theme.h"
class NumberArea : public FormField
{
public:
NumberArea(NumberEdit* parent, const rect_t& rect) :
FormField(parent, rect, etx_textarea_create),
numEdit(parent)
{
lv_obj_clear_flag(lvobj, LV_OBJ_FLAG_CLICK_FOCUSABLE);
if (parent->getTextFlags() & CENTERED)
etx_obj_add_style(lvobj, styles->text_align_center, LV_PART_MAIN);
else
etx_obj_add_style(lvobj, styles->text_align_right, LV_PART_MAIN);
// Allow encoder acceleration
lv_obj_add_flag(lvobj, LV_OBJ_FLAG_ENCODER_ACCEL);
lv_obj_add_event_cb(lvobj, NumberArea::numberedit_cb, LV_EVENT_KEY, this);
setFocusHandler([=](bool focus) {
if (!focus && editMode) {
setEditMode(false);
hide();
lv_obj_clear_state(parent->getLvObj(), LV_STATE_FOCUSED);
}
});
update();
}
#if defined(DEBUG_WINDOWS)
std::string getName() const override
{
return "NumberArea(" + std::to_string(getValue()) + ")";
}
#endif
void onEvent(event_t event) override
{
TRACE_WINDOWS("%s received event 0x%X", getWindowDebugString().c_str(),
event);
if (editMode) {
int value = numEdit->getValue();
switch (event) {
#if defined(HARDWARE_KEYS)
case EVT_ROTARY_RIGHT: {
auto step = numEdit->step;
step += (rotaryEncoderGetAccel() * numEdit->accelFactor) / 8;
do {
#if defined(USE_HATS_AS_KEYS)
value -= step;
#else
value += step;
#endif
} while (numEdit->isValueAvailable && !numEdit->isValueAvailable(value) &&
value <= numEdit->vmax);
if (value <= numEdit->vmax) {
numEdit->setValue(value);
} else {
numEdit->setValue(numEdit->vmax);
onKeyError();
}
return;
}
case EVT_ROTARY_LEFT: {
auto step = numEdit->step;
step += (rotaryEncoderGetAccel() * numEdit->accelFactor) / 8;
do {
#if defined(USE_HATS_AS_KEYS)
value += step;
#else
value -= step;
#endif
} while (numEdit->isValueAvailable && !numEdit->isValueAvailable(value) &&
value >= numEdit->vmin);
if (value >= numEdit->vmin) {
numEdit->setValue(value);
} else {
numEdit->setValue(numEdit->vmin);
onKeyError();
}
return;
}
#endif
case EVT_VIRTUAL_KEY_PLUS:
numEdit->setValue(value + numEdit->step);
break;
case EVT_VIRTUAL_KEY_MINUS:
numEdit->setValue(value - numEdit->step);
break;
case EVT_VIRTUAL_KEY_FORWARD:
numEdit->setValue(value + numEdit->fastStep * numEdit->step);
break;
case EVT_VIRTUAL_KEY_BACKWARD:
numEdit->setValue(value - numEdit->fastStep * numEdit->step);
break;
case EVT_VIRTUAL_KEY_DEFAULT:
numEdit->setValue(numEdit->vdefault);
break;
case EVT_VIRTUAL_KEY_MAX:
numEdit->setValue(numEdit->vmax);
break;
case EVT_VIRTUAL_KEY_MIN:
numEdit->setValue(numEdit->vmin);
break;
case EVT_VIRTUAL_KEY_SIGN:
numEdit->setValue(-value);
break;
}
}
FormField::onEvent(event);
}
void onClicked() override
{
lv_indev_type_t indev_type = lv_indev_get_type(lv_indev_get_act());
if (indev_type == LV_INDEV_TYPE_POINTER) {
setEditMode(true);
} else {
FormField::onClicked();
if (!editMode) changeEnd();
}
}
void openKeyboard() { NumberKeyboard::show(this); }
void directEdit() { FormField::onClicked(); }
void update()
{
if (lvobj != nullptr)
lv_textarea_set_text(lvobj, numEdit->getDisplayVal().c_str());
}
protected:
NumberEdit* numEdit = nullptr;
void onCancel() override
{
onClicked();
}
static void numberedit_cb(lv_event_t* e)
{
NumberArea* numEdit = (NumberArea*)lv_event_get_user_data(e);
if (!numEdit || numEdit->deleted()) return;
uint32_t key = lv_event_get_key(e);
switch (key) {
case LV_KEY_LEFT:
numEdit->onEvent(EVT_ROTARY_LEFT);
break;
case LV_KEY_RIGHT:
numEdit->onEvent(EVT_ROTARY_RIGHT);
break;
}
}
};
/*
The lv_textarea object is slow. To avoid too much overhead on views with multiple
edit fields, the text area is initially displayed as a button. When the button
is pressed, a text area object is created over the top of the button in order
to edit the value.
*/
NumberEdit::NumberEdit(Window* parent, const rect_t& rect, int vmin, int vmax,
std::function<int()> getValue,
std::function<void(int)> setValue, LcdFlags textFlags) :
TextButton(parent, rect, "",
[=]() {
openEdit();
return 0;
}),
_getValue(std::move(getValue)),
_setValue(std::move(setValue)),
vmin(vmin),
vmax(vmax)
{
if (rect.w == 0) setWidth(DEF_W);
setTextFlag(textFlags);
lv_obj_set_width(label, width() - PAD_MEDIUM * 2 - 2);
if (textFlags & CENTERED)
etx_obj_add_style(label, styles->text_align_center, LV_PART_MAIN);
else
etx_obj_add_style(label, styles->text_align_right, LV_PART_MAIN);
update();
}
void NumberEdit::openEdit()
{
if (edit == nullptr) {
edit = new NumberArea(
this,
{-(PAD_MEDIUM + 2), -(PAD_TINY + 2),
lv_obj_get_width(lvobj), lv_obj_get_height(lvobj)});
edit->setChangeHandler([=]() {
update();
if (edit->hasFocus())
lv_group_focus_obj(lvobj);
edit->hide();
});
}
edit->update();
edit->show();
lv_group_focus_obj(edit->getLvObj());
lv_indev_type_t indev_type =
lv_indev_get_type(lv_indev_get_act());
if (indev_type == LV_INDEV_TYPE_POINTER) {
edit->openKeyboard();
} else {
edit->directEdit();
}
lv_obj_add_state(lvobj, LV_STATE_FOCUSED);
}
void NumberEdit::update()
{
if (_getValue == nullptr) return;
currentValue = _getValue();
updateDisplay();
}
std::string NumberEdit::getDisplayVal()
{
std::string str;
if (displayFunction != nullptr) {
str = displayFunction(currentValue);
} else if (!zeroText.empty() && currentValue == 0) {
str = zeroText;
} else {
str = formatNumberAsString(currentValue, textFlags, 0, prefix.c_str(),
suffix.c_str());
}
return str;
}
void NumberEdit::updateDisplay()
{
setText(getDisplayVal());
}
void NumberEdit::setValue(int value)
{
auto newValue = limit(vmin, value, vmax);
if (newValue != currentValue) {
currentValue = newValue;
if (_setValue != nullptr) {
_setValue(currentValue);
}
}
updateDisplay();
if (edit) edit->update();
}
-119
View File
@@ -1,119 +0,0 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* libopenui - https://github.com/opentx/libopenui
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#pragma once
#include "form.h"
#include "button.h"
class NumberArea;
class NumberEdit : public TextButton
{
public:
NumberEdit(Window* parent, const rect_t& rect, int vmin, int vmax,
std::function<int()> getValue,
std::function<void(int)> setValue = nullptr,
LcdFlags textFlags = 0);
#if defined(DEBUG_WINDOWS)
std::string getName() const override
{
return "NumberEdit";
}
#endif
virtual void update();
int32_t getMax() const { return vmax; }
void setMin(int value) { vmin = value; }
void setMax(int value) { vmax = value; }
void setDefault(int value) { vdefault = value; }
void setStep(int value) { step = value; }
void setFastStep(int value) { fastStep = value; }
void setAccelFactor(int value) { accelFactor = value; }
void setValue(int value);
void setPrefix(std::string value)
{
prefix = std::move(value);
update();
}
void setSuffix(std::string value)
{
suffix = std::move(value);
update();
}
void setZeroText(std::string value)
{
zeroText = std::move(value);
update();
}
void setAvailableHandler(std::function<bool(int)> handler)
{
isValueAvailable = std::move(handler);
}
void setDisplayHandler(std::function<std::string(int value)> function)
{
displayFunction = std::move(function);
update();
}
void setSetValueHandler(std::function<void(int)> handler)
{
_setValue = std::move(handler);
}
void setGetValueHandler(std::function<int()> handler)
{
_getValue = std::move(handler);
}
int32_t getValue() const { return _getValue != nullptr ? _getValue() : 0; }
static LAYOUT_VAL(DEF_W, 100, 100)
protected:
friend class NumberArea;
NumberArea* edit = nullptr;
std::function<int()> _getValue;
std::function<void(int)> _setValue;
int vdefault = 0;
int vmin;
int vmax;
int step = 1;
int fastStep = 10;
int accelFactor = 4;
int currentValue;
std::string prefix;
std::string suffix;
std::string zeroText;
std::function<std::string(int)> displayFunction;
std::function<bool(int)> isValueAvailable;
std::string getDisplayVal();
void updateDisplay();
void openEdit();
};
-63
View File
@@ -1,63 +0,0 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* libopenui - https://github.com/opentx/libopenui
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "progress.h"
#include "themes/etx_lv_theme.h"
static void etx_bar_constructor(const lv_obj_class_t* class_p, lv_obj_t* obj)
{
etx_solid_bg(obj, COLOR_THEME_SECONDARY2_INDEX);
etx_obj_add_style(obj, styles->rounded, LV_PART_MAIN);
etx_solid_bg(obj, COLOR_THEME_SECONDARY1_INDEX, LV_PART_INDICATOR);
etx_obj_add_style(obj, styles->rounded, LV_PART_INDICATOR);
}
static const lv_obj_class_t bar_class = {
.base_class = &lv_bar_class,
.constructor_cb = etx_bar_constructor,
.destructor_cb = nullptr,
.user_data = nullptr,
.event_cb = nullptr,
.width_def = LV_PCT(100),
.height_def = EdgeTxStyles::UI_ELEMENT_HEIGHT,
.editable = LV_OBJ_CLASS_EDITABLE_INHERIT,
.group_def = LV_OBJ_CLASS_GROUP_DEF_INHERIT,
.instance_size = sizeof(lv_bar_t),
};
static lv_obj_t* bar_create(lv_obj_t* parent)
{
return etx_create(&bar_class, parent);
}
Progress::Progress(Window* parent, const rect_t& rect) : Window(parent, rect)
{
bar = bar_create(lvobj);
lv_bar_set_range(bar, 0, 100);
setValue(0);
}
void Progress::setValue(int newValue)
{
if (newValue != value) {
value = newValue;
lv_bar_set_value(bar, value, LV_ANIM_OFF);
}
}
-37
View File
@@ -1,37 +0,0 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* libopenui - https://github.com/opentx/libopenui
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#pragma once
#include "window.h"
class Progress : public Window
{
public:
Progress(Window* parent, const rect_t& rect);
#if defined(DEBUG_WINDOWS)
std::string getName() const override { return "Progress"; }
#endif
void setValue(int newValue);
protected:
int value = -1;
lv_obj_t* bar;
};
-159
View File
@@ -1,159 +0,0 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* libopenui - https://github.com/opentx/libopenui
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "slider.h"
#include "form.h"
#include "themes/etx_lv_theme.h"
// Slider
const lv_style_const_prop_t slider_knob_props[] = {
LV_STYLE_CONST_PAD_TOP(9), LV_STYLE_CONST_PAD_BOTTOM(9),
LV_STYLE_CONST_PAD_LEFT(4), LV_STYLE_CONST_PAD_RIGHT(4),
LV_STYLE_CONST_RADIUS(4), LV_STYLE_CONST_BORDER_WIDTH(2),
LV_STYLE_PROP_INV,
};
LV_STYLE_CONST_MULTI_INIT(slider_knob, slider_knob_props);
static void slider_constructor(const lv_obj_class_t* class_p, lv_obj_t* obj)
{
etx_solid_bg(obj, COLOR_THEME_SECONDARY1_INDEX);
etx_obj_add_style(obj, styles->rounded, LV_PART_MAIN);
etx_bg_color(obj, COLOR_THEME_FOCUS_INDEX, LV_PART_MAIN | LV_STATE_FOCUSED);
etx_bg_color(obj, COLOR_THEME_EDIT_INDEX,
LV_PART_MAIN | LV_STATE_FOCUSED | LV_STATE_EDITED);
etx_bg_color(obj, COLOR_THEME_FOCUS_INDEX,
LV_PART_INDICATOR | LV_STATE_FOCUSED);
etx_bg_color(obj, COLOR_THEME_EDIT_INDEX,
LV_PART_INDICATOR | LV_STATE_FOCUSED | LV_STATE_EDITED);
etx_obj_add_style(obj, styles->bg_opacity_cover, LV_PART_KNOB);
etx_obj_add_style(obj, slider_knob, LV_PART_KNOB);
etx_obj_add_style(obj, styles->border_color[COLOR_THEME_SECONDARY1_INDEX], LV_PART_MAIN);
etx_obj_add_style(obj, styles->border_color[COLOR_THEME_FOCUS_INDEX],
LV_PART_KNOB | LV_STATE_FOCUSED);
}
static const lv_obj_class_t slider_class = {
.base_class = &lv_slider_class,
.constructor_cb = slider_constructor,
.destructor_cb = nullptr,
.user_data = nullptr,
.event_cb = nullptr,
.width_def = 0,
.height_def = 8,
.editable = LV_OBJ_CLASS_EDITABLE_INHERIT,
.group_def = LV_OBJ_CLASS_GROUP_DEF_INHERIT,
.instance_size = sizeof(lv_slider_t),
};
static lv_obj_t* slider_create(lv_obj_t* parent)
{
return etx_create(&slider_class, parent);
}
static void slider_changed_cb(lv_event_t* e)
{
if (lv_event_get_code(e) == LV_EVENT_VALUE_CHANGED) {
Slider* sl = (Slider*)lv_event_get_user_data(e);
if (sl != nullptr) {
lv_obj_t* target = lv_event_get_target(e);
sl->setValue(lv_slider_get_value(target));
}
}
}
Slider::Slider(Window* parent, coord_t width, int32_t vmin, int32_t vmax,
std::function<int()> getValue,
std::function<void(int)> setValue) :
Window(parent, {0, 0, width, 32}, window_create),
vmin(vmin),
vmax(vmax),
_getValue(std::move(getValue)),
_setValue(std::move(setValue))
{
padTop(9);
padLeft(8);
padRight(8);
slider = (new FormField(this, rect_t{}, slider_create))->getLvObj();
lv_obj_set_width(slider, lv_pct(100));
lv_obj_add_event_cb(slider, slider_changed_cb, LV_EVENT_VALUE_CHANGED, this);
lv_slider_set_range(slider, vmin, vmax);
lv_obj_add_event_cb(lvobj, Slider::on_draw, LV_EVENT_DRAW_MAIN_BEGIN,
nullptr);
int range = vmax - vmin;
if (range < 10) {
tickPts = new lv_obj_t*[range - 1];
for (int n = 1; n < range; n += 1) {
lv_obj_t* p = lv_obj_create(lvobj);
lv_obj_set_size(p, 2, 6);
etx_solid_bg(p, COLOR_THEME_PRIMARY2_INDEX);
tickPts[n - 1] = p;
}
}
update();
}
void Slider::on_draw(lv_event_t* e)
{
auto dlg = (Slider*)lv_obj_get_user_data(lv_event_get_target(e));
if (dlg) dlg->delayedInit();
}
void Slider::delayedInit()
{
if (!loaded) {
loaded = true;
coord_t w = lv_obj_get_width(lvobj) - 16;
coord_t x = -1;
int range = vmax - vmin;
if (range < 10) {
for (int n = 1; n < range; n += 1) {
lv_obj_set_pos(tickPts[n - 1], x + (w * n) / range, 1);
}
}
}
}
void Slider::update()
{
if (_getValue != nullptr) {
// Fix for lv_slider_set_value not working when using the rotary encoder to
// update value
auto bar = (lv_bar_t*)slider;
bar->cur_value_anim.anim_state = -1;
bar->cur_value_anim.anim_end = _getValue();
lv_slider_set_value(slider, _getValue(), LV_ANIM_OFF);
}
}
void Slider::deleteLater(bool detach, bool trash)
{
if (!deleted()) {
if (tickPts) delete tickPts;
Window::deleteLater(detach, trash);
}
}
-54
View File
@@ -1,54 +0,0 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* libopenui - https://github.com/opentx/libopenui
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#pragma once
#include "window.h"
class Slider : public Window
{
public:
Slider(Window* parent, coord_t width, int32_t vmin, int32_t vmax,
std::function<int()> getValue, std::function<void(int)> setValue);
#if defined(DEBUG_WINDOWS)
std::string getName() const override { return "Slider"; }
#endif
void setValue(int value)
{
if (_setValue != nullptr) _setValue(limit(vmin, value, vmax));
}
void update();
protected:
int vmin;
int vmax;
lv_obj_t* slider;
lv_obj_t** tickPts = nullptr;
bool loaded = false;
std::function<int()> _getValue;
std::function<void(int)> _setValue;
static void on_draw(lv_event_t* e);
void delayedInit();
void deleteLater(bool detach, bool trash) override;
};
-368
View File
@@ -1,368 +0,0 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* libopenui - https://github.com/opentx/libopenui
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "static.h"
#include "bitmaps.h"
#include "libopenui/thirdparty/lz4/lz4.h"
#include "sdcard.h"
#include "themes/etx_lv_theme.h"
//-----------------------------------------------------------------------------
StaticText::StaticText(Window* parent, const rect_t& rect, std::string txt,
LcdColorIndex color, LcdFlags textFlags) :
Window(parent, rect, lv_label_create), text(std::move(txt))
{
setTextFlag(textFlags);
lv_obj_clear_flag(lvobj, LV_OBJ_FLAG_CLICK_FOCUSABLE);
etx_font(lvobj, FONT_INDEX(textFlags));
etx_txt_color(lvobj, color);
if (textFlags & CENTERED)
etx_obj_add_style(lvobj, styles->text_align_center, LV_PART_MAIN);
else if (textFlags & RIGHT)
etx_obj_add_style(lvobj, styles->text_align_right, LV_PART_MAIN);
lv_obj_set_style_grid_cell_x_align(lvobj, LV_GRID_ALIGN_STRETCH,
LV_PART_MAIN);
lv_label_set_text(lvobj, text.c_str());
if (rect.h == 0) lv_obj_set_height(lvobj, LV_SIZE_CONTENT);
}
#if defined(DEBUG_WINDOWS)
std::string StaticText::getName() const
{
return "StaticText \"" + text.substr(0, 20) + "\"";
}
#endif
void StaticText::setText(std::string value)
{
if (text != value) {
text = std::move(value);
if (lvobj) lv_label_set_text(lvobj, text.c_str());
}
}
const std::string& StaticText::getText() const { return text; }
//-----------------------------------------------------------------------------
template <>
void DynamicNumber<uint32_t>::updateText()
{
if (lvobj) {
const char* p = prefix ? prefix : "";
const char* s = suffix ? suffix : "";
if ((textFlags & PREC2) == PREC2) {
lv_label_set_text_fmt(lvobj, "%s%" PRIu32 ".%02" PRIu32 "%s", p,
value / 100, value % 100, s);
} else if (textFlags & PREC1) {
lv_label_set_text_fmt(lvobj, "%s%" PRIu32 ".%01" PRIu32 "%s", p,
value / 10, value % 10, s);
} else {
lv_label_set_text_fmt(lvobj, "%s%" PRIu32 "%s", p, value, s);
}
}
}
template <>
void DynamicNumber<int32_t>::updateText()
{
if (lvobj) {
const char* p = prefix ? prefix : "";
const char* s = suffix ? suffix : "";
if ((textFlags & PREC2) == PREC2) {
lv_label_set_text_fmt(lvobj, "%s%" PRId32 ".%02" PRIu32 "%s", p,
value / 100, (uint32_t)abs(value % 100), s);
} else if (textFlags & PREC1) {
lv_label_set_text_fmt(lvobj, "%s%" PRId32 ".%01" PRIu32 "%s", p,
value / 10, (uint32_t)abs(value % 10), s);
} else {
lv_label_set_text_fmt(lvobj, "%s%" PRId32 "%s", p, value, s);
}
}
}
template <>
void DynamicNumber<uint16_t>::updateText()
{
if (lvobj) {
const char* p = prefix ? prefix : "";
const char* s = suffix ? suffix : "";
if ((textFlags & PREC2) == PREC2) {
lv_label_set_text_fmt(lvobj, "%s%" PRIu16 ".%02" PRIu16 "%s", p,
(uint16_t)(value / 100), (uint16_t)(value % 100),
s);
} else if (textFlags & PREC1) {
lv_label_set_text_fmt(lvobj, "%s%" PRIu16 ".%01" PRIu16 "%s", p,
(uint16_t)(value / 10), (uint16_t)(value % 10), s);
} else {
lv_label_set_text_fmt(lvobj, "%s%" PRIu16 "%s", p, value, s);
}
}
}
template <>
void DynamicNumber<int16_t>::updateText()
{
if (lvobj) {
const char* p = prefix ? prefix : "";
const char* s = suffix ? suffix : "";
if ((textFlags & PREC2) == PREC2) {
lv_label_set_text_fmt(lvobj, "%s%" PRId16 ".%02" PRIu16 "%s", p,
(int16_t)(value / 100), (uint16_t)abs(value % 100),
s);
} else if (textFlags & PREC1) {
lv_label_set_text_fmt(lvobj, "%s%" PRId16 ".%01" PRIu16 "%s", p,
(int16_t)(value / 10), (uint16_t)abs(value % 10),
s);
} else {
lv_label_set_text_fmt(lvobj, "%s%" PRId16 "%s", p, value, s);
}
}
}
//-----------------------------------------------------------------------------
StaticIcon::StaticIcon(Window* parent, coord_t x, coord_t y, EdgeTxIcon icon,
LcdColorIndex color) :
Window(parent, rect_t{x, y, 0, 0}, lv_canvas_create),
currentColor(color)
{
setWindowFlag(NO_FOCUS);
lv_obj_clear_flag(lvobj, LV_OBJ_FLAG_CLICKABLE);
setIcon(icon);
etx_img_color(lvobj, currentColor, LV_PART_MAIN);
}
StaticIcon::StaticIcon(Window* parent, coord_t x, coord_t y, const char* filename,
LcdColorIndex color) :
Window(parent, rect_t{x, y, 0, 0}, lv_canvas_create),
currentColor(color)
{
setWindowFlag(NO_FOCUS);
lv_obj_clear_flag(lvobj, LV_OBJ_FLAG_CLICKABLE);
auto bm = BitmapBuffer::loadBitmap(filename, BMP_RGB565);
if (bm) {
size_t size;
mask = bm->to8bitMask(&size);
if (mask) {
setSize(mask->width, mask->height);
lv_canvas_set_buffer(lvobj, (void*)mask->data, mask->width, mask->height,
LV_IMG_CF_ALPHA_8BIT);
}
delete bm;
}
etx_img_color(lvobj, currentColor, LV_PART_MAIN);
}
void StaticIcon::deleteLater(bool detach, bool trash)
{
if (_deleted) return;
if (mask) free(mask);
mask = nullptr;
}
void StaticIcon::setColor(LcdColorIndex color)
{
if (currentColor != color) {
etx_img_color(lvobj, color, LV_PART_MAIN);
currentColor = color;
}
}
void StaticIcon::setIcon(EdgeTxIcon icon)
{
auto newMask = getBuiltinIcon(icon);
setSize(newMask->width, newMask->height);
lv_canvas_set_buffer(lvobj, (void*)newMask->data, newMask->width, newMask->height,
LV_IMG_CF_ALPHA_8BIT);
}
void StaticIcon::center(coord_t w, coord_t h)
{
setPos((w - width()) / 2, (h - height()) / 2);
}
//-----------------------------------------------------------------------------
// Display image from file system using LVGL with LVGL scaling
// - LVGL scaling is slow so don't use this if there are many images
StaticImage::StaticImage(Window* parent, const rect_t& rect,
const char* filename, bool fillFrame, bool dontEnlarge) :
Window(parent, rect), fillFrame(fillFrame), dontEnlarge(dontEnlarge)
{
setWindowFlag(NO_FOCUS);
lv_obj_clear_flag(lvobj, LV_OBJ_FLAG_CLICKABLE);
if (!filename) filename = "";
setSource(filename);
}
void StaticImage::setSource(std::string filename)
{
if (!filename.empty()) {
std::string fullpath = std::string("A" PATH_SEPARATOR) + filename;
if (!image) image = lv_img_create(lvobj);
lv_obj_set_pos(image, 0, 0);
lv_obj_set_size(image, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
lv_obj_center(image);
lv_img_set_src(image, fullpath.c_str());
if (!hasImage()) {
// Failed to load
TRACE("could not load image '%s'", filename.c_str());
lv_obj_del(image);
image = nullptr;
}
setZoom();
} else {
clearSource();
}
}
void StaticImage::clearSource()
{
if (image) lv_obj_del(image);
image = nullptr;
}
bool StaticImage::hasImage() const
{
lv_img_t* img = (lv_img_t*)image;
return img && img->w && img->h;
}
void StaticImage::setZoom()
{
lv_img_t* img = (lv_img_t*)image;
if (img && img->w && img->h) {
uint16_t zw = (width() * 256) / img->w;
uint16_t zh = (height() * 256) / img->h;
uint16_t z = fillFrame ? max(zw, zh) : min(zw, zh);
if (dontEnlarge) z = min(z, (uint16_t)256);
lv_img_set_zoom(image, z);
}
}
//-----------------------------------------------------------------------------
// Display image from file system with software scaling
// - uglier but much faster than LVGL scaling
StaticBitmap::StaticBitmap(Window* parent, const rect_t& rect,
const char* filename) :
Window(parent, rect)
{
setWindowFlag(NO_FOCUS);
lv_obj_clear_flag(lvobj, LV_OBJ_FLAG_CLICKABLE);
setSource(filename);
}
void StaticBitmap::setSource(const char *filename)
{
if (filename) {
if (img) delete img;
img = BitmapBuffer::loadBitmap(filename, BMP_ARGB4444);
if (img) {
img->resizeToLVGL(width(), height());
if (canvas) lv_obj_del(canvas);
canvas = lv_canvas_create(lvobj);
lv_obj_center(canvas);
lv_canvas_set_buffer(canvas, img->getData(), img->width(), img->height(),
LV_IMG_CF_TRUE_COLOR_ALPHA);
}
}
}
StaticBitmap::~StaticBitmap()
{
if (img) delete img;
}
bool StaticBitmap::hasImage() const
{
return img && canvas;
}
//-----------------------------------------------------------------------------
StaticLZ4Image::StaticLZ4Image(Window* parent, coord_t x, coord_t y,
const LZ4Bitmap* lz4Bitmap) :
Window(parent, {x, y, lz4Bitmap->width, lz4Bitmap->height},
lv_canvas_create)
{
setWindowFlag(NO_FOCUS);
lv_obj_clear_flag(lvobj, LV_OBJ_FLAG_CLICKABLE);
// Convert ARGB4444 to LV_IMG_CF_TRUE_COLOR_ALPHA
uint16_t w = lz4Bitmap->width;
uint16_t h = lz4Bitmap->height;
uint32_t pixels = w * h;
uint32_t size = (pixels + 1) & 0xFFFFFFFE;
imgData = (uint8_t*)lv_mem_alloc(size * 3);
uint16_t* decompData = (uint16_t*)(imgData + size);
LZ4_decompress_safe((const char*)lz4Bitmap->data, (char*)decompData,
lz4Bitmap->compressedSize, pixels * sizeof(uint16_t));
uint8_t* dest = imgData;
for (uint32_t i = 0; i < pixels; i += 1) {
uint16_t c = *decompData;
ARGB_SPLIT(c, a, r, g, b);
c = RGB_JOIN(r * 2, g * 4, b * 2);
*dest++ = c & 0xFF;
*dest++ = c >> 8;
*dest++ = (a * 255) / 15;
decompData += 1;
}
lv_canvas_set_buffer(lvobj, imgData, w, h, LV_IMG_CF_TRUE_COLOR_ALPHA);
}
void StaticLZ4Image::deleteLater(bool detach, bool trash)
{
if (!deleted()) {
if (imgData) lv_mem_free(imgData);
imgData = nullptr;
Window::deleteLater(detach, trash);
}
}
//-----------------------------------------------------------------------------
QRCode::QRCode(Window *parent, coord_t x, coord_t y, coord_t sz, std::string data,
LcdFlags color, LcdFlags bgColor) :
Window(parent, {x, y, sz, sz})
{
auto qr = lv_qrcode_create(lvobj, sz, makeLvColor(color), makeLvColor(bgColor));
lv_qrcode_update(qr, data.c_str(), data.length());
}
-219
View File
@@ -1,219 +0,0 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* libopenui - https://github.com/opentx/libopenui
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#pragma once
#include "bitmaps.h"
#include "window.h"
struct LZ4Bitmap;
class BitmapBuffer;
//-----------------------------------------------------------------------------
class StaticText : public Window
{
public:
StaticText(Window *parent, const rect_t &rect, std::string text = "",
LcdColorIndex color = COLOR_THEME_PRIMARY1_INDEX, LcdFlags textFlags = 0);
#if defined(DEBUG_WINDOWS)
std::string getName() const override;
#endif
void setText(std::string value);
const std::string &getText() const;
protected:
std::string text;
};
#define Subtitle(parent, text) \
StaticText(parent, rect_t{}, text, COLOR_THEME_PRIMARY1_INDEX, FONT(BOLD))
//-----------------------------------------------------------------------------
class DynamicText : public StaticText
{
public:
DynamicText(Window *parent, const rect_t &rect,
std::function<std::string()> textHandler,
LcdColorIndex color = COLOR_THEME_PRIMARY1_INDEX, LcdFlags textFlags = 0) :
StaticText(parent, rect, "", color, textFlags),
textHandler(std::move(textHandler))
{
}
void checkEvents() override
{
StaticText::checkEvents();
setText(textHandler());
}
protected:
std::function<std::string()> textHandler;
};
template <class T>
class DynamicNumber : public StaticText
{
public:
DynamicNumber(Window *parent, const rect_t &rect,
std::function<T()> numberHandler,
LcdColorIndex color = COLOR_THEME_PRIMARY1_INDEX, LcdFlags textFlags = 0,
const char *prefix = nullptr, const char *suffix = nullptr) :
StaticText(parent, rect, "", color, textFlags),
numberHandler(std::move(numberHandler)),
prefix(prefix),
suffix(suffix)
{
value = this->numberHandler();
updateText();
}
void checkEvents() override
{
T newValue = numberHandler();
if (value != newValue) {
value = newValue;
updateText();
}
}
void setPrefix(const char *value)
{
prefix = value;
updateText();
}
void setSuffix(const char *value)
{
suffix = value;
updateText();
}
protected:
T value = 0;
std::function<T()> numberHandler;
const char *prefix;
const char *suffix;
void updateText();
};
//-----------------------------------------------------------------------------
class StaticIcon : public Window
{
public:
StaticIcon(Window *parent, coord_t x, coord_t y, EdgeTxIcon icon,
LcdColorIndex color);
StaticIcon(Window *parent, coord_t x, coord_t y, const char* filename,
LcdColorIndex color);
void deleteLater(bool detach = true, bool trash = true) override;
#if defined(DEBUG_WINDOWS)
std::string getName() const override { return "StaticIcon"; }
#endif
void setColor(LcdColorIndex color);
void setIcon(EdgeTxIcon icon);
void center(coord_t w, coord_t h);
protected:
LcdColorIndex currentColor;
MaskBitmap* mask = nullptr;
};
//-----------------------------------------------------------------------------
class StaticImage : public Window
{
public:
StaticImage(Window *parent, const rect_t &rect,
const char *filename = nullptr, bool fillFrame = false, bool dontEnlarge = false);
#if defined(DEBUG_WINDOWS)
std::string getName() const override { return "StaticImage"; }
#endif
void setZoom();
void setSource(std::string filename);
void clearSource();
bool hasImage() const;
protected:
bool fillFrame;
bool dontEnlarge = false;
lv_obj_t *image = nullptr;
};
//-----------------------------------------------------------------------------
class StaticBitmap : public Window
{
public:
StaticBitmap(Window *parent, const rect_t &rect,
const char *filename = nullptr);
~StaticBitmap();
#if defined(DEBUG_WINDOWS)
std::string getName() const override { return "StaticBitmap"; }
#endif
void setSource(const char *filename);
bool hasImage() const;
protected:
lv_obj_t *canvas = nullptr;
BitmapBuffer *img = nullptr;
};
//-----------------------------------------------------------------------------
class StaticLZ4Image : public Window
{
public:
StaticLZ4Image(Window *parent, coord_t x, coord_t y,
const LZ4Bitmap *lz4Bitmap);
#if defined(DEBUG_WINDOWS)
std::string getName() const override { return "StaticILZ4mage"; }
#endif
protected:
uint8_t *imgData = nullptr;
void deleteLater(bool detach, bool trash) override;
};
//-----------------------------------------------------------------------------
class QRCode : public Window
{
public:
QRCode(Window *parent, coord_t x, coord_t y, coord_t sz, std::string data,
LcdFlags color = COLOR_THEME_SECONDARY1, LcdFlags bgColor = COLOR_THEME_SECONDARY3);
#if defined(DEBUG_WINDOWS)
std::string getName() const override { return "QRCode"; }
#endif
protected:
};
-284
View File
@@ -1,284 +0,0 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* libopenui - https://github.com/opentx/libopenui
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "table.h"
#include "themes/etx_lv_theme.h"
static void table_event(const lv_obj_class_t* class_p, lv_event_t* e);
// Table
const lv_style_const_prop_t table_cell_props[] = {
LV_STYLE_CONST_BORDER_WIDTH(1),
LV_STYLE_CONST_BORDER_SIDE(LV_BORDER_SIDE_TOP | LV_BORDER_SIDE_BOTTOM),
LV_STYLE_CONST_PAD_TOP(7), LV_STYLE_CONST_PAD_BOTTOM(7),
LV_STYLE_CONST_PAD_LEFT(4), LV_STYLE_CONST_PAD_RIGHT(4),
LV_STYLE_PROP_INV,
};
LV_STYLE_CONST_MULTI_INIT(table_cell, table_cell_props);
static void table_constructor(const lv_obj_class_t* class_p, lv_obj_t* obj)
{
etx_obj_add_style(obj, styles->pad_zero, LV_PART_MAIN);
etx_scrollbar(obj);
etx_solid_bg(obj, COLOR_THEME_PRIMARY2_INDEX, LV_PART_ITEMS);
etx_txt_color(obj, COLOR_THEME_PRIMARY1_INDEX, LV_PART_ITEMS);
etx_obj_add_style(obj, table_cell, LV_PART_ITEMS);
etx_obj_add_style(obj, styles->border_color[COLOR_THEME_SECONDARY2_INDEX], LV_PART_ITEMS);
etx_bg_color(obj, COLOR_THEME_FOCUS_INDEX, LV_PART_ITEMS | LV_STATE_EDITED);
etx_txt_color(obj, COLOR_THEME_PRIMARY2_INDEX, LV_PART_ITEMS | LV_STATE_EDITED);
}
static const lv_obj_class_t table_class = {
.base_class = &lv_table_class,
.constructor_cb = table_constructor,
.destructor_cb = nullptr,
.user_data = nullptr,
.event_cb = table_event,
.width_def = 0,
.height_def = 0,
.editable = LV_OBJ_CLASS_EDITABLE_TRUE,
.group_def = LV_OBJ_CLASS_GROUP_DEF_TRUE,
.instance_size = sizeof(lv_table_t),
};
static lv_obj_t* table_create(lv_obj_t* parent)
{
return etx_create(&table_class, parent);
}
static void table_event(const lv_obj_class_t* class_p, lv_event_t* e)
{
lv_obj_t* obj = lv_event_get_target(e);
if (obj) {
TableField* tf = (TableField*)lv_obj_get_user_data(obj);
if (tf) {
lv_event_code_t code = lv_event_get_code(e);
switch (code) {
case LV_EVENT_VALUE_CHANGED: {
uint16_t row;
uint16_t col;
lv_table_get_selected_cell(obj, &row, &col);
if (row != LV_TABLE_CELL_NONE && col != LV_TABLE_CELL_NONE) {
lv_indev_type_t indev_type = lv_indev_get_type(lv_indev_get_act());
if (indev_type == LV_INDEV_TYPE_ENCODER) {
// Encoder send VALUE_CHANGED when selection changes
tf->adjustScroll();
tf->onSelected(row, col);
} else {
// Otherwise it's a click
if (lv_group_get_editing((lv_group_t*)lv_obj_get_group(obj)) ||
indev_type == LV_INDEV_TYPE_POINTER) {
tf->onPress(row, col);
} else {
tf->onClicked();
}
// Note: VALUE_CHANGED is generated on RELEASED
//
// It must be avoided that CLICKED be generated afterwards
// in case the object has been deleted meanwhile and to
// avoid onClicked() being called.
//
lv_indev_reset(lv_indev_get_act(), nullptr);
}
}
} break;
case LV_EVENT_DRAW_PART_BEGIN: {
lv_obj_draw_part_dsc_t* dsc = lv_event_get_draw_part_dsc(e);
if (dsc->part == LV_PART_ITEMS) {
uint16_t cols = lv_table_get_col_cnt(obj);
if (cols) {
uint16_t row = dsc->id / cols;
uint16_t col = dsc->id % cols;
tf->onDrawBegin(row, col, dsc);
}
}
} break;
case LV_EVENT_DRAW_PART_END: {
lv_obj_draw_part_dsc_t* dsc = lv_event_get_draw_part_dsc(e);
if (dsc->part == LV_PART_ITEMS) {
uint16_t cols = lv_table_get_col_cnt(obj);
if (cols) {
uint16_t row = dsc->id / cols;
uint16_t col = dsc->id % cols;
tf->onDrawEnd(row, col, dsc);
}
}
} break;
case LV_EVENT_DRAW_POST: {
bool has_focus = lv_obj_has_state(obj, LV_STATE_FOCUS_KEY);
bool is_edited =
lv_group_get_editing((lv_group_t*)lv_obj_get_group(obj));
if (has_focus && !is_edited) {
lv_draw_ctx_t* draw_ctx = lv_event_get_draw_ctx(e);
lv_draw_rect_dsc_t rect_dsc;
lv_draw_rect_dsc_init(&rect_dsc);
rect_dsc.bg_opa = LV_OPA_TRANSP;
rect_dsc.bg_img_opa = LV_OPA_TRANSP;
rect_dsc.outline_opa = LV_OPA_TRANSP;
rect_dsc.shadow_opa = LV_OPA_TRANSP;
rect_dsc.border_color = makeLvColor(COLOR_THEME_FOCUS);
rect_dsc.border_opa = LV_OPA_100;
rect_dsc.border_width = lv_dpx(2);
lv_area_t coords;
lv_area_copy(&coords, &obj->coords);
lv_draw_rect(draw_ctx, &rect_dsc, &coords);
}
} break;
case LV_EVENT_RELEASED: {
lv_table_t* table = (lv_table_t*)obj;
/*From lv_table.c: handler for LV_EVENT_RELEASED*/
lv_obj_invalidate(obj);
lv_indev_t* indev = lv_indev_get_act();
lv_obj_t* scroll_obj = lv_indev_get_scroll_obj(indev);
if (table->col_act != LV_TABLE_CELL_NONE &&
table->row_act != LV_TABLE_CELL_NONE && scroll_obj == NULL) {
lv_event_send(obj, LV_EVENT_VALUE_CHANGED, NULL);
}
return;
} break;
default:
break;
}
}
}
/*Call the ancestor's event handler*/
lv_obj_event_base(&table_class, e);
}
TableField::TableField(Window* parent, const rect_t& rect) :
Window(parent, rect, table_create)
{
setWindowFlag(OPAQUE);
etx_scrollbar(lvobj);
lv_table_set_col_cnt(lvobj, 1);
}
void TableField::setRowCount(uint16_t rows)
{
lv_table_set_row_cnt(lvobj, rows);
}
uint16_t TableField::getRowCount() const { return lv_table_get_row_cnt(lvobj); }
void TableField::setColumnWidth(uint16_t col, coord_t w)
{
lv_table_set_col_width(lvobj, col, w);
}
void TableField::select(uint16_t row, uint16_t col, bool force)
{
lv_table_t* table = (lv_table_t*)lvobj;
if (!force && table->row_act == row && table->col_act == row) return;
if (row >= table->row_cnt || col >= table->col_cnt) {
table->col_act = LV_TABLE_CELL_NONE;
table->row_act = LV_TABLE_CELL_NONE;
} else {
table->row_act = row;
table->col_act = col;
}
lv_obj_invalidate(lvobj);
adjustScroll();
}
void TableField::adjustScroll()
{
lv_table_t* table = (lv_table_t*)lvobj;
// only vertical scroll for now
lv_coord_t h_before = 0;
for (uint16_t i = 0; i < table->row_act; i++) h_before += table->row_h[i];
lv_coord_t row_h = table->row_h[table->row_act];
lv_coord_t scroll_y = lv_obj_get_scroll_y(lvobj);
lv_obj_update_layout(lvobj);
lv_coord_t h = lv_obj_get_height(lvobj);
lv_coord_t diff_y = 0;
if (h_before < scroll_y) {
diff_y = scroll_y - h_before;
} else if (scroll_y + h < h_before + row_h) {
diff_y = scroll_y + h - h_before - row_h;
} else {
return;
}
lv_obj_scroll_by_bounded(lvobj, 0, diff_y, LV_ANIM_OFF);
}
void TableField::selectNext(int16_t dir)
{
lv_table_t* table = (lv_table_t*)lvobj;
if (table->col_act == LV_TABLE_CELL_NONE ||
table->row_act == LV_TABLE_CELL_NONE) {
if (table->col_cnt > 0 && table->row_cnt > 0) {
table->col_act = 0;
table->row_act = 0;
}
} else {
table->col_act += dir;
if (table->col_act >= table->col_cnt) {
table->col_act = 0;
table->row_act += dir;
if (table->row_act >= table->row_cnt) {
table->col_act = LV_TABLE_CELL_NONE;
table->row_act = LV_TABLE_CELL_NONE;
// wrap around
if (table->col_cnt > 0 && table->row_cnt > 0) {
if (dir < 0) {
table->col_act = table->col_cnt - 1;
table->row_act = table->row_cnt - 1;
} else {
table->col_act = 0;
table->row_act = 0;
}
}
}
}
}
lv_obj_invalidate(lvobj);
adjustScroll();
}
void TableField::onEvent(event_t event)
{
if (event == EVT_ROTARY_RIGHT) {
selectNext(1);
} else if (event == EVT_ROTARY_LEFT) {
selectNext(-1);
} else {
Window::onEvent(event);
}
}
-49
View File
@@ -1,49 +0,0 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* libopenui - https://github.com/opentx/libopenui
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#pragma once
#include "window.h"
class TableField : public Window
{
public:
TableField(Window* parent, const rect_t& rect);
#if defined(DEBUG_WINDOWS)
std::string getName() const override { return "Table"; }
#endif
void setRowCount(uint16_t rows);
uint16_t getRowCount() const;
void setColumnWidth(uint16_t col, coord_t w);
void select(uint16_t row, uint16_t col, bool force = false);
virtual void onPress(uint16_t row, uint16_t col) {}
virtual void onSelected(uint16_t row, uint16_t col) {}
virtual void onDrawBegin(uint16_t row, uint16_t col, lv_obj_draw_part_dsc_t* dsc) {}
virtual void onDrawEnd(uint16_t row, uint16_t col, lv_obj_draw_part_dsc_t* dsc) {}
void adjustScroll();
void selectNext(int16_t dir);
protected:
void onEvent(event_t event) override;
};
-192
View File
@@ -1,192 +0,0 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* libopenui - https://github.com/opentx/libopenui
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "textedit.h"
#include "keyboard_text.h"
#include "myeeprom.h"
#include "storage/storage.h"
#include "themes/etx_lv_theme.h"
#if defined(HARDWARE_KEYS)
#include "menu.h"
#endif
class TextArea : public FormField
{
public:
TextArea(Window* parent, const rect_t& rect, char* value, uint8_t length) :
FormField(parent, rect, etx_textarea_create), value(value), length(length)
{
lv_obj_clear_flag(lvobj, LV_OBJ_FLAG_CLICK_FOCUSABLE);
lv_textarea_set_max_length(lvobj, length);
lv_textarea_set_placeholder_text(lvobj, "---");
update();
}
#if defined(DEBUG_WINDOWS)
std::string getName() const override { return "TextArea"; }
#endif
void update()
{
// value may not be null-terminated
std::string txt(value, length);
lv_textarea_set_text(lvobj, txt.c_str());
}
void onClicked() override {
setEditMode(true);
}
void openKeyboard() {
TextKeyboard::show(this);
}
void setCancelHandler(std::function<void(void)> handler)
{
cancelHandler = std::move(handler);
}
protected:
char* value;
uint8_t length;
std::function<void(void)> cancelHandler = nullptr;
void trim()
{
for (int i = length - 1; i >= 0; i--) {
if (value[i] == ' ' || value[i] == '\0')
value[i] = '\0';
else
break;
}
}
void changeEnd(bool forceChanged = false) override
{
if (lvobj == nullptr) return;
bool changed = false;
auto text = lv_textarea_get_text(lvobj);
if (strncmp(value, text, length) != 0) {
changed = true;
}
if (changed || forceChanged) {
strncpy(value, text, length);
trim();
FormField::changeEnd();
} else if (cancelHandler) {
cancelHandler();
}
}
void onCancel() override
{
if (cancelHandler)
cancelHandler();
else
FormField::onCancel();
}
};
/*
The lv_textarea object is slow. To avoid too much overhead on views with multiple
edit fields, the text area is initially displayed as a button. When the button
is pressed, a text area object is created over the top of the button in order
to edit the value.
*/
TextEdit::TextEdit(Window* parent, const rect_t& rect, char* text,
uint8_t length,
std::function<void(void)> updateHandler) :
TextButton(parent, rect, "", [=]() {
openEdit();
return 0;
}),
updateHandler(updateHandler), text(text), length(length)
{
if (rect.w == 0) setWidth(DEF_W);
update();
lv_obj_align(label, LV_ALIGN_OUT_LEFT_MID, 0, PAD_TINY);
}
void TextEdit::update()
{
if (text[0]) {
std::string s(text, length);
setText(s);
} else {
setText("---");
}
}
void TextEdit::openEdit()
{
if (edit == nullptr) {
edit = new TextArea(this,
{-(PAD_MEDIUM + 2), -(PAD_TINY + 2),
lv_obj_get_width(lvobj), lv_obj_get_height(lvobj)},
text, length);
edit->setChangeHandler([=]() {
std::string s(text, length);
setText(s);
if (updateHandler) updateHandler();
lv_group_focus_obj(lvobj);
edit->hide();
});
edit->setCancelHandler([=]() {
lv_group_focus_obj(lvobj);
edit->hide();
});
}
edit->show();
lv_group_focus_obj(edit->getLvObj());
edit->openKeyboard();
lv_obj_add_state(lvobj, LV_STATE_FOCUSED);
}
void TextEdit::preview(bool edited, char* text, uint8_t length)
{
edit = new TextArea(this,
{-(PAD_MEDIUM + 2), -(PAD_TINY + 2), width(), height()},
text, length);
lv_group_focus_obj(edit->getLvObj());
lv_obj_clear_flag(lvobj, LV_OBJ_FLAG_CLICKABLE);
lv_obj_clear_flag(edit->getLvObj(), LV_OBJ_FLAG_CLICKABLE);
lv_obj_clear_flag(lvobj, LV_OBJ_FLAG_CLICK_FOCUSABLE);
lv_obj_add_state(edit->getLvObj(), LV_STATE_FOCUSED);
if (edited) lv_obj_add_state(edit->getLvObj(), LV_STATE_EDITED);
}
ModelTextEdit::ModelTextEdit(Window* parent, const rect_t& rect, char* value,
uint8_t length) :
TextEdit(parent, rect, value, length,
[]() { storageDirty(EE_MODEL); })
{
}
RadioTextEdit::RadioTextEdit(Window* parent, const rect_t& rect, char* value,
uint8_t length) :
TextEdit(parent, rect, value, length,
[]() { storageDirty(EE_GENERAL); })
{
}
-62
View File
@@ -1,62 +0,0 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* libopenui - https://github.com/opentx/libopenui
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#pragma once
#include "form.h"
#include "button.h"
class TextArea;
class TextEdit : public TextButton
{
public:
TextEdit(Window* parent, const rect_t& rect, char* text, uint8_t length,
std::function<void(void)> updateHandler = nullptr);
#if defined(DEBUG_WINDOWS)
std::string getName() const override { return "TextEdit \"" + text + "\""; }
#endif
void preview(bool edited, char* text, uint8_t length);
void update();
static LAYOUT_VAL(DEF_W, 100, 100)
protected:
std::function<void(void)> updateHandler = nullptr;
TextArea* edit = nullptr;
char* text;
uint8_t length;
void openEdit();
};
class ModelTextEdit : public TextEdit
{
public:
ModelTextEdit(Window* parent, const rect_t& rect, char* value,
uint8_t length);
};
class RadioTextEdit : public TextEdit
{
public:
RadioTextEdit(Window* parent, const rect_t& rect, char* value,
uint8_t length);
};
-102
View File
@@ -1,102 +0,0 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* libopenui - https://github.com/opentx/libopenui
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "toggleswitch.h"
#include "themes/etx_lv_theme.h"
// Animation
LV_STYLE_CONST_SINGLE_INIT(anim_fast, LV_STYLE_ANIM_TIME, 120);
// Toggle switch
const lv_style_const_prop_t switch_knob_props[] = {
LV_STYLE_CONST_PAD_TOP(-4), LV_STYLE_CONST_PAD_BOTTOM(-4),
LV_STYLE_CONST_PAD_LEFT(-4), LV_STYLE_CONST_PAD_RIGHT(-4),
LV_STYLE_PROP_INV,
};
LV_STYLE_CONST_MULTI_INIT(switch_knob, switch_knob_props);
static void switch_constructor(const lv_obj_class_t* class_p, lv_obj_t* obj)
{
etx_std_style(obj, LV_PART_MAIN, PAD_ZERO);
etx_obj_add_style(obj, styles->circle, LV_PART_MAIN);
etx_obj_add_style(obj, anim_fast, LV_PART_MAIN);
etx_std_style(obj, LV_PART_INDICATOR, PAD_ZERO);
etx_obj_add_style(obj, styles->circle, LV_PART_INDICATOR);
etx_obj_add_style(obj, styles->bg_opacity_cover, LV_PART_KNOB);
etx_obj_add_style(obj, styles->circle, LV_PART_KNOB);
etx_obj_add_style(obj, switch_knob, LV_PART_KNOB);
etx_bg_color(obj, COLOR_THEME_SECONDARY1_INDEX, LV_PART_KNOB);
etx_obj_add_style(obj, styles->disabled, LV_PART_KNOB | LV_STATE_DISABLED);
}
static const lv_obj_class_t switch_class = {
.base_class = &lv_switch_class,
.constructor_cb = switch_constructor,
.destructor_cb = nullptr,
.user_data = nullptr,
.event_cb = nullptr,
.width_def = ToggleSwitch::TOGGLE_W,
.height_def = EdgeTxStyles::UI_ELEMENT_HEIGHT,
.editable = LV_OBJ_CLASS_EDITABLE_INHERIT,
.group_def = LV_OBJ_CLASS_GROUP_DEF_INHERIT,
.instance_size = sizeof(lv_switch_t),
};
static lv_obj_t* switch_create(lv_obj_t* parent)
{
return etx_create(&switch_class, parent);
}
static void toggleswitch_event_handler(lv_event_t* e)
{
lv_obj_t* target = lv_event_get_target(e);
ToggleSwitch* cb = (ToggleSwitch*)lv_obj_get_user_data(target);
if (cb) cb->setValue(lv_obj_get_state(target) & LV_STATE_CHECKED);
}
ToggleSwitch::ToggleSwitch(Window* parent, const rect_t& rect,
std::function<uint8_t()> getValue,
std::function<void(uint8_t)> setValue) :
FormField(parent, rect, switch_create),
_getValue(std::move(getValue)),
_setValue(std::move(setValue))
{
update();
lv_obj_add_event_cb(lvobj, toggleswitch_event_handler, LV_EVENT_VALUE_CHANGED,
this);
}
void ToggleSwitch::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 ToggleSwitch::onClicked()
{
// prevent FormField::onClicked()
}
-56
View File
@@ -1,56 +0,0 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* libopenui - https://github.com/opentx/libopenui
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#pragma once
#include "form.h"
class ToggleSwitch : public FormField
{
public:
ToggleSwitch(Window* parent, const rect_t& rect,
std::function<uint8_t()> getValue,
std::function<void(uint8_t)> setValue);
#if defined(DEBUG_WINDOWS)
std::string getName() const override { return "ToggleSwitch"; }
#endif
void onClicked() override;
uint8_t getValue() const { return _getValue(); }
void setValue(uint8_t value) { _setValue(value); }
void setSetValueHandler(std::function<void(uint8_t)> handler)
{
_setValue = std::move(handler);
}
void setGetValueHandler(std::function<uint8_t()> handler)
{
_getValue = std::move(handler);
}
void update() const;
static LAYOUT_VAL(TOGGLE_W, 52, 52)
protected:
std::function<uint8_t()> _getValue;
std::function<void(uint8_t)> _setValue;
};
-545
View File
@@ -1,545 +0,0 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* libopenui - https://github.com/opentx/libopenui
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "window.h"
#include "button.h"
#include "form.h"
#include "static.h"
#include "themes/etx_lv_theme.h"
std::list<Window *> Window::trash;
bool Window::_longPressed = false;
const lv_obj_class_t window_base_class = {
.base_class = &lv_obj_class,
.constructor_cb = nullptr,
.destructor_cb = nullptr,
.user_data = nullptr,
.event_cb = nullptr,
.width_def = LV_DPI_DEF,
.height_def = LV_DPI_DEF,
.editable = LV_OBJ_CLASS_EDITABLE_FALSE,
.group_def = LV_OBJ_CLASS_GROUP_DEF_FALSE,
.instance_size = sizeof(lv_obj_t)};
lv_obj_t *window_create(lv_obj_t *parent)
{
return etx_create(&window_base_class, parent);
}
void Window::window_event_cb(lv_event_t *e)
{
Window *window = (Window *)lv_obj_get_user_data(lv_event_get_target(e));
if (window)
window->eventHandler(e);
}
void Window::eventHandler(lv_event_t *e)
{
lv_obj_t *target = lv_event_get_target(e);
lv_event_code_t code = lv_event_get_code(e);
if (code == LV_EVENT_DELETE || deleted()) return;
switch (code) {
case LV_EVENT_SCROLL: {
// exclude pointer based scrolling (only focus scrolling)
if (!lv_obj_is_scrolling(target)) {
lv_point_t *p = (lv_point_t *)lv_event_get_param(e);
lv_coord_t scroll_bottom = lv_obj_get_scroll_bottom(target);
lv_coord_t scroll_y = lv_obj_get_scroll_y(target);
TRACE("SCROLL[x=%d;y=%d;top=%d;bottom=%d]", p->x, p->y, scroll_y,
scroll_bottom);
if (scroll_y <= 45 && p->y > 0) {
lv_obj_scroll_by(target, 0, scroll_y, LV_ANIM_OFF);
} else if (scroll_bottom <= 16 && p->y < 0) {
lv_obj_scroll_by(target, 0, -scroll_bottom, LV_ANIM_OFF);
}
}
} break;
case LV_EVENT_CLICKED:
if (!_longPressed) {
TRACE("CLICKED[%p]", this);
onClicked();
}
_longPressed = false;
break;
case LV_EVENT_CANCEL:
TRACE("CANCEL[%p]", this);
onCancel();
break;
case LV_EVENT_FOCUSED:
if (focusHandler) focusHandler(true);
break;
case LV_EVENT_DEFOCUSED:
if (focusHandler) focusHandler(false);
break;
case LV_EVENT_LONG_PRESSED:
TRACE("LONG PRESS[%p]", this);
_longPressed = onLongPress();
break;
default:
break;
}
}
// Constructor to allow lvobj to be created separately - used by NumberEdit and
// TextEdit
Window::Window(const rect_t &rect) : rect(rect), parent(nullptr)
{
lvobj = nullptr;
}
Window::Window(Window *parent, const rect_t &rect, LvglCreate objConstruct) :
rect(rect), parent(parent)
{
lv_obj_t *lv_parent = parent ? parent->lvobj : nullptr;
if (objConstruct == nullptr) objConstruct = window_create;
lvobj = objConstruct(lv_parent);
lv_obj_set_user_data(lvobj, this);
lv_obj_add_event_cb(lvobj, Window::window_event_cb, LV_EVENT_ALL, nullptr);
if (rect.x || rect.y) lv_obj_set_pos(lvobj, rect.x, rect.y);
if (rect.w) lv_obj_set_width(lvobj, rect.w);
if (rect.h) lv_obj_set_height(lvobj, rect.h);
lv_obj_clear_flag(lvobj, LV_OBJ_FLAG_SCROLL_ELASTIC);
if (parent) {
parent->addChild(this);
}
}
Window::~Window()
{
TRACE_WINDOWS("Destroy %p %s", this, getWindowDebugString().c_str());
if (children.size() > 0) deleteChildren();
if (lvobj != nullptr) {
lv_obj_set_user_data(lvobj, nullptr);
lv_obj_del(lvobj);
lvobj = nullptr;
}
}
#if defined(DEBUG_WINDOWS)
std::string Window::getName() const { return "Window"; }
std::string Window::getRectString() const
{
char result[32];
sprintf(result, "[%d, %d, %d, %d]", left(), top(), width(), height());
return result;
}
std::string Window::getIndentString() const
{
std::string result;
auto tmp = parent;
while (tmp) {
result += " ";
tmp = tmp->getParent();
}
return result;
}
std::string Window::getWindowDebugString(const char *name) const
{
return getName() + (name ? std::string(" [") + name + "] " : " ") +
getRectString();
}
#endif
Window *Window::getFullScreenWindow()
{
if (width() == LCD_W && height() == LCD_H) return this;
if (parent) return parent->getFullScreenWindow();
return nullptr;
}
void Window::setWindowFlag(WindowFlags flag)
{
windowFlags |= flag;
// honor the no focus flag of libopenui
if (this->windowFlags & NO_FOCUS)
lv_obj_clear_flag(lvobj, LV_OBJ_FLAG_CLICK_FOCUSABLE);
}
void Window::clearWindowFlag(WindowFlags flag) { windowFlags &= ~flag; }
void Window::setTextFlag(LcdFlags flag) { textFlags |= flag; }
void Window::clearTextFlag(LcdFlags flag) { textFlags &= ~flag; }
void Window::attach(Window *newParent)
{
if (parent) detach();
parent = newParent;
if (newParent) {
newParent->addChild(this);
}
}
void Window::detach()
{
if (parent) {
parent->removeChild(this);
parent = nullptr;
}
}
void Window::deleteLater(bool detach, bool trash)
{
if (_deleted) return;
TRACE_WINDOWS("Delete %p %s", this, getWindowDebugString().c_str());
_deleted = true;
if (closeHandler) {
closeHandler();
}
if (detach)
this->detach();
else
parent = nullptr;
if (trash) {
Window::trash.push_back(this);
}
deleteChildren();
if (lvobj != nullptr) {
auto obj = lvobj;
lvobj = nullptr;
lv_obj_del(obj);
}
}
void Window::clear()
{
deleteChildren();
if (lvobj != nullptr) {
lv_obj_clean(lvobj);
}
invalidate();
}
void Window::deleteChildren()
{
// prevent LVGL refocus while mass-deleting
// inhibit_focus = true;
for (auto window : children) {
window->deleteLater(false);
}
// inhibit_focus = false;
children.clear();
}
bool Window::hasFocus() const
{
return lvobj && lv_obj_has_state(lvobj, LV_STATE_FOCUSED);
}
void Window::padLeft(coord_t pad) { lv_obj_set_style_pad_left(lvobj, pad, 0); }
void Window::padRight(coord_t pad)
{
lv_obj_set_style_pad_right(lvobj, pad, 0);
}
void Window::padTop(coord_t pad) { lv_obj_set_style_pad_top(lvobj, pad, 0); }
void Window::padBottom(coord_t pad)
{
lv_obj_set_style_pad_bottom(lvobj, pad, 0);
}
void Window::padAll(PaddingSize pad) { etx_padding(lvobj, pad, LV_PART_MAIN); }
void Window::padRow(coord_t pad) { lv_obj_set_style_pad_row(lvobj, pad, 0); }
void Window::padColumn(coord_t pad)
{
lv_obj_set_style_pad_column(lvobj, pad, 0);
}
void Window::bringToTop()
{
attach(parent); // does a detach + attach
if (lvobj && lv_obj_get_parent(lvobj)) lv_obj_move_foreground(lvobj);
}
void Window::checkEvents()
{
auto copy = children;
for (auto child : copy) {
if (!child->deleted()) {
child->checkEvents();
}
}
}
void Window::onEvent(event_t event)
{
TRACE_WINDOWS("%s received event 0x%X",
Window::getWindowDebugString("Window").c_str(), event);
if (parent)
parent->onEvent(event);
}
void Window::onClicked()
{
if (parent && !(windowFlags & OPAQUE))
parent->onClicked();
}
void Window::onCancel()
{
if (parent)
parent->onCancel();
}
bool Window::onLongPress()
{
return true;
}
void Window::addChild(Window *window)
{
auto lv_parent = lv_obj_get_parent(window->lvobj);
if (lv_parent && (lv_parent != lvobj)) {
lv_obj_set_parent(window->lvobj, lvobj);
}
children.push_back(window);
}
void Window::removeChild(Window *window)
{
children.remove(window);
invalidate();
}
void Window::invalidate()
{
if (lvobj) lv_obj_invalidate(lvobj);
}
void Window::setFlexLayout(lv_flex_flow_t flow, lv_coord_t padding,
coord_t width, coord_t height)
{
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, width);
lv_obj_set_height(lvobj, height);
}
FormLine *Window::newLine(FlexGridLayout &layout)
{
return new FormLine(this, layout);
}
void Window::show(bool visible)
{
if (!_deleted && lvobj) {
if (lv_obj_has_flag(lvobj, LV_OBJ_FLAG_HIDDEN) == visible) {
if (visible)
lv_obj_clear_flag(lvobj, LV_OBJ_FLAG_HIDDEN);
else
lv_obj_add_flag(lvobj, LV_OBJ_FLAG_HIDDEN);
}
}
}
void Window::enable(bool enabled)
{
if (!_deleted && lvobj) {
if (lv_obj_has_state(lvobj, LV_STATE_DISABLED) == enabled) {
if (enabled)
lv_obj_clear_state(lvobj, LV_STATE_DISABLED);
else
lv_obj_add_state(lvobj, LV_STATE_DISABLED);
}
}
}
#if defined(HARDWARE_TOUCH)
void Window::addBackButton()
{
new ButtonBase(
this, {0, 0, EdgeTxStyles::MENU_HEADER_HEIGHT, EdgeTxStyles::MENU_HEADER_HEIGHT},
[=]() -> uint8_t {
onCancel();
return 0;
},
window_create);
}
#endif
void NavWindow::onEvent(event_t event)
{
switch (event) {
#if defined(HARDWARE_KEYS)
case EVT_KEY_BREAK(KEY_MODEL):
onPressMDL();
break;
case EVT_KEY_LONG(KEY_MODEL):
onLongPressMDL();
break;
case EVT_KEY_BREAK(KEY_SYS):
onPressSYS();
break;
case EVT_KEY_LONG(KEY_SYS):
onLongPressSYS();
break;
case EVT_KEY_BREAK(KEY_TELE):
onPressTELE();
break;
case EVT_KEY_LONG(KEY_TELE):
onLongPressTELE();
break;
case EVT_KEY_BREAK(KEY_PAGEDN):
onPressPGDN();
break;
case EVT_KEY_BREAK(KEY_PAGEUP):
onPressPGUP();
break;
#endif
default:
if (bubbleEvents()) Window::onEvent(event);
}
}
NavWindow::NavWindow(Window *parent, const rect_t &rect,
LvglCreate objConstruct) :
Window(parent, rect, objConstruct)
{
setWindowFlag(OPAQUE);
}
SetupButtonGroup::SetupButtonGroup(Window* parent, const rect_t& rect, const char* title, int cols,
PaddingSize padding, PageDefs pages, coord_t btnHeight) :
Window(parent, rect)
{
padAll(padding);
coord_t buttonWidth = (width() - PAD_SMALL * (cols + 1) - PAD_TINY * 2) / cols;
int rows = (pages.size() + cols - 1) / cols;
int height = rows * btnHeight + (rows - 1) * PAD_MEDIUM + PAD_TINY * 2;
if (title) {
height += EdgeTxStyles::PAGE_LINE_HEIGHT + PAD_TINY;
}
setHeight(height);
if (title)
new Subtitle(this, title);
int n = 0;
int remaining = pages.size();
coord_t yo = title ? EdgeTxStyles::PAGE_LINE_HEIGHT + PAD_TINY : 0;
coord_t xw = buttonWidth + PAD_SMALL;
coord_t xo = (width() - (cols * xw - PAD_SMALL)) / 2;
coord_t x, y;
for (auto& entry : pages) {
if (remaining < cols && (n % cols == 0)) {
coord_t space = ((cols - remaining) * xw) / (remaining + 1);
xw += space;
xo += space;
}
x = xo + (n % cols) * xw;
y = yo + (n / cols) * (btnHeight + PAD_MEDIUM);
// TODO: sort out all caps title strings VS quick menu strings
std::string title(entry.title);
for (std::string::iterator it = title.begin(); it != title.end(); ++it) {
if (*it == '\n')
*it = ' ';
}
auto btn = new TextButton(this, rect_t{x, y, buttonWidth, btnHeight}, title, [&, entry]() {
entry.createPage();
return 0;
});
btn->setWrap();
if (entry.isActive) btn->setCheckHandler([=]() { btn->check(entry.isActive()); });
n += 1;
remaining -= 1;
}
}
SetupLine::SetupLine(Window* parent, coord_t y, coord_t col2, PaddingSize padding, const char* title,
std::function<void(Window*, coord_t, coord_t)> createEdit, coord_t lblYOffset) :
Window(parent, {0, y, LCD_W - padding * 2, 0})
{
padAll(PAD_ZERO);
coord_t titleY = PAD_LARGE + lblYOffset;
coord_t titleH = EdgeTxStyles::PAGE_LINE_HEIGHT;
coord_t h = EdgeTxStyles::UI_ELEMENT_HEIGHT + PAD_TINY * 2 + lblYOffset * 2;
if (createEdit) {
coord_t editY = PAD_TINY;
coord_t lblWidth = col2 - PAD_SMALL - PAD_TINY;
if (title) {
if (getTextWidth(title) >= lblWidth) {
h += PAD_MEDIUM;
titleY = 0;
titleH = EdgeTxStyles::UI_ELEMENT_HEIGHT + PAD_TINY + PAD_LARGE;
editY = PAD_SMALL + 1;
}
new StaticText(this, {PAD_TINY, titleY, lblWidth, titleH}, title);
}
setHeight(h);
createEdit(this, col2, editY);
} else {
setHeight(h);
new StaticText(this, {0, titleY, 0, titleH}, title, COLOR_THEME_PRIMARY1_INDEX, FONT(BOLD));
}
}
coord_t SetupLine::showLines(Window* parent, coord_t y, coord_t col2, PaddingSize padding, SetupLineDef* setupLines, int lineCount)
{
Window* w;
for (int i = 0; i < lineCount; i += 1) {
w = new SetupLine(parent, y, col2, padding, setupLines[i].title, setupLines[i].createEdit);
y += w->height() + padding;
}
return y;
}
-269
View File
@@ -1,269 +0,0 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* libopenui - https://github.com/opentx/libopenui
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#pragma once
#include <functional>
#include <list>
#include <string>
#include "LvglWrapper.h"
#include "bitmapbuffer.h"
#include "libopenui_defines.h"
#include "opentx_helpers.h"
#include "themes/etx_lv_theme.h"
typedef uint32_t WindowFlags;
#if !defined(_GNUC_)
#undef OPAQUE
#endif
constexpr WindowFlags OPAQUE = 1u << 0u;
constexpr WindowFlags NO_FOCUS = 1u << 1u;
typedef lv_obj_t *(*LvglCreate)(lv_obj_t *);
class FlexGridLayout;
class FormLine;
class Window
{
public:
Window(const rect_t &rect);
Window(Window *parent, const rect_t &rect, LvglCreate objConstruct = nullptr);
virtual ~Window();
#if defined(DEBUG_WINDOWS)
virtual std::string getName() const;
std::string getRectString() const;
std::string getIndentString() const;
std::string getWindowDebugString(const char *name = nullptr) const;
#endif
Window *getParent() const { return parent; }
Window *getFullScreenWindow();
void setWindowFlag(WindowFlags flag);
void clearWindowFlag(WindowFlags flag);
bool hasWindowFlag(WindowFlags flag) const { return windowFlags & flag; }
void setTextFlag(LcdFlags flag);
void clearTextFlag(LcdFlags flag);
LcdFlags getTextFlags() const { return textFlags; }
typedef std::function<void()> CloseHandler;
void setCloseHandler(CloseHandler h) { closeHandler = std::move(h); }
typedef std::function<void(bool)> FocusHandler;
void setFocusHandler(FocusHandler h) { focusHandler = std::move(h); }
virtual void clear();
virtual void deleteLater(bool detach = true, bool trash = true);
bool hasFocus() const;
void setRect(rect_t value)
{
rect = value;
lv_obj_set_pos(lvobj, rect.x, rect.y);
lv_obj_set_size(lvobj, rect.w, rect.h);
}
void setWidth(coord_t value)
{
rect.w = value;
lv_obj_set_width(lvobj, rect.w);
}
void setHeight(coord_t value)
{
rect.h = value;
lv_obj_set_height(lvobj, rect.h);
}
void setTop(coord_t y)
{
rect.y = y;
lv_obj_set_pos(lvobj, rect.x, rect.y);
}
void setSize(coord_t w, coord_t h)
{
rect.w = w;
rect.h = h;
lv_obj_set_size(lvobj, w, h);
}
void setPos(coord_t x, coord_t y)
{
rect.x = x;
rect.y = y;
lv_obj_set_pos(lvobj, x, y);
}
coord_t left() const { return rect.x; }
coord_t right() const { return rect.x + rect.w; }
coord_t top() const { return rect.y; }
coord_t bottom() const { return rect.y + rect.h; }
coord_t width() const { return rect.w; }
coord_t height() const { return rect.h; }
rect_t getRect() const { return rect; }
void padLeft(coord_t pad);
void padRight(coord_t pad);
void padTop(coord_t pad);
void padBottom(coord_t pad);
void padAll(PaddingSize pad);
void padRow(coord_t pad);
void padColumn(coord_t pad);
virtual void onEvent(event_t event);
virtual void onClicked();
virtual void onCancel();
virtual bool onLongPress();
void invalidate();
void bringToTop();
virtual void checkEvents();
void attach(Window *window);
void detach();
bool deleted() const { return _deleted; }
#if defined(HARDWARE_TOUCH)
void addBackButton();
#endif
inline lv_obj_t *getLvObj() { return lvobj; }
virtual bool isTopBar() { return false; }
virtual bool isWidgetsContainer() { return false; }
virtual bool isBubblePopup() { return false; }
void setFlexLayout(lv_flex_flow_t flow = LV_FLEX_FLOW_COLUMN,
lv_coord_t padding = PAD_TINY, coord_t width = LV_PCT(100),
coord_t height = LV_SIZE_CONTENT);
FormLine *newLine(FlexGridLayout &layout);
virtual void show(bool visible = true);
void hide() { show(false); }
void enable(bool enabled = true);
void disable() { enable(false); }
protected:
static std::list<Window *> trash;
rect_t rect;
Window *parent = nullptr;
lv_obj_t *lvobj = nullptr;
std::list<Window *> children;
WindowFlags windowFlags = 0;
LcdFlags textFlags = 0;
bool _deleted = false;
static bool _longPressed;
std::function<void()> closeHandler;
std::function<void(bool)> focusHandler;
void deleteChildren();
virtual void addChild(Window *window);
void removeChild(Window *window);
void eventHandler(lv_event_t *e);
static void window_event_cb(lv_event_t *e);
};
class NavWindow : public Window
{
public:
NavWindow(Window *parent, const rect_t &rect,
LvglCreate objConstruct = nullptr);
protected:
#if defined(HARDWARE_KEYS)
virtual void onPressSYS() {}
virtual void onLongPressSYS() {}
virtual void onPressMDL() {}
virtual void onLongPressMDL() {}
virtual void onPressTELE() {}
virtual void onLongPressTELE() {}
virtual void onPressPGUP() {}
virtual void onPressPGDN() {}
virtual void onLongPressPGUP() {}
virtual void onLongPressPGDN() {}
#endif
virtual bool bubbleEvents() { return true; }
void onEvent(event_t event) override;
};
struct PageButtonDef {
const char* title;
std::function<void()> createPage;
std::function<bool()> isActive;
PageButtonDef(const char* title, std::function<void()> createPage, std::function<bool()> isActive = nullptr) :
title(title), createPage(std::move(createPage)), isActive(std::move(isActive))
{}
};
class SetupButtonGroup : public Window
{
public:
typedef std::list<PageButtonDef> PageDefs;
SetupButtonGroup(Window* parent, const rect_t& rect, const char* title, int cols,
PaddingSize padding, PageDefs pages, coord_t btnHeight = EdgeTxStyles::UI_ELEMENT_HEIGHT);
protected:
};
struct SetupLineDef {
const char* title;
std::function<void(Window*, coord_t, coord_t)> createEdit;
};
class SetupLine : public Window
{
public:
SetupLine(Window* parent, coord_t y, coord_t col2, PaddingSize padding, const char* title,
std::function<void(Window*, coord_t, coord_t)> createEdit, coord_t lblYOffset = 0);
static coord_t showLines(Window* parent, coord_t y, coord_t col2, PaddingSize padding, SetupLineDef* setupLines, int lineCount);
protected:
};
-155
View File
@@ -1,155 +0,0 @@
set(LVGL_SOURCES_MINIMAL
core/lv_refr.c
draw/lv_draw_mask.c
draw/lv_img_cache.c
draw/sw/lv_draw_sw_gradient.c
draw/sw/lv_draw_sw.c
draw/sw/lv_draw_sw_blend.c
draw/sw/lv_draw_sw_letter.c
draw/sw/lv_draw_sw_arc.c
draw/sw/lv_draw_sw_polygon.c
draw/sw/lv_draw_sw_img.c
draw/sw/lv_draw_sw_line.c
draw/sw/lv_draw_sw_dither.c
draw/sw/lv_draw_sw_rect.c
draw/lv_draw_line.c
draw/lv_draw.c
draw/lv_img_buf.c
draw/lv_draw_rect.c
draw/lv_draw_label.c
draw/lv_draw_img.c
draw/lv_draw_arc.c
draw/lv_draw_triangle.c
draw/lv_img_decoder.c
# STM32 specific
draw/stm32_dma2d/lv_gpu_stm32_dma2d.c
# SDL specific
draw/sdl/lv_draw_sdl_rect.c
draw/sdl/lv_draw_sdl_arc.c
draw/sdl/lv_draw_sdl.c
draw/sdl/lv_draw_sdl_mask.c
draw/sdl/lv_draw_sdl_polygon.c
draw/sdl/lv_draw_sdl_stack_blur.c
draw/sdl/lv_draw_sdl_utils.c
draw/sdl/lv_draw_sdl_img.c
draw/sdl/lv_draw_sdl_bg.c
draw/sdl/lv_draw_sdl_label.c
draw/sdl/lv_draw_sdl_texture_cache.c
draw/sdl/lv_draw_sdl_line.c
draw/sdl/lv_draw_sdl_composite.c
hal/lv_hal_disp.c
hal/lv_hal_tick.c
font/lv_font.c
font/lv_font_loader.c
font/lv_font_fmt_txt.c
misc/lv_timer.c
misc/lv_txt.c
misc/lv_gc.c
misc/lv_style_gen.c
misc/lv_templ.c
misc/lv_color.c
misc/lv_fs.c
misc/lv_printf.c
misc/lv_style.c
misc/lv_log.c
misc/lv_area.c
misc/lv_math.c
misc/lv_lru.c
misc/lv_tlsf.c
misc/lv_anim_timeline.c
misc/lv_ll.c
misc/lv_bidi.c
misc/lv_mem.c
misc/lv_utils.c
misc/lv_async.c
misc/lv_anim.c
misc/lv_txt_ap.c
extra/lv_extra.c
)
set(LVGL_SOURCES
${LVGL_SOURCES_MINIMAL}
core/lv_group.c
core/lv_obj.c
core/lv_event.c
core/lv_indev.c
core/lv_obj_scroll.c
core/lv_obj_draw.c
core/lv_disp.c
core/lv_obj_style.c
core/lv_theme.c
core/lv_obj_style_gen.c
core/lv_indev_scroll.c
core/lv_obj_class.c
core/lv_obj_tree.c
core/lv_obj_pos.c
hal/lv_hal_indev.c
widgets/lv_line.c
widgets/lv_dropdown.c
widgets/lv_objx_templ.c
widgets/lv_canvas.c
widgets/lv_switch.c
widgets/lv_btnmatrix.c
widgets/lv_bar.c
widgets/lv_roller.c
widgets/lv_img.c
widgets/lv_btn.c
widgets/lv_checkbox.c
widgets/lv_label.c
widgets/lv_table.c
widgets/lv_slider.c
widgets/lv_textarea.c
widgets/lv_arc.c
extra/others/monkey/lv_monkey.c
extra/others/gridnav/lv_gridnav.c
extra/others/snapshot/lv_snapshot.c
extra/layouts/grid/lv_grid.c
extra/layouts/flex/lv_flex.c
extra/libs/bmp/lv_bmp.c
extra/libs/ffmpeg/lv_ffmpeg.c
extra/libs/gif/lv_gif.c
extra/libs/gif/gifdec.c
extra/libs/freetype/lv_freetype.c
extra/libs/qrcode/lv_qrcode.c
extra/libs/qrcode/qrcodegen.c
extra/libs/sjpg/tjpgd.c
extra/libs/sjpg/lv_sjpg.c
extra/libs/fsdrv/lv_fs_win32.c
extra/libs/fsdrv/lv_fs_stdio.c
extra/libs/fsdrv/lv_fs_posix.c
extra/libs/fsdrv/lv_fs_fatfs.c
extra/libs/rlottie/lv_rlottie.c
extra/libs/png/lv_png.c
extra/libs/png/lodepng.c
extra/widgets/imgbtn/lv_imgbtn.c
extra/widgets/menu/lv_menu.c
extra/widgets/win/lv_win.c
extra/widgets/colorwheel/lv_colorwheel.c
extra/widgets/list/lv_list.c
extra/widgets/chart/lv_chart.c
extra/widgets/calendar/lv_calendar_header_arrow.c
extra/widgets/calendar/lv_calendar.c
extra/widgets/calendar/lv_calendar_header_dropdown.c
extra/widgets/tileview/lv_tileview.c
extra/widgets/led/lv_led.c
extra/widgets/spinner/lv_spinner.c
extra/widgets/tabview/lv_tabview.c
extra/widgets/meter/lv_meter.c
extra/widgets/spinbox/lv_spinbox.c
extra/widgets/animimg/lv_animimg.c
extra/widgets/span/lv_span.c
extra/widgets/keyboard/lv_keyboard.c
extra/widgets/msgbox/lv_msgbox.c
)
list(TRANSFORM LVGL_SOURCES_MINIMAL PREPEND ${LVGL_SRC_DIR}/)
list(TRANSFORM LVGL_SOURCES PREPEND ${LVGL_SRC_DIR}/)
-213
View File
@@ -1,213 +0,0 @@
#!/usr/bin/env python
import argparse
from PIL import Image
# LZ4 compression
import lz4.block
class RawMixin:
def encode_byte(self, byte):
self.write(byte)
def encode_end(self):
pass
class RleMixin:
RLE_BYTE = 0
RLE_SEQ = 1
def __init__(self):
self.state = self.RLE_BYTE
self.count = 0
self.prev_byte = None
def eq_prev_byte(self, byte):
if self.prev_byte is None:
return False
return byte == self.prev_byte
def encode_byte(self, byte):
if self.state == self.RLE_BYTE:
self.write(byte)
if self.eq_prev_byte(byte):
self.state = self.RLE_SEQ
self.count = 0
else:
self.prev_byte = byte
elif self.state == self.RLE_SEQ:
if self.eq_prev_byte(byte):
self.count += 1
if self.count == 255:
self.write(self.count)
self.prev_byte = None
self.state = self.RLE_BYTE
else:
self.write(self.count)
self.write(byte)
self.prev_byte = byte
self.state = self.RLE_BYTE
def encode_end(self):
if self.state == self.RLE_SEQ:
self.write(self.count)
class Lz4Mixin:
def __init__(self):
self.buffer = []
def encode_byte(self,byte):
self.buffer.append(byte)
def encode_end(self):
compressed_data = lz4.block.compress(bytes(self.buffer), compression=12, mode='high_compression', store_size=False)
data_len = len(compressed_data).to_bytes(4, byteorder='little')
for b in data_len:
self.write(b)
for b in compressed_data:
self.write(b)
class ImageEncoder:
def __init__(self, filename, size_format, reverse=False):
self.f = open(filename, "w")
self.size_format = size_format
self.reverse = reverse
def write(self, value):
if isinstance(value, bytes):
value = int.from_bytes(value, 'little')
self.f.write("0x%02x," % value)
def write_size(self, width, height):
if self.size_format == 2:
self.f.write("%d,%d,%d,%d,\n" % (width % 256, width // 256, height % 256, height // 256))
else:
self.f.write("%d,%d,\n" % (width, height))
def encode_1bit(self, image, rows):
image = image.convert(mode='1')
width, height = image.size
self.write_size(width, height // rows)
for y in range(0, height, 8):
for x in range(width):
value = 0
for z in range(8):
if y + z < height:
if image.format == "XBM":
if image.getpixel((x, y + z)) > 0:
value += 1 << z
else:
if image.getpixel((x, y + z)) == 0:
value += 1 << z
self.encode_byte(value)
self.f.write("\n")
self.encode_end()
def encode_4bits(self, image):
image = image.convert(mode='L')
width, height = image.size
self.write_size(width, height)
for y in range(0, height, 2):
for x in range(width):
value = 0xFF
gray1 = self.get_pixel(image, x, y)
if y + 1 < height:
gray2 = self.get_pixel(image, x, y + 1)
else:
gray2 = 255
for i in range(4):
if gray1 & (1 << (4 + i)):
value -= 1 << i
if gray2 & (1 << (4 + i)):
value -= 1 << (4 + i)
self.encode_byte(value)
self.f.write("\n")
self.encode_end()
def encode_8bits(self, image):
image = image.convert(mode='L')
width, height = image.size
self.write_size(width, height)
for y in range(height):
for x in range(width):
value = self.get_pixel(image, x, y)
value = 0xFF - value
self.encode_byte(value)
self.encode_end()
def encode_5_6_5(self, image):
width, height = image.size
self.write_size(width, height)
for y in range(height):
for x in range(width):
pixel = self.get_pixel(image, x, y)
val = ((pixel[0] >> 3) << 11) + ((pixel[1] >> 2) << 5) + ((pixel[2] >> 3) << 0)
self.encode_byte(val & 255)
self.encode_byte(val >> 8)
self.encode_end()
def encode_4_4_4_4(self, image):
width, height = image.size
self.write_size(width, height)
for y in range(height):
for x in range(width):
pixel = self.get_pixel(image, x, y)
val = ((pixel[3] // 16) << 12) + ((pixel[0] // 16) << 8) + ((pixel[1] // 16) << 4) + ((pixel[2] // 16) << 0)
self.encode_byte(val & 255)
self.encode_byte(val >> 8)
self.encode_end()
def get_pixel(self, image, x, y):
if self.reverse:
return image.getpixel((image.width - x - 1, image.height - y - 1))
else:
return image.getpixel((x, y))
@staticmethod
def create(filename, size_format=1, reverse=False, encode_mixin=RawMixin):
class ResultClass(ImageEncoder, encode_mixin):
def __init__(self, *args, **kwargs):
ImageEncoder.__init__(self, *args, **kwargs)
encode_mixin.__init__(self)
return ResultClass(filename, size_format, reverse)
def main():
parser = argparse.ArgumentParser(description='Bitmaps encoder')
parser.add_argument('input', action="store", help="Input file name")
parser.add_argument('output', action="store", help="Output file name")
parser.add_argument('--format', action="store", help="Output format")
parser.add_argument("--reverse", help="Invert pixels order", action="store_true")
parser.add_argument("--rle", help="Enable RLE compression", action="store_true")
parser.add_argument("--lz4", help="Enable RLE compression", action="store_true")
parser.add_argument("--rows", help="Image rows count (for 1bit format)", type=int, default=1)
parser.add_argument("--size-format", help="Header image size format (1 or 2 bytes)", type=int, default=1)
args = parser.parse_args()
image = Image.open(args.input)
output = args.output
byte_encoder = RawMixin
if args.rle:
byte_encoder = RleMixin
if args.lz4:
byte_encoder = Lz4Mixin
encoder = ImageEncoder.create(output, args.size_format, args.reverse, byte_encoder)
if args.format == "1bit":
encoder.encode_1bit(image, args.rows)
elif args.format == "4bits":
encoder.encode_4bits(image)
elif args.format == "8bits":
encoder.encode_8bits(image)
elif args.format == "4/4/4/4":
encoder.encode_4_4_4_4(image)
elif args.format == "5/6/5":
encoder.encode_5_6_5(image)
if __name__ == "__main__":
main()