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()
 | 
			
		||||
| 
						 | 
				
			
			@ -46,3 +101,11 @@ void WatchFace::DrawBatteryIcon()
 | 
			
		|||
 | 
			
		||||
	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,31 +22,32 @@ 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);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -20,12 +20,15 @@ public:
 | 
			
		|||
  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…
	
	Add table
		Add a link
		
	
		Reference in a new issue