Add menu system

This commit is contained in:
Lewis Jackson 2023-05-29 18:25:28 +03:00
parent 2b27d18248
commit 5b7ef1c24f
7 changed files with 270 additions and 18 deletions

View file

@ -4,6 +4,7 @@ WatchFace watchy;
void setup() void setup()
{ {
Serial.begin(9600);
watchy.Init(); watchy.Init();
} }

129
src/Menu.cpp Normal file
View file

@ -0,0 +1,129 @@
#include "config.h"
#include "Menu.h"
#include <Fonts/FreeSans9pt7b.h>
#include <Fonts/FreeSansBold9pt7b.h>
RTC_DATA_ATTR WatchyDisplay * Menu::m_display = nullptr;
RTC_DATA_ATTR std::function<void()> Menu::m_exitCallback = nullptr;
RTC_DATA_ATTR uint8_t Menu::m_numPages = 0, Menu::m_currentPage = 0, Menu::m_numMenuItemsinCurrentPage = 0, Menu::m_currentMenuItem = 0;
Menu::Menu()
{
}
Menu::~Menu()
{
}
void Menu::Init(WatchyDisplay & display)
{
m_display = &display;
m_pages.clear();
}
void Menu::HandleButtonPress(uint64_t buttonMask)
{
if (m_currentPage == 0) {
if (buttonMask & BACK_BTN_MASK) {
if (m_exitCallback) {
m_exitCallback();
}
}
}
if (m_pages.size() == 0) {
if (m_exitCallback) {
m_exitCallback();
}
return;
}
if (buttonMask & UP_BTN_MASK) {
if (m_currentMenuItem > 0) {
m_currentMenuItem--;
}
}
if (buttonMask & DOWN_BTN_MASK) {
if (m_currentMenuItem < m_pages[m_currentPage].menuItems.size() - 1) {
m_currentMenuItem++;
}
}
if (buttonMask & MENU_BTN_MASK) {
if (m_pages[m_currentPage].menuItems[m_currentMenuItem].callback) {
m_pages[m_currentPage].menuItems[m_currentMenuItem].callback();
}
if (m_pages[m_currentPage].menuItems[m_currentMenuItem].pageNum > 0) {
m_currentPage = m_pages[m_currentPage].menuItems[m_currentMenuItem].pageNum;
m_currentMenuItem = 0;
}
}
}
void Menu::SetPages(const std::vector<MenuPage> & pages)
{
// If the number of pages has changed, reset the current page and menu item.
if (pages.size() != m_numPages) {
m_currentPage = 0;
m_currentMenuItem = 0;
}
// If the number of menu items has changed, reset the current menu item.
if (pages.size() > 0 && pages[m_currentMenuItem].menuItems.size() != m_numMenuItemsinCurrentPage) {
m_currentMenuItem = 0;
}
m_pages = pages;
}
void Menu::SetExitCallback(std::function<void()> callback)
{
m_exitCallback = callback;
}
void Menu::Redraw(bool partialRefresh)
{
if (m_display == nullptr) {
return;
}
if (!partialRefresh) {
m_display->clearScreen();
}
m_display->setFullWindow();
m_display->fillScreen(GxEPD_WHITE);
if (m_pages.size() > 0) {
bool hasTitle = false;
if (m_pages[m_currentPage].title.length() > 0) {
hasTitle = true;
m_display->setFont(&FreeSansBold9pt7b);
m_display->setTextColor(GxEPD_BLACK);
int16_t x, y;
uint16_t w, h;
m_display->getTextBounds(m_pages[m_currentPage].title.c_str(), 0, 0, &x, &y, &w, &h);
if (m_pages[m_currentPage].titleAlignment == ALIGNMENT_LEFT) { // Left
x = 5;
} else if (m_pages[m_currentPage].titleAlignment == ALIGNMENT_CENTER) { // Center
x = (m_display->width() - w) / 2;
} else if (m_pages[m_currentPage].titleAlignment == ALIGNMENT_RIGHT) { // Right
x = m_display->width() - w - 5;
}
m_display->setCursor(x, 15);
m_display->print(m_pages[m_currentPage].title.c_str());
m_display->drawLine(5, 25, m_display->width() - 10, 25, GxEPD_BLACK);
}
}
m_display->display(partialRefresh);
}

49
src/Menu.h Normal file
View file

@ -0,0 +1,49 @@
#pragma once
#include <string>
#include <functional>
#include <vector>
#include "WatchyDisplay.h"
struct MenuItem
{
std::string title;
// If callback is set, when the menu item is selected, the callback will be called.
// If pageNum is set, when the menu item is selected, the menu will switch to the page with the given pageNum.
std::function<void()> callback;
uint8_t pageNum;
};
enum MenuTextAlignment {
ALIGNMENT_LEFT,
ALIGNMENT_CENTER,
ALIGNMENT_RIGHT
};
struct MenuPage
{
std::string title;
MenuTextAlignment titleAlignment;
std::string body;
MenuTextAlignment bodyAlignment;
std::vector<MenuItem> menuItems;
};
class Menu
{
public:
Menu();
~Menu();
void Init(WatchyDisplay & display);
void HandleButtonPress(uint64_t buttonMask);
void SetPages(const std::vector<MenuPage> & pages);
void SetExitCallback(std::function<void()> callback);
void Redraw(bool partialRefresh = false);
private:
static RTC_DATA_ATTR WatchyDisplay * m_display;
static RTC_DATA_ATTR std::function<void()> m_exitCallback;
static RTC_DATA_ATTR uint8_t m_numPages, m_currentPage, m_numMenuItemsinCurrentPage, m_currentMenuItem;
std::vector<MenuPage> m_pages;
};

View file

@ -2,8 +2,61 @@
#include "SevenSegment.h" #include "SevenSegment.h"
#include <cmath> #include <cmath>
void WatchFace::DrawWatchFace() RTC_DATA_ATTR bool WatchFace::m_menuSetup = false;
RTC_DATA_ATTR bool WatchFace::m_inMenu = false;
Menu WatchFace::m_menu;
static const std::vector<MenuPage> menuPages = {
{
"Watchy",
ALIGNMENT_CENTER,
"",
ALIGNMENT_CENTER,
{
{
"Sync NTP",
nullptr,
0
}
}
}
};
void WatchFace::Setup() // Called after hardware is set up
{ {
if (!m_menuSetup) {
m_menuSetup = true;
m_menu.Init(m_display);
}
m_menu.SetPages(menuPages);
m_menu.SetExitCallback(std::bind(&WatchFace::MenuExited, this));
}
void WatchFace::HandleButtonPress(uint64_t buttonMask)
{
if (m_inMenu) {
m_menu.HandleButtonPress(buttonMask);
} else {
if (buttonMask & MENU_BTN_MASK) {
m_inMenu = true;
m_menu.Redraw(false);
}
}
}
void WatchFace::DrawWatchFace(bool partialRefresh)
{
if (m_inMenu) {
return;
}
m_display.setFullWindow();
if (!partialRefresh) {
m_display.clearScreen();
}
m_display.fillScreen(GxEPD_WHITE); m_display.fillScreen(GxEPD_WHITE);
DrawBatteryIcon(); DrawBatteryIcon();
@ -29,6 +82,8 @@ void WatchFace::DrawWatchFace()
m_display.fillRect(87, 90, 5, 5, GxEPD_BLACK); m_display.fillRect(87, 90, 5, 5, GxEPD_BLACK);
m_display.fillRect(87, 110, 5, 5, GxEPD_BLACK); m_display.fillRect(87, 110, 5, 5, GxEPD_BLACK);
m_display.display(partialRefresh);
} }
void WatchFace::DrawBatteryIcon() void WatchFace::DrawBatteryIcon()
@ -46,3 +101,11 @@ void WatchFace::DrawBatteryIcon()
m_display.fillRect(200 - 44, 9, (int)std::round(35.0f * level), 15, GxEPD_BLACK); m_display.fillRect(200 - 44, 9, (int)std::round(35.0f * level), 15, GxEPD_BLACK);
} }
void WatchFace::MenuExited()
{
if (m_inMenu) {
m_inMenu = false;
DrawWatchFace(false);
}
}

View file

@ -1,12 +1,20 @@
#pragma once #pragma once
#include "Watchy.h" #include "Watchy.h"
#include "Menu.h"
class WatchFace : public Watchy class WatchFace : public Watchy
{ {
public: public:
void DrawWatchFace(); void Setup() override; // Called after hardware is set up
void HandleButtonPress(uint64_t buttonMask) override;
void DrawWatchFace(bool partialRefresh = false) override;
private: private:
RTC_DATA_ATTR static bool m_menuSetup;
RTC_DATA_ATTR static bool m_inMenu;
RTC_DATA_ATTR static Menu m_menu;
void DrawBatteryIcon(); void DrawBatteryIcon();
void MenuExited();
}; };

View file

@ -1,7 +1,7 @@
#include "Watchy.h" #include "Watchy.h"
WatchyDisplayBase Watchy::m_displayBase; WatchyDisplayBase Watchy::m_displayBase;
GxEPD2_BW<WatchyDisplayBase, WatchyDisplayBase::HEIGHT> Watchy::m_display(Watchy::m_displayBase); WatchyDisplay Watchy::m_display(Watchy::m_displayBase);
WatchyRTC Watchy::m_RTC; WatchyRTC Watchy::m_RTC;
RTC_DATA_ATTR bool g_displayFullInit = true; RTC_DATA_ATTR bool g_displayFullInit = true;
@ -22,30 +22,31 @@ void Watchy::Init()
m_display.init(0, g_displayFullInit, 10, true); m_display.init(0, g_displayFullInit, 10, true);
m_display.epd2.setBusyCallback(DisplayBusyCallback); m_display.epd2.setBusyCallback(DisplayBusyCallback);
Setup();
switch(wakeup_reason) { switch(wakeup_reason) {
case ESP_SLEEP_WAKEUP_EXT0: case ESP_SLEEP_WAKEUP_EXT0:
{
// RTC interrupt // RTC interrupt
ShowWatchFace(true); ShowWatchFace(true);
break; break;
}
case ESP_SLEEP_WAKEUP_EXT1: case ESP_SLEEP_WAKEUP_EXT1:
{
// Button press // Button press
uint64_t wakeupBit = esp_sleep_get_ext1_wakeup_status();
HandleButtonPress(wakeupBit);
break; break;
}
default: default:
{
ConnectWiFi(); ConnectWiFi();
SyncNTPTime(); SyncNTPTime();
DisconnectWiFi(); DisconnectWiFi();
m_display.clearScreen(0xFF);
ShowWatchFace(false); ShowWatchFace(false);
// pinMode(VIB_MOTOR_PIN, OUTPUT);
// bool motorOn = false;
// for (int i = 0; i < 4; i++) {
// motorOn = !motorOn;
// digitalWrite(VIB_MOTOR_PIN, motorOn);
// delay(75);
// }
break; break;
}
} }
DeepSleep(); DeepSleep();
@ -137,7 +138,5 @@ void Watchy::DisconnectWiFi()
void Watchy::ShowWatchFace(bool partialRefresh) void Watchy::ShowWatchFace(bool partialRefresh)
{ {
m_display.setFullWindow(); DrawWatchFace(partialRefresh);
DrawWatchFace();
m_display.display(partialRefresh);
} }

View file

@ -20,12 +20,15 @@ public:
void DisconnectWiFi(); void DisconnectWiFi();
void ShowWatchFace(bool partialRefresh = false); void ShowWatchFace(bool partialRefresh = false);
virtual void DrawWatchFace() = 0; // Called after hardware is setup
virtual void Setup() = 0;
virtual void HandleButtonPress(uint64_t buttonMask) = 0;
virtual void DrawWatchFace(bool partialRefresh = false) = 0;
protected: protected:
static void DisplayBusyCallback(const void *); static void DisplayBusyCallback(const void *);
static WatchyDisplayBase m_displayBase; static WatchyDisplayBase m_displayBase;
static GxEPD2_BW<WatchyDisplayBase, WatchyDisplayBase::HEIGHT> m_display; static WatchyDisplay m_display;
static WatchyRTC m_RTC; static WatchyRTC m_RTC;
}; };