|  | /* | 
|  | * | 
|  | *    Copyright (c) 2020 Project CHIP Authors | 
|  | *    All rights reserved. | 
|  | * | 
|  | *    Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | *    you may not use this file except in compliance with the License. | 
|  | *    You may obtain a copy of the License at | 
|  | * | 
|  | *        http://www.apache.org/licenses/LICENSE-2.0 | 
|  | * | 
|  | *    Unless required by applicable law or agreed to in writing, software | 
|  | *    distributed under the License is distributed on an "AS IS" BASIS, | 
|  | *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | *    See the License for the specific language governing permissions and | 
|  | *    limitations under the License. | 
|  | */ | 
|  |  | 
|  | /** | 
|  | * @file ScreenManager.cpp | 
|  | * | 
|  | * Simple screen manager. | 
|  | * | 
|  | */ | 
|  |  | 
|  | #include "ScreenManager.h" | 
|  |  | 
|  | #if CONFIG_HAVE_DISPLAY | 
|  |  | 
|  | #include <lib/support/CHIPMem.h> | 
|  |  | 
|  | #include <cassert> | 
|  | #include <string> | 
|  | #include <vector> | 
|  |  | 
|  | uint16_t ScreenFontHeight; | 
|  | uint16_t ScreenTitleSafeTop; | 
|  | uint16_t ScreenTitleSafeBottom; | 
|  |  | 
|  | color_t ScreenNormalColor = { 255, 255, 255 }; | 
|  | color_t ScreenFocusColor  = { 128, 128, 255 }; | 
|  | color_t ScreenButtonColor = { 64, 64, 64 }; | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | constexpr int kMainFont   = DEJAVU24_FONT; | 
|  | constexpr int kButtonFont = DEJAVU18_FONT; | 
|  |  | 
|  | constexpr int kVLEDWidth  = 16; | 
|  | constexpr int kVLEDHeight = 16; | 
|  |  | 
|  | SemaphoreHandle_t mutex; | 
|  |  | 
|  | struct Lock | 
|  | { | 
|  | Lock() { xSemaphoreTakeRecursive(mutex, portMAX_DELAY); } | 
|  | ~Lock() { xSemaphoreGiveRecursive(mutex); } | 
|  | }; | 
|  |  | 
|  | struct VLED | 
|  | { | 
|  | color_t color; | 
|  | color_t color_off; | 
|  | bool on; | 
|  | VLED(color_t color) : color(color), on(false) | 
|  | { | 
|  | color_off = color; | 
|  | color_off.r &= 0x1F; | 
|  | color_off.g &= 0x1F; | 
|  | color_off.b &= 0x1F; | 
|  | } | 
|  | }; | 
|  |  | 
|  | std::vector<VLED> vleds; | 
|  |  | 
|  | std::vector<Screen *> screens; | 
|  |  | 
|  | bool focusBack = false; | 
|  |  | 
|  | int lazyDisplay   = 0; | 
|  | bool dirtyDisplay = false; | 
|  |  | 
|  | struct LazyDisplay | 
|  | { | 
|  | LazyDisplay() { ++lazyDisplay; } | 
|  | ~LazyDisplay() | 
|  | { | 
|  | if (--lazyDisplay == 0) | 
|  | { | 
|  | if (dirtyDisplay) | 
|  | { | 
|  | ScreenManager::Display(); | 
|  | dirtyDisplay = false; | 
|  | } | 
|  | } | 
|  | } | 
|  | }; | 
|  |  | 
|  | // Print text centered horizontally at x. | 
|  | void PrintCentered(const char * s, int x, int y) | 
|  | { | 
|  | TFT_print(s, x - (TFT_getStringWidth(s) / 2), y); | 
|  | } | 
|  |  | 
|  | // Print button text in appropriate location (1 to 3). | 
|  | void DisplayButtonText(int id, const char * s) | 
|  | { | 
|  | tft_fg = ScreenButtonColor; | 
|  | int x  = (DisplayWidth / 2) + (id - 2) * (DisplayWidth * 3 / 10); | 
|  | PrintCentered(s, x, DisplayHeight - (ScreenTitleSafeBottom / 2)); // within ScreenTitleSafeBottom | 
|  | } | 
|  |  | 
|  | void DisplayVLED(int id) | 
|  | { | 
|  | TFT_fillRect(0, ScreenFontHeight * 3 / 2 + id * (kVLEDHeight + 2), kVLEDWidth, kVLEDHeight, | 
|  | vleds[id].on ? vleds[id].color : vleds[id].color_off); | 
|  | } | 
|  |  | 
|  | } // namespace | 
|  |  | 
|  | namespace ScreenManager { | 
|  |  | 
|  | void Init() | 
|  | { | 
|  | mutex = xSemaphoreCreateRecursiveMutex(); | 
|  |  | 
|  | // https://github.com/loboris/ESP32_TFT_library/issues/48 | 
|  | TFT_setFont(kButtonFont, nullptr); | 
|  | ScreenTitleSafeBottom = TFT_getfontheight() * 2; | 
|  | TFT_setFont(kMainFont, nullptr); | 
|  | ScreenFontHeight   = TFT_getfontheight(); | 
|  | ScreenTitleSafeTop = ScreenFontHeight * 5 / 2; | 
|  | } | 
|  |  | 
|  | void Display() | 
|  | { | 
|  | Lock lock; | 
|  |  | 
|  | if (lazyDisplay) | 
|  | { | 
|  | dirtyDisplay = true; | 
|  | return; | 
|  | } | 
|  |  | 
|  | TFT_fillScreen(TFT_BLACK); | 
|  | TFT_setFont(kMainFont, nullptr); | 
|  |  | 
|  | if (screens.empty()) | 
|  | { | 
|  | tft_fg = TFT_RED; | 
|  | PrintCentered("No Screen", DisplayWidth / 2, DisplayHeight / 2); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (screens.size() > 1) | 
|  | { | 
|  | tft_fg = focusBack ? ScreenFocusColor : ScreenNormalColor; | 
|  | TFT_print("<", ScreenFontHeight, ScreenFontHeight / 2); | 
|  | } | 
|  |  | 
|  | std::string title = screens.back()->GetTitle(); | 
|  | tft_fg            = ScreenNormalColor; | 
|  | TFT_print(title.c_str(), ScreenTitleSafeTop, ScreenFontHeight / 2); // within ScreenTitleSafeTop | 
|  | TFT_drawRect(ScreenTitleSafeTop, ScreenFontHeight * 3 / 2, TFT_getStringWidth(title.c_str()), 2, ScreenNormalColor); | 
|  |  | 
|  | TFT_setFont(kButtonFont, nullptr); | 
|  | if (screens.back()->IsFocusable()) | 
|  | { | 
|  | DisplayButtonText(1, screens.back()->GetButtonText(1).c_str()); | 
|  | DisplayButtonText(2, screens.back()->GetButtonText(2).c_str()); | 
|  | } | 
|  | if (focusBack) | 
|  | { | 
|  | DisplayButtonText(3, "Back"); | 
|  | } | 
|  | else if (screens.back()->IsFocusable()) | 
|  | { | 
|  | DisplayButtonText(3, screens.back()->GetButtonText(3).c_str()); | 
|  | } | 
|  | TFT_setFont(kMainFont, nullptr); | 
|  |  | 
|  | for (int i = 0; i < vleds.size(); ++i) | 
|  | { | 
|  | DisplayVLED(i); | 
|  | } | 
|  |  | 
|  | screens.back()->Display(); | 
|  | } | 
|  |  | 
|  | void ButtonPressed(int id) | 
|  | { | 
|  | Lock lock; | 
|  | LazyDisplay lazy; | 
|  |  | 
|  | if (screens.empty()) | 
|  | { | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (focusBack && id == 3) | 
|  | { | 
|  | PopScreen(); | 
|  | } | 
|  | else if (screens.back()->IsFocusable()) | 
|  | { | 
|  | switch (id) | 
|  | { | 
|  | case 1: | 
|  | focusBack = false; | 
|  | screens.back()->Focus(Screen::FocusType::PREVIOUS); | 
|  | break; | 
|  | case 2: | 
|  | focusBack = false; | 
|  | screens.back()->Focus(Screen::FocusType::NEXT); | 
|  | break; | 
|  | case 3: | 
|  | screens.back()->Action(); | 
|  | break; | 
|  | } | 
|  | Display(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void PushScreen(Screen * screen) | 
|  | { | 
|  | Lock lock; | 
|  | LazyDisplay lazy; | 
|  |  | 
|  | if (!screens.empty()) | 
|  | { | 
|  | if (screens.back()->IsFocusable()) | 
|  | { | 
|  | screens.back()->Focus(Screen::FocusType::BLUR); | 
|  | } | 
|  | screens.back()->Exit(false); | 
|  | } | 
|  |  | 
|  | screen->Enter(true);       // screen is not top when enter/pushed | 
|  | screens.push_back(screen); // screen is pushed immediately after first enter | 
|  |  | 
|  | focusBack = false; | 
|  |  | 
|  | if (screens.back()->IsFocusable()) | 
|  | { | 
|  | screens.back()->Focus(Screen::FocusType::NEXT); | 
|  | } | 
|  | else | 
|  | { | 
|  | focusBack = true; | 
|  | } | 
|  |  | 
|  | Display(); | 
|  | } | 
|  |  | 
|  | void PopScreen() | 
|  | { | 
|  | Lock lock; | 
|  | LazyDisplay lazy; | 
|  |  | 
|  | if (screens.empty()) | 
|  | { | 
|  | return; | 
|  | } | 
|  |  | 
|  | Screen * screen = screens.back(); | 
|  | screens.pop_back(); // screen is popped immediately before last exit | 
|  | screen->Exit(true); // screen is not top when exit/popped | 
|  | chip::Platform::Delete(screen); | 
|  |  | 
|  | focusBack = false; | 
|  |  | 
|  | if (!screens.empty()) | 
|  | { | 
|  | screens.back()->Enter(false); | 
|  | if (screens.back()->IsFocusable()) | 
|  | { | 
|  | screens.back()->Focus(Screen::FocusType::UNBLUR); | 
|  | } | 
|  | else | 
|  | { | 
|  | focusBack = true; | 
|  | } | 
|  | } | 
|  |  | 
|  | Display(); | 
|  | } | 
|  |  | 
|  | void FocusBack() | 
|  | { | 
|  | Lock lock; | 
|  | if (screens.size() > 1) | 
|  | { | 
|  | focusBack = true; | 
|  | if (screens.back()->IsFocusable()) | 
|  | { | 
|  | screens.back()->Focus(Screen::FocusType::NONE); | 
|  | } | 
|  | } | 
|  | else | 
|  | { | 
|  | focusBack = false; | 
|  | } | 
|  | } | 
|  |  | 
|  | int AddVLED(color_t color) | 
|  | { | 
|  | Lock lock; | 
|  | int id = vleds.size(); | 
|  | vleds.emplace_back(color); | 
|  | DisplayVLED(id); | 
|  | return id; | 
|  | } | 
|  |  | 
|  | void SetVLED(int id, bool on) | 
|  | { | 
|  | Lock lock; | 
|  | if (vleds[id].on == on) | 
|  | { | 
|  | return; | 
|  | } | 
|  |  | 
|  | vleds[id].on = on; | 
|  | DisplayVLED(id); | 
|  | WakeDisplay(); | 
|  | } | 
|  |  | 
|  | void ToggleVLED(int id) | 
|  | { | 
|  | Lock lock; | 
|  | vleds[id].on = !vleds[id].on; | 
|  | DisplayVLED(id); | 
|  | WakeDisplay(); | 
|  | } | 
|  |  | 
|  | } // namespace ScreenManager | 
|  |  | 
|  | #endif // CONFIG_HAVE_DISPLAY |