mirror of
https://github.com/EdgeTX/edgetx.git
synced 2025-07-24 16:55:15 +03:00
[Horus] Widgets can be written in Lua
This commit is contained in:
parent
01ef176173
commit
d1be1b4ea0
12 changed files with 354 additions and 186 deletions
|
@ -31,10 +31,12 @@ void Layout::load()
|
||||||
{
|
{
|
||||||
unsigned int count = getZonesCount();
|
unsigned int count = getZonesCount();
|
||||||
for (unsigned int i=0; i<count; i++) {
|
for (unsigned int i=0; i<count; i++) {
|
||||||
char name[sizeof(persistentData->zones[i].widgetName)+1];
|
if (persistentData->zones[i].widgetName[0]) {
|
||||||
memset(name, 0, sizeof(name));
|
char name[sizeof(persistentData->zones[i].widgetName)+1];
|
||||||
strncpy(name, persistentData->zones[i].widgetName, sizeof(persistentData->zones[i].widgetName));
|
memset(name, 0, sizeof(name));
|
||||||
widgets[i] = loadWidget(name, getZone(i), &persistentData->zones[i].widgetData);
|
strncpy(name, persistentData->zones[i].widgetName, sizeof(persistentData->zones[i].widgetName));
|
||||||
|
widgets[i] = loadWidget(name, getZone(i), &persistentData->zones[i].widgetData);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,4 +90,4 @@ void loadCustomScreens()
|
||||||
if (customScreens[0] == NULL) {
|
if (customScreens[0] == NULL) {
|
||||||
customScreens[0] = registeredLayouts[0]->create(&g_model.screenData[0].layoutData);
|
customScreens[0] = registeredLayouts[0]->create(&g_model.screenData[0].layoutData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,6 +50,14 @@ class Layout
|
||||||
memset(widgets, 0, sizeof(widgets));
|
memset(widgets, 0, sizeof(widgets));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual ~Layout()
|
||||||
|
{
|
||||||
|
for (uint8_t i=0; i<MAX_LAYOUT_ZONES; i++) {
|
||||||
|
delete widgets[i];
|
||||||
|
}
|
||||||
|
memset(widgets, 0, sizeof(widgets));
|
||||||
|
}
|
||||||
|
|
||||||
const LayoutFactory * getFactory() const
|
const LayoutFactory * getFactory() const
|
||||||
{
|
{
|
||||||
return factory;
|
return factory;
|
||||||
|
|
|
@ -355,6 +355,7 @@ bool menuScreenSetup(evt_t event)
|
||||||
lcdDrawText(MENUS_MARGIN_LEFT, y + FH / 2, "Layout");
|
lcdDrawText(MENUS_MARGIN_LEFT, y + FH / 2, "Layout");
|
||||||
const LayoutFactory * factory = editThemeChoice<const LayoutFactory>(SCREENS_SETUP_2ND_COLUMN, y, registeredLayouts, countRegisteredLayouts, currentScreen->getFactory(), needsOffsetCheck, attr, event);
|
const LayoutFactory * factory = editThemeChoice<const LayoutFactory>(SCREENS_SETUP_2ND_COLUMN, y, registeredLayouts, countRegisteredLayouts, currentScreen->getFactory(), needsOffsetCheck, attr, event);
|
||||||
if (factory) {
|
if (factory) {
|
||||||
|
delete customScreens[T];
|
||||||
customScreens[T] = factory->create(&g_model.screenData[T].layoutData);
|
customScreens[T] = factory->create(&g_model.screenData[T].layoutData);
|
||||||
strncpy(g_model.screenData[T].layoutName, factory->getName(), sizeof(g_model.screenData[T].layoutName));
|
strncpy(g_model.screenData[T].layoutName, factory->getName(), sizeof(g_model.screenData[T].layoutName));
|
||||||
storageDirty(EE_MODEL);
|
storageDirty(EE_MODEL);
|
||||||
|
@ -375,7 +376,7 @@ bool menuScreenSetup(evt_t event)
|
||||||
default:
|
default:
|
||||||
if (k < linesCount) {
|
if (k < linesCount) {
|
||||||
uint8_t index = k - 3;
|
uint8_t index = k - 3;
|
||||||
const ZoneOption *option = &options[index];
|
const ZoneOption * option = &options[index];
|
||||||
ZoneOptionValue value = currentScreen->getOptionValue(index);
|
ZoneOptionValue value = currentScreen->getOptionValue(index);
|
||||||
value = editZoneOption(y, option, value, attr, event);
|
value = editZoneOption(y, option, value, attr, event);
|
||||||
if (attr) {
|
if (attr) {
|
||||||
|
|
|
@ -30,23 +30,6 @@ struct Zone
|
||||||
uint16_t x, y, w, h;
|
uint16_t x, y, w, h;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ZoneOption
|
|
||||||
{
|
|
||||||
enum Type {
|
|
||||||
Bool,
|
|
||||||
Integer,
|
|
||||||
String,
|
|
||||||
TextSize,
|
|
||||||
Timer,
|
|
||||||
Source,
|
|
||||||
Switch,
|
|
||||||
Color
|
|
||||||
};
|
|
||||||
|
|
||||||
const char * name;
|
|
||||||
Type type;
|
|
||||||
};
|
|
||||||
|
|
||||||
union ZoneOptionValue
|
union ZoneOptionValue
|
||||||
{
|
{
|
||||||
bool boolValue;
|
bool boolValue;
|
||||||
|
@ -55,6 +38,24 @@ union ZoneOptionValue
|
||||||
char stringValue[8];
|
char stringValue[8];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ZoneOption
|
||||||
|
{
|
||||||
|
enum Type {
|
||||||
|
Integer,
|
||||||
|
Source,
|
||||||
|
Bool,
|
||||||
|
String,
|
||||||
|
TextSize,
|
||||||
|
Timer,
|
||||||
|
Switch,
|
||||||
|
Color
|
||||||
|
};
|
||||||
|
|
||||||
|
const char * name;
|
||||||
|
Type type;
|
||||||
|
ZoneOptionValue deflt;
|
||||||
|
};
|
||||||
|
|
||||||
class WidgetFactory;
|
class WidgetFactory;
|
||||||
class Widget
|
class Widget
|
||||||
{
|
{
|
||||||
|
@ -74,11 +75,6 @@ class Widget
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void init()
|
|
||||||
{
|
|
||||||
memset(persistentData, 0, sizeof(PersistentData));
|
|
||||||
}
|
|
||||||
|
|
||||||
const WidgetFactory * getFactory() const
|
const WidgetFactory * getFactory() const
|
||||||
{
|
{
|
||||||
return factory;
|
return factory;
|
||||||
|
@ -107,23 +103,39 @@ void registerWidget(const WidgetFactory * factory);
|
||||||
class WidgetFactory
|
class WidgetFactory
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
WidgetFactory(const char * name):
|
WidgetFactory(const char * name, const ZoneOption * options):
|
||||||
name(name)
|
name(name),
|
||||||
|
options(options)
|
||||||
{
|
{
|
||||||
registerWidget(this);
|
registerWidget(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
const char * getName() const
|
inline const char * getName() const
|
||||||
{
|
{
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual const ZoneOption * getOptions() const = 0;
|
inline const ZoneOption * getOptions() const
|
||||||
|
{
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
void initPersistentData(Widget::PersistentData * persistentData) const
|
||||||
|
{
|
||||||
|
memset(persistentData, 0, sizeof(Widget::PersistentData));
|
||||||
|
if (options) {
|
||||||
|
int i = 0;
|
||||||
|
for (const ZoneOption * option = options; option->name; option++) {
|
||||||
|
persistentData->options[i++] = option->deflt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
virtual Widget * create(const Zone & zone, Widget::PersistentData * persistentData, bool init=true) const = 0;
|
virtual Widget * create(const Zone & zone, Widget::PersistentData * persistentData, bool init=true) const = 0;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
const char * name;
|
const char * name;
|
||||||
|
const ZoneOption * options;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<class T>
|
template<class T>
|
||||||
|
@ -131,27 +143,18 @@ class BaseWidgetFactory: public WidgetFactory
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
BaseWidgetFactory(const char * name, const ZoneOption * options):
|
BaseWidgetFactory(const char * name, const ZoneOption * options):
|
||||||
WidgetFactory(name),
|
WidgetFactory(name, options)
|
||||||
options(options)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual const ZoneOption * getOptions() const
|
|
||||||
{
|
|
||||||
return options;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual Widget * create(const Zone & zone, Widget::PersistentData * persistentData, bool init=true) const
|
virtual Widget * create(const Zone & zone, Widget::PersistentData * persistentData, bool init=true) const
|
||||||
{
|
{
|
||||||
Widget * widget = new T(this, zone, persistentData);
|
|
||||||
if (init) {
|
if (init) {
|
||||||
widget->init();
|
initPersistentData(persistentData);
|
||||||
}
|
}
|
||||||
return widget;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
return new T(this, zone, persistentData);
|
||||||
const ZoneOption * options;
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
#define MAX_REGISTERED_WIDGETS 10
|
#define MAX_REGISTERED_WIDGETS 10
|
||||||
|
|
|
@ -28,24 +28,16 @@ class GaugeWidget: public Widget
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void init()
|
|
||||||
{
|
|
||||||
persistentData->options[0].unsignedValue = 1;
|
|
||||||
persistentData->options[1].signedValue = -RESX;
|
|
||||||
persistentData->options[2].signedValue = RESX;
|
|
||||||
persistentData->options[3].unsignedValue = RED;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void refresh();
|
virtual void refresh();
|
||||||
|
|
||||||
static const ZoneOption options[];
|
static const ZoneOption options[];
|
||||||
};
|
};
|
||||||
|
|
||||||
const ZoneOption GaugeWidget::options[] = {
|
const ZoneOption GaugeWidget::options[] = {
|
||||||
{ "Source", ZoneOption::Source },
|
{ "Source", ZoneOption::Source, { .unsignedValue = 1 } },
|
||||||
{ "Min", ZoneOption::Integer },
|
{ "Min", ZoneOption::Integer, { .signedValue = -RESX } },
|
||||||
{ "Max", ZoneOption::Integer },
|
{ "Max", ZoneOption::Integer, { .signedValue = RESX } },
|
||||||
{ "Color", ZoneOption::Color },
|
{ "Color", ZoneOption::Color, { .unsignedValue = RED } },
|
||||||
{ NULL, ZoneOption::Bool }
|
{ NULL, ZoneOption::Bool }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -28,22 +28,15 @@ class TextWidget: public Widget
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void init()
|
|
||||||
{
|
|
||||||
str2zchar(persistentData->options[0].stringValue, "Label", sizeof(persistentData->options[0].stringValue));
|
|
||||||
persistentData->options[1].unsignedValue = RED;
|
|
||||||
persistentData->options[2].unsignedValue = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void refresh();
|
virtual void refresh();
|
||||||
|
|
||||||
static const ZoneOption options[];
|
static const ZoneOption options[];
|
||||||
};
|
};
|
||||||
|
|
||||||
const ZoneOption TextWidget::options[] = {
|
const ZoneOption TextWidget::options[] = {
|
||||||
{ "Text", ZoneOption::String },
|
{ "Text", ZoneOption::String, { .stringValue = { '\015', '\347', '\0', '\14', '\377', '\376', '\373', '\364' } } },
|
||||||
{ "Color", ZoneOption::Color },
|
{ "Color", ZoneOption::Color, { .unsignedValue = RED } },
|
||||||
{ "Size", ZoneOption::TextSize },
|
{ "Size", ZoneOption::TextSize, { .unsignedValue = 0 } },
|
||||||
{ NULL, ZoneOption::Bool }
|
{ NULL, ZoneOption::Bool }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ class TimerWidget: public Widget
|
||||||
};
|
};
|
||||||
|
|
||||||
const ZoneOption TimerWidget::options[] = {
|
const ZoneOption TimerWidget::options[] = {
|
||||||
{ "Timer source", ZoneOption::Timer },
|
{ "Timer source", ZoneOption::Timer, { .unsignedValue = 0 } },
|
||||||
{ NULL, ZoneOption::Bool }
|
{ NULL, ZoneOption::Bool }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -28,19 +28,13 @@ class ValueWidget: public Widget
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void init()
|
|
||||||
{
|
|
||||||
persistentData->options[0].unsignedValue = 1;
|
|
||||||
persistentData->options[3].unsignedValue = RED;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void refresh();
|
virtual void refresh();
|
||||||
|
|
||||||
static const ZoneOption options[];
|
static const ZoneOption options[];
|
||||||
};
|
};
|
||||||
|
|
||||||
const ZoneOption ValueWidget::options[] = {
|
const ZoneOption ValueWidget::options[] = {
|
||||||
{ "Source", ZoneOption::Source },
|
{ "Source", ZoneOption::Source, { .unsignedValue = MIXSRC_Rud } },
|
||||||
{ NULL, ZoneOption::Bool }
|
{ NULL, ZoneOption::Bool }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -812,8 +812,8 @@ const luaR_value_entry opentxConstants[] = {
|
||||||
{ "LEFT", LEFT },
|
{ "LEFT", LEFT },
|
||||||
{ "PREC1", PREC1 },
|
{ "PREC1", PREC1 },
|
||||||
{ "PREC2", PREC2 },
|
{ "PREC2", PREC2 },
|
||||||
{ "VALUE", 0 },
|
{ "VALUE", 0 }, // TODO reuse ZoneOption::Integer
|
||||||
{ "SOURCE", 1 },
|
{ "SOURCE", 1 }, // TODO reuse ZoneOption::Source
|
||||||
{ "REPLACE", MLTPX_REP },
|
{ "REPLACE", MLTPX_REP },
|
||||||
{ "MIXSRC_FIRST_INPUT", MIXSRC_FIRST_INPUT },
|
{ "MIXSRC_FIRST_INPUT", MIXSRC_FIRST_INPUT },
|
||||||
{ "MIXSRC_Rud", MIXSRC_Rud },
|
{ "MIXSRC_Rud", MIXSRC_Rud },
|
||||||
|
@ -832,6 +832,7 @@ const luaR_value_entry opentxConstants[] = {
|
||||||
{ "SWSRC_LAST", SWSRC_LAST_LOGICAL_SWITCH },
|
{ "SWSRC_LAST", SWSRC_LAST_LOGICAL_SWITCH },
|
||||||
{ "EVT_MENU_BREAK", EVT_KEY_BREAK(KEY_MENU) },
|
{ "EVT_MENU_BREAK", EVT_KEY_BREAK(KEY_MENU) },
|
||||||
#if defined(COLORLCD)
|
#if defined(COLORLCD)
|
||||||
|
{ "COLOR", ZoneOption::Color },
|
||||||
{ "TEXT_COLOR_INDEX", TEXT_COLOR_INDEX },
|
{ "TEXT_COLOR_INDEX", TEXT_COLOR_INDEX },
|
||||||
{ "TEXT_BGCOLOR_INDEX", TEXT_BGCOLOR_INDEX },
|
{ "TEXT_BGCOLOR_INDEX", TEXT_BGCOLOR_INDEX },
|
||||||
{ "TEXT_INVERTED_COLOR_INDEX", TEXT_INVERTED_COLOR_INDEX },
|
{ "TEXT_INVERTED_COLOR_INDEX", TEXT_INVERTED_COLOR_INDEX },
|
||||||
|
|
|
@ -183,46 +183,6 @@ void luaRegisterAll()
|
||||||
luaL_openlibs(L);
|
luaL_openlibs(L);
|
||||||
}
|
}
|
||||||
|
|
||||||
void luaInit()
|
|
||||||
{
|
|
||||||
TRACE("luaInit");
|
|
||||||
|
|
||||||
#if !defined(COLORLCD)
|
|
||||||
luaClose();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (luaState != INTERPRETER_PANIC) {
|
|
||||||
#if defined(USE_BIN_ALLOCATOR)
|
|
||||||
L = lua_newstate(bin_l_alloc, NULL); //we use our own allocator!
|
|
||||||
#else
|
|
||||||
L = lua_newstate(l_alloc, NULL); //we use Lua default allocator
|
|
||||||
#endif
|
|
||||||
if (L) {
|
|
||||||
// install our panic handler
|
|
||||||
lua_atpanic(L, &custom_lua_atpanic);
|
|
||||||
|
|
||||||
// protect libs and constants registration
|
|
||||||
PROTECT_LUA() {
|
|
||||||
luaRegisterAll();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// if we got panic during registration
|
|
||||||
// we disable Lua for this session
|
|
||||||
luaDisable();
|
|
||||||
}
|
|
||||||
UNPROTECT_LUA();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
/* log error and return */
|
|
||||||
luaDisable();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#if defined(COLORLCD)
|
|
||||||
luaLoadThemes();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void luaFree(ScriptInternalData & sid)
|
void luaFree(ScriptInternalData & sid)
|
||||||
{
|
{
|
||||||
PROTECT_LUA() {
|
PROTECT_LUA() {
|
||||||
|
@ -803,11 +763,22 @@ int luaGetMemUsed()
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(COLORLCD)
|
#if defined(COLORLCD)
|
||||||
#define THEME_FILENAME_MAXLEN (42) // max length (example: /SCRIPTS/THEMES/mytheme.lua)
|
#define LUA_FULLPATH_MAXLEN (42) // max length (example: /SCRIPTS/THEMES/mytheme.lua)
|
||||||
|
|
||||||
|
void exec(int function, int nresults=0)
|
||||||
|
{
|
||||||
|
if (function) {
|
||||||
|
SET_LUA_INSTRUCTIONS_COUNT(PERMANENT_SCRIPTS_MAX_INSTRUCTIONS);
|
||||||
|
lua_rawgeti(L, LUA_REGISTRYINDEX, function);
|
||||||
|
if (lua_pcall(L, 0, nresults, 0) != 0) {
|
||||||
|
TRACE("Error in theme %s", lua_tostring(L, -1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class LuaTheme: public Theme
|
class LuaTheme: public Theme
|
||||||
{
|
{
|
||||||
friend int luaLoadTheme(const char * filename);
|
friend void luaLoadThemeCallback();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
LuaTheme(const char * name, const uint8_t * bitmap):
|
LuaTheme(const char * name, const uint8_t * bitmap):
|
||||||
|
@ -819,17 +790,6 @@ class LuaTheme: public Theme
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void exec(int function) const
|
|
||||||
{
|
|
||||||
if (function) {
|
|
||||||
SET_LUA_INSTRUCTIONS_COUNT(PERMANENT_SCRIPTS_MAX_INSTRUCTIONS);
|
|
||||||
lua_rawgeti(L, LUA_REGISTRYINDEX, function);
|
|
||||||
if (lua_pcall(L, 0, 0, 0) != 0) {
|
|
||||||
TRACE("Error in theme %s: %s", name, lua_tostring(L, -1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void load() const
|
virtual void load() const
|
||||||
{
|
{
|
||||||
luaLcdAllowed = true;
|
luaLcdAllowed = true;
|
||||||
|
@ -860,14 +820,218 @@ class LuaTheme: public Theme
|
||||||
int drawAlertBoxFunction;
|
int drawAlertBoxFunction;
|
||||||
};
|
};
|
||||||
|
|
||||||
int luaLoadTheme(const char * filename)
|
void luaLoadThemeCallback()
|
||||||
{
|
{
|
||||||
TRACE("luaLoadTheme from file %s", filename);
|
const char * name=NULL, * bitmap=NULL;
|
||||||
|
int loadFunction=0, drawBackgroundFunction=0, drawTopbarBackgroundFunction=0;
|
||||||
|
|
||||||
int init = 0;
|
luaL_checktype(L, -1, LUA_TTABLE);
|
||||||
|
|
||||||
|
for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) {
|
||||||
|
const char * key = lua_tostring(L, -2);
|
||||||
|
if (!strcmp(key, "name")) {
|
||||||
|
name = luaL_checkstring(L, -1);
|
||||||
|
}
|
||||||
|
else if (!strcmp(key, "bitmap")) {
|
||||||
|
bitmap = luaL_checkstring(L, -1);
|
||||||
|
}
|
||||||
|
else if (!strcmp(key, "load")) {
|
||||||
|
loadFunction = luaL_ref(L, LUA_REGISTRYINDEX);
|
||||||
|
lua_pushnil(L);
|
||||||
|
}
|
||||||
|
else if (!strcmp(key, "drawBackground")) {
|
||||||
|
drawBackgroundFunction = luaL_ref(L, LUA_REGISTRYINDEX);
|
||||||
|
lua_pushnil(L);
|
||||||
|
}
|
||||||
|
else if (!strcmp(key, "drawTopbarBackground")) {
|
||||||
|
drawTopbarBackgroundFunction = luaL_ref(L, LUA_REGISTRYINDEX);
|
||||||
|
lua_pushnil(L);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name && bitmap) {
|
||||||
|
char path[LUA_FULLPATH_MAXLEN+1];
|
||||||
|
strcpy(path, THEMES_PATH "/");
|
||||||
|
strcpy(path+sizeof(THEMES_PATH), bitmap);
|
||||||
|
uint8_t * bitmapData = (uint8_t *)malloc(BITMAP_BUFFER_SIZE(51, 31));
|
||||||
|
bmpLoad(bitmapData, path, 51, 31);
|
||||||
|
LuaTheme * theme = new LuaTheme(name, bitmapData);
|
||||||
|
theme->loadFunction = loadFunction;
|
||||||
|
theme->drawBackgroundFunction = drawBackgroundFunction;
|
||||||
|
theme->drawTopbarBackgroundFunction = drawTopbarBackgroundFunction;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LuaWidget: public Widget
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LuaWidget(const WidgetFactory * factory, const Zone & zone, Widget::PersistentData * persistentData, int widgetData):
|
||||||
|
Widget(factory, zone, persistentData),
|
||||||
|
widgetData(widgetData)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ~LuaWidget()
|
||||||
|
{
|
||||||
|
luaL_unref(L, LUA_REGISTRYINDEX, widgetData);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void refresh();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
int widgetData;
|
||||||
|
};
|
||||||
|
|
||||||
|
ZoneOption * createOptionsArray(int reference)
|
||||||
|
{
|
||||||
|
int count = 0;
|
||||||
|
lua_rawgeti(L, LUA_REGISTRYINDEX, reference);
|
||||||
|
for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
ZoneOption * options = (ZoneOption *)malloc(sizeof(ZoneOption) * (count+1));
|
||||||
|
|
||||||
|
lua_rawgeti(L, LUA_REGISTRYINDEX, reference);
|
||||||
|
ZoneOption * option = options;
|
||||||
|
for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) {
|
||||||
|
luaL_checktype(L, -2, LUA_TNUMBER); // key is number
|
||||||
|
luaL_checktype(L, -1, LUA_TTABLE); // value is table
|
||||||
|
// const char * key = NULL;
|
||||||
|
// ZoneOption::Type type = ZoneOption::Integer;
|
||||||
|
// int val = 0;
|
||||||
|
uint8_t field = 0;
|
||||||
|
for (lua_pushnil(L); lua_next(L, -2) && field<3; lua_pop(L, 1), field++) {
|
||||||
|
switch (field) {
|
||||||
|
case 0:
|
||||||
|
luaL_checktype(L, -2, LUA_TNUMBER); // key is number
|
||||||
|
luaL_checktype(L, -1, LUA_TSTRING); // value is string
|
||||||
|
option->name = lua_tostring(L, -1);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
luaL_checktype(L, -2, LUA_TNUMBER); // key is number
|
||||||
|
luaL_checktype(L, -1, LUA_TNUMBER); // value is number
|
||||||
|
option->type = (ZoneOption::Type)lua_tointeger(L, -1);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
luaL_checktype(L, -2, LUA_TNUMBER); // key is number
|
||||||
|
luaL_checktype(L, -1, LUA_TNUMBER); // value is number
|
||||||
|
option->deflt.signedValue = lua_tointeger(L, -1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TRACE("option[%d] = %s %d %d", i, key, type, val);
|
||||||
|
option++;
|
||||||
|
// options[i++]. = (ZoneOption) { key, type, { .signedValue = val } };
|
||||||
|
}
|
||||||
|
|
||||||
|
option->name = NULL; // sentinel
|
||||||
|
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
void l_pushtableint(const char * key, int value)
|
||||||
|
{
|
||||||
|
lua_pushstring(L, key);
|
||||||
|
lua_pushinteger(L, value);
|
||||||
|
lua_settable(L, -3);
|
||||||
|
}
|
||||||
|
|
||||||
|
class LuaWidgetFactory: public WidgetFactory
|
||||||
|
{
|
||||||
|
friend void luaLoadWidgetCallback();
|
||||||
|
friend class LuaWidget;
|
||||||
|
|
||||||
|
public:
|
||||||
|
LuaWidgetFactory(const char * name, int widgetOptions, int createFunction):
|
||||||
|
WidgetFactory(name, createOptionsArray(widgetOptions)),
|
||||||
|
createFunction(createFunction),
|
||||||
|
refreshFunction(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual Widget * create(const Zone & zone, Widget::PersistentData * persistentData, bool init=true) const
|
||||||
|
{
|
||||||
|
if (init) {
|
||||||
|
initPersistentData(persistentData);
|
||||||
|
}
|
||||||
|
|
||||||
|
SET_LUA_INSTRUCTIONS_COUNT(PERMANENT_SCRIPTS_MAX_INSTRUCTIONS);
|
||||||
|
lua_rawgeti(L, LUA_REGISTRYINDEX, createFunction);
|
||||||
|
|
||||||
|
lua_newtable(L);
|
||||||
|
l_pushtableint("x", zone.x);
|
||||||
|
l_pushtableint("y", zone.y);
|
||||||
|
l_pushtableint("w", zone.w);
|
||||||
|
l_pushtableint("h", zone.h);
|
||||||
|
|
||||||
|
lua_newtable(L);
|
||||||
|
int i = 0;
|
||||||
|
for (const ZoneOption * option = options; option->name; option++, i++) {
|
||||||
|
l_pushtableint(option->name, persistentData->options[i].signedValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lua_pcall(L, 2, 1, 0) != 0) {
|
||||||
|
TRACE("Error in widget %s: %s", getName(), lua_tostring(L, -1));
|
||||||
|
}
|
||||||
|
int widgetData = luaL_ref(L, LUA_REGISTRYINDEX);
|
||||||
|
Widget * widget = new LuaWidget(this, zone, persistentData, widgetData);
|
||||||
|
return widget;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
int createFunction;
|
||||||
|
int refreshFunction;
|
||||||
|
};
|
||||||
|
|
||||||
|
void LuaWidget::refresh()
|
||||||
|
{
|
||||||
|
SET_LUA_INSTRUCTIONS_COUNT(PERMANENT_SCRIPTS_MAX_INSTRUCTIONS);
|
||||||
|
LuaWidgetFactory * factory = (LuaWidgetFactory *)this->factory;
|
||||||
|
lua_rawgeti(L, LUA_REGISTRYINDEX, factory->refreshFunction);
|
||||||
|
lua_rawgeti(L, LUA_REGISTRYINDEX, widgetData);
|
||||||
|
if (lua_pcall(L, 1, 0, 0) != 0) {
|
||||||
|
TRACE("Error in widget %s: %s", factory->getName(), lua_tostring(L, -1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void luaLoadWidgetCallback()
|
||||||
|
{
|
||||||
|
const char * name=NULL, * options=NULL;
|
||||||
|
int widgetOptions=0, createFunction=0, refreshFunction=0;
|
||||||
|
|
||||||
|
luaL_checktype(L, -1, LUA_TTABLE);
|
||||||
|
|
||||||
|
for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) {
|
||||||
|
const char * key = lua_tostring(L, -2);
|
||||||
|
if (!strcmp(key, "name")) {
|
||||||
|
name = luaL_checkstring(L, -1);
|
||||||
|
}
|
||||||
|
else if (!strcmp(key, "options")) {
|
||||||
|
widgetOptions = luaL_ref(L, LUA_REGISTRYINDEX);
|
||||||
|
lua_pushnil(L);
|
||||||
|
}
|
||||||
|
else if (!strcmp(key, "create")) {
|
||||||
|
createFunction = luaL_ref(L, LUA_REGISTRYINDEX);
|
||||||
|
lua_pushnil(L);
|
||||||
|
}
|
||||||
|
else if (!strcmp(key, "refresh")) {
|
||||||
|
refreshFunction = luaL_ref(L, LUA_REGISTRYINDEX);
|
||||||
|
lua_pushnil(L);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name && createFunction) {
|
||||||
|
LuaWidgetFactory * factory = new LuaWidgetFactory(name, widgetOptions, createFunction);
|
||||||
|
factory->refreshFunction = refreshFunction;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void luaLoadFile(const char * filename, void (*callback)())
|
||||||
|
{
|
||||||
if (luaState == INTERPRETER_PANIC) {
|
if (luaState == INTERPRETER_PANIC) {
|
||||||
return SCRIPT_PANIC;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(LUA_COMPILER) && defined(SIMU)
|
#if defined(LUA_COMPILER) && defined(SIMU)
|
||||||
|
@ -880,45 +1044,7 @@ int luaLoadTheme(const char * filename)
|
||||||
if (luaL_loadfile(L, filename) == 0 &&
|
if (luaL_loadfile(L, filename) == 0 &&
|
||||||
lua_pcall(L, 0, 1, 0) == 0 &&
|
lua_pcall(L, 0, 1, 0) == 0 &&
|
||||||
lua_istable(L, -1)) {
|
lua_istable(L, -1)) {
|
||||||
|
(*callback)();
|
||||||
const char * name=NULL, * bitmap=NULL;
|
|
||||||
int loadFunction=0, drawBackgroundFunction=0, drawTopbarBackgroundFunction=0;
|
|
||||||
|
|
||||||
luaL_checktype(L, -1, LUA_TTABLE);
|
|
||||||
|
|
||||||
for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) {
|
|
||||||
const char * key = lua_tostring(L, -2);
|
|
||||||
if (!strcmp(key, "name")) {
|
|
||||||
name = luaL_checkstring(L, -1);
|
|
||||||
}
|
|
||||||
else if (!strcmp(key, "bitmap")) {
|
|
||||||
bitmap = luaL_checkstring(L, -1);
|
|
||||||
}
|
|
||||||
else if (!strcmp(key, "load")) {
|
|
||||||
loadFunction = luaL_ref(L, LUA_REGISTRYINDEX);
|
|
||||||
lua_pushnil(L);
|
|
||||||
}
|
|
||||||
else if (!strcmp(key, "drawBackground")) {
|
|
||||||
drawBackgroundFunction = luaL_ref(L, LUA_REGISTRYINDEX);
|
|
||||||
lua_pushnil(L);
|
|
||||||
}
|
|
||||||
else if (!strcmp(key, "drawTopbarBackground")) {
|
|
||||||
drawTopbarBackgroundFunction = luaL_ref(L, LUA_REGISTRYINDEX);
|
|
||||||
lua_pushnil(L);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name && bitmap) {
|
|
||||||
char path[THEME_FILENAME_MAXLEN+1] = THEMES_PATH;
|
|
||||||
path[sizeof(THEMES_PATH)-1] = '/';
|
|
||||||
strcpy(path+sizeof(THEMES_PATH), bitmap);
|
|
||||||
uint8_t * bitmapData = (uint8_t *)malloc(BITMAP_BUFFER_SIZE(51, 31));
|
|
||||||
bmpLoad(bitmapData, path, 51, 31);
|
|
||||||
LuaTheme * theme = new LuaTheme(name, bitmapData);
|
|
||||||
theme->loadFunction = loadFunction;
|
|
||||||
theme->drawBackgroundFunction = drawBackgroundFunction;
|
|
||||||
theme->drawTopbarBackgroundFunction = drawTopbarBackgroundFunction;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
TRACE("Error in script %s: %s", filename, lua_tostring(L, -1));
|
TRACE("Error in script %s: %s", filename, lua_tostring(L, -1));
|
||||||
|
@ -926,14 +1052,14 @@ int luaLoadTheme(const char * filename)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
luaDisable();
|
luaDisable();
|
||||||
return SCRIPT_PANIC;
|
return;
|
||||||
}
|
}
|
||||||
UNPROTECT_LUA();
|
UNPROTECT_LUA();
|
||||||
}
|
}
|
||||||
|
|
||||||
void luaLoadThemes()
|
void luaLoadFiles(const char * directory, void (*callback)())
|
||||||
{
|
{
|
||||||
char path[THEME_FILENAME_MAXLEN+1] = THEMES_PATH;
|
char path[LUA_FULLPATH_MAXLEN+1];
|
||||||
FILINFO fno;
|
FILINFO fno;
|
||||||
DIR dir;
|
DIR dir;
|
||||||
char * fn; /* This function is assuming non-Unicode cfg. */
|
char * fn; /* This function is assuming non-Unicode cfg. */
|
||||||
|
@ -941,21 +1067,64 @@ void luaLoadThemes()
|
||||||
fno.lfname = lfn;
|
fno.lfname = lfn;
|
||||||
fno.lfsize = sizeof(lfn);
|
fno.lfsize = sizeof(lfn);
|
||||||
|
|
||||||
|
strcpy(path, directory);
|
||||||
|
int pathlen = strlen(path);
|
||||||
|
|
||||||
FRESULT res = f_opendir(&dir, path); /* Open the directory */
|
FRESULT res = f_opendir(&dir, path); /* Open the directory */
|
||||||
if (res == FR_OK) {
|
if (res == FR_OK) {
|
||||||
|
path[pathlen++] = '/';
|
||||||
for (;;) {
|
for (;;) {
|
||||||
res = f_readdir(&dir, &fno); /* Read a directory item */
|
res = f_readdir(&dir, &fno); /* Read a directory item */
|
||||||
if (res != FR_OK || fno.fname[0] == 0) break; /* Break on error or end of dir */
|
if (res != FR_OK || fno.fname[0] == 0) break; /* Break on error or end of dir */
|
||||||
fn = * fno.lfname ? fno.lfname : fno.fname;
|
fn = * fno.lfname ? fno.lfname : fno.fname;
|
||||||
uint8_t len = strlen(fn);
|
uint8_t len = strlen(fn);
|
||||||
bool found = false;
|
|
||||||
|
|
||||||
// Eliminates directories / non wav files
|
// Eliminates directories / non wav files
|
||||||
if (len < 5 || strcasecmp(fn+len-4, SCRIPTS_EXT) || (fno.fattrib & AM_DIR)) continue;
|
if (len < 5 || strcasecmp(fn+len-4, SCRIPTS_EXT) || (fno.fattrib & AM_DIR)) continue;
|
||||||
path[sizeof(THEMES_PATH)-1] = '/';
|
strcpy(&path[pathlen], fn);
|
||||||
strcpy(path+sizeof(THEMES_PATH), fn);
|
luaLoadFile(path, callback);
|
||||||
luaLoadTheme(path);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void luaInit()
|
||||||
|
{
|
||||||
|
TRACE("luaInit");
|
||||||
|
|
||||||
|
#if !defined(COLORLCD)
|
||||||
|
luaClose();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (luaState != INTERPRETER_PANIC) {
|
||||||
|
#if defined(USE_BIN_ALLOCATOR)
|
||||||
|
L = lua_newstate(bin_l_alloc, NULL); //we use our own allocator!
|
||||||
|
#else
|
||||||
|
L = lua_newstate(l_alloc, NULL); //we use Lua default allocator
|
||||||
|
#endif
|
||||||
|
if (L) {
|
||||||
|
// install our panic handler
|
||||||
|
lua_atpanic(L, &custom_lua_atpanic);
|
||||||
|
|
||||||
|
// protect libs and constants registration
|
||||||
|
PROTECT_LUA() {
|
||||||
|
luaRegisterAll();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// if we got panic during registration
|
||||||
|
// we disable Lua for this session
|
||||||
|
luaDisable();
|
||||||
|
}
|
||||||
|
UNPROTECT_LUA();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
/* log error and return */
|
||||||
|
luaDisable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(COLORLCD)
|
||||||
|
luaLoadFiles(THEMES_PATH, luaLoadThemeCallback);
|
||||||
|
luaLoadFiles(WIDGETS_PATH, luaLoadWidgetCallback);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1920,11 +1920,6 @@ void opentxStart()
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(COLORLCD)
|
|
||||||
luaInit();
|
|
||||||
loadTheme();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(GUI)
|
#if defined(GUI)
|
||||||
checkAlarm();
|
checkAlarm();
|
||||||
checkAll();
|
checkAll();
|
||||||
|
@ -2476,6 +2471,11 @@ void opentxInit(OPENTX_INIT_ARGS)
|
||||||
{
|
{
|
||||||
TRACE("opentxInit()");
|
TRACE("opentxInit()");
|
||||||
|
|
||||||
|
#if defined(COLORLCD)
|
||||||
|
luaInit();
|
||||||
|
loadTheme();
|
||||||
|
#endif
|
||||||
|
|
||||||
storageReadAll();
|
storageReadAll();
|
||||||
|
|
||||||
#if defined(CPUARM)
|
#if defined(CPUARM)
|
||||||
|
|
|
@ -336,6 +336,11 @@ void * main_thread(void *)
|
||||||
menuHandlers[0] = menuMainView;
|
menuHandlers[0] = menuMainView;
|
||||||
menuHandlers[1] = menuModelSelect;
|
menuHandlers[1] = menuModelSelect;
|
||||||
|
|
||||||
|
#if defined(COLORLCD)
|
||||||
|
luaInit();
|
||||||
|
loadTheme();
|
||||||
|
#endif
|
||||||
|
|
||||||
storageReadAll(); // load general setup and selected model
|
storageReadAll(); // load general setup and selected model
|
||||||
|
|
||||||
#if defined(SIMU_DISKIO)
|
#if defined(SIMU_DISKIO)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue