diff --git a/README.md b/README.md index 5f40ec6..e68d696 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # T-Display S3 Pro RSVP Reader +![Animated preview of the RSVP reader display](docs/reader-preview.gif) + An offline RSVP reader for the LilyGO T-Display S3 Pro. It renders one word at a time on a black, landscape display and never starts Wi-Fi or Bluetooth. Detected dialogue appears grey when Colors is on; other words appear white. @@ -136,10 +138,14 @@ section. Its versioned, delta-encoded records preserve italic and bold EPUB markup—including semantic tags, CSS classes, and inline CSS—reading-timing metadata, and a word color flag. Quoted dialogue and wholly italic or uppercase punctuated clauses receive the `Other` flag; speaker attribution is not used -during conversion. With coloring enabled, the device shows those words in grey -and other words in white. The reader menu has a persisted **Colors: On/Off** -setting. The fingerprint covers the source `.txt` bytes, before the device -applies display-only punctuation normalization. During conversion, the +during conversion. Flagged dialogue defaults to gray, and other words default +to white. The **Colors** submenu stores separate +Text and Dialogue colors, selected from white, gray, and the nine hues in +`docs/history/PLAN.md`. Text color applies to non-dialogue words and the reader +header and footer; Dialogue color applies to flagged dialogue words. Its live +preview shows “text” and italic “dialogue”. The fingerprint covers the source +`.txt` bytes, before the device applies display-only punctuation normalization. +During conversion, the desktop-only English vocabulary classifier marks each unfamiliar word. It combines a dictionary with word-frequency data and recognizes normal English inflections, contractions, possessives, and hyphenated compounds. The first diff --git a/book-processing/attribution.py b/book-processing/attribution.py index 3bddd63..a9bb28e 100644 --- a/book-processing/attribution.py +++ b/book-processing/attribution.py @@ -51,7 +51,7 @@ def identify_dialogue(paragraph, word_start, italic_word_indexes, paragraph_inde def _styled_dialogue_word_indexes(paragraph, word_start, italic_word_indexes): - """Find words in wholly italic or uppercase punctuated clauses.""" + """Find italic or uppercase punctuated clauses, bridging one italic gap.""" words = list(WORD.finditer(paragraph)) clauses = {} @@ -62,9 +62,12 @@ def _styled_dialogue_word_indexes(paragraph, word_start, italic_word_indexes): clause = clauses.setdefault( clause_number, {"italic": True, "uppercase": True, "last_start": match.start(), - "first_start": match.start(), "word_indexes": []}, + "first_start": match.start(), "word_indexes": [], + "italic_flags": []}, + ) + clause["italic_flags"].append( + word_start + word_number in italic_word_indexes ) - clause["italic"] &= word_start + word_number in italic_word_indexes clause["uppercase"] &= bool(letters) and letters.isupper() clause["last_start"] = match.start() clause["word_indexes"].append(word_start + word_number) @@ -77,7 +80,16 @@ def _styled_dialogue_word_indexes(paragraph, word_start, italic_word_indexes): punctuated = CLAUSE_PUNCTUATION.search( paragraph, clause["last_start"], boundary_end ) is not None - if punctuated and (clause["italic"] or clause["uppercase"]): + italic_flags = clause["italic_flags"] + italic_clause = all(italic_flags) + if not italic_clause and italic_flags.count(False) == 1: + gap = italic_flags.index(False) + italic_clause = ( + 0 < gap < len(italic_flags) - 1 + and all(italic_flags[:gap]) + and all(italic_flags[gap + 1:]) + ) + if punctuated and (italic_clause or clause["uppercase"]): dialogue_indexes.update(clause["word_indexes"]) return dialogue_indexes @@ -100,19 +112,30 @@ def dialogue_word_indexes(text, style_records): paragraph, word_start, italic_indexes )) single_quotes = list(PAIRED_SINGLE_QUOTES.finditer(paragraph)) - continuation_quote = in_double_quote and paragraph.startswith('"') for word_number, match in enumerate(words): quoted_word = False for position in range(match.start(), match.end()): character = paragraph[position] if character == '"': quoted_word = True - if continuation_quote and position == 0: - continuation_quote = False - else: + is_opening_mark = position == match.start() + is_closing_mark = position == match.end() - 1 + if is_opening_mark and is_closing_mark: in_double_quote = not in_double_quote + elif is_opening_mark: + in_double_quote = True + elif is_closing_mark: + if in_double_quote: + in_double_quote = False + else: + dialogue_indexes.update( + word_start + leading_word + for leading_word in range(word_number + 1) + ) elif in_double_quote: quoted_word = True + if in_double_quote: + quoted_word = True if quoted_word or any( match.start() < quote.end() and match.end() > quote.start() for quote in single_quotes diff --git a/docs/reader-preview.gif b/docs/reader-preview.gif new file mode 100644 index 0000000..a620ffc Binary files /dev/null and b/docs/reader-preview.gif differ diff --git a/firmware/include/Board.h b/firmware/include/Board.h index a1a8409..e5b51d6 100644 --- a/firmware/include/Board.h +++ b/firmware/include/Board.h @@ -12,9 +12,14 @@ class Board { uint32_t wordCount, uint8_t progressPercent); void drawWord(const String& word, uint16_t wpm, uint8_t styleFlags = 0, uint8_t wordColorFlag = 0); + void setReadingColors(uint16_t textColor, uint16_t dialogueColor); void drawChapterTitle(const String& chapter, uint16_t wpm); void drawTheEnd(uint16_t wpm); - void drawMenu(uint8_t selectedOption, bool colorsEnabled); + void drawMenu(uint8_t selectedOption); + void drawColorsMenu(uint8_t selectedOption, uint8_t textColor, + uint8_t dialogueColor); + void drawColorPicker(bool dialogueSetting, uint8_t selectedColor, + uint16_t textColor, uint16_t dialogueColor); void drawPicker(const String& title, const String& value); void drawFontStylePicker(uint8_t style, uint8_t size); void drawFontSizePicker(uint8_t style, uint8_t size); @@ -46,6 +51,8 @@ class Board { uint8_t preferredBrightness_ = 5; uint8_t fontStyle_ = 0; uint8_t fontSize_ = 20; + uint16_t textColor_ = 0xFFFF; + uint16_t dialogueColor_ = 0xB596; bool landscape_ = false; bool powerMonitorAvailable_ = false; bool statusDrawn_ = false; diff --git a/firmware/include/Palette.h b/firmware/include/Palette.h index 7b0d8a9..bf97016 100644 --- a/firmware/include/Palette.h +++ b/firmware/include/Palette.h @@ -11,6 +11,7 @@ constexpr uint16_t rgb565(uint8_t red, uint8_t green, uint8_t blue) { } constexpr uint16_t kOtherGrey = rgb565(176, 176, 176); +constexpr uint16_t kWhite = rgb565(255, 255, 255); constexpr uint16_t kHues[] = {0, 30, 60, 120, 180, 225, 270, 300, 330}; constexpr uint16_t kColors[] = { @@ -18,6 +19,26 @@ constexpr uint16_t kColors[] = { rgb565(128, 255, 128), rgb565(128, 255, 255), rgb565(128, 159, 255), rgb565(191, 128, 255), rgb565(255, 128, 255), rgb565(255, 128, 191), }; +constexpr const char* kHueNames[] = { + "Red", "Orange", "Yellow", "Green", "Cyan", "Blue", + "Violet", "Magenta", "Pink", +}; + +// The color picker includes the two existing neutral choices before the hue +// palette documented in docs/history/PLAN.md. +constexpr uint8_t kChoiceCount = 11; + +inline uint16_t colorForChoice(uint8_t choice) { + if (choice == 0) return kWhite; + if (choice == 1) return kOtherGrey; + return choice < kChoiceCount ? kColors[choice - 2] : kWhite; +} + +inline const char* nameForChoice(uint8_t choice) { + if (choice == 0) return "White"; + if (choice == 1) return "Gray"; + return choice < kChoiceCount ? kHueNames[choice - 2] : "White"; +} inline int indexForHue(uint16_t hue) { for (size_t index = 0; index < sizeof(kHues) / sizeof(kHues[0]); ++index) { diff --git a/firmware/include/Settings.h b/firmware/include/Settings.h index 6c19bd8..72ff29e 100644 --- a/firmware/include/Settings.h +++ b/firmware/include/Settings.h @@ -9,7 +9,8 @@ struct SavedState { uint8_t brightness = 5; uint8_t fontStyle = 0; uint8_t fontSize = 20; - bool colorsEnabled = true; + uint8_t textColor = 0; + uint8_t dialogueColor = 1; }; class Settings { diff --git a/firmware/src/Board.cpp b/firmware/src/Board.cpp index 1e25a36..cab1601 100644 --- a/firmware/src/Board.cpp +++ b/firmware/src/Board.cpp @@ -173,7 +173,7 @@ void Board::drawStatus(uint16_t wpm) { constexpr int16_t kBatteryTipWidth = 3; setUiFont(fontStyle_); - display_->setTextColor(TFT_WHITE); + display_->setTextColor(textColor_); display_->setTextDatum(ML_DATUM); const String wpmLabel = String(wpm) + " WPM"; display_->drawString(wpmLabel, kMargin, kStatusHeight / 2); @@ -184,11 +184,14 @@ void Board::drawStatus(uint16_t wpm) { const String label = percentage < 0 ? "--" : String(percentage) + "%"; display_->setTextDatum(MR_DATUM); display_->drawString(label, batteryX - 5, kStatusHeight / 2); - display_->drawRoundRect(batteryX, batteryY, kBatteryWidth, kBatteryHeight, 2, TFT_WHITE); - display_->fillRect(batteryX + kBatteryWidth, batteryY + 4, kBatteryTipWidth, 4, TFT_WHITE); + display_->drawRoundRect(batteryX, batteryY, kBatteryWidth, kBatteryHeight, 2, + textColor_); + display_->fillRect(batteryX + kBatteryWidth, batteryY + 4, kBatteryTipWidth, 4, + textColor_); if (percentage > 0) { const int16_t fillWidth = (kBatteryWidth - 4) * percentage / 100; - display_->fillRect(batteryX + 2, batteryY + 2, fillWidth, kBatteryHeight - 4, TFT_WHITE); + display_->fillRect(batteryX + 2, batteryY + 2, fillWidth, + kBatteryHeight - 4, textColor_); } const int16_t left = kMargin + display_->textWidth(wpmLabel) + 8; @@ -225,7 +228,7 @@ void Board::drawFooter() { display_->fillRect(0, display_->height() - kFooterHeight, display_->width(), kFooterHeight, TFT_BLACK); setUiFont(fontStyle_); - display_->setTextColor(TFT_WHITE); + display_->setTextColor(textColor_); const String details = String(wordCount_) + " words - " + String(progressPercent_) + "%"; const String separator = " - "; @@ -242,6 +245,15 @@ void Board::drawFooter() { displayedProgressPercent_ = progressPercent_; } +void Board::setReadingColors(uint16_t textColor, uint16_t dialogueColor) { + if (textColor_ == textColor && dialogueColor_ == dialogueColor) return; + textColor_ = textColor; + dialogueColor_ = dialogueColor; + statusDrawn_ = false; + footerDrawn_ = false; + displayedProgressPercent_ = 255; +} + void Board::drawWord(const String& word, uint16_t wpm, uint8_t styleFlags, uint8_t wordColorFlag) { if (display_ == nullptr) return; @@ -272,7 +284,7 @@ void Board::drawWord(const String& word, uint16_t wpm, uint8_t styleFlags, renderedSize -= config::kFontSizeStep; setReaderFont(fontStyle_, renderedSize, styleFlags); } - display_->setTextColor(wordColorFlag == 1 ? palette::kOtherGrey : TFT_WHITE); + display_->setTextColor(wordColorFlag == 1 ? dialogueColor_ : textColor_); display_->setTextDatum(MC_DATUM); const int16_t centerX = display_->width() / 2; @@ -310,7 +322,7 @@ void Board::drawChapterTitle(const String& chapter, uint16_t wpm) { void Board::drawTheEnd(uint16_t wpm) { drawWord("The End", wpm); } -void Board::drawMenu(uint8_t selectedOption, bool colorsEnabled) { +void Board::drawMenu(uint8_t selectedOption) { if (display_ == nullptr) return; if (landscape_) { @@ -323,7 +335,7 @@ void Board::drawMenu(uint8_t selectedOption, bool colorsEnabled) { displayedProgressPercent_ = 255; previousWordBounds_.valid = false; setUiFont(fontStyle_); - display_->setTextColor(TFT_WHITE); + display_->setTextColor(textColor_); display_->setTextDatum(TC_DATUM); display_->drawString("MENU", display_->width() / 2, 18); @@ -331,21 +343,22 @@ void Board::drawMenu(uint8_t selectedOption, bool colorsEnabled) { constexpr int16_t kFirstRowY = 54; constexpr int16_t kRowSpacing = 56; const char* labels[] = {"Back", "Book selection", "Chapter selection", - "Brightness", "Font", - colorsEnabled ? "Colors: On" : "Colors: Off"}; + "Brightness", "Font", "Colors"}; display_->setTextDatum(ML_DATUM); for (uint8_t index = 0; index < 6; ++index) { const int16_t y = kFirstRowY + index * kRowSpacing; if (index == selectedOption) { - display_->fillRoundRect(10, y, display_->width() - 20, kRowHeight, 5, TFT_WHITE); + display_->fillRoundRect(10, y, display_->width() - 20, kRowHeight, 5, + textColor_); display_->setTextColor(TFT_BLACK); } else { - display_->drawRoundRect(10, y, display_->width() - 20, kRowHeight, 5, TFT_WHITE); - display_->setTextColor(TFT_WHITE); + display_->drawRoundRect(10, y, display_->width() - 20, kRowHeight, 5, + textColor_); + display_->setTextColor(textColor_); } display_->drawString(labels[index], 24, y + kRowHeight / 2); } - display_->setTextColor(TFT_WHITE); + display_->setTextColor(textColor_); display_->setTextDatum(BC_DATUM); display_->drawString("PREV/NEXT choose", display_->width() / 2, display_->height() - 36); @@ -353,6 +366,85 @@ void Board::drawMenu(uint8_t selectedOption, bool colorsEnabled) { display_->height() - 8); } +void Board::drawColorsMenu(uint8_t selectedOption, uint8_t textColor, + uint8_t dialogueColor) { + if (display_ == nullptr) return; + if (landscape_) { + display_->setRotation(0); + landscape_ = false; + } + display_->fillScreen(TFT_BLACK); + statusDrawn_ = false; + footerDrawn_ = false; + previousWordBounds_.valid = false; + setUiFont(fontStyle_); + display_->setTextColor(textColor_); + display_->setTextDatum(TC_DATUM); + display_->drawString("COLORS", display_->width() / 2, 18); + + const char* labels[] = {"Text", "Dialogue", "Back"}; + const uint8_t choices[] = {textColor, dialogueColor, 0}; + constexpr int16_t kRowHeight = 48; + constexpr int16_t kFirstRowY = 64; + constexpr int16_t kRowSpacing = 58; + display_->setTextDatum(ML_DATUM); + for (uint8_t index = 0; index < 3; ++index) { + const int16_t y = kFirstRowY + index * kRowSpacing; + if (index == selectedOption) { + display_->fillRoundRect(10, y, display_->width() - 20, kRowHeight, 5, + textColor_); + display_->setTextColor(TFT_BLACK); + } else { + display_->drawRoundRect(10, y, display_->width() - 20, kRowHeight, 5, + textColor_); + display_->setTextColor(textColor_); + } + const String label = index < 2 + ? String(labels[index]) + ": " + + palette::nameForChoice(choices[index]) + : String(labels[index]); + display_->drawString(label, 24, y + kRowHeight / 2); + } + display_->setTextColor(textColor_); + display_->setTextDatum(BC_DATUM); + display_->drawString("PREV/NEXT choose", display_->width() / 2, + display_->height() - 36); + display_->drawString("BOOT select", display_->width() / 2, + display_->height() - 8); +} + +void Board::drawColorPicker(bool dialogueSetting, uint8_t selectedColor, + uint16_t textColor, uint16_t dialogueColor) { + if (display_ == nullptr) return; + setLandscape(); + display_->fillScreen(TFT_BLACK); + statusDrawn_ = false; + footerDrawn_ = false; + previousWordBounds_.valid = false; + setUiFont(fontStyle_); + display_->setTextColor(textColor_); + display_->setTextDatum(TC_DATUM); + display_->drawString(dialogueSetting ? "DIALOGUE COLOR" : "TEXT COLOR", + display_->width() / 2, 12); + display_->setTextColor(palette::colorForChoice(selectedColor)); + display_->drawString(palette::nameForChoice(selectedColor), + display_->width() / 2, 54); + + setReaderFont(fontStyle_, fontSize_, dialogueSetting ? kItalicStyle : 0); + display_->setTextDatum(MC_DATUM); + display_->setTextColor(dialogueSetting ? dialogueColor : textColor); + display_->drawString(dialogueSetting ? "dialogue" : "text", + display_->width() / 2, 118); + + setUiFont(fontStyle_); + display_->setTextColor(textColor_); + display_->setTextDatum(BC_DATUM); + display_->drawString("PREV/NEXT color", display_->width() / 2, + display_->height() - 36); + display_->drawString("BOOT save", display_->width() / 2, + display_->height() - 8); +} + void Board::drawPicker(const String& title, const String& value) { if (display_ == nullptr) return; setLandscape(); @@ -361,7 +453,7 @@ void Board::drawPicker(const String& title, const String& value) { statusDrawn_ = false; previousWordBounds_.valid = false; setUiFont(fontStyle_); - display_->setTextColor(TFT_WHITE); + display_->setTextColor(textColor_); display_->setTextDatum(TC_DATUM); display_->drawString(title, display_->width() / 2, 16); @@ -375,6 +467,7 @@ void Board::drawPicker(const String& title, const String& value) { display_->width() / 2, display_->height() / 2 - 8); setUiFont(fontStyle_); + display_->setTextColor(textColor_); display_->setTextDatum(BC_DATUM); display_->drawString("PREV/NEXT choose", display_->width() / 2, display_->height() - 36); @@ -390,18 +483,21 @@ void Board::drawFontStylePicker(uint8_t style, uint8_t size) { statusDrawn_ = false; previousWordBounds_.valid = false; setUiFont(style); - display_->setTextColor(TFT_WHITE); + display_->setTextColor(textColor_); display_->setTextDatum(TC_DATUM); display_->drawString("FONT STYLE", display_->width() / 2, 16); const char* names[] = {"Sans-serif", "Serif", "Monospace"}; setReaderFont(style, size); + display_->setTextColor(textColor_); display_->setTextDatum(MC_DATUM); display_->drawString("Sample", display_->width() / 2, display_->height() / 2 - 8); setUiFont(style); + display_->setTextColor(textColor_); display_->setTextDatum(TC_DATUM); display_->drawString(names[constrain(style, 0, 2)], display_->width() / 2, 48); + display_->setTextColor(textColor_); display_->setTextDatum(BC_DATUM); display_->drawString("PREV/NEXT choose", display_->width() / 2, display_->height() - 36); @@ -417,16 +513,18 @@ void Board::drawFontSizePicker(uint8_t style, uint8_t size) { statusDrawn_ = false; previousWordBounds_.valid = false; setUiFont(style); - display_->setTextColor(TFT_WHITE); + display_->setTextColor(textColor_); display_->setTextDatum(TC_DATUM); display_->drawString("FONT SIZE", display_->width() / 2, 16); setReaderFont(style, size); + display_->setTextColor(textColor_); display_->setTextDatum(MC_DATUM); display_->drawString("size", display_->width() / 2, display_->height() / 2 - 8); setUiFont(style); + display_->setTextColor(textColor_); display_->setTextDatum(BC_DATUM); display_->drawString("PREV/NEXT choose", display_->width() / 2, display_->height() - 36); diff --git a/firmware/src/Settings.cpp b/firmware/src/Settings.cpp index 95b8ff7..7acd2e7 100644 --- a/firmware/src/Settings.cpp +++ b/firmware/src/Settings.cpp @@ -18,7 +18,8 @@ SavedState Settings::load() const { state.brightness = preferences.getUChar("brightness", 5); state.fontStyle = preferences.getUChar("font_style", 0); state.fontSize = preferences.getUChar("font_size", 20); - state.colorsEnabled = preferences.getBool("colors", true); + state.textColor = preferences.getUChar("text_color", 0); + state.dialogueColor = preferences.getUChar("dialogue_color", 1); preferences.end(); return state; } @@ -46,9 +47,13 @@ void Settings::save(const SavedState& state) const { if (!preferences.isKey("font_size") || preferences.getUChar("font_size") != state.fontSize) { preferences.putUChar("font_size", state.fontSize); } - if (!preferences.isKey("colors") || - preferences.getBool("colors") != state.colorsEnabled) { - preferences.putBool("colors", state.colorsEnabled); + if (!preferences.isKey("text_color") || + preferences.getUChar("text_color") != state.textColor) { + preferences.putUChar("text_color", state.textColor); + } + if (!preferences.isKey("dialogue_color") || + preferences.getUChar("dialogue_color") != state.dialogueColor) { + preferences.putUChar("dialogue_color", state.dialogueColor); } preferences.end(); } diff --git a/firmware/src/main.cpp b/firmware/src/main.cpp index bce621b..dbbe2c1 100644 --- a/firmware/src/main.cpp +++ b/firmware/src/main.cpp @@ -9,6 +9,7 @@ #include "Board.h" #include "Config.h" #include "Library.h" +#include "Palette.h" #include "Reader.h" #include "Settings.h" @@ -35,6 +36,9 @@ enum class Screen { kBrightnessPicker, kFontStylePicker, kFontSizePicker, + kColorsMenu, + kTextColorPicker, + kDialogueColorPicker, }; enum class ReaderView { @@ -49,7 +53,9 @@ String pendingChapterPath; uint8_t pendingBrightness = config::kNormalBrightness; uint8_t pendingFontStyle = config::kDefaultFontStyle; uint8_t pendingFontSize = config::kDefaultFontSize; +uint8_t pendingColor = 0; uint8_t menuSelection = 0; +uint8_t colorsMenuSelection = 0; struct Button { explicit Button(uint8_t buttonPin) : pin(buttonPin) {} @@ -97,14 +103,18 @@ void setReadingContext(uint8_t progressPercent) { void showCurrentWord() { setReadingContext(reader.progressPercent()); + board.setReadingColors(palette::colorForChoice(state.textColor), + palette::colorForChoice(state.dialogueColor)); board.drawWord(reader.currentWord(), state.wpm, reader.currentStyleFlags(), - state.colorsEnabled ? reader.currentColorFlag() : 0); + reader.currentColorFlag()); } void wakeDisplay(); void configureTouchLandscape(); void showReaderScreen() { + board.setReadingColors(palette::colorForChoice(state.textColor), + palette::colorForChoice(state.dialogueColor)); switch (readerView) { case ReaderView::kChapterTitle: setReadingContext(0); @@ -161,6 +171,19 @@ void drawPicker() { case Screen::kFontSizePicker: board.drawFontSizePicker(pendingFontStyle, pendingFontSize); break; + case Screen::kColorsMenu: + board.drawColorsMenu(colorsMenuSelection, state.textColor, state.dialogueColor); + break; + case Screen::kTextColorPicker: + case Screen::kDialogueColorPicker: + board.drawColorPicker(screen == Screen::kDialogueColorPicker, pendingColor, + palette::colorForChoice( + screen == Screen::kTextColorPicker ? pendingColor + : state.textColor), + palette::colorForChoice( + screen == Screen::kDialogueColorPicker ? pendingColor + : state.dialogueColor)); + break; default: break; } @@ -173,7 +196,22 @@ void openMenu() { menuSelection = 0; screen = Screen::kMenu; wakeDisplay(); - board.drawMenu(menuSelection, state.colorsEnabled); + board.drawMenu(menuSelection); +} + +void openColorsMenu() { + colorsMenuSelection = 0; + screen = Screen::kColorsMenu; + configureTouchLandscape(); + drawPicker(); +} + +void openColorPicker(Screen picker) { + screen = picker; + pendingColor = picker == Screen::kTextColorPicker ? state.textColor + : state.dialogueColor; + configureTouchLandscape(); + drawPicker(); } void openPicker(Screen picker) { @@ -210,6 +248,11 @@ void changePicker(int direction) { static_cast(pendingFontSize) + direction * config::kFontSizeStep, config::kMinimumFontSize, config::kMaximumFontSize); break; + case Screen::kTextColorPicker: + case Screen::kDialogueColorPicker: + pendingColor = (pendingColor + palette::kChoiceCount + direction) % + palette::kChoiceCount; + break; default: return; } @@ -217,6 +260,22 @@ void changePicker(int direction) { } void applyPicker() { + if (screen == Screen::kTextColorPicker) { + state.textColor = pendingColor; + persistState(); + screen = Screen::kColorsMenu; + colorsMenuSelection = 1; + drawPicker(); + return; + } + if (screen == Screen::kDialogueColorPicker) { + state.dialogueColor = pendingColor; + persistState(); + screen = Screen::kColorsMenu; + colorsMenuSelection = 2; + drawPicker(); + return; + } if (screen == Screen::kFontStylePicker) { screen = Screen::kFontSizePicker; drawPicker(); @@ -333,14 +392,20 @@ void confirmMenuSelection() { case 2: openPicker(Screen::kChapterPicker); break; case 3: openPicker(Screen::kBrightnessPicker); break; case 4: openPicker(Screen::kFontStylePicker); break; - case 5: - state.colorsEnabled = !state.colorsEnabled; - persistState(); - board.drawMenu(menuSelection, state.colorsEnabled); - break; + case 5: openColorsMenu(); break; } return; } + if (screen == Screen::kColorsMenu) { + if (colorsMenuSelection == 2) { + screen = Screen::kMenu; + board.drawMenu(menuSelection); + return; + } + openColorPicker(colorsMenuSelection == 0 ? Screen::kTextColorPicker + : Screen::kDialogueColorPicker); + return; + } applyPicker(); } @@ -397,7 +462,12 @@ void handleInput() { wakeDisplay(); if (screen == Screen::kMenu) { menuSelection = (menuSelection + 5) % 6; - board.drawMenu(menuSelection, state.colorsEnabled); + board.drawMenu(menuSelection); + return; + } + if (screen == Screen::kColorsMenu) { + colorsMenuSelection = (colorsMenuSelection + 2) % 3; + drawPicker(); return; } if (screen != Screen::kReader) { @@ -417,7 +487,12 @@ void handleInput() { wakeDisplay(); if (screen == Screen::kMenu) { menuSelection = (menuSelection + 1) % 6; - board.drawMenu(menuSelection, state.colorsEnabled); + board.drawMenu(menuSelection); + return; + } + if (screen == Screen::kColorsMenu) { + colorsMenuSelection = (colorsMenuSelection + 1) % 3; + drawPicker(); return; } if (screen != Screen::kReader) {