/*
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
#define TFT_CS 10
#define TFT_DC 9
#define TFT_RST 8
Adafruit_ILI9341 tft(TFT_CS, TFT_DC, TFT_RST);
#define BT_UP 2
#define BT_DOWN 3
#define BT_OK 4
#define BT_BACK 5
const char* drinkNames[] = { "Red TEA", "Green TEA", "Wulong TEA", "Milk TEA" };
const uint8_t prices[] = { 30, 35, 40, 45 };
const uint8_t N_DRINKS = sizeof(prices);
const int16_t TFT_W = 320, TFT_H = 240;
const uint8_t TITLE_SIZE = 3;
const uint8_t ITEM_SIZE = 2;
const uint8_t ROW_H = 30; // vertical gap
enum Page { SPLASH, MENU, PRICE } page = SPLASH;
uint8_t curIdx = 0, prevIdx = 0;
unsigned long splashStart, lastPoll;
const unsigned long debounceMs = 120;
void setup() {
pinMode(BT_UP, INPUT_PULLUP);
pinMode(BT_DOWN, INPUT_PULLUP);
pinMode(BT_OK, INPUT_PULLUP);
pinMode(BT_BACK, INPUT_PULLUP);
tft.begin();
tft.setRotation(1); // 320×240 landscape
splashStart = millis();
}
void loop() {
switch (page) {
case SPLASH: splashScreen(); break;
case MENU: handleMenu(); break;
case PRICE: handlePrice(); break;
}
}
void splashScreen() {
static bool on = false;
static unsigned long lastBlink = 0;
if (millis() - lastBlink > 500) {
lastBlink = millis();
on = !on;
tft.fillScreen(ILI9341_BLACK);
if (on) {
tft.setTextSize(4);
tft.setTextColor(ILI9341_CYAN, ILI9341_BLACK);
centerPrint("CTAS", TFT_H / 2 - 12);
}
}
if (millis() - splashStart >= 3000) {
page = MENU;
drawStaticMenu();
drawRow(curIdx, true); // highlight first item
}
}
void handleMenu() {
if (millis() - lastPoll < debounceMs) return;
lastPoll = millis();
if (!digitalRead(BT_UP)) {
prevIdx = curIdx;
curIdx = (curIdx == 0) ? N_DRINKS - 1 : curIdx - 1;
drawRow(prevIdx, false);
drawRow(curIdx, true);
} else if (!digitalRead(BT_DOWN)) {
prevIdx = curIdx;
curIdx = (curIdx + 1) % N_DRINKS;
drawRow(prevIdx, false);
drawRow(curIdx, true);
} else if (!digitalRead(BT_OK)) {
page = PRICE;
drawPrice(curIdx);
}
}
void handlePrice() {
if (millis() - lastPoll < debounceMs) return;
lastPoll = millis();
if (!digitalRead(BT_BACK)) {
page = MENU;
drawStaticMenu();
drawRow(curIdx, true);
}
}
int rowY(uint8_t idx) {
int listTop = (TFT_H - N_DRINKS * ROW_H) / 2; // vertical center
return listTop + idx * ROW_H;
}
void centerPrint(const char* txt, int y) {
int16_t x1, y1; uint16_t w, h;
tft.getTextBounds((char*)txt, 0, 0, &x1, &y1, &w, &h);
int x = (TFT_W - w) / 2;
tft.setCursor(x, y);
tft.print(txt);
}
void drawStaticMenu() {
tft.fillScreen(ILI9341_BLACK);
tft.setTextSize(TITLE_SIZE);
tft.setTextColor(ILI9341_YELLOW, ILI9341_BLACK);
centerPrint("Drink Menu", (TFT_H - N_DRINKS*ROW_H)/2 - 40);
tft.setTextSize(ITEM_SIZE);
for (uint8_t i = 0; i < N_DRINKS; i++)
drawRow(i, i == curIdx); // highlight current for first load
}
void drawRow(uint8_t idx, bool highlight) {
tft.setTextSize(ITEM_SIZE);
int16_t x1, y1; uint16_t w, h;
tft.getTextBounds((char*)drinkNames[idx], 0, 0, &x1, &y1, &w, &h);
const uint8_t pad = 6;
int x = (TFT_W - w) / 2;
int y = rowY(idx);
uint16_t bg = highlight ? ILI9341_YELLOW : ILI9341_BLACK;
uint16_t fg = highlight ? ILI9341_BLACK : ILI9341_WHITE;
tft.fillRect(x - pad, y - 2, w + pad*2, h + 4, bg);
tft.setTextColor(fg, bg);
tft.setCursor(x, y);
tft.print(drinkNames[idx]);
}
void drawPrice(uint8_t idx) {
tft.fillScreen(ILI9341_BLACK);
tft.setTextSize(3);
tft.setTextColor(ILI9341_CYAN, ILI9341_BLACK);
centerPrint(drinkNames[idx], TFT_H/2 - 40);
char buf[12];
sprintf(buf, "$%d", prices[idx]);
tft.setTextSize(4);
tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK);
centerPrint(buf, TFT_H/2 + 10);
tft.setTextSize(1);
tft.setTextColor(ILI9341_LIGHTGREY, ILI9341_BLACK);
centerPrint("(Press BACK)", TFT_H - 20);
}
*/
/* =====================================================
COLOR Menu on ILI9341 TFT ‒ 4‑Button Navigation
Requires: Adafruit_GFX + Adafruit_ILI9341 libraries
===================================================== */
/* =====================================================
COLOR Menu (Arrow Cursor) ‒ ILI9341 + 4 Buttons
Libraries: Adafruit_GFX, Adafruit_ILI9341
===================================================== */
/* =====================================================
COLOR Menu - Triangle Cursor (No residual artifacts)
===================================================== */
/*
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
// ---- TFT SPI pins ----
#define TFT_CS 10
#define TFT_DC 9
#define TFT_RST 8
Adafruit_ILI9341 tft(TFT_CS, TFT_DC, TFT_RST);
// ---- Button pins ----
#define BT_UP 2
#define BT_DOWN 3
#define BT_OK 4
#define BT_BACK 5
// ---- Color table ----
const char* colorNames[] = { "Red", "Green", "Blue", "Black", "White" };
const uint16_t colorVals[] = { ILI9341_RED, ILI9341_GREEN,
ILI9341_BLUE, ILI9341_BLACK,
ILI9341_WHITE };
const uint8_t N_COLORS = sizeof(colorVals) / sizeof(colorVals[0]);
// ---- Layout ----
const int16_t TFT_W = 320, TFT_H = 240;
const uint8_t TITLE_SIZE = 3;
const uint8_t ITEM_SIZE = 2;
const uint8_t ROW_H = 30;
const int16_t TRI_X = 40; // 三角形左側 x 座標
const uint8_t TRI_W = 12; // 三角形寬
// ---- State ----
enum Page { SPLASH, MENU, SHOW_COLOR } page = SPLASH;
uint8_t curIdx = 0, prevIdx = 0;
// ---- Timing ----
unsigned long splashStart, lastPoll;
const unsigned long debounceMs = 120;
// -------------------- Setup --------------------
void setup()
{
pinMode(BT_UP, INPUT_PULLUP);
pinMode(BT_DOWN, INPUT_PULLUP);
pinMode(BT_OK, INPUT_PULLUP);
pinMode(BT_BACK, INPUT_PULLUP);
tft.begin();
tft.setRotation(3);
splashStart = millis();
}
// -------------------- Loop --------------------
void loop() {
switch (page) {
case SPLASH: splash(); break;
case MENU: menuLoop(); break;
case SHOW_COLOR: colorLoop(); break;
}
}
// ========== Splash Screen ==========
void splash() {
static bool on = false;
static unsigned long lastBlink = 0;
if (millis() - lastBlink > 500) {
lastBlink = millis();
on = !on;
tft.fillScreen(ILI9341_BLACK);
if (on) {
tft.setTextSize(4);
tft.setTextColor(ILI9341_CYAN, ILI9341_BLACK);
centerPrint("CTAS", TFT_H / 2 - 12);
}
}
if (millis() - splashStart >= 3000) {
page = MENU;
drawMenuBase();
drawRow(curIdx, true);
}
}
// ========== Menu ==========
void menuLoop() {
if (millis() - lastPoll < debounceMs) return;
lastPoll = millis();
if (!digitalRead(BT_UP)) {
prevIdx = curIdx;
curIdx = (curIdx == 0) ? N_COLORS - 1 : curIdx - 1;
drawRow(prevIdx, false);
drawRow(curIdx, true);
} else if (!digitalRead(BT_DOWN)) {
prevIdx = curIdx;
curIdx = (curIdx + 1) % N_COLORS;
drawRow(prevIdx, false);
drawRow(curIdx, true);
} else if (!digitalRead(BT_OK)) {
page = SHOW_COLOR;
showColor(curIdx);
}
}
// ========== Color Page ==========
void colorLoop() {
if (millis() - lastPoll < debounceMs) return;
lastPoll = millis();
if (!digitalRead(BT_BACK)) {
page = MENU;
drawMenuBase();
drawRow(curIdx, true);
}
}
// ---------- Helpers ----------
int rowY(uint8_t idx) {
int listTop = (TFT_H - N_COLORS * ROW_H) / 2;
return listTop + idx * ROW_H;
}
void centerPrint(const char* txt, int y) {
int16_t x1, y1; uint16_t w, h;
tft.getTextBounds((char*)txt, 0, 0, &x1, &y1, &w, &h);
int x = (TFT_W - w) / 2;
tft.setCursor(x, y);
tft.print(txt);
}
// Draw static parts (title + list without cursor)
void drawMenuBase() {
tft.fillScreen(ILI9341_BLACK);
tft.setTextSize(TITLE_SIZE);
tft.setTextColor(ILI9341_YELLOW, ILI9341_BLACK);
centerPrint("COLOR Menu", (TFT_H - N_COLORS*ROW_H)/2 - 40);
tft.setTextSize(ITEM_SIZE);
for (uint8_t i = 0; i < N_COLORS; i++)
drawRow(i, i == curIdx); // 只有 current 會立即畫三角
}
// Draw / erase a single row
void drawRow(uint8_t idx, bool drawTri) {
tft.setTextSize(ITEM_SIZE);
// 計算文字置中位置
int16_t x1, y1; uint16_t w, h;
tft.getTextBounds((char*)colorNames[idx], 0, 0, &x1, &y1, &w, &h);
int textX = (TFT_W - w) / 2;
int y = rowY(idx);
// (1) 清除舊三角區域(保留黑底)
tft.fillRect(TRI_X, y - 2, TRI_W, h + 4, ILI9341_BLACK);
// (2) 輸出白字項目名稱
tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK);
tft.setCursor(textX, y);
tft.print(colorNames[idx]);
// (3) 若需三角游標,畫實心右向三角形
if (drawTri) {
int triYmid = y + h / 2;
tft.fillTriangle(
TRI_X, triYmid - 6, // 上端點
TRI_X, triYmid + 6, // 下端點
TRI_X + TRI_W, triYmid, // 右端點 (箭頭尖)
ILI9341_YELLOW);
}
}
// Show full‑screen color
void showColor(uint8_t idx) {
uint16_t bg = colorVals[idx];
tft.fillScreen(bg);
uint16_t fg = (bg == ILI9341_WHITE) ? ILI9341_BLACK : ILI9341_WHITE;
tft.setTextColor(fg, bg);
tft.setTextSize(4);
centerPrint(colorNames[idx], TFT_H / 2 - 12);
tft.setTextSize(1);
centerPrint("(Press BACK)", TFT_H - 20);
}
*/
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
#include <Adafruit_NeoPixel.h>
// ---------- TFT Pins (ILI9341 SPI) ----------
#define TFT_CS 10
#define TFT_DC 9
#define TFT_RST 8
Adafruit_ILI9341 tft(TFT_CS, TFT_DC, TFT_RST);
// ---------- NeoPixel ----------
#define LED_PIN 6
#define NUM_LEDS 8
Adafruit_NeoPixel strip(NUM_LEDS, LED_PIN, NEO_GRB + NEO_KHZ800);
// ---------- Buttons (INPUT_PULLUP) ----------
#define BTN_UP 2
#define BTN_DOWN 3
#define BTN_OK 4
#define BTN_BACK 5
// 改良按鍵:邊沿偵測 + 長按自動重複
struct Btn {
uint8_t pin; bool last; unsigned long lastTs; unsigned long repeatTs;
Btn(): pin(0), last(HIGH), lastTs(0), repeatTs(0) {}
Btn(uint8_t p): pin(p), last(HIGH), lastTs(0), repeatTs(0) {}
};
Btn bUp(BTN_UP), bDown(BTN_DOWN), bOk(BTN_OK), bBack(BTN_BACK);
const unsigned long DEBOUNCE_MS = 60;
const unsigned long REPEAT_DELAY = 400;
const unsigned long REPEAT_RATE = 140;
bool pressed(Btn &b){
bool now = digitalRead(b.pin); unsigned long t=millis(); bool fire=false;
if (b.last==HIGH && now==LOW && (t-b.lastTs)>DEBOUNCE_MS){ fire=true; b.repeatTs=t+REPEAT_DELAY; b.lastTs=t; }
if (now==LOW && b.last==LOW && b.repeatTs && t>=b.repeatTs){ fire=true; b.repeatTs+=REPEAT_RATE; }
if (b.last==LOW && now==HIGH){ b.repeatTs=0; b.lastTs=t; }
b.last=now; return fire;
}
// ---------- App State ----------
enum Screen { SCR_MENU, SCR_SOLID_SUB, SCR_BRIGHTNESS, SCR_SPEED, SCR_ABOUT };
enum Mode { MODE_OFF, MODE_SOLID, MODE_CHASE, MODE_RAINBOW };
Screen screen = SCR_MENU;
Mode mode = MODE_OFF;
// ---------- Menu model ----------
const char* menuItems[] = {
"Solid Color", "Purple Chase", "Rainbow Cycle", "Brightness", "Speed", "About"
};
const uint8_t MENU_SIZE = sizeof(menuItems)/sizeof(menuItems[0]);
int menuIndex = 0, lastMenuIndex = 0;
struct ColorItem { const char* name; uint32_t color; };
ColorItem solidItems[] = {
{"Red", Adafruit_NeoPixel::Color(255, 0, 0)},
{"Green", Adafruit_NeoPixel::Color( 0, 255, 0)},
{"Blue", Adafruit_NeoPixel::Color( 0, 0, 255)},
{"Yellow", Adafruit_NeoPixel::Color(255, 255, 0)},
{"Cyan", Adafruit_NeoPixel::Color( 0, 255, 255)},
{"Magenta", Adafruit_NeoPixel::Color(255, 0, 255)},
{"White", Adafruit_NeoPixel::Color(255, 255, 255)},
{"Off", Adafruit_NeoPixel::Color( 0, 0, 0)}
};
const uint8_t SOLID_SIZE = sizeof(solidItems)/sizeof(solidItems[0]);
int solidIndex = 0, lastSolidIndex = 0;
// ---------- Parameters ----------
uint8_t brightness = 220; // 更亮
uint16_t speedMs = 100;
const uint16_t SPEED_MIN = 30, SPEED_MAX = 300, SPEED_STEP = 10;
// ---------- Animation state ----------
unsigned long animTs = 0;
uint16_t rainbowHue = 0;
int chasePos = 0;
// ---------- UI layout(直立) ----------
const int TITLE_H = 24;
const int LINE_H = 26;
int firstLineY(uint8_t itemCount){
int contentH = itemCount * LINE_H;
int avail = tft.height() - TITLE_H;
int top = TITLE_H + (avail - contentH)/2;
if (top < TITLE_H) top = TITLE_H;
return top;
}
int centerX(const char* text, int textSize=2){
int16_t x1,y1; uint16_t w,h;
tft.setTextSize(textSize);
tft.getTextBounds(text, 0, 0, &x1, &y1, &w, &h);
int x = (tft.width() - w)/2;
return (x < 0) ? 0 : x;
}
void drawHeaderStatic(const char* title){
tft.fillScreen(ILI9341_BLACK);
tft.setTextColor(ILI9341_WHITE);
tft.setTextSize(2);
tft.setCursor(centerX(title,2), 4);
tft.print(title);
tft.drawFastHLine(0, TITLE_H, tft.width(), ILI9341_WHITE);
}
void drawRowCentered(int rowY, const char* text, bool selected){
uint16_t bg = selected ? ILI9341_BLUE : ILI9341_BLACK;
uint16_t fg = selected ? ILI9341_WHITE: ILI9341_YELLOW;
tft.fillRect(0, rowY-2, tft.width(), LINE_H, bg);
tft.setTextColor(fg);
tft.setTextSize(2);
tft.setCursor(centerX(text,2), rowY);
tft.print(text);
}
void drawMenuStatic(){
drawHeaderStatic("RGB MENU");
int y = firstLineY(MENU_SIZE);
for (int i=0;i<MENU_SIZE;i++){
drawRowCentered(y, menuItems[i], i==menuIndex);
y += LINE_H;
}
tft.drawFastHLine(0, tft.height()-18, tft.width(), ILI9341_WHITE);
tft.setTextSize(1); tft.setTextColor(ILI9341_WHITE);
tft.setCursor(6, tft.height()-14);
tft.print("Bri:"); tft.print(brightness);
tft.print(" Spd:"); tft.print(speedMs);
}
void updateMenuSelection(int prev, int curr){
if (prev==curr) return;
int base = firstLineY(MENU_SIZE);
drawRowCentered(base + prev*LINE_H, menuItems[prev], false);
drawRowCentered(base + curr*LINE_H, menuItems[curr], true);
}
// ---- Solid Color:含彩色小方塊 ----
void drawRowSolidCentered(int rowY, const char* text, uint32_t rgb, bool selected){
uint16_t bg = selected ? ILI9341_BLUE : ILI9341_BLACK;
uint16_t fg = selected ? ILI9341_WHITE: ILI9341_YELLOW;
tft.fillRect(0, rowY-2, tft.width(), LINE_H, bg);
uint8_t r = (rgb >> 16) & 0xFF;
uint8_t g = (rgb >> 8) & 0xFF;
uint8_t b = (rgb ) & 0xFF;
uint16_t c565 = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
int sw = 22, sh = LINE_H-6;
int swX = 10, swY = rowY - (sh/2) + 6;
tft.fillRect(swX, swY, sw, sh, c565);
tft.drawRect(swX-1, swY-1, sw+2, sh+2, selected ? ILI9341_WHITE : ILI9341_DARKGREY);
tft.setTextColor(fg);
tft.setTextSize(2);
int16_t x1,y1; uint16_t w,h;
tft.getTextBounds(text, 0, 0, &x1, &y1, &w, &h);
int textX = max(swX + sw + 12, (tft.width() - (int)w)/2);
tft.setCursor(textX, rowY);
tft.print(text);
}
void drawSolidStatic(){
drawHeaderStatic("Solid Color");
int y = firstLineY(SOLID_SIZE);
for (int i=0;i<SOLID_SIZE;i++){
drawRowSolidCentered(y, solidItems[i].name, solidItems[i].color, i==solidIndex);
y += LINE_H;
}
tft.setTextSize(1); tft.setTextColor(ILI9341_WHITE);
const char* hint = "UP/DOWN=Select OK=Apply BACK=Menu";
tft.setCursor(centerX(hint,1), tft.height()-14);
tft.print(hint);
}
void updateSolidSelection(int prev, int curr){
if (prev==curr) return;
int base = firstLineY(SOLID_SIZE);
drawRowSolidCentered(base + prev*LINE_H,
solidItems[prev].name, solidItems[prev].color, false);
drawRowSolidCentered(base + curr*LINE_H,
solidItems[curr].name, solidItems[curr].color, true);
}
void drawNumberAdjust(const char* title, int value, int vmin, int vmax, int step, const char* unit=""){
drawHeaderStatic(title);
tft.setTextColor(ILI9341_WHITE);
tft.setTextSize(4);
char buf[20];
if (unit && unit[0]) sprintf(buf, "%d %s", value, unit);
else sprintf(buf, "%d", value);
int x = centerX(buf,4);
int y = TITLE_H + (tft.height()-TITLE_H)/2 - 16;
tft.fillRect(0, TITLE_H+10, tft.width(), tft.height()-TITLE_H-40, ILI9341_BLACK);
tft.setCursor(x, y); tft.print(buf);
tft.setTextSize(2);
const char* hint = "UP/DOWN=Adjust OK=Save BACK=Exit";
tft.setCursor(centerX(hint,2), tft.height()-60); tft.print(hint);
char rangeBuf[40]; sprintf(rangeBuf,"Range: %d~%d (%d)", vmin, vmax, step);
tft.setCursor(centerX(rangeBuf,2), tft.height()-32); tft.print(rangeBuf);
}
void drawAbout(){
drawHeaderStatic("About");
tft.setTextColor(ILI9341_WHITE); tft.setTextSize(2);
const char* l1="School : CTAS";
const char* l2="Project: RGB Menu + WS2812";
const char* l3="Author : _______";
const char* l4="Date : 2025-08-13";
int y = TITLE_H + 30;
tft.setCursor(centerX(l1,2), y); tft.print(l1);
tft.setCursor(centerX(l2,2), y+28); tft.print(l2);
tft.setCursor(centerX(l3,2), y+56); tft.print(l3);
tft.setCursor(centerX(l4,2), y+84); tft.print(l4);
tft.setTextSize(1);
const char* back="BACK=menu";
tft.setCursor(centerX(back,1), tft.height()-14); tft.print(back);
}
// ---------- LED helpers ----------
void applyBrightness(){ strip.setBrightness(brightness); strip.show(); }
void showSolid(uint32_t c){ for (int i=0;i<NUM_LEDS;i++) strip.setPixelColor(i,c); strip.show(); }
uint32_t brightPurple(){ return strip.Color(255,0,255); }
// ---------- Animations ----------
void animChase(){
if (millis()-animTs < speedMs) return;
animTs = millis();
for (int i=0;i<NUM_LEDS;i++) strip.setPixelColor(i,0);
strip.setPixelColor(chasePos, brightPurple());
strip.show();
chasePos = (chasePos + 1) % NUM_LEDS;
}
uint32_t colorWheel(uint16_t hue){
uint8_t p=(hue>>8)&0xFF;
if (p<85) return strip.Color(255-p*3, p*3, 0);
if (p<170){ p-=85; return strip.Color(0, 255-p*3, p*3); }
p-=170; return strip.Color(p*3, 0, 255-p*3);
}
void animRainbow(){
if (millis()-animTs < speedMs) return;
animTs = millis();
for (int i=0;i<NUM_LEDS;i++){
uint16_t h = rainbowHue + (i * 65535UL / NUM_LEDS);
strip.setPixelColor(i, colorWheel(h));
}
strip.show();
rainbowHue += 1024;
}
// ---------- Setup ----------
void setup(){
pinMode(BTN_UP,INPUT_PULLUP); pinMode(BTN_DOWN,INPUT_PULLUP);
pinMode(BTN_OK,INPUT_PULLUP); pinMode(BTN_BACK,INPUT_PULLUP);
tft.begin();
tft.setRotation(0); // 直立(240x320)
tft.fillScreen(ILI9341_BLACK);
strip.begin(); strip.setBrightness(brightness); strip.show();
drawHeaderStatic("CTAS RGB Controller");
tft.setTextColor(ILI9341_GREEN); tft.setTextSize(3);
const char* hello="Welcome!";
tft.setCursor(centerX(hello,3), TITLE_H + (tft.height()-TITLE_H)/2 - 16);
tft.print(hello);
delay(3000);
drawMenuStatic();
}
// ---------- Loop ----------
void loop(){
bool up=pressed(bUp), down=pressed(bDown), ok=pressed(bOk), back=pressed(bBack);
switch (screen){
case SCR_MENU:{
if (up){ lastMenuIndex = menuIndex; menuIndex = (menuIndex-1+MENU_SIZE)%MENU_SIZE; updateMenuSelection(lastMenuIndex, menuIndex); }
if (down){ lastMenuIndex = menuIndex; menuIndex = (menuIndex+1)%MENU_SIZE; updateMenuSelection(lastMenuIndex, menuIndex); }
if (ok){
if (menuIndex==0){
// ★ 變更點:進入 Solid Color 後「直接顯示目前索引顏色」,且「不回主選單」
screen=SCR_SOLID_SUB;
drawSolidStatic();
showSolid(solidItems[solidIndex].color);
mode = (solidItems[solidIndex].color == 0) ? MODE_OFF : MODE_SOLID;
}
else if (menuIndex==1){ mode=MODE_CHASE; chasePos=0; }
else if (menuIndex==2){ mode=MODE_RAINBOW; rainbowHue=0; }
else if (menuIndex==3){ screen=SCR_BRIGHTNESS; drawNumberAdjust("Brightness", brightness, 0, 255, 8); }
else if (menuIndex==4){ screen=SCR_SPEED; drawNumberAdjust("Speed (ms)", speedMs, SPEED_MIN, SPEED_MAX, SPEED_STEP, "ms"); }
else if (menuIndex==5){ screen=SCR_ABOUT; drawAbout(); }
}
if (mode==MODE_CHASE) animChase();
if (mode==MODE_RAINBOW) animRainbow();
break;
}
case SCR_SOLID_SUB:{
if (up){
lastSolidIndex = solidIndex;
solidIndex = (solidIndex - 1 + SOLID_SIZE) % SOLID_SIZE;
updateSolidSelection(lastSolidIndex, solidIndex);
// 即時預覽(不需 OK)
showSolid(solidItems[solidIndex].color);
mode = (solidItems[solidIndex].color == 0) ? MODE_OFF : MODE_SOLID;
}
if (down){
lastSolidIndex = solidIndex;
solidIndex = (solidIndex + 1) % SOLID_SIZE;
updateSolidSelection(lastSolidIndex, solidIndex);
// 即時預覽
showSolid(solidItems[solidIndex].color);
mode = (solidItems[solidIndex].color == 0) ? MODE_OFF : MODE_SOLID;
}
if (ok){
// ★ 變更點:按 OK「只套用」,但「不離開」固色子選單
showSolid(solidItems[solidIndex].color);
mode = (solidItems[solidIndex].color == 0) ? MODE_OFF : MODE_SOLID;
// 保持在 SCR_SOLID_SUB,不跳走
}
if (back){
screen = SCR_MENU;
drawMenuStatic();
}
break;
}
case SCR_BRIGHTNESS:{
static int tmp = brightness;
if (up){ tmp = min(255, tmp+8); drawNumberAdjust("Brightness", tmp, 0, 255, 8); }
if (down){ tmp = max(0, tmp-8); drawNumberAdjust("Brightness", tmp, 0, 255, 8); }
if (ok){ brightness = tmp; applyBrightness(); screen=SCR_MENU; drawMenuStatic(); }
if (back){ screen=SCR_MENU; drawMenuStatic(); }
if (mode==MODE_CHASE) animChase(); if (mode==MODE_RAINBOW) animRainbow();
break;
}
case SCR_SPEED:{
static int tmp = speedMs;
if (up){ tmp = min((int)SPEED_MAX, tmp+SPEED_STEP); drawNumberAdjust("Speed (ms)", tmp, SPEED_MIN, SPEED_MAX, SPEED_STEP, "ms"); }
if (down){ tmp = max((int)SPEED_MIN, tmp-SPEED_STEP); drawNumberAdjust("Speed (ms)", tmp, SPEED_MIN, SPEED_MAX, SPEED_STEP, "ms"); }
if (ok){ speedMs = tmp; screen=SCR_MENU; drawMenuStatic(); }
if (back){ screen=SCR_MENU; drawMenuStatic(); }
if (mode==MODE_CHASE) animChase(); if (mode==MODE_RAINBOW) animRainbow();
break;
}
case SCR_ABOUT:{
if (back || ok){ screen=SCR_MENU; drawMenuStatic(); }
if (mode==MODE_CHASE) animChase(); if (mode==MODE_RAINBOW) animRainbow();
break;
}
}
}