Add menu system
This commit is contained in:
parent
2b27d18248
commit
5b7ef1c24f
7 changed files with 270 additions and 18 deletions
|
@ -4,6 +4,7 @@ WatchFace watchy;
|
|||
|
||||
void setup()
|
||||
{
|
||||
Serial.begin(9600);
|
||||
watchy.Init();
|
||||
}
|
||||
|
||||
|
|
129
src/Menu.cpp
Normal file
129
src/Menu.cpp
Normal 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
49
src/Menu.h
Normal 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;
|
||||
};
|
|
@ -2,8 +2,61 @@
|
|||
#include "SevenSegment.h"
|
||||
#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);
|
||||
DrawBatteryIcon();
|
||||
|
||||
|
@ -29,6 +82,8 @@ void WatchFace::DrawWatchFace()
|
|||
|
||||
m_display.fillRect(87, 90, 5, 5, GxEPD_BLACK);
|
||||
m_display.fillRect(87, 110, 5, 5, GxEPD_BLACK);
|
||||
|
||||
m_display.display(partialRefresh);
|
||||
}
|
||||
|
||||
void WatchFace::DrawBatteryIcon()
|
||||
|
@ -45,4 +100,12 @@ void WatchFace::DrawBatteryIcon()
|
|||
level = 0.5f - sin(asin(1.0f - 2.0f * level) / 3.0f);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -1,12 +1,20 @@
|
|||
#pragma once
|
||||
|
||||
#include "Watchy.h"
|
||||
#include "Menu.h"
|
||||
|
||||
class WatchFace : public Watchy
|
||||
{
|
||||
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:
|
||||
RTC_DATA_ATTR static bool m_menuSetup;
|
||||
RTC_DATA_ATTR static bool m_inMenu;
|
||||
RTC_DATA_ATTR static Menu m_menu;
|
||||
|
||||
void DrawBatteryIcon();
|
||||
void MenuExited();
|
||||
};
|
|
@ -1,7 +1,7 @@
|
|||
#include "Watchy.h"
|
||||
|
||||
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;
|
||||
|
||||
RTC_DATA_ATTR bool g_displayFullInit = true;
|
||||
|
@ -22,30 +22,31 @@ void Watchy::Init()
|
|||
m_display.init(0, g_displayFullInit, 10, true);
|
||||
m_display.epd2.setBusyCallback(DisplayBusyCallback);
|
||||
|
||||
Setup();
|
||||
|
||||
switch(wakeup_reason) {
|
||||
case ESP_SLEEP_WAKEUP_EXT0:
|
||||
{
|
||||
// RTC interrupt
|
||||
ShowWatchFace(true);
|
||||
break;
|
||||
}
|
||||
case ESP_SLEEP_WAKEUP_EXT1:
|
||||
{
|
||||
// Button press
|
||||
uint64_t wakeupBit = esp_sleep_get_ext1_wakeup_status();
|
||||
HandleButtonPress(wakeupBit);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
ConnectWiFi();
|
||||
SyncNTPTime();
|
||||
DisconnectWiFi();
|
||||
|
||||
m_display.clearScreen(0xFF);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
DeepSleep();
|
||||
|
@ -137,7 +138,5 @@ void Watchy::DisconnectWiFi()
|
|||
|
||||
void Watchy::ShowWatchFace(bool partialRefresh)
|
||||
{
|
||||
m_display.setFullWindow();
|
||||
DrawWatchFace();
|
||||
m_display.display(partialRefresh);
|
||||
DrawWatchFace(partialRefresh);
|
||||
}
|
|
@ -19,13 +19,16 @@ public:
|
|||
void SyncNTPTime();
|
||||
void DisconnectWiFi();
|
||||
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:
|
||||
static void DisplayBusyCallback(const void *);
|
||||
|
||||
static WatchyDisplayBase m_displayBase;
|
||||
static GxEPD2_BW<WatchyDisplayBase, WatchyDisplayBase::HEIGHT> m_display;
|
||||
static WatchyDisplay m_display;
|
||||
static WatchyRTC m_RTC;
|
||||
};
|
Loading…
Reference in a new issue