/* * * 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 #include #include #include 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 vleds; std::vector 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