blob: 6e2dfe141e03ec3e135b5dc0286ac322bd71417e [file] [log] [blame]
/*
*
* 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