diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e4a354df64..89f552f9f9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -366,6 +366,8 @@ list(APPEND SOURCE_FILES displayapp/DisplayApp.cpp displayapp/screens/Screen.cpp displayapp/screens/Tile.cpp + displayapp/screens/FileView.cpp + displayapp/screens/Gallery.cpp displayapp/screens/InfiniPaint.cpp displayapp/screens/Paddle.cpp displayapp/screens/StopWatch.cpp diff --git a/src/displayapp/DisplayApp.cpp b/src/displayapp/DisplayApp.cpp index 84fa603622..b197ffdbe6 100644 --- a/src/displayapp/DisplayApp.cpp +++ b/src/displayapp/DisplayApp.cpp @@ -13,6 +13,7 @@ #include "displayapp/screens/ApplicationList.h" #include "displayapp/screens/FirmwareUpdate.h" #include "displayapp/screens/FirmwareValidation.h" +#include "displayapp/screens/Gallery.h" #include "displayapp/screens/InfiniPaint.h" #include "displayapp/screens/Paddle.h" #include "displayapp/screens/StopWatch.h" diff --git a/src/displayapp/apps/Apps.h.in b/src/displayapp/apps/Apps.h.in index d440b598d1..ab38d84dda 100644 --- a/src/displayapp/apps/Apps.h.in +++ b/src/displayapp/apps/Apps.h.in @@ -30,6 +30,7 @@ namespace Pinetime { Steps, Dice, Weather, + Gallery, PassKey, QuickSettings, Settings, diff --git a/src/displayapp/fonts/fonts.json b/src/displayapp/fonts/fonts.json index 90be1febe6..3f50a6d734 100644 --- a/src/displayapp/fonts/fonts.json +++ b/src/displayapp/fonts/fonts.json @@ -7,7 +7,7 @@ }, { "file": "FontAwesome5-Solid+Brands+Regular.woff", - "range": "0xf294, 0xf242, 0xf54b, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c, 0xf0f3, 0xf522, 0xf743, 0xf1ec, 0xf55a, 0xf3ed" + "range": "0xf294, 0xf242, 0xf54b, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c, 0xf0f3, 0xf522, 0xf743, 0xf1ec, 0xf55a, 0xf3ed, 0xf302" } ], "bpp": 1, diff --git a/src/displayapp/screens/FileView.cpp b/src/displayapp/screens/FileView.cpp new file mode 100644 index 0000000000..c6fbebcff4 --- /dev/null +++ b/src/displayapp/screens/FileView.cpp @@ -0,0 +1,110 @@ +#include +#include "displayapp/screens/FileView.h" +#include "displayapp/DisplayApp.h" +#include "displayapp/InfiniTimeTheme.h" + +using namespace Pinetime::Applications::Screens; + +FileView::FileView(uint8_t screenID, uint8_t nScreens, const char* path) + : Screen(), pageIndicator(screenID, nScreens), screenID(screenID), nScreens(nScreens) { + label = nullptr; + + const char* c = strrchr(path, '/') + 1; + if (c == nullptr) + c = path; + + strncpy(name, c, LFS_NAME_MAX - 1); + char* pchar = strchr(name, '_'); + while (pchar != nullptr) { + *pchar = ' '; + pchar = strchr(pchar + 1, '_'); + } +} + +void FileView::ShowInfo() { + if (label != nullptr) { + return; + } + label = lv_btn_create(lv_scr_act(), nullptr); + label->user_data = this; + + lv_obj_set_height(label, 20); + lv_obj_set_width(label, LV_HOR_RES); + lv_obj_align(label, lv_scr_act(), LV_ALIGN_IN_BOTTOM_MID, 0, 0); + + lv_obj_t* txtMessage = lv_label_create(label, nullptr); + lv_obj_set_style_local_bg_color(label, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_NAVY); + lv_label_set_text_static(txtMessage, name); + + pageIndicator.CreateHorizontal(); +} + +void FileView::HideInfo() { + lv_obj_del(label); + pageIndicator.Hide(); + + label = nullptr; +} + +void FileView::ToggleInfo() { + if (label == nullptr) + ShowInfo(); + else + HideInfo(); +} + +FileView::~FileView() { + lv_obj_clean(lv_scr_act()); +} + +ImageView::ImageView(uint8_t screenID, uint8_t nScreens, const char* path) : FileView(screenID, nScreens, path) { + lv_obj_t* image = lv_img_create(lv_scr_act(), nullptr); + lv_img_set_src(image, path); + lv_obj_align(image, lv_scr_act(), LV_ALIGN_CENTER, 0, 0); + + ShowInfo(); +} + +TextView::TextView(uint8_t screenID, uint8_t nScreens, const char* path, Pinetime::Controllers::FS& fs) + : FileView(screenID, nScreens, path) { + + lv_obj_t* label = lv_label_create(lv_scr_act(), nullptr); + lv_label_set_long_mode(label, LV_LABEL_LONG_BREAK); + lv_obj_set_width(label, LV_HOR_RES); + + lfs_info info = {0}; + if (fs.Stat(path + 2, &info) != LFS_ERR_OK) { + lv_label_set_text_static(label, "could not stat file"); + return; + } + if (info.type != LFS_TYPE_REG) { + lv_label_set_text_static(label, "not a file"); + return; + } + + buf = (char*) lv_mem_alloc(info.size); + if (buf == nullptr) { + lv_label_set_text_static(label, "could not allocate buffer"); + return; + } + + lfs_file_t fp; + if (fs.FileOpen(&fp, path + 2, LFS_O_RDONLY) != LFS_ERR_OK) { + lv_label_set_text_static(label, "could not open file"); + lv_mem_free(buf); + return; + } + + fs.FileRead(&fp, reinterpret_cast(buf), info.size); + lv_label_set_text_static(label, buf); + + fs.FileClose(&fp); + + ShowInfo(); +} + +TextView::~TextView() { + if (buf != nullptr) + lv_mem_free(buf); + lv_obj_clean(lv_scr_act()); +} diff --git a/src/displayapp/screens/FileView.h b/src/displayapp/screens/FileView.h new file mode 100644 index 0000000000..51374605fb --- /dev/null +++ b/src/displayapp/screens/FileView.h @@ -0,0 +1,43 @@ +#pragma once + +#include "displayapp/screens/Screen.h" +#include "displayapp/DisplayApp.h" +#include "displayapp/widgets/PageIndicator.h" +#include + +namespace Pinetime { + namespace Applications { + namespace Screens { + class FileView : public Screen { + public: + FileView(uint8_t screenID, uint8_t nScreens, const char* path); + ~FileView() override; + + void ShowInfo(); + void HideInfo(); + void ToggleInfo(); + + private: + char name[LFS_NAME_MAX]; + lv_obj_t* label; + + Widgets::PageIndicator pageIndicator; + uint8_t screenID, nScreens; + }; + + class ImageView : public FileView { + public: + ImageView(uint8_t screenID, uint8_t nScreens, const char* path); + }; + + class TextView : public FileView { + public: + TextView(uint8_t screenID, uint8_t nScreens, const char* path, Pinetime::Controllers::FS& fs); + ~TextView() override; + + private: + char* buf; + }; + } + } +} diff --git a/src/displayapp/screens/Gallery.cpp b/src/displayapp/screens/Gallery.cpp new file mode 100644 index 0000000000..fe032640c7 --- /dev/null +++ b/src/displayapp/screens/Gallery.cpp @@ -0,0 +1,117 @@ +#include +#include "displayapp/screens/Gallery.h" +#include "displayapp/DisplayApp.h" + +using namespace Pinetime::Applications::Screens; + +Gallery::Gallery(Pinetime::Controllers::FS& filesystem) : Screen(), filesystem(filesystem) { + ListDir(); + + if (nScreens == 0) { + lv_obj_t* title = lv_label_create(lv_scr_act(), nullptr); + lv_label_set_text_static(title, "no images found"); + lv_label_set_align(title, LV_LABEL_ALIGN_CENTER); + lv_obj_align(title, lv_scr_act(), LV_ALIGN_CENTER, 0, 0); + } else { + Open(0); + } +} + +Gallery::~Gallery() { + lv_obj_clean(lv_scr_act()); +} + +bool Gallery::OnTouchEvent(Pinetime::Applications::TouchEvents event) { + switch (event) { + case Pinetime::Applications::TouchEvents::SwipeRight: + return Open(index - 1); + case Pinetime::Applications::TouchEvents::SwipeLeft: + return Open(index + 1); + case Pinetime::Applications::TouchEvents::LongTap: + case Pinetime::Applications::TouchEvents::DoubleTap: + current->ToggleInfo(); + return true; + case Pinetime::Applications::TouchEvents::None: + case Pinetime::Applications::TouchEvents::Tap: + case Pinetime::Applications::TouchEvents::SwipeUp: + case Pinetime::Applications::TouchEvents::SwipeDown: + return false; + } + return false; +} + +void Gallery::ListDir() { + lfs_dir_t dir = {0}; + lfs_info info = {0}; + nScreens = 0; + + int res = filesystem.DirOpen(directory, &dir); + if (res != 0) { + NRF_LOG_INFO("[Gallery] can't find directory"); + return; + } + while (filesystem.DirRead(&dir, &info)) { + if (info.type == LFS_TYPE_DIR) + continue; + nScreens++; + } + res = filesystem.DirClose(&dir); + if (res != 0) { + NRF_LOG_INFO("[Gallery] DirClose failed"); + return; + } +} + +bool Gallery::Open(int n) { + if ((n < 0) || (n >= nScreens)) + return false; + + index = n; + + lfs_dir_t dir = {0}; + lfs_info info = {0}; + + int res = filesystem.DirOpen(directory, &dir); + if (res != 0) { + NRF_LOG_INFO("[Gallery] can't find directory"); + return false; + } + int i = 0; + while (filesystem.DirRead(&dir, &info)) { + if (info.type == LFS_TYPE_DIR) + continue; + if (n == i) + break; + i++; + } + res = filesystem.DirClose(&dir); + if (res != 0) { + NRF_LOG_INFO("[Gallery] DirClose failed"); + return false; + } + + if (current != nullptr) { + current.reset(nullptr); + } + + char fullname[LFS_NAME_MAX] = "F:"; + strncat(fullname, directory, sizeof(fullname) - 2 - 1); + strncat(fullname, info.name, sizeof(fullname) - strlen(directory) - 2 - 1); + + if (StringEndsWith(fullname, ".bin")) { + current = std::make_unique(n, nScreens, fullname); + } else if (StringEndsWith(fullname, ".txt")) { + current = std::make_unique(n, nScreens, fullname, filesystem); + } else { + return false; + } + + return true; +} + +int Gallery::StringEndsWith(const char* str, const char* suffix) { + int str_len = strlen(str); + int suffix_len = strlen(suffix); + + return (str_len >= suffix_len) && (0 == strcmp(str + (str_len - suffix_len), suffix)); +} diff --git a/src/displayapp/screens/Gallery.h b/src/displayapp/screens/Gallery.h new file mode 100644 index 0000000000..44d94e0158 --- /dev/null +++ b/src/displayapp/screens/Gallery.h @@ -0,0 +1,47 @@ +#pragma once + +#include "displayapp/screens/Screen.h" +#include "displayapp/screens/FileView.h" +#include "displayapp/DisplayApp.h" +#include +#include "Symbols.h" + +namespace Pinetime { + namespace Applications { + namespace Screens { + class Gallery : public Screen { + public: + static constexpr const char* directory = "/gallery/"; + + Gallery(Pinetime::Controllers::FS& filesystem); + ~Gallery() override; + bool OnTouchEvent(Pinetime::Applications::TouchEvents event) override; + + private: + int StringEndsWith(const char* str, const char* suffix); + + Pinetime::Controllers::FS& filesystem; + std::unique_ptr current; + + void ListDir(); + bool Open(int n); + int nScreens; + int index; + }; + } + + template <> + struct AppTraits { + static constexpr Apps app = Apps::Gallery; + static constexpr const char* icon = Screens::Symbols::gallery; + + static Screens::Screen* Create(AppControllers& controllers) { + return new Screens::Gallery(controllers.filesystem); + }; + + static bool IsAvailable(Pinetime::Controllers::FS& filesystem) { + return true; + }; + }; + } +} diff --git a/src/displayapp/screens/Symbols.h b/src/displayapp/screens/Symbols.h index fb93e80e87..5eb309b8a9 100644 --- a/src/displayapp/screens/Symbols.h +++ b/src/displayapp/screens/Symbols.h @@ -19,6 +19,7 @@ namespace Pinetime { static constexpr const char* check = "\xEF\x95\xA0"; static constexpr const char* music = "\xEF\x80\x81"; static constexpr const char* tachometer = "\xEF\x8F\xBD"; + static constexpr const char* gallery = "\xEF\x8C\x82"; static constexpr const char* paintbrush = "\xEF\x87\xBC"; static constexpr const char* paddle = "\xEF\x91\x9D"; static constexpr const char* map = "\xEF\x96\xa0"; diff --git a/src/displayapp/widgets/PageIndicator.cpp b/src/displayapp/widgets/PageIndicator.cpp index cee979f22c..cee94c96d5 100644 --- a/src/displayapp/widgets/PageIndicator.cpp +++ b/src/displayapp/widgets/PageIndicator.cpp @@ -34,3 +34,36 @@ void PageIndicator::SetPageIndicatorPosition(uint8_t position) { lv_line_set_points(pageIndicator, pageIndicatorPoints, 2); } + +void PageIndicator::CreateHorizontal() { + pageIndicatorBasePoints[0].x = 0; + pageIndicatorBasePoints[0].y = 1; + pageIndicatorBasePoints[1].x = LV_HOR_RES - 1; + pageIndicatorBasePoints[1].y = 1; + + pageIndicatorBase = lv_line_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_line_width(pageIndicatorBase, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, 3); + lv_obj_set_style_local_line_color(pageIndicatorBase, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, Colors::bgDark); + lv_line_set_points(pageIndicatorBase, pageIndicatorBasePoints, 2); + + const int16_t indicatorSize = LV_HOR_RES / nScreens; + const int16_t indicatorPos = indicatorSize * nCurrentScreen; + + pageIndicatorPoints[0].x = indicatorPos; + pageIndicatorPoints[0].y = 1; + pageIndicatorPoints[1].x = indicatorPos + indicatorSize; + pageIndicatorPoints[1].y = 1; + + pageIndicator = lv_line_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_line_width(pageIndicator, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, 3); + lv_obj_set_style_local_line_color(pageIndicator, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, Colors::lightGray); + lv_line_set_points(pageIndicator, pageIndicatorPoints, 2); +} + +void PageIndicator::Hide() { + lv_obj_del(pageIndicatorBase); + lv_obj_del(pageIndicator); + + pageIndicatorBase = nullptr; + pageIndicator = nullptr; +} diff --git a/src/displayapp/widgets/PageIndicator.h b/src/displayapp/widgets/PageIndicator.h index b9aaffe408..29a00174bc 100644 --- a/src/displayapp/widgets/PageIndicator.h +++ b/src/displayapp/widgets/PageIndicator.h @@ -9,6 +9,8 @@ namespace Pinetime { PageIndicator(uint8_t nCurrentScreen, uint8_t nScreens); void Create(); void SetPageIndicatorPosition(uint8_t position); + void CreateHorizontal(); + void Hide(); private: uint8_t nCurrentScreen;