Clean up dialogue detection, add dialogue and non-dialogue text coloring settings
This commit is contained in:
parent
e06b886c28
commit
e64062c2c8
14
README.md
14
README.md
|
|
@ -1,5 +1,7 @@
|
|||
# T-Display S3 Pro RSVP Reader
|
||||
|
||||

|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 179 KiB |
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<int>(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) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue