#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <vector>
// add line numbers for following two comments
/*
SCREEN_WIDTH
SCREEN_HEIGHT
display
cursorX
cursorY
charWidth
charHeight
folderIcon
fileIcon
selectedIndex
path
files
customPrint
customPrintln
basename
listContents
goBackInPath
JOYSTICK_X_PIN
JOYSTICK_Y_PIN
JOYSTICK_BUTTON_PIN
vec
types
setup
yPlusPressed
xPlusPressed
xMinusPressed
yMinusPressed
lastClicked
firstTime
lastPath
currentPath
visibleStartIndex
MAX_VISIBLE_LINES
results
resultTypes
*/
/*
utils.cpp
1
2
menu.cpp
1
2
3
4
5
controls.cpp
1
2
3
*/
// start1 utils.cpp
// OLED display settings
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
// Cursor position tracking
int cursorX = 0;
int cursorY = 0;
// Constants for screen layout
const int charWidth = 6; // Adjust based on font size
const int charHeight = 8; // Adjust based on font size
// end1 utils.cpp
// start1 menu.cpp
const unsigned char folderIcon [] PROGMEM = {
0x00, 0xf0, 0x9c, 0x84, 0x84, 0xfc, 0x00, 0x00
};
// File icon (8x8 pixels)
const unsigned char fileIcon [] PROGMEM = {
0x78, 0x94, 0x8c, 0x84, 0xb4, 0xb4, 0x84, 0x78
};
int selectedIndex = 0;
String path = "/";
String files =
"d Folder 1\n"
" f file 11\n"
" f file 21\n"
" d Folder 11\n"
" f file 12\n"
"d Folder 2\n"
" f file 13\n"
"d Folder 3\n"
"d Folder 4\n"
"d Folder 5\n"
"d Folder 6\n"
"d Folder 7\n"
"d Folder 8\n"
"d Folder 9";
// end1 menu.cpp
// start2 utils.cpp
void customPrint(const char* text, bool invert) {
int textWidth = strlen(text) * charWidth;
if (invert) {
// Draw inversion background for the text
display.fillRect(cursorX, cursorY, textWidth, charHeight, SSD1306_WHITE);
display.setTextColor(SSD1306_BLACK, SSD1306_WHITE); // Inverted text
} else {
display.setTextColor(SSD1306_WHITE, SSD1306_BLACK); // Normal text
}
// Print the text
display.setCursor(cursorX, cursorY);
display.print(text);
// Move cursor to the right after the printed text
cursorX += textWidth;
// Reset text color
display.setTextColor(SSD1306_WHITE, SSD1306_BLACK);
}
void customPrintln(const char* text, bool invert) {
int textWidth = strlen(text) * charWidth;
if (invert) {
// Draw inversion background for the entire line
display.fillRect(0, cursorY, SCREEN_WIDTH, charHeight, SSD1306_WHITE);
display.setTextColor(SSD1306_BLACK, SSD1306_WHITE); // Inverted text
} else {
display.setTextColor(SSD1306_WHITE, SSD1306_BLACK); // Normal text
}
// Print the text
display.setCursor(cursorX, cursorY);
display.print(text);
// Move to the next line
cursorX = 0;
cursorY += charHeight;
}
// end2 utils.cpp
// start2 menu.cpp
String basename(String path, bool removeExt = false) {
int lastSlash = path.lastIndexOf('/');
String name = (lastSlash != -1) ? path.substring(lastSlash + 1) : path;
if (removeExt) {
int dotIndex = name.lastIndexOf('.');
if (dotIndex != -1) {
name = name.substring(0, dotIndex);
}
}
name.replace("/", "");
return name;
}
std::vector<String> listContents(String& files) {
int currentDepth = 0;
int lastDepth = 0;
String currentPath = "/";
String itemName = "";
String itemPath = "";
currentPath.reserve(100); // Reserve memory for currentPath to avoid resizing
itemName.reserve(50);
itemPath.reserve(50);
std::vector<String> vec;
for (int i = 0; i < files.length(); i++) {
int lineEnd = files.indexOf('\n', i);
if (lineEnd == -1) lineEnd = files.length();
String line = files.substring(i, lineEnd);
i = lineEnd;
currentDepth = 0;
while (line[currentDepth] == ' ') currentDepth++;
if (currentDepth == 0) {
currentPath = "/";
lastDepth = 0;
}
while (currentDepth < lastDepth) {
Serial.println(currentPath);
currentPath = goBackInPath(currentPath); // Move up a level
Serial.println(currentPath);
lastDepth--;
}
lastDepth = currentDepth;
bool isFolder = line[currentDepth] == 'd';
itemName = line.substring(currentDepth + 2);
itemPath = (isFolder ? "d" : "f") + currentPath + itemName; // Avoid concatenating too often
vec.push_back(itemPath);
if (isFolder) {
currentDepth++;
currentPath += itemName + "/"; // Avoid frequent resizing
}
}
itemName.remove(0);
itemPath.remove(0);
return vec;
}
String goBackInPath(String path) {
if (path == "/") {
return "/";
}
int lastSlashIndex = path.lastIndexOf('/', path.length() - 2); // Find previous folder
return path.substring(0, lastSlashIndex + 1); // Keep trailing '/'
}
#define JOYSTICK_X_PIN 34 // X-axis (left/right) - digital pin (not used yet)
#define JOYSTICK_Y_PIN 35 // Y-axis (up/down) - digital pin
#define JOYSTICK_BUTTON_PIN 32
std::vector<String> vec = listContents(files);
std::vector<bool> types(vec.size(), false);
// end2 menu.cpp
void setup() {
Serial.begin(115200);
// Initialize the OLED display
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println(F("SSD1306 allocation failed"));
for (;;);
}
display.display();
pinMode(JOYSTICK_BUTTON_PIN, INPUT_PULLUP);
display.clearDisplay();
display.display();
// start3 menu.cpp
for (int i = 0; i < vec.size(); i++) {
types[i] = vec[i].startsWith("d");
Serial.println(vec[i]);
}
// end3 menu.cpp
}
// start controls.cpp
bool yPlusPressed = false;
bool yMinusPressed = false;
bool xPlusPressed = false;
bool xMinusPressed = false;
bool lastClicked = false;
// end controls.cpp
// start4 menu.cpp
bool firstTime = true;
String lastPath = "/";
String currentPath = "/";
int visibleStartIndex = 0; // Start index for visible lines
#define MAX_VISIBLE_LINES 8 // Number of lines visible on the screen
std::vector<String> results = {};
std::vector<bool> resultTypes = {};
// end4 menu.cpp
void loop() {
// start controls.cpp
int yValue = analogRead(JOYSTICK_Y_PIN); // Read Y-axis (up/down)
int xValue = analogRead(JOYSTICK_X_PIN);
int clicked = digitalRead(JOYSTICK_BUTTON_PIN) == LOW;
// end controls.cpp
// start5 menu.cpp
if (vec.size() > 0) {
// Update cursor position
cursorY = 0;
int moved = 0;
while ((selectedIndex * charHeight) >= moved + SCREEN_HEIGHT) {
moved += charHeight; // Move the cursor up
cursorY -= charHeight;
}
// Scroll down if needed
while ((selectedIndex * charHeight) <= moved - ((MAX_VISIBLE_LINES - 1) * charHeight)) {
moved -= charHeight; // Move the cursor down
cursorY += charHeight;
}
// Display updated items only if necessary
if ((clicked != lastClicked && clicked == false) || yPlusPressed || yMinusPressed || firstTime || path != lastPath) {
display.clearDisplay();
int currentIndex = 0;
results.clear();
resultTypes.clear();
for (int i = 0; i < vec.size(); i++) {
int lastIndexOfSlash = -1;
lastIndexOfSlash = vec[i].lastIndexOf('/');
currentPath = vec[i].substring(1, lastIndexOfSlash + 1);
if (path != "/" && i == 0) {
customPrintln("..", currentIndex == selectedIndex);
results.push_back("..");
resultTypes.push_back(true);
currentIndex++;
continue;
}
if (currentPath == path) {
cursorX = charWidth * 2;
customPrintln(basename(vec[i]).c_str(), currentIndex == selectedIndex);
cursorX = 0;
cursorY -= charHeight;
display.drawBitmap(cursorX, cursorY, types[i] ? folderIcon : fileIcon, 8, 8, currentIndex == selectedIndex ? BLACK : WHITE);
cursorY += charHeight;
results.push_back(basename(vec[i]));
resultTypes.push_back(types[i]);
currentIndex++;
}
}
display.display();
firstTime = false;
lastPath = path;
}
}
int resultCount = results.size();
firstTime = true;
// Handle wrap-around logic for selection index
if (selectedIndex >= resultCount) {
selectedIndex = 0; // Wrap around to the first element
} else if (selectedIndex < 0) {
selectedIndex = resultCount - 1; // Wrap around to the last element
} else {
firstTime = false;
}
// end5 menu.cpp
// start controls.cpp
// Joystick Y-axis movement (up/down)
if (yValue < 1000 && !yPlusPressed) { // Joystick moved down
selectedIndex++;
yPlusPressed = true;
} else if (yValue >= 1000 && yValue <= 3000) { // Reset flag in neutral position
yPlusPressed = false;
}
if (yValue > 3000 && !yMinusPressed) { // Joystick moved up
selectedIndex--;
yMinusPressed = true;
} else if (yValue >= 1000 && yValue <= 3000) { // Reset flag in neutral position
yMinusPressed = false;
}
// X-Movement (left/right)
if (xValue < 1000 && !xPlusPressed) { // Joystick moved right
xPlusPressed = true;
} else if (xValue >= 1000 && xValue <= 3000) { // Reset flag in neutral position
xPlusPressed = false;
}
if (xValue > 3000 && !xMinusPressed) { // Joystick moved left
xMinusPressed = true;
} else if (xValue >= 1000 && xValue <= 3000) { // Reset flag in neutral position
xMinusPressed = false;
}
// Handle click action
if (clicked != lastClicked && clicked == false) {
if (results[selectedIndex] == "..") {
path = goBackInPath(path);
} else if (resultTypes[selectedIndex] == true) {
path = path + results[selectedIndex] + "/";
selectedIndex = 0;
}
}
lastClicked = clicked;
// end controls.cpp
delay(50);
}