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()
|
void setup()
|
||||||
{
|
{
|
||||||
|
Serial.begin(9600);
|
||||||
watchy.Init();
|
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 "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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
};
|
};
|
|
@ -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,31 +22,32 @@ 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);
|
|
||||||
}
|
}
|
|
@ -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;
|
||||||
};
|
};
|
Loading…
Reference in a new issue