Separate display into its own file.
This commit is contained in:
@ -22,87 +22,11 @@
|
||||
#include "app_state.h"
|
||||
#include "channel.h"
|
||||
#include "save_state.h"
|
||||
#include "display.h"
|
||||
|
||||
AppState app;
|
||||
|
||||
StateManager stateManager;
|
||||
|
||||
enum ParamsMainPage {
|
||||
PARAM_MAIN_TEMPO,
|
||||
PARAM_MAIN_SOURCE,
|
||||
PARAM_MAIN_ENCODER_DIR,
|
||||
PARAM_MAIN_RESET_STATE,
|
||||
PARAM_MAIN_LAST,
|
||||
};
|
||||
|
||||
enum ParamsChannelPage {
|
||||
PARAM_CH_MOD,
|
||||
PARAM_CH_PROB,
|
||||
PARAM_CH_DUTY,
|
||||
PARAM_CH_OFFSET,
|
||||
PARAM_CH_CV_SRC,
|
||||
PARAM_CH_CV_DEST,
|
||||
PARAM_CH_LAST,
|
||||
};
|
||||
|
||||
const PROGMEM uint8_t TEXT_FONT[437] U8G2_FONT_SECTION("velvetscreen") =
|
||||
"\64\0\2\2\3\3\2\3\4\5\5\0\0\5\0\5\0\0\221\0\0\1\230 \4\200\134%\11\255tT"
|
||||
"R\271RI(\6\252\334T\31)\7\252\134bJ\12+\7\233\345\322J\0,\5\221T\4-\5\213"
|
||||
"f\6.\5\211T\2/\6\244\354c\33\60\10\254\354T\64\223\2\61\7\353\354\222\254\6\62\11\254l"
|
||||
"\66J*\217\0\63\11\254l\66J\32\215\4\64\10\254l\242\34\272\0\65\11\254l\206\336h$\0\66"
|
||||
"\11\254\354T^\61)\0\67\10\254lF\216u\4\70\11\254\354TL*&\5\71\11\254\354TL;"
|
||||
")\0:\6\231UR\0A\10\254\354T\34S\6B\11\254lV\34)\216\4C\11\254\354T\324\61"
|
||||
")\0D\10\254lV\64G\2E\10\254l\206\36z\4F\10\254l\206^\71\3G\11\254\354TN"
|
||||
"\63)\0H\10\254l\242\34S\6I\6\251T\206\0J\10\254\354k\231\24\0K\11\254l\242J\62"
|
||||
"\225\1L\7\254lr{\4M\11\255t\362ZI\353\0N\11\255t\362TI\356\0O\10\254\354T"
|
||||
"\64\223\2P\11\254lV\34)g\0Q\10\254\354T\264b\12R\10\254lV\34\251\31S\11\254\354"
|
||||
"FF\32\215\4T\7\253dVl\1U\10\254l\242\63)\0V\11\255t\262Ne\312\21W\12\255"
|
||||
"t\262J*\251.\0X\11\254l\242L*\312\0Y\12\255tr\252\63\312(\2Z\7\253df*"
|
||||
"\7p\10\255\364V\266\323\2q\7\255\364\216\257\5r\10\253d\242\32*\2t\6\255t\376#w\11"
|
||||
"\255\364V\245FN\13x\6\233dR\7\0\0\0\4\377\377\0";
|
||||
|
||||
const PROGMEM uint8_t LARGE_FONT[916] U8G2_FONT_SECTION("stk-l") =
|
||||
"#\0\4\4\4\5\2\1\6\17\30\1\0\27\0\0\0\1\77\0\0\3w%'\17\37\313\330R#&"
|
||||
"\32!F\14\211I\310\24!\65\204(MF\21)Cd\304\10\62b\14\215\60Vb\334\20\0/\14"
|
||||
"\272\336\336d\244\350\263q\343\0\60\37|\377\216!%*\10\35\263\253ChD\30\21bB\14\242S"
|
||||
"\306lv\210\204\22Ef\0\61\24z\337\322\60R\205\314\234\31\61F\310\270\371\177\224\42\3\62\33|"
|
||||
"\377\216)\64*\10\35\63\66r\206\304\314`c\252\34\301\221\263|\360\300\0\63\34|\377\216)\64*"
|
||||
"\10\35\63\66r \71\332YIr\226\306\16\221P\203\312\14\0\64 |\377\226\220AC\306\20\31B"
|
||||
"f\310\240\21\204F\214\32\61j\304(cv\366\200\305\312\371\0\65\32|\377\206\212-F\316\27\204\224"
|
||||
"\254\30\65t\344,\215\35\42\241\6\225\31\0\66\33}\17\317\251\64+\206\235\63:/\314,aA\352"
|
||||
"\234\335\235\42\261&\325\31\0\67\23|\377\302\212\7)\347Crt\70\345\300\221\363\16\0\70 |\377"
|
||||
"\216)\64*\10\35\263\354\20\11\42d\20\235BC\204\4\241cvv\210\204\32Tf\0\71\32|\377"
|
||||
"\216)\64*\10\35\263\263C$\226\250I\71_\14\42\241\6\225\31\0A\26}\17S\271Si(\31"
|
||||
"\65d\324\210q\366\356\301w\366\273\1B$}\17C\42\65KF\221\30\66b\330\210a#\206\215\30"
|
||||
"Eb\311&\243H\14;g\317\36\204`\261\4\0C\27}\17\317\251\64K\10!\63:\377\247\304F"
|
||||
"\20\42\261F\21\22\0D\33}\17C\42\65KF\15\31\66b\330\210q\366\77;\66b\24\211%j"
|
||||
"\22\1E\21|\377\302\7)\347%\42\214F\316/\37<\60F\20|\377\302\7)\347\313\64\331\214\234"
|
||||
"\177\11\0G\31\216\37\17*\65L\206\35\264v>\322\241\15\217\221 \65\204\215\262\63\0H\17|\377"
|
||||
"\302\60\373g\17\36\60\263\177\66\0I\7so\302\37$J\22|\377\346\374\377\322\230\261C\210H\250"
|
||||
"Ae\6\0K\42|\377\302\60S\247F\14\42\61h\310\30\42c&!\63\202\320\251\64JV\14\42"
|
||||
"\61\352\230\375l\0L\15{\357\302\300\371\377\37>x\60\0M$}\17\203\310r\346N\245Q\263\202"
|
||||
"E\12)L\224\60Q\302\310\20#C\214\14\61\23\306L\30s\366\335\0N#}\17\203@s\346\216"
|
||||
"\35C\205*Q\42\23cL\214\61\62\304\310\20\63#\314\214\60\224\25f\327\231\33O\26}\17\317\251"
|
||||
"\64KF\215\30g\377\337\215\30\65dM\252\63\0P\26|\377B\32%+F\35\263W\207H\254H"
|
||||
"\203h\344\374%\0Q\31}\17S\261\64KF\215\30g\377oF\230\31q\246\210\42E%F\0R"
|
||||
"\61\216\37\203\242\65L\206\221\30\67b\334\210q#\306\215\30\67b\30\211QD\230(J\65d\330\230"
|
||||
"Qc\10\315j\314(\42\303H\214\33\61\356\340\0S!\216\37\317\261DKH\221\30\67b\334\210\261"
|
||||
"c)M\246Ji\331\331\32\64\207\212D\223Uh\0T\15}\17\303\7\251\206\316\377\377\12\0U\21"
|
||||
"|\377\302\60\373\377\317F\14\32\242\6\225\31\0X)~\37\303@\203\307H\14\33B\210\14\21RC"
|
||||
"\206\241\63h\222(I\203\346\220\15\31E\204\14!\42\303F\20;h\341\0x\24\312\336\302 CG"
|
||||
"H\240\61E\312\14\222)\6Y\64\0\0\0\0\4\377\377\0";
|
||||
|
||||
#define play_icon_width 14
|
||||
#define play_icon_height 14
|
||||
static const unsigned char play_icon[] = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x7C, 0x00, 0xFC, 0x00, 0xFC, 0x03,
|
||||
0xFC, 0x0F, 0xFC, 0x0F, 0xFC, 0x03, 0xFC, 0x00, 0x7C, 0x00, 0x3C, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00};
|
||||
static const unsigned char pause_icon[] = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x38, 0x0E, 0x38, 0x0E, 0x38, 0x0E, 0x38, 0x0E,
|
||||
0x38, 0x0E, 0x38, 0x0E, 0x38, 0x0E, 0x38, 0x0E, 0x38, 0x0E, 0x38, 0x0E,
|
||||
0x38, 0x0E, 0x00, 0x00};
|
||||
|
||||
//
|
||||
// Arduino setup and loop.
|
||||
//
|
||||
@ -316,271 +240,8 @@ void InitAppState(AppState& app) {
|
||||
gravity.encoder.SetReverseDirection(app.encoder_reversed);
|
||||
}
|
||||
|
||||
Channel& GetSelectedChannel() {
|
||||
return app.channel[app.selected_channel - 1];
|
||||
}
|
||||
|
||||
void ResetOutputs() {
|
||||
for (int i = 0; i < Gravity::OUTPUT_COUNT; i++) {
|
||||
gravity.outputs[i].Low();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// UI Display functions.
|
||||
//
|
||||
|
||||
// Constants for screen layout and fonts
|
||||
constexpr int SCREEN_CENTER_X = 32;
|
||||
constexpr int MAIN_TEXT_Y = 26;
|
||||
constexpr int SUB_TEXT_Y = 40;
|
||||
constexpr int VISIBLE_MENU_ITEMS = 3;
|
||||
constexpr int MENU_ITEM_HEIGHT = 14;
|
||||
constexpr int MENU_BOX_PADDING = 4;
|
||||
constexpr int MENU_BOX_WIDTH = 64;
|
||||
constexpr int CHANNEL_BOXES_Y = 50;
|
||||
constexpr int CHANNEL_BOX_WIDTH = 18;
|
||||
constexpr int CHANNEL_BOX_HEIGHT = 14;
|
||||
|
||||
void UpdateDisplay() {
|
||||
app.refresh_screen = false;
|
||||
gravity.display.firstPage();
|
||||
do {
|
||||
if (app.selected_channel == 0) {
|
||||
DisplayMainPage();
|
||||
} else {
|
||||
DisplayChannelPage();
|
||||
}
|
||||
// Global channel select UI.
|
||||
DisplaySelectedChannel();
|
||||
} while (gravity.display.nextPage());
|
||||
}
|
||||
|
||||
void DisplayMainPage() {
|
||||
gravity.display.setFontMode(1);
|
||||
gravity.display.setDrawColor(2);
|
||||
gravity.display.setFont(TEXT_FONT);
|
||||
|
||||
// Display selected editable value
|
||||
char mainText[8];
|
||||
const char* subText;
|
||||
|
||||
switch (app.selected_param) {
|
||||
case PARAM_MAIN_TEMPO:
|
||||
// Serial MIDI is too unstable to display bpm in real time.
|
||||
if (app.selected_source == Clock::SOURCE_EXTERNAL_MIDI) {
|
||||
sprintf(mainText, "%s", "EXT");
|
||||
} else {
|
||||
sprintf(mainText, "%d", gravity.clock.Tempo());
|
||||
}
|
||||
subText = "BPM";
|
||||
break;
|
||||
case PARAM_MAIN_SOURCE:
|
||||
switch (app.selected_source) {
|
||||
case Clock::SOURCE_INTERNAL:
|
||||
sprintf(mainText, "%s", "INT");
|
||||
subText = "CLOCK";
|
||||
break;
|
||||
case Clock::SOURCE_EXTERNAL_PPQN_24:
|
||||
sprintf(mainText, "%s", "EXT");
|
||||
subText = "24 PPQN";
|
||||
break;
|
||||
case Clock::SOURCE_EXTERNAL_PPQN_4:
|
||||
sprintf(mainText, "%s", "EXT");
|
||||
subText = "4 PPQN";
|
||||
break;
|
||||
case Clock::SOURCE_EXTERNAL_MIDI:
|
||||
sprintf(mainText, "%s", "EXT");
|
||||
subText = "MIDI";
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case PARAM_MAIN_ENCODER_DIR:
|
||||
sprintf(mainText, "%s", "DIR");
|
||||
subText = app.selected_sub_param == 0 ? "DEFAULT" : "REVERSED";
|
||||
break;
|
||||
case PARAM_MAIN_RESET_STATE:
|
||||
sprintf(mainText, "%s", "RST");
|
||||
subText = app.selected_sub_param == 0 ? "RESET ALL" : "BACK";
|
||||
break;
|
||||
}
|
||||
|
||||
drawCenteredText(mainText, MAIN_TEXT_Y, LARGE_FONT);
|
||||
drawCenteredText(subText, SUB_TEXT_Y, TEXT_FONT);
|
||||
|
||||
// Draw Main Page menu items
|
||||
const char* menu_items[PARAM_MAIN_LAST] = {"TEMPO", "SOURCE", "ENCODER DIR", "RESET"};
|
||||
drawMenuItems(menu_items, PARAM_MAIN_LAST);
|
||||
}
|
||||
|
||||
void DisplayChannelPage() {
|
||||
auto& ch = GetSelectedChannel();
|
||||
|
||||
gravity.display.setFontMode(1);
|
||||
gravity.display.setDrawColor(2);
|
||||
|
||||
// Display selected editable value
|
||||
char mainText[5];
|
||||
const char* subText;
|
||||
|
||||
// When editing a param, just show the base value. When not editing show
|
||||
// the value with cv mod.
|
||||
bool withCvMod = !app.editing_param;
|
||||
|
||||
switch (app.selected_param) {
|
||||
case PARAM_CH_MOD: {
|
||||
int mod_value = ch.getClockMod(withCvMod);
|
||||
if (mod_value > 1) {
|
||||
sprintf(mainText, "/%d", mod_value);
|
||||
subText = "DIVIDE";
|
||||
} else {
|
||||
sprintf(mainText, "x%d", abs(mod_value));
|
||||
subText = "MULTIPLY";
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PARAM_CH_PROB:
|
||||
sprintf(mainText, "%d%%", ch.getProbability(withCvMod));
|
||||
subText = "HIT CHANCE";
|
||||
break;
|
||||
case PARAM_CH_DUTY:
|
||||
sprintf(mainText, "%d%%", ch.getDutyCycle(withCvMod));
|
||||
subText = "PULSE WIDTH";
|
||||
break;
|
||||
case PARAM_CH_OFFSET:
|
||||
sprintf(mainText, "%d%%", ch.getOffset(withCvMod));
|
||||
subText = "SHIFT HIT";
|
||||
break;
|
||||
case PARAM_CH_CV_SRC: {
|
||||
switch (ch.getCvSource()) {
|
||||
case CV_NONE:
|
||||
sprintf(mainText, "SRC");
|
||||
subText = "NONE";
|
||||
break;
|
||||
case CV_1:
|
||||
sprintf(mainText, "SRC");
|
||||
subText = "CV 1";
|
||||
break;
|
||||
case CV_2:
|
||||
sprintf(mainText, "SRC");
|
||||
subText = "CV 2";
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PARAM_CH_CV_DEST: {
|
||||
switch (ch.getCvDestination()) {
|
||||
case CV_DEST_NONE:
|
||||
sprintf(mainText, "DEST");
|
||||
subText = "NONE";
|
||||
break;
|
||||
case CV_DEST_MOD:
|
||||
sprintf(mainText, "DEST");
|
||||
subText = "CLOCK MOD";
|
||||
break;
|
||||
case CV_DEST_PROB:
|
||||
sprintf(mainText, "DEST");
|
||||
subText = "PROBABILITY";
|
||||
break;
|
||||
case CV_DEST_DUTY:
|
||||
sprintf(mainText, "DEST");
|
||||
subText = "DUTY CYCLE";
|
||||
break;
|
||||
case CV_DEST_OFFSET:
|
||||
sprintf(mainText, "DEST");
|
||||
subText = "OFFSET";
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
drawCenteredText(mainText, MAIN_TEXT_Y, LARGE_FONT);
|
||||
drawCenteredText(subText, SUB_TEXT_Y, TEXT_FONT);
|
||||
|
||||
// Draw Channel Page menu items
|
||||
const char* menu_items[PARAM_CH_LAST] = {
|
||||
"MOD", "PROBABILITY", "DUTY", "OFFSET", "CV SOURCE", "CV DEST"};
|
||||
drawMenuItems(menu_items, PARAM_CH_LAST);
|
||||
}
|
||||
|
||||
void DisplaySelectedChannel() {
|
||||
int boxX = CHANNEL_BOX_WIDTH;
|
||||
int boxY = CHANNEL_BOXES_Y;
|
||||
int boxWidth = CHANNEL_BOX_WIDTH;
|
||||
int boxHeight = CHANNEL_BOX_HEIGHT;
|
||||
int textOffset = 7; // Half of font width
|
||||
|
||||
// Draw top and right side of frame.
|
||||
gravity.display.drawHLine(1, boxY, SCREEN_WIDTH - 2);
|
||||
gravity.display.drawVLine(SCREEN_WIDTH - 2, boxY, boxHeight);
|
||||
|
||||
for (int i = 0; i < Gravity::OUTPUT_COUNT + 1; i++) {
|
||||
// Draw box frame or filled selected box.
|
||||
gravity.display.setDrawColor(1);
|
||||
(app.selected_channel == i)
|
||||
? gravity.display.drawBox(i * boxWidth, boxY, boxWidth, boxHeight)
|
||||
: gravity.display.drawVLine(i * boxWidth, boxY, boxHeight);
|
||||
|
||||
// Draw clock status icon or each channel number.
|
||||
gravity.display.setDrawColor(2);
|
||||
if (i == 0) {
|
||||
gravity.display.setBitmapMode(1);
|
||||
auto icon = gravity.clock.IsPaused() ? pause_icon : play_icon;
|
||||
gravity.display.drawXBM(2, boxY, play_icon_width, play_icon_height, icon);
|
||||
} else {
|
||||
gravity.display.setFont(TEXT_FONT);
|
||||
gravity.display.setCursor((i * boxWidth) + textOffset, SCREEN_HEIGHT - 3);
|
||||
gravity.display.print(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void drawMenuItems(const char* menu_items[], int menu_size) {
|
||||
// Draw menu items
|
||||
gravity.display.setFont(TEXT_FONT);
|
||||
|
||||
// Draw selected menu item box
|
||||
int selectedBoxY = 0;
|
||||
if (menu_size >= VISIBLE_MENU_ITEMS && app.selected_param == menu_size - 1) {
|
||||
selectedBoxY = MENU_ITEM_HEIGHT * min(2, app.selected_param);
|
||||
} else if (app.selected_param > 0) {
|
||||
selectedBoxY = MENU_ITEM_HEIGHT;
|
||||
}
|
||||
|
||||
int boxX = MENU_BOX_WIDTH + 1;
|
||||
int boxY = selectedBoxY + 2;
|
||||
int boxWidth = MENU_BOX_WIDTH - 1;
|
||||
int boxHeight = MENU_ITEM_HEIGHT + 1;
|
||||
|
||||
app.editing_param
|
||||
? gravity.display.drawBox(boxX, boxY, boxWidth, boxHeight)
|
||||
: gravity.display.drawFrame(boxX, boxY, boxWidth, boxHeight);
|
||||
|
||||
// Draw the visible menu items
|
||||
int start_index = 0;
|
||||
if (menu_size >= VISIBLE_MENU_ITEMS && app.selected_param == menu_size - 1) {
|
||||
start_index = menu_size - VISIBLE_MENU_ITEMS;
|
||||
} else if (app.selected_param > 0) {
|
||||
start_index = app.selected_param - 1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < min(menu_size, VISIBLE_MENU_ITEMS); ++i) {
|
||||
int idx = start_index + i;
|
||||
drawRightAlignedText(menu_items[idx], MENU_ITEM_HEIGHT * (i + 1) - 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to draw centered text
|
||||
void drawCenteredText(const char* text, int y, const uint8_t* font) {
|
||||
gravity.display.setFont(font);
|
||||
int textWidth = gravity.display.getUTF8Width(text);
|
||||
gravity.display.drawStr(SCREEN_CENTER_X - (textWidth / 2), y, text);
|
||||
}
|
||||
|
||||
// Helper function to draw right-aligned text
|
||||
void drawRightAlignedText(const char* text, int y) {
|
||||
int textWidth = gravity.display.getUTF8Width(text);
|
||||
int drawX = (SCREEN_WIDTH - textWidth) - MENU_BOX_PADDING;
|
||||
gravity.display.drawStr(drawX, y, text);
|
||||
}
|
||||
@ -18,4 +18,28 @@ struct AppState {
|
||||
Channel channel[Gravity::OUTPUT_COUNT];
|
||||
};
|
||||
|
||||
extern AppState app;
|
||||
|
||||
static Channel& GetSelectedChannel() {
|
||||
return app.channel[app.selected_channel - 1];
|
||||
}
|
||||
|
||||
enum ParamsMainPage {
|
||||
PARAM_MAIN_TEMPO,
|
||||
PARAM_MAIN_SOURCE,
|
||||
PARAM_MAIN_ENCODER_DIR,
|
||||
PARAM_MAIN_RESET_STATE,
|
||||
PARAM_MAIN_LAST,
|
||||
};
|
||||
|
||||
enum ParamsChannelPage {
|
||||
PARAM_CH_MOD,
|
||||
PARAM_CH_PROB,
|
||||
PARAM_CH_DUTY,
|
||||
PARAM_CH_OFFSET,
|
||||
PARAM_CH_CV_SRC,
|
||||
PARAM_CH_CV_DEST,
|
||||
PARAM_CH_LAST,
|
||||
};
|
||||
|
||||
#endif // APP_STATE_H
|
||||
346
examples/Gravity/display.h
Normal file
346
examples/Gravity/display.h
Normal file
@ -0,0 +1,346 @@
|
||||
#ifndef DISPLAY_H
|
||||
#define DISPLAY_H
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#include "app_state.h"
|
||||
|
||||
//
|
||||
// UI Display functions for drawing the UI to the OLED display.
|
||||
//
|
||||
|
||||
const PROGMEM uint8_t TEXT_FONT[437] U8G2_FONT_SECTION("velvetscreen") =
|
||||
"\64\0\2\2\3\3\2\3\4\5\5\0\0\5\0\5\0\0\221\0\0\1\230 \4\200\134%\11\255tT"
|
||||
"R\271RI(\6\252\334T\31)\7\252\134bJ\12+\7\233\345\322J\0,\5\221T\4-\5\213"
|
||||
"f\6.\5\211T\2/\6\244\354c\33\60\10\254\354T\64\223\2\61\7\353\354\222\254\6\62\11\254l"
|
||||
"\66J*\217\0\63\11\254l\66J\32\215\4\64\10\254l\242\34\272\0\65\11\254l\206\336h$\0\66"
|
||||
"\11\254\354T^\61)\0\67\10\254lF\216u\4\70\11\254\354TL*&\5\71\11\254\354TL;"
|
||||
")\0:\6\231UR\0A\10\254\354T\34S\6B\11\254lV\34)\216\4C\11\254\354T\324\61"
|
||||
")\0D\10\254lV\64G\2E\10\254l\206\36z\4F\10\254l\206^\71\3G\11\254\354TN"
|
||||
"\63)\0H\10\254l\242\34S\6I\6\251T\206\0J\10\254\354k\231\24\0K\11\254l\242J\62"
|
||||
"\225\1L\7\254lr{\4M\11\255t\362ZI\353\0N\11\255t\362TI\356\0O\10\254\354T"
|
||||
"\64\223\2P\11\254lV\34)g\0Q\10\254\354T\264b\12R\10\254lV\34\251\31S\11\254\354"
|
||||
"FF\32\215\4T\7\253dVl\1U\10\254l\242\63)\0V\11\255t\262Ne\312\21W\12\255"
|
||||
"t\262J*\251.\0X\11\254l\242L*\312\0Y\12\255tr\252\63\312(\2Z\7\253df*"
|
||||
"\7p\10\255\364V\266\323\2q\7\255\364\216\257\5r\10\253d\242\32*\2t\6\255t\376#w\11"
|
||||
"\255\364V\245FN\13x\6\233dR\7\0\0\0\4\377\377\0";
|
||||
|
||||
const PROGMEM uint8_t LARGE_FONT[916] U8G2_FONT_SECTION("stk-l") =
|
||||
"#\0\4\4\4\5\2\1\6\17\30\1\0\27\0\0\0\1\77\0\0\3w%'\17\37\313\330R#&"
|
||||
"\32!F\14\211I\310\24!\65\204(MF\21)Cd\304\10\62b\14\215\60Vb\334\20\0/\14"
|
||||
"\272\336\336d\244\350\263q\343\0\60\37|\377\216!%*\10\35\263\253ChD\30\21bB\14\242S"
|
||||
"\306lv\210\204\22Ef\0\61\24z\337\322\60R\205\314\234\31\61F\310\270\371\177\224\42\3\62\33|"
|
||||
"\377\216)\64*\10\35\63\66r\206\304\314`c\252\34\301\221\263|\360\300\0\63\34|\377\216)\64*"
|
||||
"\10\35\63\66r \71\332YIr\226\306\16\221P\203\312\14\0\64 |\377\226\220AC\306\20\31B"
|
||||
"f\310\240\21\204F\214\32\61j\304(cv\366\200\305\312\371\0\65\32|\377\206\212-F\316\27\204\224"
|
||||
"\254\30\65t\344,\215\35\42\241\6\225\31\0\66\33}\17\317\251\64+\206\235\63:/\314,aA\352"
|
||||
"\234\335\235\42\261&\325\31\0\67\23|\377\302\212\7)\347Crt\70\345\300\221\363\16\0\70 |\377"
|
||||
"\216)\64*\10\35\263\354\20\11\42d\20\235BC\204\4\241cvv\210\204\32Tf\0\71\32|\377"
|
||||
"\216)\64*\10\35\263\263C$\226\250I\71_\14\42\241\6\225\31\0A\26}\17S\271Si(\31"
|
||||
"\65d\324\210q\366\356\301w\366\273\1B$}\17C\42\65KF\221\30\66b\330\210a#\206\215\30"
|
||||
"Eb\311&\243H\14;g\317\36\204`\261\4\0C\27}\17\317\251\64K\10!\63:\377\247\304F"
|
||||
"\20\42\261F\21\22\0D\33}\17C\42\65KF\15\31\66b\330\210q\366\77;\66b\24\211%j"
|
||||
"\22\1E\21|\377\302\7)\347%\42\214F\316/\37<\60F\20|\377\302\7)\347\313\64\331\214\234"
|
||||
"\177\11\0G\31\216\37\17*\65L\206\35\264v>\322\241\15\217\221 \65\204\215\262\63\0H\17|\377"
|
||||
"\302\60\373g\17\36\60\263\177\66\0I\7so\302\37$J\22|\377\346\374\377\322\230\261C\210H\250"
|
||||
"Ae\6\0K\42|\377\302\60S\247F\14\42\61h\310\30\42c&!\63\202\320\251\64JV\14\42"
|
||||
"\61\352\230\375l\0L\15{\357\302\300\371\377\37>x\60\0M$}\17\203\310r\346N\245Q\263\202"
|
||||
"E\12)L\224\60Q\302\310\20#C\214\14\61\23\306L\30s\366\335\0N#}\17\203@s\346\216"
|
||||
"\35C\205*Q\42\23cL\214\61\62\304\310\20\63#\314\214\60\224\25f\327\231\33O\26}\17\317\251"
|
||||
"\64KF\215\30g\377\337\215\30\65dM\252\63\0P\26|\377B\32%+F\35\263W\207H\254H"
|
||||
"\203h\344\374%\0Q\31}\17S\261\64KF\215\30g\377oF\230\31q\246\210\42E%F\0R"
|
||||
"\61\216\37\203\242\65L\206\221\30\67b\334\210q#\306\215\30\67b\30\211QD\230(J\65d\330\230"
|
||||
"Qc\10\315j\314(\42\303H\214\33\61\356\340\0S!\216\37\317\261DKH\221\30\67b\334\210\261"
|
||||
"c)M\246Ji\331\331\32\64\207\212D\223Uh\0T\15}\17\303\7\251\206\316\377\377\12\0U\21"
|
||||
"|\377\302\60\373\377\317F\14\32\242\6\225\31\0X)~\37\303@\203\307H\14\33B\210\14\21RC"
|
||||
"\206\241\63h\222(I\203\346\220\15\31E\204\14!\42\303F\20;h\341\0x\24\312\336\302 CG"
|
||||
"H\240\61E\312\14\222)\6Y\64\0\0\0\0\4\377\377\0";
|
||||
|
||||
#define play_icon_width 14
|
||||
#define play_icon_height 14
|
||||
static const unsigned char play_icon[] = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x7C, 0x00, 0xFC, 0x00, 0xFC, 0x03,
|
||||
0xFC, 0x0F, 0xFC, 0x0F, 0xFC, 0x03, 0xFC, 0x00, 0x7C, 0x00, 0x3C, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00};
|
||||
static const unsigned char pause_icon[] = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x38, 0x0E, 0x38, 0x0E, 0x38, 0x0E, 0x38, 0x0E,
|
||||
0x38, 0x0E, 0x38, 0x0E, 0x38, 0x0E, 0x38, 0x0E, 0x38, 0x0E, 0x38, 0x0E,
|
||||
0x38, 0x0E, 0x00, 0x00};
|
||||
|
||||
// Constants for screen layout and fonts
|
||||
constexpr int SCREEN_CENTER_X = 32;
|
||||
constexpr int MAIN_TEXT_Y = 26;
|
||||
constexpr int SUB_TEXT_Y = 40;
|
||||
constexpr int VISIBLE_MENU_ITEMS = 3;
|
||||
constexpr int MENU_ITEM_HEIGHT = 14;
|
||||
constexpr int MENU_BOX_PADDING = 4;
|
||||
constexpr int MENU_BOX_WIDTH = 64;
|
||||
constexpr int CHANNEL_BOXES_Y = 50;
|
||||
constexpr int CHANNEL_BOX_WIDTH = 18;
|
||||
constexpr int CHANNEL_BOX_HEIGHT = 14;
|
||||
|
||||
// Helper function to draw centered text
|
||||
void drawCenteredText(const char* text, int y, const uint8_t* font) {
|
||||
gravity.display.setFont(font);
|
||||
int textWidth = gravity.display.getUTF8Width(text);
|
||||
gravity.display.drawStr(SCREEN_CENTER_X - (textWidth / 2), y, text);
|
||||
}
|
||||
|
||||
// Helper function to draw right-aligned text
|
||||
void drawRightAlignedText(const char* text, int y) {
|
||||
int textWidth = gravity.display.getUTF8Width(text);
|
||||
int drawX = (SCREEN_WIDTH - textWidth) - MENU_BOX_PADDING;
|
||||
gravity.display.drawStr(drawX, y, text);
|
||||
}
|
||||
|
||||
void drawSelectHero() {
|
||||
gravity.display.setDrawColor(1);
|
||||
const int tickSize = 3;
|
||||
const int heroWidth = SCREEN_WIDTH/2;
|
||||
const int heroHeight = 49;
|
||||
gravity.display.drawLine(0, 0, tickSize, 0);
|
||||
gravity.display.drawLine(0, 0, 0, tickSize);
|
||||
gravity.display.drawLine(heroWidth, 0, heroWidth-tickSize, 0);
|
||||
gravity.display.drawLine(heroWidth, 0, heroWidth, tickSize);
|
||||
gravity.display.drawLine(heroWidth, heroHeight, heroWidth, heroHeight-tickSize);
|
||||
gravity.display.drawLine(heroWidth, heroHeight, heroWidth-tickSize, heroHeight);
|
||||
gravity.display.drawLine(0, heroHeight, tickSize, heroHeight);
|
||||
gravity.display.drawLine(0, heroHeight, 0, heroHeight-tickSize);
|
||||
gravity.display.setDrawColor(2);
|
||||
}
|
||||
|
||||
void drawMenuItems(const char* menu_items[], int menu_size) {
|
||||
// Draw menu items
|
||||
gravity.display.setFont(TEXT_FONT);
|
||||
|
||||
// Draw selected menu item box
|
||||
int selectedBoxY = 0;
|
||||
if (menu_size >= VISIBLE_MENU_ITEMS && app.selected_param == menu_size - 1) {
|
||||
selectedBoxY = MENU_ITEM_HEIGHT * min(2, app.selected_param);
|
||||
} else if (app.selected_param > 0) {
|
||||
selectedBoxY = MENU_ITEM_HEIGHT;
|
||||
}
|
||||
|
||||
int boxX = MENU_BOX_WIDTH + 1;
|
||||
int boxY = selectedBoxY + 2;
|
||||
int boxWidth = MENU_BOX_WIDTH - 1;
|
||||
int boxHeight = MENU_ITEM_HEIGHT + 1;
|
||||
|
||||
if (app.editing_param) {
|
||||
gravity.display.drawBox(boxX, boxY, boxWidth, boxHeight);
|
||||
drawSelectHero();
|
||||
} else {
|
||||
gravity.display.drawFrame(boxX, boxY, boxWidth, boxHeight);
|
||||
}
|
||||
|
||||
// Draw the visible menu items
|
||||
int start_index = 0;
|
||||
if (menu_size >= VISIBLE_MENU_ITEMS && app.selected_param == menu_size - 1) {
|
||||
start_index = menu_size - VISIBLE_MENU_ITEMS;
|
||||
} else if (app.selected_param > 0) {
|
||||
start_index = app.selected_param - 1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < min(menu_size, VISIBLE_MENU_ITEMS); ++i) {
|
||||
int idx = start_index + i;
|
||||
drawRightAlignedText(menu_items[idx], MENU_ITEM_HEIGHT * (i + 1) - 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Main display functions
|
||||
|
||||
void DisplayMainPage() {
|
||||
gravity.display.setFontMode(1);
|
||||
gravity.display.setDrawColor(2);
|
||||
gravity.display.setFont(TEXT_FONT);
|
||||
|
||||
// Display selected editable value
|
||||
char mainText[8];
|
||||
const char* subText;
|
||||
|
||||
switch (app.selected_param) {
|
||||
case PARAM_MAIN_TEMPO:
|
||||
// Serial MIDI is too unstable to display bpm in real time.
|
||||
if (app.selected_source == Clock::SOURCE_EXTERNAL_MIDI) {
|
||||
sprintf(mainText, "%s", "EXT");
|
||||
} else {
|
||||
sprintf(mainText, "%d", gravity.clock.Tempo());
|
||||
}
|
||||
subText = "BPM";
|
||||
break;
|
||||
case PARAM_MAIN_SOURCE:
|
||||
switch (app.selected_source) {
|
||||
case Clock::SOURCE_INTERNAL:
|
||||
sprintf(mainText, "%s", "INT");
|
||||
subText = "CLOCK";
|
||||
break;
|
||||
case Clock::SOURCE_EXTERNAL_PPQN_24:
|
||||
sprintf(mainText, "%s", "EXT");
|
||||
subText = "24 PPQN";
|
||||
break;
|
||||
case Clock::SOURCE_EXTERNAL_PPQN_4:
|
||||
sprintf(mainText, "%s", "EXT");
|
||||
subText = "4 PPQN";
|
||||
break;
|
||||
case Clock::SOURCE_EXTERNAL_MIDI:
|
||||
sprintf(mainText, "%s", "EXT");
|
||||
subText = "MIDI";
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case PARAM_MAIN_ENCODER_DIR:
|
||||
sprintf(mainText, "%s", "DIR");
|
||||
subText = app.selected_sub_param == 0 ? "DEFAULT" : "REVERSED";
|
||||
break;
|
||||
case PARAM_MAIN_RESET_STATE:
|
||||
sprintf(mainText, "%s", "RST");
|
||||
subText = app.selected_sub_param == 0 ? "RESET ALL" : "BACK";
|
||||
break;
|
||||
}
|
||||
|
||||
drawCenteredText(mainText, MAIN_TEXT_Y, LARGE_FONT);
|
||||
drawCenteredText(subText, SUB_TEXT_Y, TEXT_FONT);
|
||||
|
||||
// Draw Main Page menu items
|
||||
const char* menu_items[PARAM_MAIN_LAST] = {"TEMPO", "SOURCE", "ENCODER DIR", "RESET"};
|
||||
drawMenuItems(menu_items, PARAM_MAIN_LAST);
|
||||
}
|
||||
|
||||
void DisplayChannelPage() {
|
||||
auto& ch = GetSelectedChannel();
|
||||
|
||||
gravity.display.setFontMode(1);
|
||||
gravity.display.setDrawColor(2);
|
||||
|
||||
// Display selected editable value
|
||||
char mainText[5];
|
||||
const char* subText;
|
||||
|
||||
// When editing a param, just show the base value. When not editing show
|
||||
// the value with cv mod.
|
||||
bool withCvMod = !app.editing_param;
|
||||
|
||||
switch (app.selected_param) {
|
||||
case PARAM_CH_MOD: {
|
||||
int mod_value = ch.getClockMod(withCvMod);
|
||||
if (mod_value > 1) {
|
||||
sprintf(mainText, "/%d", mod_value);
|
||||
subText = "DIVIDE";
|
||||
} else {
|
||||
sprintf(mainText, "x%d", abs(mod_value));
|
||||
subText = "MULTIPLY";
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PARAM_CH_PROB:
|
||||
sprintf(mainText, "%d%%", ch.getProbability(withCvMod));
|
||||
subText = "HIT CHANCE";
|
||||
break;
|
||||
case PARAM_CH_DUTY:
|
||||
sprintf(mainText, "%d%%", ch.getDutyCycle(withCvMod));
|
||||
subText = "PULSE WIDTH";
|
||||
break;
|
||||
case PARAM_CH_OFFSET:
|
||||
sprintf(mainText, "%d%%", ch.getOffset(withCvMod));
|
||||
subText = "SHIFT HIT";
|
||||
break;
|
||||
case PARAM_CH_CV_SRC: {
|
||||
switch (ch.getCvSource()) {
|
||||
case CV_NONE:
|
||||
sprintf(mainText, "SRC");
|
||||
subText = "NONE";
|
||||
break;
|
||||
case CV_1:
|
||||
sprintf(mainText, "SRC");
|
||||
subText = "CV 1";
|
||||
break;
|
||||
case CV_2:
|
||||
sprintf(mainText, "SRC");
|
||||
subText = "CV 2";
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PARAM_CH_CV_DEST: {
|
||||
switch (ch.getCvDestination()) {
|
||||
case CV_DEST_NONE:
|
||||
sprintf(mainText, "DEST");
|
||||
subText = "NONE";
|
||||
break;
|
||||
case CV_DEST_MOD:
|
||||
sprintf(mainText, "DEST");
|
||||
subText = "CLOCK MOD";
|
||||
break;
|
||||
case CV_DEST_PROB:
|
||||
sprintf(mainText, "DEST");
|
||||
subText = "PROBABILITY";
|
||||
break;
|
||||
case CV_DEST_DUTY:
|
||||
sprintf(mainText, "DEST");
|
||||
subText = "DUTY CYCLE";
|
||||
break;
|
||||
case CV_DEST_OFFSET:
|
||||
sprintf(mainText, "DEST");
|
||||
subText = "OFFSET";
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
drawCenteredText(mainText, MAIN_TEXT_Y, LARGE_FONT);
|
||||
drawCenteredText(subText, SUB_TEXT_Y, TEXT_FONT);
|
||||
|
||||
// Draw Channel Page menu items
|
||||
const char* menu_items[PARAM_CH_LAST] = {
|
||||
"MOD", "PROBABILITY", "DUTY", "OFFSET", "CV SOURCE", "CV DEST"};
|
||||
drawMenuItems(menu_items, PARAM_CH_LAST);
|
||||
}
|
||||
|
||||
void DisplaySelectedChannel() {
|
||||
int boxX = CHANNEL_BOX_WIDTH;
|
||||
int boxY = CHANNEL_BOXES_Y;
|
||||
int boxWidth = CHANNEL_BOX_WIDTH;
|
||||
int boxHeight = CHANNEL_BOX_HEIGHT;
|
||||
int textOffset = 7; // Half of font width
|
||||
|
||||
// Draw top and right side of frame.
|
||||
gravity.display.drawHLine(1, boxY, SCREEN_WIDTH - 2);
|
||||
gravity.display.drawVLine(SCREEN_WIDTH - 2, boxY, boxHeight);
|
||||
|
||||
for (int i = 0; i < Gravity::OUTPUT_COUNT + 1; i++) {
|
||||
// Draw box frame or filled selected box.
|
||||
gravity.display.setDrawColor(1);
|
||||
(app.selected_channel == i)
|
||||
? gravity.display.drawBox(i * boxWidth, boxY, boxWidth, boxHeight)
|
||||
: gravity.display.drawVLine(i * boxWidth, boxY, boxHeight);
|
||||
|
||||
// Draw clock status icon or each channel number.
|
||||
gravity.display.setDrawColor(2);
|
||||
if (i == 0) {
|
||||
gravity.display.setBitmapMode(1);
|
||||
auto icon = gravity.clock.IsPaused() ? pause_icon : play_icon;
|
||||
gravity.display.drawXBM(2, boxY, play_icon_width, play_icon_height, icon);
|
||||
} else {
|
||||
gravity.display.setFont(TEXT_FONT);
|
||||
gravity.display.setCursor((i * boxWidth) + textOffset, SCREEN_HEIGHT - 3);
|
||||
gravity.display.print(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateDisplay() {
|
||||
app.refresh_screen = false;
|
||||
gravity.display.firstPage();
|
||||
do {
|
||||
if (app.selected_channel == 0) {
|
||||
DisplayMainPage();
|
||||
} else {
|
||||
DisplayChannelPage();
|
||||
}
|
||||
// Global channel select UI.
|
||||
DisplaySelectedChannel();
|
||||
} while (gravity.display.nextPage());
|
||||
}
|
||||
|
||||
#endif // DISPLAY_H
|
||||
Reference in New Issue
Block a user