From 06712d9b977ee19170a3abc3c183b2f17bd8fa35 Mon Sep 17 00:00:00 2001 From: Malte Langermann <54738901+gagarinlg@users.noreply.github.com> Date: Tue, 29 Aug 2023 11:18:39 +0200 Subject: [PATCH] feat: Interactive/progressive preflight checklist (#3564) * initial version of clickable checklist * do not display checkboxes for empty lines if checklist is shorter than screen size * remove checklist exit after completing it cannot exit menu from menuTextView as level above there is while loop that cannot be stopped from thic place in code * menu entry to enable and disable interactive checklist * only when complete interactive checklist is closed * config updated & not display checkboxes when entered from "View Notes" * add setup entry for colorlcd * do not make interactive checkist when loaded from menu * fix datastruct size after merge * Companion support for interactive checklist * mark checklist lines as non-checkable with '=' for B&W radios * Process only items visible on screen This is also crucial when it comes to analyse non-checkable items on a checklist, as in this way there is no need to load non-visible items from SD-card * WIP fix merge problems * update yaml datastructs * restore most functionalities for interactive check lists on color LCD radios currently no automatic line wrapping and you can always exit the checklist, even when it is not fully done * finalise interactive checklists for color LCD * fix: rebase/merge issues * fix: model setup checklist section * fix: fix 212 model setup * Re-work of the some of the color lcd checklist code (#3787) * Move checklist logic to sub-class. Show model name instead of file name in title, show 'Preflight checks' in title2 for checklist. Handle long line wrapping of text. Don't indent lines without checkbox. Add 'RTN' button when done. * Code cleanup. * Simplify code by storing a list of checkboxes instead of scanning LVGL object hierarchy. * Indent checkboxes to make touch activation easier. Keep 'RTN' button visible so last checkbox is further from bottom of screen (for touch activation). 'RTN' button is disabled until checklist complete. * Add checkbox class constructor for preflight checks. * Fix checkbox size. --------- Co-authored-by: Piotr Rzeszut Co-authored-by: 3djc Co-authored-by: philmoz Co-authored-by: Phil Mitchell --- .../src/firmwares/edgetx/yaml_modeldata.cpp | 2 + companion/src/firmwares/modeldata.h | 1 + companion/src/modeledit/setup.cpp | 7 + companion/src/modeledit/setup.h | 1 + companion/src/modeledit/setup.ui | 10 + radio/src/datastructs_private.h | 3 +- radio/src/gui/128x64/model_setup.cpp | 6 + radio/src/gui/212x64/model_setup.cpp | 6 + radio/src/gui/colorlcd/preflight_checks.cpp | 13 +- .../src/gui/colorlcd/themes/etx_lv_theme.cpp | 53 +++- radio/src/gui/colorlcd/view_main_menu.cpp | 2 +- radio/src/gui/colorlcd/view_text.cpp | 270 +++++++++++++++--- radio/src/gui/colorlcd/view_text.h | 27 +- radio/src/gui/common/stdlcd/model_notes.cpp | 3 +- radio/src/gui/common/stdlcd/view_text.cpp | 52 +++- radio/src/opentx.h | 2 + .../storage/yaml/yaml_datastructs_128x64.cpp | 3 +- .../storage/yaml/yaml_datastructs_nv14.cpp | 3 +- .../src/storage/yaml/yaml_datastructs_t20.cpp | 3 +- .../storage/yaml/yaml_datastructs_tpro.cpp | 3 +- .../src/storage/yaml/yaml_datastructs_x10.cpp | 3 +- .../storage/yaml/yaml_datastructs_x12s.cpp | 3 +- .../src/storage/yaml/yaml_datastructs_x9d.cpp | 3 +- .../src/storage/yaml/yaml_datastructs_x9e.cpp | 3 +- .../storage/yaml/yaml_datastructs_x9lite.cpp | 3 +- .../storage/yaml/yaml_datastructs_xlites.cpp | 3 +- .../libopenui/src/widgets/etx_obj_create.h | 1 + radio/src/translations.cpp | 1 + radio/src/translations.h | 1 + radio/src/translations/cn.h | 1 + radio/src/translations/cz.h | 1 + radio/src/translations/da.h | 1 + radio/src/translations/de.h | 1 + radio/src/translations/en.h | 1 + radio/src/translations/es.h | 1 + radio/src/translations/fi.h | 1 + radio/src/translations/fr.h | 1 + radio/src/translations/it.h | 1 + radio/src/translations/jp.h | 1 + radio/src/translations/nl.h | 1 + radio/src/translations/pl.h | 1 + radio/src/translations/pt.h | 1 + radio/src/translations/se.h | 1 + radio/src/translations/tw.h | 1 + 44 files changed, 434 insertions(+), 72 deletions(-) diff --git a/companion/src/firmwares/edgetx/yaml_modeldata.cpp b/companion/src/firmwares/edgetx/yaml_modeldata.cpp index 1da4e3520fd..5ca2d5a5de4 100644 --- a/companion/src/firmwares/edgetx/yaml_modeldata.cpp +++ b/companion/src/firmwares/edgetx/yaml_modeldata.cpp @@ -920,6 +920,7 @@ Node convert::encode(const ModelData& rhs) node["extendedLimits"] = (int)rhs.extendedLimits; node["extendedTrims"] = (int)rhs.extendedTrims; node["throttleReversed"] = (int)rhs.throttleReversed; + node["checklistInteractive"] = (int)rhs.checklistInteractive; for (int i = 0; i < CPN_MAX_FLIGHT_MODES; i++) { if (!rhs.flightModeData[i].isEmpty(i)) { @@ -1201,6 +1202,7 @@ bool convert::decode(const Node& node, ModelData& rhs) node["extendedLimits"] >> rhs.extendedLimits; node["extendedTrims"] >> rhs.extendedTrims; node["throttleReversed"] >> rhs.throttleReversed; + node["checklistInteractive"] >> rhs.checklistInteractive; node["flightModeData"] >> rhs.flightModeData; node["mixData"] >> rhs.mixData; diff --git a/companion/src/firmwares/modeldata.h b/companion/src/firmwares/modeldata.h index 09e1b000aef..806f1c1d3ac 100644 --- a/companion/src/firmwares/modeldata.h +++ b/companion/src/firmwares/modeldata.h @@ -145,6 +145,7 @@ class ModelData { bool extendedLimits; // TODO xml bool extendedTrims; bool throttleReversed; + bool checklistInteractive; FlightModeData flightModeData[CPN_MAX_FLIGHT_MODES]; MixData mixData[CPN_MAX_MIXERS]; LimitData limitData[CPN_MAX_CHNOUT]; diff --git a/companion/src/modeledit/setup.cpp b/companion/src/modeledit/setup.cpp index 134179d3784..1d6f4f4177b 100644 --- a/companion/src/modeledit/setup.cpp +++ b/companion/src/modeledit/setup.cpp @@ -1797,6 +1797,7 @@ void SetupPanel::update() ui->extendedLimits->setChecked(model->extendedLimits); ui->extendedTrims->setChecked(model->extendedTrims); ui->displayText->setChecked(model->displayChecklist); + ui->checklistInteractive->setChecked(model->checklistInteractive); ui->gfEnabled->setChecked(!model->noGlobalFunctions); ui->jitterFilter->setCurrentIndex(model->jitterFilter); @@ -1944,6 +1945,12 @@ void SetupPanel::on_displayText_toggled(bool checked) emit modified(); } +void SetupPanel::on_checklistInteractive_toggled(bool checked) +{ + model->checklistInteractive = checked; + emit modified(); +} + void SetupPanel::on_gfEnabled_toggled(bool checked) { model->noGlobalFunctions = !checked; diff --git a/companion/src/modeledit/setup.h b/companion/src/modeledit/setup.h index cd5c21c243e..ae76a64a0fc 100644 --- a/companion/src/modeledit/setup.h +++ b/companion/src/modeledit/setup.h @@ -191,6 +191,7 @@ class SetupPanel : public ModelPanel void on_customThrottleWarningPosition_valueChanged(int value); void on_throttleReverse_toggled(bool checked); void on_displayText_toggled(bool checked); + void on_checklistInteractive_toggled(bool checked); void on_gfEnabled_toggled(bool checked); void on_image_currentIndexChanged(int index); void on_trimIncrement_currentIndexChanged(int index); diff --git a/companion/src/modeledit/setup.ui b/companion/src/modeledit/setup.ui index 010ce675806..9ff59393630 100644 --- a/companion/src/modeledit/setup.ui +++ b/companion/src/modeledit/setup.ui @@ -579,6 +579,16 @@ + + + + Qt::LeftToRight + + + Interactive Checklist + + + diff --git a/radio/src/datastructs_private.h b/radio/src/datastructs_private.h index 727974e9202..325b2af76f5 100644 --- a/radio/src/datastructs_private.h +++ b/radio/src/datastructs_private.h @@ -678,7 +678,8 @@ PACK(struct ModelData { uint8_t enableCustomThrottleWarning:1; uint8_t disableTelemetryWarning:1; uint8_t showInstanceIds:1; - uint8_t spare3:5 SKIP; + uint8_t checklistInteractive:1; + uint8_t spare3:4 SKIP; // padding to 8-bit aligment int8_t customThrottleWarningPosition; BeepANACenter beepANACenter; MixData mixData[MAX_MIXERS] NO_IDX; diff --git a/radio/src/gui/128x64/model_setup.cpp b/radio/src/gui/128x64/model_setup.cpp index e31d2dbc73e..4bf0d7b2b64 100644 --- a/radio/src/gui/128x64/model_setup.cpp +++ b/radio/src/gui/128x64/model_setup.cpp @@ -99,6 +99,7 @@ enum MenuModelSetupItems { ITEM_MODEL_SETUP_THROTTLE_TRIM_SWITCH, ITEM_MODEL_SETUP_PREFLIGHT_LABEL, ITEM_MODEL_SETUP_CHECKLIST_DISPLAY, + ITEM_MODEL_SETUP_CHECKLIST_INTERACTIVE, ITEM_MODEL_SETUP_THROTTLE_WARNING, ITEM_MODEL_SETUP_CUSTOM_THROTTLE_WARNING, ITEM_MODEL_SETUP_CUSTOM_THROTTLE_WARNING_VALUE, @@ -584,6 +585,7 @@ void menuModelSetup(event_t event) 0, // Preflight section PREFLIGHT_ROW(0), // Checklist + PREFLIGHT_ROW(0), // Checklist interactive PREFLIGHT_ROW(0), // Throttle warning PREFLIGHT_ROW(0), // Custom position for throttle warning enable PREFLIGHT_ROW(0), // Custom position for throttle warning value @@ -923,6 +925,10 @@ void menuModelSetup(event_t event) case ITEM_MODEL_SETUP_CHECKLIST_DISPLAY: g_model.displayChecklist = editCheckBox(g_model.displayChecklist, MODEL_SETUP_2ND_COLUMN, y, STR_CHECKLIST, attr, event); break; + + case ITEM_MODEL_SETUP_CHECKLIST_INTERACTIVE: + g_model.checklistInteractive = editCheckBox(g_model.checklistInteractive, MODEL_SETUP_2ND_COLUMN, y, STR_CHECKLIST_INTERACTIVE, attr, event); + break; case ITEM_MODEL_SETUP_THROTTLE_WARNING: g_model.disableThrottleWarning = !editCheckBox(!g_model.disableThrottleWarning, MODEL_SETUP_2ND_COLUMN, y, STR_THROTTLE_WARNING, attr, event); diff --git a/radio/src/gui/212x64/model_setup.cpp b/radio/src/gui/212x64/model_setup.cpp index 5d86a23f8c4..457c35797c8 100644 --- a/radio/src/gui/212x64/model_setup.cpp +++ b/radio/src/gui/212x64/model_setup.cpp @@ -87,6 +87,7 @@ enum MenuModelSetupItems { ITEM_MODEL_SETUP_THROTTLE_TRIM_SWITCH, ITEM_MODEL_SETUP_PREFLIGHT_LABEL, ITEM_MODEL_SETUP_CHECKLIST_DISPLAY, + ITEM_MODEL_SETUP_CHECKLIST_INTERACTIVE, ITEM_MODEL_SETUP_THROTTLE_WARNING, ITEM_MODEL_SETUP_CUSTOM_THROTTLE_WARNING, ITEM_MODEL_SETUP_CUSTOM_THROTTLE_WARNING_VALUE, @@ -528,6 +529,7 @@ void menuModelSetup(event_t event) 0, // Preflight section PREFLIGHT_ROW(0), // Checklist + PREFLIGHT_ROW(0), // Checklist interactive PREFLIGHT_ROW(0), // Throttle warning PREFLIGHT_ROW(0), // Custom position for throttle warning enable PREFLIGHT_ROW(0), // Custom position for throttle warning value @@ -819,6 +821,10 @@ void menuModelSetup(event_t event) case ITEM_MODEL_SETUP_CHECKLIST_DISPLAY: g_model.displayChecklist = editCheckBox(g_model.displayChecklist, MODEL_SETUP_2ND_COLUMN, y, STR_CHECKLIST, attr, event); break; + + case ITEM_MODEL_SETUP_CHECKLIST_INTERACTIVE: + g_model.checklistInteractive = editCheckBox(g_model.checklistInteractive, MODEL_SETUP_2ND_COLUMN, y, STR_CHECKLIST_INTERACTIVE, attr, event); + break; case ITEM_MODEL_SETUP_THROTTLE_WARNING: g_model.disableThrottleWarning = !editCheckBox(!g_model.disableThrottleWarning, MODEL_SETUP_2ND_COLUMN, y, STR_THROTTLE_WARNING, attr, event); diff --git a/radio/src/gui/colorlcd/preflight_checks.cpp b/radio/src/gui/colorlcd/preflight_checks.cpp index 781067687bc..12b946f7533 100644 --- a/radio/src/gui/colorlcd/preflight_checks.cpp +++ b/radio/src/gui/colorlcd/preflight_checks.cpp @@ -118,7 +118,18 @@ PreflightChecks::PreflightChecks() : Page(ICON_MODEL_SETUP) // Display checklist auto line = form->newLine(&grid); new StaticText(line, rect_t{}, STR_CHECKLIST, 0, COLOR_THEME_PRIMARY1); - new ToggleSwitch(line, rect_t{}, GET_SET_DEFAULT(g_model.displayChecklist)); + auto chkList = new ToggleSwitch(line, rect_t{}, GET_SET_DEFAULT(g_model.displayChecklist)); + + // Interactive checklist + line = form->newLine(&grid); + new StaticText(line, rect_t{}, STR_CHECKLIST_INTERACTIVE, 0, COLOR_THEME_PRIMARY1); + auto interactiveChkList = new ToggleSwitch(line, rect_t{}, GET_SET_DEFAULT(g_model.checklistInteractive)); + if(!chkList->getValue()) + interactiveChkList->disable(); + chkList->setSetValueHandler([=](int32_t newValue) { + g_model.displayChecklist = newValue; SET_DIRTY(); + (g_model.displayChecklist)?interactiveChkList->enable():interactiveChkList->disable(); + }); // Throttle warning line = form->newLine(&grid); diff --git a/radio/src/gui/colorlcd/themes/etx_lv_theme.cpp b/radio/src/gui/colorlcd/themes/etx_lv_theme.cpp index af6484701df..c47ace5a4a5 100644 --- a/radio/src/gui/colorlcd/themes/etx_lv_theme.cpp +++ b/radio/src/gui/colorlcd/themes/etx_lv_theme.cpp @@ -82,7 +82,7 @@ typedef struct { // Choice lv_style_t choice_main; - // Checkbox + // Toggle switch lv_style_t switch_knob; // Table @@ -103,6 +103,10 @@ typedef struct { lv_style_t progress_main; lv_style_t progress_indicator; + // Check Box + lv_style_t cb_marker; + lv_style_t cb_marker_checked; + } my_theme_styles_t; /********************** @@ -237,7 +241,7 @@ static void style_init(void) lv_style_init(&styles.bg_color_transparent); lv_style_set_bg_opa(&styles.bg_color_transparent, LV_OPA_TRANSP); - // Checkbox and slider knob rounding + // Toggle switch and slider knob rounding lv_style_init(&styles.circle); lv_style_set_radius(&styles.circle, LV_RADIUS_CIRCLE); @@ -265,7 +269,7 @@ static void style_init(void) lv_style_init(&styles.anim_fast); lv_style_set_anim_time(&styles.anim_fast, 120); - // Checkbox + // Toggle switch lv_style_init(&styles.switch_knob); lv_style_set_pad_all(&styles.switch_knob, -3); lv_style_set_bg_opa(&styles.switch_knob, LV_OPA_100); @@ -315,6 +319,14 @@ static void style_init(void) // Text align lv_style_init(&styles.text_align_right); lv_style_set_text_align(&styles.text_align_right, LV_TEXT_ALIGN_RIGHT); + + // Check Box + lv_style_init(&styles.cb_marker); + lv_style_set_bg_opa(&styles.cb_marker, LV_OPA_COVER); + lv_style_set_pad_all(&styles.cb_marker, lv_disp_dpx(theme.disp, 3)); + lv_style_init(&styles.cb_marker_checked); + lv_style_set_bg_img_src(&styles.cb_marker_checked, LV_SYMBOL_OK); + lv_style_set_text_font(&styles.cb_marker_checked, theme.font_small); } // Always update colors in case theme changes @@ -377,6 +389,12 @@ static void style_init(void) lv_style_set_bg_color(&styles.progress_main, makeLvColor(COLOR_THEME_SECONDARY2)); lv_style_set_bg_color(&styles.progress_indicator, makeLvColor(COLOR_THEME_SECONDARY1)); + + lv_style_set_border_color(&styles.cb_marker, makeLvColor(COLOR_THEME_SECONDARY2)); + lv_style_set_bg_color(&styles.cb_marker, makeLvColor(COLOR_THEME_PRIMARY2)); + lv_style_set_border_color(&styles.cb_marker_checked, makeLvColor(COLOR_THEME_SECONDARY1)); + lv_style_set_bg_color(&styles.cb_marker_checked, makeLvColor(COLOR_THEME_SECONDARY1)); + lv_style_set_text_color(&styles.cb_marker_checked, makeLvColor(COLOR_THEME_PRIMARY2)); } /********************** @@ -569,6 +587,17 @@ void etx_bar_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj) lv_obj_add_style(obj, &styles.rounded, LV_PART_INDICATOR); } +void etx_checkbox_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + lv_obj_add_style(obj, &styles.rounded, LV_PART_INDICATOR); + lv_obj_add_style(obj, &styles.pad_zero, LV_PART_INDICATOR); + lv_obj_add_style(obj, &styles.cb_marker, LV_PART_INDICATOR); + lv_obj_add_style(obj, &styles.cb_marker_checked, LV_PART_INDICATOR | LV_STATE_CHECKED); + lv_obj_add_style(obj, &styles.border, LV_PART_INDICATOR); + lv_obj_add_style(obj, &styles.focussed, LV_PART_INDICATOR | LV_STATE_FOCUSED); + lv_obj_add_style(obj, &styles.disabled, LV_PART_INDICATOR | LV_STATE_DISABLED); +} + } // Object classes @@ -754,6 +783,19 @@ const lv_obj_class_t etx_bar_class = { .instance_size = sizeof(lv_bar_t), }; +const lv_obj_class_t etx_checkbox_class = { + .base_class = &lv_checkbox_class, + .constructor_cb = etx_checkbox_constructor, + .destructor_cb = nullptr, + .user_data = nullptr, + .event_cb = nullptr, + .width_def = lv_pct(100), + .height_def = lv_pct(100), + .editable = LV_OBJ_CLASS_EDITABLE_INHERIT, + .group_def = LV_OBJ_CLASS_GROUP_DEF_INHERIT, + .instance_size = sizeof(lv_checkbox_t), +}; + // Event handlers static void field_edit_event(const lv_obj_class_t* class_p, lv_event_t* e) { @@ -876,6 +918,11 @@ lv_obj_t* etx_bar_create(lv_obj_t* parent) return etx_create(&etx_bar_class, parent); } +lv_obj_t* etx_checkbox_create(lv_obj_t* parent) +{ + return etx_create(&etx_checkbox_class, parent); +} + lv_obj_t* etx_modal_create(lv_obj_t* parent) { lv_obj_t* obj = window_create(parent); diff --git a/radio/src/gui/colorlcd/view_main_menu.cpp b/radio/src/gui/colorlcd/view_main_menu.cpp index d5efe112b5e..d36fda303f1 100644 --- a/radio/src/gui/colorlcd/view_main_menu.cpp +++ b/radio/src/gui/colorlcd/view_main_menu.cpp @@ -55,7 +55,7 @@ ViewMainMenu::ViewMainMenu(Window* parent, std::function closeHandler) : if (modelHasNotes()) { carousel->addButton(ICON_MODEL_NOTES, STR_MAIN_MENU_MODEL_NOTES, [=]() -> uint8_t { deleteLater(); - readModelNotes(); + readModelNotes(true); return 0; }); } diff --git a/radio/src/gui/colorlcd/view_text.cpp b/radio/src/gui/colorlcd/view_text.cpp index acf47397d58..30a38428aa9 100644 --- a/radio/src/gui/colorlcd/view_text.cpp +++ b/radio/src/gui/colorlcd/view_text.cpp @@ -24,6 +24,35 @@ #include "opentx.h" #include "sdcard.h" +constexpr int maxTxtBuffSize = 64 * 1024; + +ViewTextWindow::ViewTextWindow(const std::string path, const std::string name, + unsigned int icon) : + Page(icon), path(std::move(path)), name(std::move(name)) +{ + fullPath = this->path + std::string(PATH_SEPARATOR) + this->name; + extractNameSansExt(); + + header.setTitle(this->name); + + lv_obj_add_event_cb(lvobj, ViewTextWindow::on_draw, LV_EVENT_DRAW_MAIN_BEGIN, nullptr); +}; + +void ViewTextWindow::on_draw(lv_event_t * e) +{ + lv_obj_t* target = lv_event_get_target(e); + auto view = (ViewTextWindow*)lv_obj_get_user_data(target); + if (view) { + if (view->buffer == nullptr) + view->buildBody(&view->body); + } +} + +void ViewTextWindow::onCancel() +{ + Page::onCancel(); +} + void ViewTextWindow::extractNameSansExt() { uint8_t nameLength; @@ -32,14 +61,11 @@ void ViewTextWindow::extractNameSansExt() const char *ext = getFileExtension(name.c_str(), 0, 0, &nameLength, &extLength); extension = std::string(ext); - if (nameLength > TEXT_FILENAME_MAXLEN) nameLength = TEXT_FILENAME_MAXLEN; - nameLength -= extLength; - name.substr(nameLength); openFromEnd = !strcmp(ext, LOGS_EXT); } -void ViewTextWindow::buildBody(Window *window) +bool ViewTextWindow::openFile() { FILINFO info; @@ -61,30 +87,39 @@ void ViewTextWindow::buildBody(Window *window) TRACE("info.fsize=%d\tbufSize=%d\toffset=%d", info.fsize, bufSize, int(info.fsize) - bufSize + 1); if (sdReadTextFileBlock(bufSize, offset) == FR_OK) { - auto obj = window->getLvObj(); - lv_obj_add_flag( - obj, LV_OBJ_FLAG_SCROLLABLE | LV_OBJ_FLAG_SCROLL_WITH_ARROW | - LV_OBJ_FLAG_SCROLL_MOMENTUM | LV_OBJ_FLAG_CLICK_FOCUSABLE); - lv_obj_set_scrollbar_mode(obj, LV_SCROLLBAR_MODE_AUTO); - // prevents resetting the group's edit mode - lv_obj_clear_flag(obj, LV_OBJ_FLAG_CLICK_FOCUSABLE); - - auto g = lv_group_get_default(); - lb = lv_label_create(obj); - lv_obj_set_size(lb, lv_pct(100), LV_SIZE_CONTENT); - lv_obj_set_style_pad_all(lb, lv_dpx(8), 0); - - lv_group_add_obj(g, obj); - lv_group_set_editing(g, true); - lv_label_set_text_static(lb, buffer); - - if (openFromEnd) - lv_obj_scroll_to_y(obj, LV_COORD_MAX, LV_ANIM_OFF); - else - lv_obj_scroll_to_y(obj, 0, LV_ANIM_OFF); + return true; } } } + + return false; +} + +void ViewTextWindow::buildBody(Window *window) +{ + if (openFile()) { + auto obj = window->getLvObj(); + lv_obj_add_flag( + obj, LV_OBJ_FLAG_SCROLLABLE | LV_OBJ_FLAG_SCROLL_WITH_ARROW | + LV_OBJ_FLAG_SCROLL_MOMENTUM | LV_OBJ_FLAG_CLICK_FOCUSABLE); + lv_obj_set_scrollbar_mode(obj, LV_SCROLLBAR_MODE_AUTO); + // prevents resetting the group's edit mode + lv_obj_clear_flag(obj, LV_OBJ_FLAG_CLICK_FOCUSABLE); + + auto g = lv_group_get_default(); + lb = lv_label_create(obj); + lv_obj_set_size(lb, lv_pct(100), LV_SIZE_CONTENT); + lv_obj_set_style_pad_all(lb, lv_dpx(8), 0); + + lv_group_add_obj(g, obj); + lv_group_set_editing(g, true); + lv_label_set_text_static(lb, buffer); + + if (openFromEnd) + lv_obj_scroll_to_y(obj, LV_COORD_MAX, LV_ANIM_OFF); + else + lv_obj_scroll_to_y(obj, 0, LV_ANIM_OFF); + } } FRESULT ViewTextWindow::sdReadTextFileBlock(const uint32_t bufSize, @@ -172,10 +207,176 @@ void ViewTextWindow::onEvent(event_t event) sdReadTextFileBlock(bufSize, offset); lv_label_set_text_static(lb, buffer); } + + if(event == EVT_KEY_BREAK(KEY_EXIT)) + onCancel(); #endif } -#include "datastructs.h" +static void checkbox_event_handler(lv_event_t* e); + +class ViewChecklistWindow : public ViewTextWindow +{ + public: + ViewChecklistWindow(const std::string path, const std::string name, + unsigned int icon) : + ViewTextWindow(path, name, icon) + { + header.setTitle(g_model.header.name); + header.setTitle2(STR_PREFLIGHT); + } + +#if defined(DEBUG_WINDOWS) + std::string getName() const override { return "ViewChecklistWindow"; }; +#endif + + void onCancel() override + { + if (allChecked()) + ViewTextWindow::onCancel(); + } + + protected: + TextButton* closeButton = nullptr; + std::list checkBoxes; + + void updateCheckboxes() + { + bool lastState = true; + + for (auto it = checkBoxes.cbegin(); it != checkBoxes.cend(); ++it) { + auto cb = *it; + if (lastState) + { + lv_obj_clear_state(cb, LV_STATE_DISABLED); + if (!(lv_obj_get_state(cb) & LV_STATE_CHECKED)) + lv_group_focus_obj(cb); + } else { + lv_obj_add_state(cb, LV_STATE_DISABLED); + lv_obj_clear_state(cb, LV_STATE_CHECKED); + } + + lastState = lv_obj_get_state(cb) & LV_STATE_CHECKED; + } + + setCloseState(); + } + + bool allChecked() + { + for (auto it = checkBoxes.cbegin(); it != checkBoxes.cend(); ++it) { + auto cb = *it; + if (!(lv_obj_get_state(cb) & LV_STATE_CHECKED)) + return false; + } + + return true; + } + + void setCloseState() + { + if (allChecked()) { + lv_obj_clear_state(closeButton->getLvObj(), LV_STATE_DISABLED); + lv_group_focus_obj(closeButton->getLvObj()); + } else { + lv_obj_add_state(closeButton->getLvObj(), LV_STATE_DISABLED); + } + } + + void onEvent(event_t event) override + { +#if defined(HARDWARE_KEYS) + if(event == EVT_KEY_BREAK(KEY_EXIT)) + onCancel(); +#endif + } + + static void checkbox_event_handler(lv_event_t* e) + { + lv_obj_t* target = lv_event_get_target(e); + ViewChecklistWindow* vtw = (ViewChecklistWindow*)lv_obj_get_user_data(target); + + if (vtw) vtw->updateCheckboxes(); + } + + void buildBody(Window* window) override + { + if (openFile()) { + auto obj = window->getLvObj(); + lv_obj_add_flag( + obj, LV_OBJ_FLAG_SCROLLABLE | LV_OBJ_FLAG_SCROLL_WITH_ARROW | + LV_OBJ_FLAG_SCROLL_MOMENTUM | LV_OBJ_FLAG_CLICK_FOCUSABLE); + lv_obj_set_scrollbar_mode(obj, LV_SCROLLBAR_MODE_AUTO); + // prevents resetting the group's edit mode + lv_obj_clear_flag(obj, LV_OBJ_FLAG_CLICK_FOCUSABLE); + + lv_obj_set_layout(obj, LV_LAYOUT_FLEX); + lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_COLUMN); + lv_obj_set_style_pad_all(obj, 3, LV_PART_MAIN); + lv_obj_set_style_pad_row(obj, 0, LV_PART_MAIN); + + auto g = lv_group_get_default(); + + checkBoxes.clear(); + + size_t cur = 0; + + for(int i=0; i= 0){ + if (checklistPosition < reusableBuffer.viewText.linesCount) { + if (checklistPosition-(int)menuVerticalOffset < LCD_LINES-1) { + ++checklistPosition; + if (checklistPosition-(int)menuVerticalOffset >= LCD_LINES-2 && menuVerticalOffset+LCD_LINES-1 < reusableBuffer.viewText.linesCount) { + ++menuVerticalOffset; + sdReadTextFile(reusableBuffer.viewText.filename, reusableBuffer.viewText.lines, reusableBuffer.viewText.linesCount); + } + } + } + else { + if (reusableBuffer.viewText.pushMenu == true) popMenu(); + reusableBuffer.viewText.checklistComplete = true; + } + } } else if (event == EVT_KEY_BREAK(KEY_EXIT)) { - popMenu(); + if (!g_model.checklistInteractive || reusableBuffer.viewText.pushMenu) { + if (reusableBuffer.viewText.pushMenu == true) popMenu(); + reusableBuffer.viewText.checklistComplete = true; + } } for (int i=0; i