Skip to content

Instantly share code, notes, and snippets.

@joeycastillo
Created June 20, 2022 22:37
Show Gist options
  • Save joeycastillo/ef2b449d40eaa14cc126c3248621cad4 to your computer and use it in GitHub Desktop.
Save joeycastillo/ef2b449d40eaa14cc126c3248621cad4 to your computer and use it in GitHub Desktop.
A simple Focus-based app for tracking progress on projects
#include "Focus.h"
#include "Arduino.h"
#include <algorithm>
Task::Task() {
}
View::View(Rect rect) {
this->frame = rect;
this->window.reset();
this->superview.reset();
}
View::~View() {
}
void View::draw(Adafruit_GFX *display, int16_t x, int16_t y) {
if (this->opaque || this->backgroundColor) {
display->fillRect(x + this->frame.origin.x, y + this->frame.origin.y, this->frame.size.width, this->frame.size.height, this->backgroundColor);
}
for(std::shared_ptr<View> view : this->subviews) {
if (!view->hidden) view->draw(display, this->frame.origin.x, this->frame.origin.y);
}
}
void View::addSubview(std::shared_ptr<View> view) {
view->superview = this->shared_from_this();
this->subviews.push_back(view);
if (std::shared_ptr<Window> window = this->getWindow().lock()) {
view->setWindow(window);
window->setNeedsDisplay(true);
}
}
void View::removeSubview(std::shared_ptr<View> view) {
if (view->isFocused()) {
view->resignFocus();
}
view->superview.reset();
view->window.reset();
int index = std::distance(this->subviews.begin(), std::find(this->subviews.begin(), this->subviews.end(), view));
this->subviews.erase(this->subviews.begin() + index);
if (std::shared_ptr<Window> window = this->getWindow().lock()) {
// FIXME: We should only refocus if we know the focused view was removed.
window->becomeFocused();
window->setNeedsDisplay(true);
}
}
bool View::isFocused() {
return this->focused;
}
bool View::canBecomeFocused() {
return false;
}
bool View::becomeFocused() {
for(std::shared_ptr<View> subview : this->subviews) {
if (subview->becomeFocused()) {
return true;
}
}
if (this->canBecomeFocused()) {
// when there are no focusable subviews, and we can become
// focused, become focused ourselves.
// note that Window can always become focused, so this
// block is guaranteed to execute when we reach the window.
if (std::shared_ptr<Window> window = this->getWindow().lock()) {
std::shared_ptr<View> oldResponder = window->getFocusedView().lock();
if (oldResponder != NULL) {
oldResponder->willResignFocus();
oldResponder->focused = false;
window->focusedView.reset();
oldResponder->didResignFocus();
}
this->willBecomeFocused();
this->focused = true;
window->focusedView = this->shared_from_this();
this->didBecomeFocused();
}
return true;
}
return false;
}
void View::resignFocus() {
if (std::shared_ptr<Window> window = this->getWindow().lock()) {
if (std::shared_ptr<View> superview = this->superview.lock()) {
superview->becomeFocused();
}
}
}
void View::movedToWindow() {
// nothing to do here
}
void View::willBecomeFocused() {
// nothing to do here
}
void View::didBecomeFocused() {
if (this->superview.lock()) {
if (std::shared_ptr<Window> window = this->getWindow().lock()) {
std::shared_ptr<View> shared_this = this->shared_from_this();
window->setNeedsDisplayInRect(this->frame, shared_this);
}
}
}
void View::willResignFocus() {
// nothing to do here
}
void View::didResignFocus() {
if (this->superview.lock()) {
if (std::shared_ptr<Window> window = this->getWindow().lock()) {
std::shared_ptr<View> shared_this = this->shared_from_this();
window->setNeedsDisplayInRect(this->frame, shared_this);
}
}
}
bool View::handleEvent(Event event) {
std::shared_ptr<View> focusedView = NULL;
std::shared_ptr<Window> window = NULL;
if (window = this->getWindow().lock()) {
focusedView = window->getFocusedView().lock();
} else {
focusedView = this->shared_from_this();
if (focusedView == NULL) return false;
window = std::static_pointer_cast<Window, View>(focusedView);
}
if (this->actions.count(event.type)) {
if (std::shared_ptr<Application> application = window->application.lock()) {
this->actions[event.type](event);
}
} else if (event.type < FOCUS_EVENT_BUTTON_TAP) {
uint32_t index = std::distance(this->subviews.begin(), std::find(this->subviews.begin(), this->subviews.end(), focusedView));
if (this->affinity == DirectionalAffinityVertical) {
switch (event.type) {
case FOCUS_EVENT_BUTTON_UP:
while (index > 0) {
if (this->subviews[index - 1]->canBecomeFocused()) this->subviews[index - 1]->becomeFocused();
else index--;
return true;
}
break;
case FOCUS_EVENT_BUTTON_DOWN:
while ((index + 1) < this->subviews.size()) {
if (this->subviews[index + 1]->canBecomeFocused()) this->subviews[index + 1]->becomeFocused();
else index--;
return true;
}
break;
default:
break;
}
} else if (this->affinity == DirectionalAffinityHorizontal) {
switch (event.type) {
case FOCUS_EVENT_BUTTON_LEFT:
while (index > 0) {
if (this->subviews[index - 1]->canBecomeFocused()) this->subviews[index - 1]->becomeFocused();
return true;
}
break;
case FOCUS_EVENT_BUTTON_RIGHT:
while ((index + 1) < this->subviews.size()) {
if (this->subviews[index + 1]->canBecomeFocused()) this->subviews[index + 1]->becomeFocused();
return true;
}
break;
default:
break;
}
}
}
if (std::shared_ptr<View> superview = this->superview.lock()) {
superview->handleEvent(event);
}
return false;
}
void View::setAction(const Action &action, int32_t type) {
this->actions[type] = action;
}
void View::removeAction(int32_t type) {
// TODO: remove the action
}
std::weak_ptr<View> View::getSuperview() {
return this->superview;
}
std::weak_ptr<Window> View::getWindow() {
return this->window;
}
void View::setWindow(std::shared_ptr<Window>window) {
this->window = window;
for(std::shared_ptr<View> subview : this->subviews) {
subview->setWindow(window);
}
}
Rect View::getFrame() {
return this->frame;
}
void View::setFrame(Rect frame) {
if (std::shared_ptr<Window> window = this->getWindow().lock()) {
Rect dirtyRect = MakeRect(min(this->frame.origin.x, frame.origin.x), min(this->frame.origin.y, frame.origin.y), 0, 0);
dirtyRect.size.width = max(this->frame.origin.x + this->frame.size.width, frame.origin.x + frame.size.width) - dirtyRect.origin.x;
dirtyRect.size.height = max(this->frame.origin.y + this->frame.size.height, frame.origin.y + frame.size.height) - dirtyRect.origin.y;
this->frame = frame;
window->setNeedsDisplayInRect(dirtyRect, window);
}
}
bool View::isOpaque() {
return this->opaque;
}
void View::setOpaque(bool value) {
if (this-> opaque == value) return;
this->opaque = value;
if (std::shared_ptr<Window> window = this->getWindow().lock()) {
window->setNeedsDisplayInRect(this->frame, window);
}
}
bool View::isHidden() {
return this->hidden;
}
void View::setHidden(bool value) {
if (this-> hidden == value) return;
this->hidden = value;
if (std::shared_ptr<Window> window = this->getWindow().lock()) {
window->setNeedsDisplayInRect(this->frame, window);
}
}
int32_t View::getTag() {
return this->tag;
}
void View::setTag(int32_t value) {
this->tag = value;
}
uint16_t View::getBackgroundColor() {
return this->backgroundColor;
}
void View::setBackgroundColor(uint16_t value) {
this->backgroundColor = value;
}
uint16_t View::getForegroundColor() {
return this->foregroundColor;
}
void View::setForegroundColor(uint16_t value) {
this->foregroundColor = value;
}
uint16_t View::getDirectionalAffinity() {
return this->affinity;
}
void View::setDirectionalAffinity(DirectionalAffinity value) {
this->affinity = value;
}
Control::Control(Rect rect) : View(rect) {
}
bool Control::isEnabled() {
return this->enabled;
}
void Control::setEnabled(bool value) {
this->enabled = value;
}
bool Control::canBecomeFocused() {
return this->enabled;
}
Window::Window(Size size) : View(MakeRect(0, 0, size.width, size.height)) {
this->setNeedsDisplay(true);
}
void Window::addSubview(std::shared_ptr<View> view) {
view->setWindow(std::static_pointer_cast<Window>(this->shared_from_this()));
View::addSubview(view);
// when we add a new view hierarchy to the window, try to focus on its innermost view.
this->becomeFocused();
}
bool Window::canBecomeFocused() {
return true;
}
bool Window::needsDisplay() {
return this->dirty;
}
void Window::setNeedsDisplay(bool needsDisplay) {
if (needsDisplay) {
this->dirtyRect = MakeRect(0, 0, this->frame.size.width, this->frame.size.height);
this->dirty = true;
} else {
this->dirty = false;
}
}
void Window::setNeedsDisplayInRect(Rect rect, std::shared_ptr<View> view) {
std::shared_ptr<View> superview(view);
while(superview = superview->superview.lock()) {
rect.origin.x += superview->frame.origin.x;
rect.origin.y += superview->frame.origin.y;
}
Rect finalRect;
if (this->dirty) {
finalRect = MakeRect(min(this->dirtyRect.origin.x, rect.origin.x), min(this->dirtyRect.origin.y, rect.origin.y), 0, 0);
finalRect.size.width = max(this->dirtyRect.origin.x + this->dirtyRect.size.width, rect.origin.x + rect.size.width) - finalRect.origin.x;
finalRect.size.height = max(this->dirtyRect.origin.y + this->dirtyRect.size.height, rect.origin.y + rect.size.height) - finalRect.origin.y;
} else {
finalRect = rect;
}
this->dirty = true;
this->dirtyRect = finalRect;
}
Rect Window::getDirtyRect() {
if (this->dirty) return this->dirtyRect;
else return {0};
}
std::weak_ptr<View> Window::getFocusedView() {
return this->focusedView;
}
std::weak_ptr<View> Window::getSuperview() {
return std::weak_ptr<View>();
}
std::weak_ptr<Window> Window::getWindow() {
return std::static_pointer_cast<Window, View>(this->shared_from_this());
}
void Window::setWindow(std::shared_ptr<Window> window) {
// nothing to do here
}
Application::Application(const std::shared_ptr<Window>& window) {
this->window = window;
}
void Application::addTask(std::shared_ptr<Task> task) {
this->tasks.push_back(task);
}
void Application::run() {
this->window->application = this->shared_from_this();
this->window->becomeFocused();
this->window->setNeedsDisplay(true);
while(true) {
for(std::shared_ptr<Task> task : this->tasks) {
if (task->run(this) != 0) return;
}
}
}
void Application::generateEvent(int32_t eventType, int32_t userInfo) {
Event event;
event.type = eventType;
event.userInfo = userInfo;
if (std::shared_ptr<View> focusedView = this->window->focusedView.lock()) {
focusedView->handleEvent(event);
}
}
std::shared_ptr<Window> Application::getWindow() {
return this->window;
}
void Application::setRootViewController(std::shared_ptr<ViewController> viewController) {
if (this->rootViewController) {
// clean up old view controller
this->rootViewController->viewWillDisappear();
this->window->removeSubview(this->rootViewController->view);
this->rootViewController->viewDidDisappear();
}
// set up new view controller
this->rootViewController = viewController;
this->rootViewController->viewWillAppear();
this->window->addSubview(this->rootViewController->view);
this->rootViewController->viewDidAppear();
}
void ViewController::viewWillAppear() {
if (!this->view) {
this->createView();
}
}
void ViewController::viewDidDisappear() {
this->destroyView();
}
void ViewController::generateEvent(int32_t eventType, int32_t userInfo) {
if (!this->view) return;
// unsure about this one: we generate an event and let it bubble up to the window,
// where the application can listen for it. seems like wasted effort to get a message
// from a view controller to the application.
if (std::shared_ptr<Window> window = this->view->getWindow().lock()) {
if (std::shared_ptr<Application> application = window->application.lock()) {
application->generateEvent(eventType, userInfo);
}
}
}
void ViewController::createView() {
if (this->view) {
this->destroyView();
}
// subclasses must override to create view here
}
void ViewController::destroyView() {
this->view.reset();
}
#ifndef Focus_h
#define Focus_h
#include <stdint.h>
#include <vector>
#include <memory>
#include <map>
#include <functional>
#include "Adafruit_GFX.h"
#define FOCUS_EVENT_BUTTON_LEFT (0)
#define FOCUS_EVENT_BUTTON_DOWN (1)
#define FOCUS_EVENT_BUTTON_UP (2)
#define FOCUS_EVENT_BUTTON_RIGHT (3)
#define FOCUS_EVENT_BUTTON_TAP (4)
#define FOCUS_EVENT_BUTTON_PREV (5)
#define FOCUS_EVENT_BUTTON_NEXT (6)
#define FOCUS_EVENT_BUTTON_LOCK (7)
typedef struct {
int16_t x;
int16_t y;
} Point;
typedef struct {
int16_t width;
int16_t height;
} Size;
typedef struct {
Point origin;
Size size;
} Rect;
inline Point MakePoint(int16_t x, int16_t y) { return {x, y}; }
inline Size MakeSize(int16_t width, int16_t height) { return {width, height}; }
inline Rect MakeRect(int16_t x, int16_t y, int16_t width, int16_t height) { return {{x, y}, {width, height}}; }
inline bool PointsEqual(Point a, Point b) { return (a.x == b.x) && (a.y == b.y); }
inline bool SizesEqual(Size a, Size b) { return (a.width == b.width) && (a.height == b.height); }
inline bool RectsEqual(Rect a, Rect b) { return PointsEqual(a.origin, b.origin) && SizesEqual(a.size, b.size); }
class Application;
class Window;
class View;
class Task;
class ViewController;
typedef struct {
int32_t type;
int32_t userInfo;
} Event;
typedef enum {
DirectionalAffinityVertical,
DirectionalAffinityHorizontal,
} DirectionalAffinity;
typedef std::function<void(Event)> Action;
class Task {
public:
Task();
virtual int16_t run(Application *application) = 0;
};
class View : public std::enable_shared_from_this<View> {
public:
View(Rect rect);
~View();
virtual void draw(Adafruit_GFX *display, int16_t x, int16_t y);
virtual void addSubview(std::shared_ptr<View> view);
void removeSubview(std::shared_ptr<View> view);
bool isFocused();
virtual bool canBecomeFocused();
virtual bool becomeFocused();
virtual void resignFocus();
virtual void movedToWindow();
virtual void willBecomeFocused();
virtual void didBecomeFocused();
virtual void willResignFocus();
virtual void didResignFocus();
virtual bool handleEvent(Event event);
void setAction(const Action &action, int32_t type);
void removeAction(int32_t type);
virtual std::weak_ptr<View>getSuperview();
virtual std::weak_ptr<Window> getWindow();
virtual void setWindow(std::shared_ptr<Window> window);
Rect getFrame();
void setFrame(Rect rect);
bool isOpaque();
void setOpaque(bool value);
bool isHidden();
void setHidden(bool value);
int32_t getTag();
void setTag(int32_t value);
uint16_t getBackgroundColor();
void setBackgroundColor(uint16_t value);
uint16_t getForegroundColor();
void setForegroundColor(uint16_t value);
uint16_t getDirectionalAffinity();
void setDirectionalAffinity(DirectionalAffinity value);
protected:
bool focused = false;
bool opaque = false;
bool hidden = false;
int32_t tag = 0;
uint16_t backgroundColor = 0;
uint16_t foregroundColor = 1;
Rect frame = {0};
DirectionalAffinity affinity = DirectionalAffinityVertical;
std::vector<std::shared_ptr<View>> subviews;
std::map<int32_t, Action> actions;
std::weak_ptr<View> superview;
private:
std::weak_ptr<Window> window;
friend class Window;
};
class Control : public View {
public:
Control(Rect rect);
bool isEnabled();
void setEnabled(bool value);
bool canBecomeFocused() override;
protected:
bool enabled = true;
};
class Window : public View {
public:
Window(Size size);
void addSubview(std::shared_ptr<View> view) override;
bool canBecomeFocused() override;
bool needsDisplay();
void setNeedsDisplay(bool needsDisplay);
void setNeedsDisplayInRect(Rect rect, std::shared_ptr<View> view);
Rect getDirtyRect();
std::weak_ptr<View> getFocusedView();
std::weak_ptr<View>getSuperview() override;
std::weak_ptr<Window> getWindow() override;
void setWindow(std::shared_ptr<Window> window) override;
protected:
std::weak_ptr<Application> application;
std::weak_ptr<View> focusedView;
bool dirty;
Rect dirtyRect;
friend class Application;
friend class View;
friend class ViewController;
};
class Application : public std::enable_shared_from_this<Application> {
public:
Application(const std::shared_ptr<Window>& window);
void run();
void addTask(std::shared_ptr<Task> task);
void generateEvent(int32_t eventType, int32_t userInfo);
std::shared_ptr<Window> getWindow();
void setRootViewController(std::shared_ptr<ViewController> viewController);
protected:
std::vector<std::shared_ptr<Task>> tasks;
std::shared_ptr<Window> window;
std::shared_ptr<ViewController> rootViewController;
};
class ViewController : public std::enable_shared_from_this<ViewController> {
public:
ViewController() {};
virtual void viewWillAppear();
virtual void viewDidAppear() {};
virtual void viewWillDisappear() {};
virtual void viewDidDisappear();
void generateEvent(int32_t eventType, int32_t userInfo = 0);
protected:
virtual void createView();
virtual void destroyView();
std::shared_ptr<View> view;
friend class Application;
};
#endif // Focus_h
#include "FocusWidgets.h"
#include <algorithm>
BitmapView::BitmapView(Rect rect, const unsigned char *bitmap) : View(rect) {
this->bitmap = bitmap;
}
void BitmapView::draw(Adafruit_GFX *display, int16_t x, int16_t y) {
View::draw(display, x, y);
display->drawBitmap(this->frame.origin.x, this->frame.origin.y, this->bitmap, this->frame.size.width, this->frame.size.height, this->foregroundColor);
}
Button::Button(Rect rect, std::string text) : Control(rect) {
this->text = text;
}
void Button::draw(Adafruit_GFX *display, int16_t x, int16_t y) {
if (std::shared_ptr<Window> window = this->getWindow().lock()) {
View::draw(display, x, y);
display->setCursor(this->frame.origin.x + x + 4, this->frame.origin.y + y + this->frame.size.height / 2 - 4);
if (this->focused) {
display->fillRect(x + this->frame.origin.x, y + this->frame.origin.y, this->frame.size.width, this->frame.size.height, this->foregroundColor);
display->setTextColor(this->backgroundColor);
display->print(this->text.c_str());
} else {
display->drawRect(x + this->frame.origin.x, y + this->frame.origin.y, this->frame.size.width, this->frame.size.height, this->foregroundColor);
display->setTextColor(this->foregroundColor);
display->print(this->text.c_str());
}
}
}
HatchedView::HatchedView(Rect rect, uint16_t color) : View(rect) {
this->foregroundColor = color;
}
void HatchedView::draw(Adafruit_GFX *display, int16_t x, int16_t y) {
for(int16_t i = x; i < x + this->frame.size.width; i++) {
for(int16_t j = y; j < y + this->frame.size.height; j++) {
if ((i + j) % 2) {
display->drawPixel(i, j, this->foregroundColor);
}
}
}
View::draw(display, x, y);
}
BorderedView::BorderedView(Rect rect) : View(rect) {
this->opaque = true;
}
void BorderedView::draw(Adafruit_GFX *display, int16_t x, int16_t y) {
View::draw(display, x, y);
display->drawRect(x + this->frame.origin.x, y + this->frame.origin.y, this->frame.size.width, this->frame.size.height, this->foregroundColor);
}
void ProgressView::draw(Adafruit_GFX *display, int16_t x, int16_t y) {
View::draw(display, x, y);
display->fillRect(x + this->frame.origin.x, y + this->frame.origin.y, this->frame.size.width, this->frame.size.height, this->backgroundColor);
display->fillRect(x + this->frame.origin.x, y + this->frame.origin.y, (int16_t)(this->frame.size.width * this->progress), this->frame.size.height, this->foregroundColor);
}
void ProgressView::setProgress(float value) {
this->progress = value;
if (std::shared_ptr<Window> window = this->getWindow().lock()) {
window->setNeedsDisplayInRect(this->frame, window);
}
}
Label::Label(Rect rect, std::string text) : View(rect) {
this->text = text;
}
void Label::draw(Adafruit_GFX *display, int16_t x, int16_t y) {
View::draw(display, x, y);
display->setTextColor(this->foregroundColor);
display->setCursor(this->frame.origin.x + x, this->frame.origin.y + y);
display->print(this->text.c_str());
}
void Label::setText(std::string text) {
this->text = text;
if (std::shared_ptr<Window> window = this->getWindow().lock()) {
window->setNeedsDisplayInRect(this->frame, window);
}
}
#ifndef FocusWidgets_h
#define FocusWidgets_h
#include <stdint.h>
#include <vector>
#include <map>
#include <string>
#include "Focus.h"
class BitmapView : public View {
public:
BitmapView(Rect rect, const unsigned char *bitmap);
void draw(Adafruit_GFX *display, int16_t x, int16_t y) override;
protected:
const unsigned char *bitmap;
};
class Button : public Control {
public:
Button(Rect rect, std::string text);
void draw(Adafruit_GFX *display, int16_t x, int16_t y) override;
protected:
std::string text;
};
class HatchedView : public View {
public:
HatchedView(Rect rect, uint16_t color);
void draw(Adafruit_GFX *display, int16_t x, int16_t y) override;
};
class BorderedView : public View {
public:
BorderedView(Rect rect);
void draw(Adafruit_GFX *display, int16_t x, int16_t y) override;
};
class ProgressView : public View {
public:
ProgressView(Rect rect) : View(rect) {};
void draw(Adafruit_GFX *display, int16_t x, int16_t y) override;
void setProgress(float value);
protected:
float progress = 0;
};
class Label : public View {
public:
Label(Rect rect, std::string text);
void draw(Adafruit_GFX *display, int16_t x, int16_t y) override;
void setText(std::string text);
protected:
std::string text;
};
#endif // FocusWidgets_h
#include "Adafruit_ThinkInk.h"
#include "Adafruit_NeoPixel.h"
#include "Focus.h"
#include "FocusWidgets.h"
typedef struct {
std::string title;
uint8_t column;
} Project;
#define USER_EVENT_SHOW_HELP (10000)
#define USER_EVENT_SHOW_PROJECTS (10001)
class MyProjectsViewController : public ViewController {
public:
MyProjectsViewController(std::vector<Project> projects) {
this->projects = projects;
}
protected:
std::vector<Project> projects;
std::vector<std::shared_ptr<Button>> cards;
virtual void createView() override {
ViewController::createView();
this->view = std::make_shared<View>(MakeRect(0, 0, 296, 128));
std::shared_ptr<Label> header = std::make_shared<Label>(MakeRect(0, 2, 296, 8), " Stagnant This Month This Week Up Next");
this->view->addSubview(header);
std::shared_ptr<BorderedView> divider = std::make_shared<BorderedView>(MakeRect(0, 12, 296, 2));
this->view->addSubview(divider);
std::shared_ptr<Button> helpButton = std::make_shared<Button>(MakeRect(282, 0, 14, 13), "?");
helpButton->setBackgroundColor(EPD_LIGHT);
this->view->addSubview(helpButton);
helpButton->setAction(std::bind(&MyProjectsViewController::helpButtonPressed, this, std::placeholders::_1), FOCUS_EVENT_BUTTON_TAP);
int16_t i = 0;
for(Project project : this->projects) {
std::shared_ptr<Button> card = std::make_shared<Button>(MakeRect(74 * project.column, 18 + (16 * i), 72, 14), project.title);
card->setBackgroundColor(EPD_LIGHT);
card->setTag(i++);
card->setAction(std::bind(&MyProjectsViewController::moveCard, this, std::placeholders::_1), FOCUS_EVENT_BUTTON_LEFT);
card->setAction(std::bind(&MyProjectsViewController::moveCard, this, std::placeholders::_1), FOCUS_EVENT_BUTTON_RIGHT);
this->cards.push_back(card);
this->view->addSubview(card);
}
this->_updateCardPositions();
}
virtual void destroyView() override {
this->cards.clear();
ViewController::destroyView();
}
void moveCard(Event event) {
int selectedProjectIndex = -1;
for(std::shared_ptr<Button> card : this->cards) {
if (card->isFocused()) selectedProjectIndex = card->getTag();
}
// if no focused view found, shrug. don't do anything tho.
if (selectedProjectIndex == -1) return;
switch (event.type) {
case FOCUS_EVENT_BUTTON_RIGHT:
if (this->projects[selectedProjectIndex].column < 3) {
this->projects[selectedProjectIndex].column++;
}
break;
case FOCUS_EVENT_BUTTON_LEFT:
if (this->projects[selectedProjectIndex].column > 0) {
this->projects[selectedProjectIndex].column--;
}
break;
default:
return;
}
this->_updateCardPositions();
}
void helpButtonPressed(Event event) {
this->generateEvent(USER_EVENT_SHOW_HELP);
}
protected:
void _updateCardPositions() {
for(int i = 0; i < this->projects.size(); i++) {
Project project = this->projects[i];
std::shared_ptr<Button> card= this->cards[i];
card->setFrame(MakeRect(74 * project.column, 18 + (16 * i), 72, 14));
}
}
};
class HelpViewController : public ViewController {
protected:
virtual void createView() override {
this->view = std::make_shared<View>(MakeRect(0, 0, 296, 128));
this->view->setDirectionalAffinity(DirectionalAffinityHorizontal);
std::shared_ptr<Label> label = std::make_shared<Label>(MakeRect(0, 18, 260, 60), " This project tracker lets you arrange your\n projects by priority and recency. Use the\n up and down buttons to select a project,\n and the left and right buttons to move\n it between the available columns.");
this->view->addSubview(label);
std::shared_ptr<Button> button1 = std::make_shared<Button>(MakeRect(10, 96, 108, 18), "OK");
button1->setBackgroundColor(EPD_LIGHT);
button1->setAction(std::bind(&HelpViewController::dismiss, this, std::placeholders::_1), FOCUS_EVENT_BUTTON_TAP);
this->view->addSubview(button1);
std::shared_ptr<Button> button2 = std::make_shared<Button>(MakeRect(128, 96, 160, 18), "OK, but with rainbows");
button2->setBackgroundColor(EPD_LIGHT);
button2->setAction(std::bind(&HelpViewController::dismissWithRainbows, this, std::placeholders::_1), FOCUS_EVENT_BUTTON_TAP);
this->view->addSubview(button2);
}
void dismiss(Event event) {
this->generateEvent(USER_EVENT_SHOW_PROJECTS);
}
void dismissWithRainbows(Event event) {
pinMode(NEOPIXEL_POWER, OUTPUT);
digitalWrite(NEOPIXEL_POWER, LOW);
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(4, PIN_NEOPIXEL, NEO_GRB + NEO_KHZ800);
pixels.begin();
for(int i = 0; i < 5; i++) {
pixels.setPixelColor(0, pixels.Color(255, 0, 0));
pixels.setPixelColor(1, pixels.Color(255, 255, 0));
pixels.setPixelColor(2, pixels.Color(0, 255, 0));
pixels.setPixelColor(3, pixels.Color(0, 0, 255));
pixels.show();
delay(250);
pixels.fill(pixels.Color(0, 0, 0));
pixels.show();
delay(50);
}
digitalWrite(NEOPIXEL_POWER, HIGH);
this->generateEvent(USER_EVENT_SHOW_PROJECTS);
}
};
class ButtonInputTask : public Task {
public:
ButtonInputTask() {
digitalWrite(LED_BUILTIN, LOW);
pinMode(BUTTON_A, INPUT_PULLUP);
pinMode(BUTTON_B, INPUT_PULLUP);
pinMode(BUTTON_C, INPUT_PULLUP);
pinMode(BUTTON_D, INPUT_PULLUP);
pinMode(0, INPUT_PULLUP);
};
int16_t run(Application *application) {
if (!digitalRead(BUTTON_A)) application->generateEvent(FOCUS_EVENT_BUTTON_LEFT, 0);
if (!digitalRead(BUTTON_B)) application->generateEvent(FOCUS_EVENT_BUTTON_UP, 0);
if (!digitalRead(BUTTON_C)) application->generateEvent(FOCUS_EVENT_BUTTON_DOWN, 0);
if (!digitalRead(BUTTON_D)) application->generateEvent(FOCUS_EVENT_BUTTON_RIGHT, 0);
if (!digitalRead(0)) application->generateEvent(FOCUS_EVENT_BUTTON_TAP, 0);
return 0;
}
};
class ThinkInkDisplayTask : public Task {
public:
ThinkInkDisplayTask() {
this->display = new ThinkInk_290_Grayscale4_T5(EPD_DC, EPD_RESET, EPD_CS, -1, EPD_BUSY);
this->display->begin(THINKINK_MONO);
};
int16_t run(Application *application) {
std::shared_ptr<Window> window = application->getWindow();
if (window->needsDisplay()) {
this->display->clearBuffer();
window->draw(this->display, 0, 0);
Rect dirtyRect = window->getDirtyRect();
if (RectsEqual(dirtyRect, window->getFrame())) {
this->display->display();
} else {
display->displayPartial(dirtyRect.origin.x, dirtyRect.origin.y, dirtyRect.origin.x + dirtyRect.size.width, dirtyRect.origin.y + dirtyRect.size.height);
}
window->setNeedsDisplay(false);
}
return 0;
}
protected:
ThinkInk_290_Grayscale4_T5 *display;
};
class ProjectTrackerApplication : public Application {
public:
ProjectTrackerApplication(const std::shared_ptr<Window>& window) : Application(window) {
// Add an input task to generate events from button presses
std::shared_ptr<Task> inputTask = std::make_shared<ButtonInputTask>();
this->addTask(inputTask);
// Add a display task to update the window
std::shared_ptr<Task> displayTask = std::make_shared<ThinkInkDisplayTask>();
this->addTask(displayTask);
// Finally, set up our view controller with the tasks we want to display!
std::vector<Project> projects;
projects.push_back({"Project 1", 1});
projects.push_back({"Project 2", 2});
projects.push_back({"Project 3", 1});
projects.push_back({"Project 4", 3});
projects.push_back({"Project 5", 0});
projects.push_back({"Project 6", 0});
this->mainViewController = std::make_shared<MyProjectsViewController>(projects);
this->setRootViewController(this->mainViewController);
// The application can listen for Focus events as well as custom events, like this request to show the Help modal.
this->window->setAction(std::bind(&ProjectTrackerApplication::showHelp, this, std::placeholders::_1), USER_EVENT_SHOW_HELP);
this->window->setAction(std::bind(&ProjectTrackerApplication::returnHome, this, std::placeholders::_1), USER_EVENT_SHOW_PROJECTS);
}
void showHelp(Event event) {
std::shared_ptr<ViewController> helpViewController = std::make_shared<HelpViewController>();
this->setRootViewController(helpViewController);
this->window->setNeedsDisplay(true);
}
void returnHome(Event event) {
this->setRootViewController(this->mainViewController);
this->window->setNeedsDisplay(true);
}
protected:
std::shared_ptr<ViewController> mainViewController;
};
void setup() {
std::shared_ptr<Window> window = std::make_shared<Window>(MakeSize(128, 296));
std::shared_ptr<Application> application = std::make_shared<ProjectTrackerApplication>(window);
application->run();
}
void loop() {
// Nothing to do here!
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment