715 lines
32 KiB
C++
715 lines
32 KiB
C++
#include <lvgl.h>
|
|
#include "applicationInternal/gui/guiBase.h"
|
|
#include "applicationInternal/gui/guiMemoryOptimizer.h"
|
|
#include "applicationInternal/gui/guiRegistry.h"
|
|
#include "applicationInternal/hardware/hardwarePresenter.h"
|
|
#include "applicationInternal/scenes/sceneRegistry.h"
|
|
|
|
struct t_gui_on_tab {
|
|
lv_obj_t* tab;
|
|
std::string GUIname;
|
|
int gui_list_index;
|
|
int gui_list_index_previous;
|
|
};
|
|
struct t_gui_state {
|
|
// the next three and the last are saved in the preferenceStorage every time they change
|
|
std::string activeScene_internalDontUse;
|
|
std::string activeGUIname_internalDontUse;
|
|
GUIlists activeGUIlist_internalDontUse;
|
|
// ---
|
|
int activeTabID = -1; // id of the active tab (one of 0,1,2)
|
|
int oldTabID = -1; // id of the tab before swiping (one of 0,1,2)
|
|
t_gui_on_tab gui_on_tab[3] = {{NULL, "", -1, -1}, {NULL, "", -1, -1}, {NULL, "", -1, -1}};
|
|
// the last active gui of scene. Will be stored to easily navigate back to it with guis_doTabCreationForNavigateToLastActiveGUIofPreviousGUIlist()
|
|
GUIlists last_active_gui_list = (GUIlists)-1;
|
|
int last_active_gui_list_index_internalDontUse = -1;
|
|
};
|
|
t_gui_state gui_state;
|
|
|
|
// Both the gui_state and the preferenceStorage should know at any time the current state (scene, GUIname, and GUIlist)
|
|
// preferenceStorage should know it because when going to sleep, it should persist the state in NVM.
|
|
// So whenever values change, it should be done through these functions.
|
|
// On startup, the gui_state is set by gui_memoryOptimizer_onStartup()
|
|
std::string gui_memoryOptimizer_getActiveSceneName() {
|
|
gui_state.activeScene_internalDontUse = get_activeScene();
|
|
return gui_state.activeScene_internalDontUse;
|
|
}
|
|
void gui_memoryOptimizer_setActiveSceneName(std::string aSceneName) {
|
|
gui_state.activeScene_internalDontUse = aSceneName;
|
|
set_activeScene(aSceneName);
|
|
}
|
|
std::string gui_memoryOptimizer_getActiveGUIname() {
|
|
gui_state.activeGUIname_internalDontUse = get_activeGUIname();
|
|
return gui_state.activeGUIname_internalDontUse;
|
|
}
|
|
void gui_memoryOptimizer_setActiveGUIname(std::string aGUIname) {
|
|
gui_state.activeGUIname_internalDontUse = aGUIname;
|
|
set_activeGUIname(aGUIname);
|
|
}
|
|
GUIlists gui_memoryOptimizer_getActiveGUIlist() {
|
|
gui_state.activeGUIlist_internalDontUse = (GUIlists)get_activeGUIlist();
|
|
return gui_state.activeGUIlist_internalDontUse;
|
|
}
|
|
void gui_memoryOptimizer_setActiveGUIlist(GUIlists aGUIlist) {
|
|
gui_state.activeGUIlist_internalDontUse = aGUIlist;
|
|
set_activeGUIlist(aGUIlist);
|
|
}
|
|
int gui_memoryOptimizer_getLastActiveGUIlistIndex() {
|
|
gui_state.last_active_gui_list_index_internalDontUse = get_lastActiveGUIlistIndex();
|
|
return gui_state.last_active_gui_list_index_internalDontUse;
|
|
}
|
|
void gui_memoryOptimizer_setLastActiveGUIlistIndex(int aGUIlistIndex) {
|
|
gui_state.last_active_gui_list_index_internalDontUse = aGUIlistIndex;
|
|
set_lastActiveGUIlistIndex(aGUIlistIndex);
|
|
}
|
|
|
|
int gui_memoryOptimizer_getActiveTabID() {
|
|
return gui_state.activeTabID;
|
|
}
|
|
|
|
bool gui_memoryOptimizer_isTabIDInMemory(int tabID) {
|
|
// range check
|
|
if ((tabID < 0) || (tabID >= 3)) {
|
|
return false;
|
|
}
|
|
return (gui_state.gui_on_tab[tabID].gui_list_index != -1);
|
|
}
|
|
|
|
bool gui_memoryOptimizer_isGUInameInMemory(std::string GUIname) {
|
|
for (uint8_t index=0; index <= 2; index++) {
|
|
if (gui_state.gui_on_tab[index].GUIname == GUIname) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void notify_active_tabs_before_delete(t_gui_state *gui_state) {
|
|
Serial.printf(" Will notify tabs about deletion\r\n");
|
|
std::string nameOfTab;
|
|
for (int index=0; index <= 2; index++) {
|
|
if (gui_state->gui_on_tab[index].gui_list_index == -1) {
|
|
Serial.printf(" Will not notify tab %d about deletion because it does not exist\r\n", index);
|
|
continue;
|
|
}
|
|
|
|
// For deletion, do not use the gui_list_index, but the name of the gui.
|
|
// The gui_list might have changed (when switching from a scene specific list to the main list or vice versa), so index could have changed as well.
|
|
nameOfTab = gui_state->gui_on_tab[index].GUIname;
|
|
if (nameOfTab == "") {
|
|
Serial.printf(" Will not notify tab %d about deletion because it is not set\r\n", index);
|
|
} else if (registered_guis_byName_map.count(nameOfTab) == 0) {
|
|
Serial.printf(" Can not notify tab %d about deletion because name \"%s\" was not found in registry\r\n", index, nameOfTab.c_str());
|
|
} else {
|
|
Serial.printf(" Will notify tab %d with name \"%s\" about deletion\r\n", index, nameOfTab.c_str());
|
|
registered_guis_byName_map.at(nameOfTab).this_notify_tab_before_delete();
|
|
}
|
|
}
|
|
}
|
|
|
|
void clear_tabview(lv_obj_t* tabview, t_gui_state *gui_state) {
|
|
if (tabview != NULL) {
|
|
// first remove events for the tabview
|
|
lv_obj_remove_event_cb(tabview, tabview_tab_changed_event_cb);
|
|
lv_obj_remove_event_cb(tabview, tabview_content_is_scrolling_event_cb);
|
|
// delete tabview
|
|
lv_obj_delete(tabview);
|
|
tabview = NULL;
|
|
}
|
|
|
|
// the gui_list_index_previous is needed for setGUIlistIndicesToBeShown_afterSlide();
|
|
gui_state->gui_on_tab[0] = {NULL, "", -1, gui_state->gui_on_tab[0].gui_list_index};
|
|
gui_state->gui_on_tab[1] = {NULL, "", -1, gui_state->gui_on_tab[1].gui_list_index};
|
|
gui_state->gui_on_tab[2] = {NULL, "", -1, gui_state->gui_on_tab[2].gui_list_index};
|
|
|
|
}
|
|
|
|
void clear_panel(lv_obj_t* panel, lv_obj_t* img1, lv_obj_t* img2) {
|
|
if (panel != NULL) {
|
|
lv_obj_delete(panel);
|
|
panel = NULL;
|
|
}
|
|
if (img1 != NULL) {
|
|
lv_obj_delete(img1);
|
|
img1 = NULL;
|
|
}
|
|
if (img2 != NULL) {
|
|
lv_obj_delete(img2);
|
|
img2 = NULL;
|
|
}
|
|
|
|
}
|
|
|
|
lv_obj_t* create_tabview() {
|
|
// Setup a scrollable tabview for devices and settings ----------------------------------------------------
|
|
lv_obj_t* tabview = lv_tabview_create(lv_screen_active()); // Hide tab labels by setting their height to 0
|
|
lv_tabview_set_tab_bar_position(tabview, LV_DIR_TOP);
|
|
lv_tabview_set_tab_bar_size(tabview, 0);
|
|
#ifdef drawRedBorderAroundMainWidgets
|
|
lv_obj_add_style(tabview, &style_red_border, LV_PART_MAIN);
|
|
#endif
|
|
lv_obj_set_style_bg_color(tabview, lv_color_black(), LV_PART_MAIN);
|
|
lv_obj_set_size(tabview, SCR_WIDTH, tabviewHeight);
|
|
lv_obj_align(tabview, LV_ALIGN_TOP_MID, 0, tabviewTop);
|
|
return tabview;
|
|
}
|
|
|
|
lv_obj_t* create_panel() {
|
|
// Create a page indicator at the bottom ------------------------------------------------------------------
|
|
lv_obj_t* panel = lv_obj_create(lv_screen_active());
|
|
lv_obj_remove_flag(panel, LV_OBJ_FLAG_CLICKABLE); // This indicator will not be clickable
|
|
lv_obj_remove_flag(panel, LV_OBJ_FLAG_SCROLLABLE); // This indicator will not be scrollable
|
|
lv_obj_set_size(panel, SCR_WIDTH, panelHeight);
|
|
lv_obj_set_flex_flow(panel, LV_FLEX_FLOW_ROW);
|
|
lv_obj_align(panel, LV_ALIGN_BOTTOM_MID, 0, 0);
|
|
lv_obj_set_scrollbar_mode(panel, LV_SCROLLBAR_MODE_OFF);
|
|
|
|
return panel;
|
|
}
|
|
|
|
std::string get_name_of_gui_to_be_shown(int index) {
|
|
if (index == -1) {
|
|
return "";
|
|
} else if (index <= get_gui_list_active_withFallback()->size() -1) {
|
|
return get_gui_list_active_withFallback()->at(index);
|
|
} else {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
void create_new_tab(lv_obj_t* tabview, t_gui_on_tab *gui_on_tab) {
|
|
std::string nameOfTab = get_name_of_gui_to_be_shown(gui_on_tab->gui_list_index);
|
|
|
|
if (nameOfTab == "") {
|
|
Serial.printf(" Will not create new tab because no name was provided\r\n");
|
|
} else if (registered_guis_byName_map.count(nameOfTab) == 0) {
|
|
Serial.printf(" Will not create new tab because name %s was not found in registry\r\n", nameOfTab.c_str());
|
|
} else {
|
|
Serial.printf(" Will create tab with name \"%s\" \r\n", nameOfTab.c_str());
|
|
// save name of tab for deletion later
|
|
gui_on_tab->GUIname = nameOfTab;
|
|
// create tab and save pointer to tab in gui_on_tab
|
|
gui_on_tab->tab = lv_tabview_add_tab(tabview, nameOfTab.c_str());
|
|
// let the gui create it's content
|
|
registered_guis_byName_map.at(nameOfTab).this_create_tab_content(gui_on_tab->tab);
|
|
}
|
|
}
|
|
|
|
// create up to three tabs and the content of the tabs
|
|
/*
|
|
example: gui_list: 0 1 2 3 4
|
|
in memory active
|
|
0 1 -1 0 <- first state, special case - also the initial state
|
|
0 1 2 1
|
|
1 2 3 1
|
|
2 3 4 1
|
|
3 4 -1 1 <- last state, special case
|
|
*/
|
|
void setGUIlistIndicesToBeShown_forSpecificGUIlistIndex(int gui_list_index, t_gui_state *gui_state) {
|
|
// Set the gui_list_indeces to be shown for a specific gui_list_index
|
|
if (gui_list_index == 0) {
|
|
// first state
|
|
Serial.printf(" GUIlistIndices: will resume at specific index with \"first state\"\r\n");
|
|
gui_state->gui_on_tab[0] = {NULL, "", 0};
|
|
// take care if there is only one gui in list
|
|
gui_state->gui_on_tab[1] = {NULL, "", get_gui_list_active_withFallback()->size() >= 2 ? 1 : -1};
|
|
gui_state->gui_on_tab[2] = {NULL, "", -1};
|
|
gui_state->activeTabID = 0;
|
|
} else if (gui_list_index == get_gui_list_active_withFallback()->size() -1) {
|
|
// last state
|
|
Serial.printf(" GUIlistIndices: will resume at specific index with \"last state\"\r\n");
|
|
gui_state->gui_on_tab[0] = {NULL, "", gui_list_index -1};
|
|
gui_state->gui_on_tab[1] = {NULL, "", gui_list_index};
|
|
gui_state->gui_on_tab[2] = {NULL, "", -1};
|
|
gui_state->activeTabID = 1;
|
|
} else {
|
|
// any other state
|
|
Serial.printf(" GUIlistIndices: will resume at specific index with \"state between\"\r\n");
|
|
gui_state->gui_on_tab[0] = {NULL, "", gui_list_index -1};
|
|
gui_state->gui_on_tab[1] = {NULL, "", gui_list_index};
|
|
gui_state->gui_on_tab[2] = {NULL, "", gui_list_index +1};
|
|
gui_state->activeTabID = 1;
|
|
}
|
|
}
|
|
|
|
void setGUIlistIndicesToBeShown_forFirstGUIinGUIlist(t_gui_state *gui_state) {
|
|
Serial.printf(" GUIlistIndices: will show the first gui from \"gui_list\" as initial state\r\n");
|
|
// take care if there is no gui in list
|
|
gui_state->gui_on_tab[0] = {NULL, "", get_gui_list_active_withFallback()->size() != 0 ? 0 : -1};
|
|
// take care if there is only one gui in list
|
|
gui_state->gui_on_tab[1] = {NULL, "", get_gui_list_active_withFallback()->size() >= 2 ? 1 : -1};
|
|
gui_state->gui_on_tab[2] = {NULL, "", -1};
|
|
gui_state->activeTabID = 0;
|
|
}
|
|
|
|
void setGUIlistIndicesToBeShown_afterSlide(t_gui_state *gui_state) {
|
|
int oldListIndex = -1;
|
|
|
|
if (gui_state->oldTabID > gui_state->activeTabID) {
|
|
// swipe to previous item in list
|
|
Serial.printf(" Will swipe to previous item in list\r\n");
|
|
oldListIndex = gui_state->gui_on_tab[1].gui_list_index_previous;
|
|
if ((oldListIndex == 1)) {
|
|
// next state is the "first state"
|
|
gui_state->gui_on_tab[0] = {NULL, "", 0};
|
|
gui_state->gui_on_tab[1] = {NULL, "", 1};
|
|
gui_state->gui_on_tab[2] = {NULL, "", -1};
|
|
gui_state->activeTabID = 0;
|
|
} else {
|
|
gui_state->gui_on_tab[0] = {NULL, "", oldListIndex -2};
|
|
gui_state->gui_on_tab[1] = {NULL, "", oldListIndex -1};
|
|
gui_state->gui_on_tab[2] = {NULL, "", oldListIndex};
|
|
gui_state->activeTabID = 1;
|
|
}
|
|
} else {
|
|
// swipe to next item in list
|
|
Serial.printf(" Will swipe to next item in list\r\n");
|
|
if (gui_state->gui_on_tab[2].gui_list_index_previous == -1) {
|
|
// last state was the first state
|
|
oldListIndex = gui_state->gui_on_tab[0].gui_list_index_previous; // is always 0
|
|
} else {
|
|
oldListIndex = gui_state->gui_on_tab[1].gui_list_index_previous;
|
|
}
|
|
if (oldListIndex == get_gui_list_active_withFallback()->size() -2) {
|
|
// next state is the "last state"
|
|
gui_state->gui_on_tab[0] = {NULL, "", oldListIndex};
|
|
gui_state->gui_on_tab[1] = {NULL, "", oldListIndex +1};
|
|
gui_state->gui_on_tab[2] = {NULL, "", -1};
|
|
gui_state->activeTabID = 1;
|
|
} else {
|
|
gui_state->gui_on_tab[0] = {NULL, "", oldListIndex};
|
|
gui_state->gui_on_tab[1] = {NULL, "", oldListIndex +1};
|
|
gui_state->gui_on_tab[2] = {NULL, "", oldListIndex +2};
|
|
gui_state->activeTabID = 1;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void doTabCreation_strategyMax3(lv_obj_t* tabview, t_gui_state *gui_state) {
|
|
|
|
// create the tabs
|
|
Serial.printf(" Will create tabs. List indices of the three tabs are %d, %d, %d, tab nr %d will be activated\r\n", gui_state->gui_on_tab[0].gui_list_index, gui_state->gui_on_tab[1].gui_list_index, gui_state->gui_on_tab[2].gui_list_index, gui_state->activeTabID);
|
|
for (int i=0; i<3; i++) {
|
|
create_new_tab(tabview, &gui_state->gui_on_tab[i]);
|
|
}
|
|
|
|
if (get_gui_list_active_withFallback()->size() > 0) {
|
|
std::string nameOfNewActiveTab = get_gui_list_active_withFallback()->at(gui_state->gui_on_tab[gui_state->activeTabID].gui_list_index);
|
|
Serial.printf(" New visible tab is \"%s\"\r\n", nameOfNewActiveTab.c_str());
|
|
|
|
// set active tab
|
|
setActiveTab(gui_state->activeTabID, LV_ANIM_OFF);
|
|
gui_memoryOptimizer_setActiveGUIname(nameOfNewActiveTab);
|
|
}
|
|
}
|
|
|
|
LV_IMAGE_DECLARE(gradientLeft);
|
|
LV_IMAGE_DECLARE(gradientRight);
|
|
|
|
void fillPanelWithPageIndicator_strategyMax3(lv_obj_t* panel, lv_obj_t* img1, lv_obj_t* img2, t_gui_state *gui_state) {
|
|
Serial.printf(" Will fill panel with page indicators\r\n");
|
|
|
|
if (get_gui_list_active_withFallback()->size() == 0) {
|
|
Serial.printf(" no tab available, so no page indicators\r\n");
|
|
// at least add the style
|
|
lv_obj_add_style(panel, &panel_style, 0);
|
|
#ifdef drawRedBorderAroundMainWidgets
|
|
lv_obj_add_style(panel, &style_red_border, LV_PART_MAIN);
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
// This small hidden button enables the page indicator to scroll further
|
|
lv_obj_t* btn = lv_button_create(panel);
|
|
lv_obj_set_size(btn, 50, lv_pct(100));
|
|
lv_obj_set_style_shadow_width(btn, 0, LV_PART_MAIN);
|
|
lv_obj_set_style_opa(btn, LV_OPA_TRANSP, LV_PART_MAIN);
|
|
|
|
/*
|
|
There needs to be two more screen indicators because of their different size (158 for page indicator, 240 for whole tab)
|
|
In some cases they need to have color black, if they are before the first tab or after the last tab.
|
|
In all other cases, they have color "color_primary". See this list:
|
|
example: gui_list: 0 1 2 3 4
|
|
in memory color active
|
|
0 1 -1 b p p p 0 <- first state, special case - also the initial state
|
|
0 1 2 b p p p p 1
|
|
1 2 3 p p p p p 1
|
|
2 3 4 p p p p b 1
|
|
3 4 -1 p p p b 1 <- last state, special case
|
|
*/
|
|
// first page indicator before the first tab
|
|
btn = lv_button_create(panel);
|
|
lv_obj_remove_flag(btn, LV_OBJ_FLAG_CLICKABLE);
|
|
lv_obj_set_size(btn, 150, lv_pct(100));
|
|
if (gui_state->gui_on_tab[0].gui_list_index == 0) {
|
|
lv_obj_set_style_bg_color(btn, lv_color_black(), LV_PART_MAIN);
|
|
} else {
|
|
lv_obj_set_style_bg_color(btn, color_primary, LV_PART_MAIN);
|
|
}
|
|
|
|
uint8_t breadcrumpDotSize = 8; // should be an even number
|
|
uint8_t breadcrumpDotDistance = 2; // should be an even number
|
|
uint8_t breadcrumpMainGuiListLength = get_gui_list_withFallback(MAIN_GUI_LIST)->size();
|
|
int8_t breadcrumpMainGuiListStartPositionX = (-1) * (breadcrumpMainGuiListLength -1) * (breadcrumpDotSize + breadcrumpDotDistance) / 2;
|
|
uint8_t breadcrumpSceneGuiListLength = get_gui_list_withFallback(SCENE_GUI_LIST)->size();
|
|
int8_t breadcrumpSceneGuiListStartPositionX = (-1) * (breadcrumpSceneGuiListLength -1) * (breadcrumpDotSize + breadcrumpDotDistance) / 2;
|
|
#if (USE_SCENE_SPECIFIC_GUI_LIST != 0)
|
|
bool show_scene_gui_list = get_scene_has_gui_list(gui_memoryOptimizer_getActiveSceneName());
|
|
#else
|
|
bool show_scene_gui_list = false;
|
|
#endif
|
|
int8_t breadcrumpMainGuiList_yPos;
|
|
int8_t breadcrumpSceneGuiList_yPos;
|
|
int8_t nameOfGUI_yPos;
|
|
if (!show_scene_gui_list) {
|
|
breadcrumpMainGuiList_yPos = -6;
|
|
nameOfGUI_yPos = 6;
|
|
} else {
|
|
breadcrumpMainGuiList_yPos = -8;
|
|
breadcrumpSceneGuiList_yPos = -1;
|
|
nameOfGUI_yPos = 8;
|
|
}
|
|
|
|
// create the panel content for the three guis (or less) which are currently in memory
|
|
std::string nameOfGUI;
|
|
uint8_t breadcrumpPosition;
|
|
for (int i=0; i<3; i++) {
|
|
if (gui_state->gui_on_tab[i].gui_list_index != -1) {
|
|
nameOfGUI = gui_state->gui_on_tab[i].GUIname;
|
|
breadcrumpPosition = gui_state->gui_on_tab[i].gui_list_index +1;
|
|
|
|
// Create actual buttons for every tab
|
|
lv_obj_t* btn = lv_button_create(panel);
|
|
if (i == gui_state->activeTabID) {
|
|
// only if this is the button for the currently active tab, make it clickable to get to scene selection gui
|
|
lv_obj_add_flag(btn, LV_OBJ_FLAG_CLICKABLE);
|
|
lv_obj_set_user_data(btn,(void *)(intptr_t)2);
|
|
lv_obj_add_event_cb(btn, sceneLabel_or_pageIndicator_event_cb, LV_EVENT_CLICKED, NULL);
|
|
|
|
} else if ((i==0 || i==1) && (gui_state->gui_on_tab[i+1].gui_list_index != -1)) {
|
|
// this is the button on the previous tab, which can be seen on the active tab
|
|
// activate click to prev tab
|
|
lv_obj_add_flag(btn, LV_OBJ_FLAG_CLICKABLE);
|
|
lv_obj_set_user_data(btn,(void *)(intptr_t)0);
|
|
lv_obj_add_event_cb(btn, pageIndicator_navigate_event_cb, LV_EVENT_CLICKED, NULL);
|
|
|
|
} else if (i==1 || i==2) {
|
|
// this is the button on the next tab, which can be seen on the active tab
|
|
// activate click to next tab
|
|
lv_obj_add_flag(btn, LV_OBJ_FLAG_CLICKABLE);
|
|
lv_obj_set_user_data(btn,(void *)(intptr_t)1);
|
|
lv_obj_add_event_cb(btn, pageIndicator_navigate_event_cb, LV_EVENT_CLICKED, NULL);
|
|
|
|
}
|
|
lv_obj_set_size(btn, 150, lv_pct(100));
|
|
lv_obj_remove_style(btn, NULL, LV_STATE_PRESSED);
|
|
lv_obj_set_style_shadow_width(btn, 0, LV_PART_MAIN);
|
|
lv_obj_set_style_bg_color(btn, color_primary, LV_PART_MAIN);
|
|
|
|
// create a breadcrump dot for each gui in the main_gui_list
|
|
for (int j=0; j<breadcrumpMainGuiListLength; j++) {
|
|
lv_obj_t* dot = lv_obj_create(btn);
|
|
lv_obj_set_size(dot, breadcrumpDotSize, breadcrumpDotSize);
|
|
lv_obj_set_style_radius(dot, LV_RADIUS_CIRCLE, LV_PART_MAIN);
|
|
// hightlight dot if it is the one for the currently active tab
|
|
#if (USE_SCENE_SPECIFIC_GUI_LIST != 0)
|
|
if ( ((gui_memoryOptimizer_getActiveGUIlist() == MAIN_GUI_LIST) || !get_scene_has_gui_list(gui_memoryOptimizer_getActiveSceneName()))
|
|
#else
|
|
if ( true
|
|
#endif
|
|
&& (j == (breadcrumpPosition-1))) {
|
|
lv_obj_set_style_bg_color(dot, lv_color_lighten(color_primary, 255), LV_PART_MAIN);
|
|
} else if ((gui_memoryOptimizer_getActiveGUIlist() == SCENE_GUI_LIST) && get_scene_has_gui_list(gui_memoryOptimizer_getActiveSceneName()) && (j == gui_memoryOptimizer_getLastActiveGUIlistIndex())) {
|
|
// hightlight dot a little bit if it is at least the one which was last active in the other gui list
|
|
lv_obj_set_style_bg_color(dot, lv_color_lighten(color_primary, 140), LV_PART_MAIN);
|
|
} else {
|
|
lv_obj_set_style_bg_color(dot, lv_color_lighten(color_primary, 30), LV_PART_MAIN);
|
|
}
|
|
|
|
lv_obj_align(dot, LV_ALIGN_TOP_MID, breadcrumpMainGuiListStartPositionX +j*(breadcrumpDotSize + breadcrumpDotDistance), breadcrumpMainGuiList_yPos);
|
|
// this dot needs to get clickable again
|
|
lv_obj_set_user_data(dot,(void *)(intptr_t)1);
|
|
lv_obj_add_flag(dot, LV_OBJ_FLAG_CLICKABLE);
|
|
lv_obj_add_flag(dot, LV_OBJ_FLAG_EVENT_BUBBLE);
|
|
}
|
|
|
|
// create a breadcrump dot for each gui in the scene gui list, if there is one
|
|
if (show_scene_gui_list) {
|
|
for (int j=0; j<breadcrumpSceneGuiListLength; j++) {
|
|
lv_obj_t* dot = lv_obj_create(btn);
|
|
lv_obj_set_size(dot, breadcrumpDotSize, breadcrumpDotSize);
|
|
lv_obj_set_style_radius(dot, LV_RADIUS_CIRCLE, LV_PART_MAIN);
|
|
if ((gui_memoryOptimizer_getActiveGUIlist() == SCENE_GUI_LIST) && (j == (breadcrumpPosition-1))) {
|
|
// hightlight dot if it is the one for the currently active tab
|
|
lv_obj_set_style_bg_color(dot, lv_color_lighten(color_primary, 255), LV_PART_MAIN);
|
|
} else if ((gui_memoryOptimizer_getActiveGUIlist() == MAIN_GUI_LIST) && (j == gui_memoryOptimizer_getLastActiveGUIlistIndex())) {
|
|
// hightlight dot a little bit if it is at least the one which was last active in the other gui list
|
|
lv_obj_set_style_bg_color(dot, lv_color_lighten(color_primary, 140), LV_PART_MAIN);
|
|
} else {
|
|
lv_obj_set_style_bg_color(dot, lv_color_lighten(color_primary, 30), LV_PART_MAIN);
|
|
}
|
|
|
|
lv_obj_align(dot, LV_ALIGN_TOP_MID, breadcrumpSceneGuiListStartPositionX +j*(breadcrumpDotSize + breadcrumpDotDistance), breadcrumpSceneGuiList_yPos);
|
|
// this dot needs to get clickable again
|
|
lv_obj_set_user_data(dot,(void *)(intptr_t)1);
|
|
lv_obj_add_flag(dot, LV_OBJ_FLAG_CLICKABLE);
|
|
lv_obj_add_flag(dot, LV_OBJ_FLAG_EVENT_BUBBLE);
|
|
}
|
|
}
|
|
|
|
|
|
// create a label for nameOfGUI
|
|
lv_obj_t* label = lv_label_create(btn);
|
|
lv_obj_set_style_text_font(label, &lv_font_montserrat_10, LV_PART_MAIN);
|
|
lv_label_set_text_fmt(label, "%s", nameOfGUI.c_str());
|
|
lv_obj_align(label, LV_ALIGN_BOTTOM_MID, 0, nameOfGUI_yPos);
|
|
|
|
}
|
|
}
|
|
|
|
// last page indicator after the last tab
|
|
btn = lv_button_create(panel);
|
|
lv_obj_remove_flag(btn, LV_OBJ_FLAG_CLICKABLE);
|
|
lv_obj_set_size(btn, 150, lv_pct(100));
|
|
// 4 at last position 4 at middle position only one tab available overall
|
|
if ((gui_state->gui_on_tab[2].gui_list_index == get_gui_list_active_withFallback()->size()-1) || (gui_state->gui_on_tab[1].gui_list_index == get_gui_list_active_withFallback()->size()-1) || (gui_state->gui_on_tab[1].gui_list_index == -1)) {
|
|
lv_obj_set_style_bg_color(btn, lv_color_black(), LV_PART_MAIN);
|
|
} else {
|
|
lv_obj_set_style_bg_color(btn, color_primary, LV_PART_MAIN);
|
|
}
|
|
|
|
// This small hidden button enables the page indicator to scroll further
|
|
btn = lv_button_create(panel);
|
|
lv_obj_set_size(btn, 50, lv_pct(100));
|
|
lv_obj_set_style_shadow_width(btn, 0, LV_PART_MAIN);
|
|
lv_obj_set_style_opa(btn, LV_OPA_TRANSP, LV_PART_MAIN);
|
|
|
|
// creation of style was moved to init_gui(void)
|
|
// otherwise repeated calls of lv_style_init will lead to a memory leak of about 46 bytes each time
|
|
// https://docs.lvgl.io/master/overview/style.html?highlight=lv_style_t#initialize-styles-and-set-get-properties
|
|
lv_obj_add_style(panel, &panel_style, 0);
|
|
#ifdef drawRedBorderAroundMainWidgets
|
|
lv_obj_add_style(panel, &style_red_border, LV_PART_MAIN);
|
|
#endif
|
|
|
|
// Make the indicator fade out at the sides using gradient bitmaps
|
|
// Bitmaps are above the buttons and labels
|
|
// don't create it here
|
|
// img1 = lv_image_create(lv_screen_active());
|
|
lv_obj_set_size(img1, panelHeight, panelHeight); // stretch the 1-pixel high image to 30px
|
|
lv_obj_align(img1, LV_ALIGN_BOTTOM_LEFT, 0, 0);
|
|
lv_image_set_src(img1, &gradientLeft);
|
|
lv_image_set_inner_align(img1, LV_IMAGE_ALIGN_STRETCH);
|
|
#ifdef drawRedBorderAroundMainWidgets
|
|
lv_obj_add_style(img1, &style_red_border, LV_PART_MAIN);
|
|
#endif
|
|
// don't create it here
|
|
// img2 = lv_image_create(lv_screen_active());
|
|
lv_obj_set_size(img2, panelHeight, panelHeight);
|
|
lv_obj_align(img2, LV_ALIGN_BOTTOM_RIGHT, 0, 0);
|
|
lv_image_set_src(img2, &gradientRight);
|
|
lv_image_set_inner_align(img2, LV_IMAGE_ALIGN_STRETCH);
|
|
#ifdef drawRedBorderAroundMainWidgets
|
|
lv_obj_add_style(img2, &style_red_border, LV_PART_MAIN);
|
|
#endif
|
|
|
|
}
|
|
|
|
void gui_memoryOptimizer_notifyAndClear(lv_obj_t** tabview, lv_obj_t** panel, lv_obj_t** img1, lv_obj_t** img2, t_gui_state *gui_state) {
|
|
|
|
// 1. notify old guis that they will be deleted so that they can persist their state if needed
|
|
notify_active_tabs_before_delete(gui_state);
|
|
// 2. clear current tabview and save gui_list_index_previous (needed for swipe)
|
|
clear_tabview(*tabview, gui_state);
|
|
// 3. clear current panel for page indicator
|
|
clear_panel(*panel, *img1, *img2);
|
|
|
|
}
|
|
|
|
void gui_memoryOptimizer_doContentCreation(lv_obj_t** tabview, lv_obj_t** panel, lv_obj_t** img1, lv_obj_t** img2, t_gui_state *gui_state);
|
|
|
|
// 1. tab creation on startup (called by init_gui())
|
|
// find the position of the current GUI in the gui list which was active last (both were automatically saved in the preferences)
|
|
void gui_memoryOptimizer_onStartup(lv_obj_t** tabview, lv_obj_t** panel, lv_obj_t** img1, lv_obj_t** img2) {
|
|
|
|
Serial.printf("Startup: try to resume at scene \"%s\" with GUI \"%s\"\r\n", gui_memoryOptimizer_getActiveSceneName().c_str(), gui_memoryOptimizer_getActiveGUIname().c_str());
|
|
|
|
// Get last state from preferences and save it in gui_state
|
|
// So it is ok to call them without using the return values.
|
|
gui_memoryOptimizer_getActiveSceneName();
|
|
gui_memoryOptimizer_getActiveGUIname();
|
|
gui_memoryOptimizer_getActiveGUIlist();
|
|
gui_memoryOptimizer_getLastActiveGUIlistIndex();
|
|
|
|
// 1. find last used gui
|
|
int gui_list_index = -1;
|
|
// find index of gui_memoryOptimizer_getActiveGUIname() in gui_list_active
|
|
for (int i=0; i<get_gui_list_active_withFallback()->size(); i++) {
|
|
if (get_gui_list_active_withFallback()->at(i) == gui_memoryOptimizer_getActiveGUIname()) {
|
|
Serial.printf("Startup: found GUI with name \"%s\" in \"gui_list_active\" at position %d\r\n", gui_memoryOptimizer_getActiveGUIname().c_str(), i);
|
|
gui_list_index = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// 2. set gui_list_indices and the tab to be activated
|
|
if ((gui_list_index >= 0) && (gui_list_index < get_gui_list_active_withFallback()->size())) {
|
|
// gui was found
|
|
setGUIlistIndicesToBeShown_forSpecificGUIlistIndex(gui_list_index, &gui_state);
|
|
|
|
} else {
|
|
// gui was not found
|
|
Serial.printf("Startup: GUI with name \"%s\" was not found. Will start with first GUI of main_gui_list\r\n", gui_memoryOptimizer_getActiveGUIname().c_str());
|
|
gui_memoryOptimizer_setActiveGUIlist(MAIN_GUI_LIST);
|
|
setGUIlistIndicesToBeShown_forFirstGUIinGUIlist(&gui_state);
|
|
|
|
}
|
|
|
|
// 3. create content
|
|
gui_memoryOptimizer_doContentCreation(tabview, panel, img1, img2, &gui_state);
|
|
|
|
}
|
|
|
|
// 2. tab creation after sliding (called by tabview_tab_changed_event_cb())
|
|
void gui_memoryOptimizer_afterSliding(lv_obj_t** tabview, lv_obj_t** panel, lv_obj_t** img1, lv_obj_t** img2, int newTabID) {
|
|
|
|
// Here the magic for dynamic creation and deletion of lvgl objects happens to keep memory usage low.
|
|
// The next and previous tab must always be available in the tabview, because they can already been seen during the animation.
|
|
// And you always need 3 tabs, otherwise you even could not slide to the next or previous tab.
|
|
// So we always have 3 tabs.
|
|
// After the animation, the tabview and hence all tabs are deleted and recreated.
|
|
|
|
Serial.printf("--- Start of tab deletion and creation\r\n");
|
|
|
|
gui_state.oldTabID = gui_state.activeTabID;
|
|
gui_state.activeTabID = newTabID;
|
|
|
|
Serial.printf("Changing from oldTabID %d \"%s\" to newTabID %d \"%s\"\r\n",
|
|
gui_state.oldTabID, gui_state.gui_on_tab[gui_state.oldTabID].GUIname.c_str(),
|
|
gui_state.activeTabID, gui_state.gui_on_tab[gui_state.activeTabID].GUIname.c_str());
|
|
|
|
// 1. notify old guis and clear tabview and panel
|
|
gui_memoryOptimizer_notifyAndClear(tabview, panel, img1, img2, &gui_state);
|
|
|
|
// only optional: delete and create the whole screen. Not necessary.
|
|
// Only used for a test. init_gui_status_bar() would need to be called again at a suitable place, because the status bar would also be deleted.
|
|
// lv_obj_t* oldscr = lv_screen_active();
|
|
// // create new screen
|
|
// lv_obj_t* newscr = lv_obj_create(NULL);
|
|
// // load this new screen
|
|
// lv_screen_load(newscr);
|
|
// lv_obj_delete(oldscr);
|
|
|
|
// 2. set gui_list_indices and the tab to be activated
|
|
setGUIlistIndicesToBeShown_afterSlide(&gui_state);
|
|
|
|
// 3. create content
|
|
gui_memoryOptimizer_doContentCreation(tabview, panel, img1, img2, &gui_state);
|
|
|
|
}
|
|
|
|
// 3. after gui list has changed (called by handleScene()), when switching between main_gui_list and scene specific list. Will show first GUi in list
|
|
void gui_memoryOptimizer_afterGUIlistChanged(lv_obj_t** tabview, lv_obj_t** panel, lv_obj_t** img1, lv_obj_t** img2, GUIlists newGUIlist) {
|
|
|
|
Serial.printf("--- Will change to new gui_list\r\n");
|
|
|
|
if (gui_state.last_active_gui_list != newGUIlist) {
|
|
// we are changing the gui_list, so save the last_active_gui_list_index
|
|
gui_memoryOptimizer_setLastActiveGUIlistIndex(gui_state.gui_on_tab[gui_state.activeTabID].gui_list_index);
|
|
}
|
|
|
|
// 1. notify old guis and clear tabview and panel
|
|
gui_memoryOptimizer_notifyAndClear(tabview, panel, img1, img2, &gui_state);
|
|
|
|
// 2. set gui_list_indices and the tab to be activated
|
|
gui_memoryOptimizer_setActiveGUIlist(newGUIlist);
|
|
setGUIlistIndicesToBeShown_forFirstGUIinGUIlist(&gui_state);
|
|
|
|
// 3. create content
|
|
gui_memoryOptimizer_doContentCreation(tabview, panel, img1, img2, &gui_state);
|
|
|
|
}
|
|
|
|
// 4. navigate to a specific GUI in gui_list
|
|
void gui_memoryOptimizer_navigateToGUI(lv_obj_t** tabview, lv_obj_t** panel, lv_obj_t** img1, lv_obj_t** img2, GUIlists GUIlist, int gui_list_index) {
|
|
|
|
Serial.printf("--- Will navigate to specific GUI\r\n");
|
|
|
|
if ( !((gui_list_index >= 0) && (gui_list_index < get_gui_list_withFallback(GUIlist)->size()))) {
|
|
Serial.printf(" cannot navigate to GUI because gui_list_index \"%d\" is out of range\r\n", gui_list_index);
|
|
return;
|
|
}
|
|
|
|
if (gui_state.last_active_gui_list != GUIlist) {
|
|
// we are changing the gui_list, so save the last_active_gui_list_index
|
|
gui_memoryOptimizer_setLastActiveGUIlistIndex(gui_state.gui_on_tab[gui_state.activeTabID].gui_list_index);
|
|
}
|
|
|
|
// 1. notify old guis and clear tabview and panel
|
|
gui_memoryOptimizer_notifyAndClear(tabview, panel, img1, img2, &gui_state);
|
|
|
|
// 2. set gui_list_indices and the tab to be activated
|
|
gui_memoryOptimizer_setActiveGUIlist(GUIlist);
|
|
setGUIlistIndicesToBeShown_forSpecificGUIlistIndex(gui_list_index, &gui_state);
|
|
|
|
// 3. create content
|
|
gui_memoryOptimizer_doContentCreation(tabview, panel, img1, img2, &gui_state);
|
|
|
|
}
|
|
|
|
// 5. navigate back to last gui in scene
|
|
void gui_memoryOptimizer_navigateToLastActiveGUIofPreviousGUIlist(lv_obj_t** tabview, lv_obj_t** panel, lv_obj_t** img1, lv_obj_t** img2) {
|
|
|
|
#if (USE_SCENE_SPECIFIC_GUI_LIST == 0)
|
|
Serial.printf("--- Cannot navigate to last GUI from scene, because scene specific gui lists are not enabled\r\n");
|
|
return;
|
|
#endif
|
|
|
|
if (gui_memoryOptimizer_getLastActiveGUIlistIndex() == -1) {
|
|
Serial.printf("--- Cannot navigate to last GUI from scene, because it is not set\r\n");
|
|
return;
|
|
} else {
|
|
Serial.printf("--- Will navigate to last GUI from scene\r\n");
|
|
}
|
|
|
|
// navigate to the other gui_list
|
|
if (gui_memoryOptimizer_getActiveGUIlist() == MAIN_GUI_LIST) {
|
|
gui_memoryOptimizer_navigateToGUI(tabview, panel, img1, img2, SCENE_GUI_LIST, gui_memoryOptimizer_getLastActiveGUIlistIndex());
|
|
} else {
|
|
gui_memoryOptimizer_navigateToGUI(tabview, panel, img1, img2, MAIN_GUI_LIST, gui_memoryOptimizer_getLastActiveGUIlistIndex());
|
|
}
|
|
|
|
}
|
|
|
|
|
|
void gui_memoryOptimizer_doContentCreation(lv_obj_t** tabview, lv_obj_t** panel, lv_obj_t** img1, lv_obj_t** img2, t_gui_state *gui_state) {
|
|
// recreate the tabview
|
|
lv_obj_t* newTabview = create_tabview();
|
|
*tabview = newTabview;
|
|
|
|
// Create the tabs. Use strategy "3 tabs at maximum" to keep memory usage low.
|
|
// Set the tab we swiped to as active
|
|
doTabCreation_strategyMax3(*tabview, gui_state);
|
|
|
|
// Create the panel for the page indicator. Panel itself takes about 2136 bytes for three tabs.
|
|
lv_obj_t* newPanel = create_panel();
|
|
*panel = newPanel;
|
|
*img1 = lv_image_create(lv_screen_active());
|
|
*img2 = lv_image_create(lv_screen_active());
|
|
fillPanelWithPageIndicator_strategyMax3(*panel, *img1, *img2, gui_state);
|
|
|
|
// now, as the correct tab is active, register again the events for the tabview
|
|
lv_obj_add_event_cb(*tabview, tabview_tab_changed_event_cb, LV_EVENT_VALUE_CHANGED, NULL);
|
|
lv_obj_add_event_cb(lv_tabview_get_content(*tabview), tabview_content_is_scrolling_event_cb, LV_EVENT_SCROLL, NULL);
|
|
// Initialize scroll position of the page indicator
|
|
lv_obj_send_event(lv_tabview_get_content(*tabview), LV_EVENT_SCROLL, NULL);
|
|
|
|
// gui_memoryOptimizer_doContentCreation() is called as last step every time the 3 tabs are recreated.
|
|
// Save here the last_active_gui_list. If the used list changes in a future navigation, save the last_active_gui_list_index
|
|
// so that we can use SCENE_BACK_TO_PREVIOUS_GUI_LIST
|
|
gui_state->last_active_gui_list = gui_memoryOptimizer_getActiveGUIlist();
|
|
|
|
Serial.printf("------------ End of tab deletion and creation\r\n");
|
|
|
|
}
|