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
|
# T-Display S3 Pro RSVP Reader
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
An offline RSVP reader for the LilyGO T-Display S3 Pro. It renders one word at
|
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.
|
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.
|
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
|
markup—including semantic tags, CSS classes, and inline CSS—reading-timing
|
||||||
metadata, and a word color flag. Quoted dialogue and wholly italic or uppercase
|
metadata, and a word color flag. Quoted dialogue and wholly italic or uppercase
|
||||||
punctuated clauses receive the `Other` flag; speaker attribution is not used
|
punctuated clauses receive the `Other` flag; speaker attribution is not used
|
||||||
during conversion. With coloring enabled, the device shows those words in grey
|
during conversion. Flagged dialogue defaults to gray, and other words default
|
||||||
and other words in white. The reader menu has a persisted **Colors: On/Off**
|
to white. The **Colors** submenu stores separate
|
||||||
setting. The fingerprint covers the source `.txt` bytes, before the device
|
Text and Dialogue colors, selected from white, gray, and the nine hues in
|
||||||
applies display-only punctuation normalization. During conversion, the
|
`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
|
desktop-only English vocabulary classifier marks each unfamiliar word. It
|
||||||
combines a dictionary with word-frequency data and recognizes normal English
|
combines a dictionary with word-frequency data and recognizes normal English
|
||||||
inflections, contractions, possessives, and hyphenated compounds. The first
|
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):
|
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))
|
words = list(WORD.finditer(paragraph))
|
||||||
clauses = {}
|
clauses = {}
|
||||||
|
|
@ -62,9 +62,12 @@ def _styled_dialogue_word_indexes(paragraph, word_start, italic_word_indexes):
|
||||||
clause = clauses.setdefault(
|
clause = clauses.setdefault(
|
||||||
clause_number,
|
clause_number,
|
||||||
{"italic": True, "uppercase": True, "last_start": match.start(),
|
{"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["uppercase"] &= bool(letters) and letters.isupper()
|
||||||
clause["last_start"] = match.start()
|
clause["last_start"] = match.start()
|
||||||
clause["word_indexes"].append(word_start + word_number)
|
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(
|
punctuated = CLAUSE_PUNCTUATION.search(
|
||||||
paragraph, clause["last_start"], boundary_end
|
paragraph, clause["last_start"], boundary_end
|
||||||
) is not None
|
) 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"])
|
dialogue_indexes.update(clause["word_indexes"])
|
||||||
return dialogue_indexes
|
return dialogue_indexes
|
||||||
|
|
||||||
|
|
@ -100,19 +112,30 @@ def dialogue_word_indexes(text, style_records):
|
||||||
paragraph, word_start, italic_indexes
|
paragraph, word_start, italic_indexes
|
||||||
))
|
))
|
||||||
single_quotes = list(PAIRED_SINGLE_QUOTES.finditer(paragraph))
|
single_quotes = list(PAIRED_SINGLE_QUOTES.finditer(paragraph))
|
||||||
continuation_quote = in_double_quote and paragraph.startswith('"')
|
|
||||||
for word_number, match in enumerate(words):
|
for word_number, match in enumerate(words):
|
||||||
quoted_word = False
|
quoted_word = False
|
||||||
for position in range(match.start(), match.end()):
|
for position in range(match.start(), match.end()):
|
||||||
character = paragraph[position]
|
character = paragraph[position]
|
||||||
if character == '"':
|
if character == '"':
|
||||||
quoted_word = True
|
quoted_word = True
|
||||||
if continuation_quote and position == 0:
|
is_opening_mark = position == match.start()
|
||||||
continuation_quote = False
|
is_closing_mark = position == match.end() - 1
|
||||||
else:
|
if is_opening_mark and is_closing_mark:
|
||||||
in_double_quote = not in_double_quote
|
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:
|
elif in_double_quote:
|
||||||
quoted_word = True
|
quoted_word = True
|
||||||
|
if in_double_quote:
|
||||||
|
quoted_word = True
|
||||||
if quoted_word or any(
|
if quoted_word or any(
|
||||||
match.start() < quote.end() and match.end() > quote.start()
|
match.start() < quote.end() and match.end() > quote.start()
|
||||||
for quote in single_quotes
|
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);
|
uint32_t wordCount, uint8_t progressPercent);
|
||||||
void drawWord(const String& word, uint16_t wpm, uint8_t styleFlags = 0,
|
void drawWord(const String& word, uint16_t wpm, uint8_t styleFlags = 0,
|
||||||
uint8_t wordColorFlag = 0);
|
uint8_t wordColorFlag = 0);
|
||||||
|
void setReadingColors(uint16_t textColor, uint16_t dialogueColor);
|
||||||
void drawChapterTitle(const String& chapter, uint16_t wpm);
|
void drawChapterTitle(const String& chapter, uint16_t wpm);
|
||||||
void drawTheEnd(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 drawPicker(const String& title, const String& value);
|
||||||
void drawFontStylePicker(uint8_t style, uint8_t size);
|
void drawFontStylePicker(uint8_t style, uint8_t size);
|
||||||
void drawFontSizePicker(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 preferredBrightness_ = 5;
|
||||||
uint8_t fontStyle_ = 0;
|
uint8_t fontStyle_ = 0;
|
||||||
uint8_t fontSize_ = 20;
|
uint8_t fontSize_ = 20;
|
||||||
|
uint16_t textColor_ = 0xFFFF;
|
||||||
|
uint16_t dialogueColor_ = 0xB596;
|
||||||
bool landscape_ = false;
|
bool landscape_ = false;
|
||||||
bool powerMonitorAvailable_ = false;
|
bool powerMonitorAvailable_ = false;
|
||||||
bool statusDrawn_ = 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 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 kHues[] = {0, 30, 60, 120, 180, 225, 270, 300, 330};
|
||||||
constexpr uint16_t kColors[] = {
|
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(128, 255, 128), rgb565(128, 255, 255), rgb565(128, 159, 255),
|
||||||
rgb565(191, 128, 255), rgb565(255, 128, 255), rgb565(255, 128, 191),
|
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) {
|
inline int indexForHue(uint16_t hue) {
|
||||||
for (size_t index = 0; index < sizeof(kHues) / sizeof(kHues[0]); ++index) {
|
for (size_t index = 0; index < sizeof(kHues) / sizeof(kHues[0]); ++index) {
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,8 @@ struct SavedState {
|
||||||
uint8_t brightness = 5;
|
uint8_t brightness = 5;
|
||||||
uint8_t fontStyle = 0;
|
uint8_t fontStyle = 0;
|
||||||
uint8_t fontSize = 20;
|
uint8_t fontSize = 20;
|
||||||
bool colorsEnabled = true;
|
uint8_t textColor = 0;
|
||||||
|
uint8_t dialogueColor = 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Settings {
|
class Settings {
|
||||||
|
|
|
||||||
|
|
@ -173,7 +173,7 @@ void Board::drawStatus(uint16_t wpm) {
|
||||||
constexpr int16_t kBatteryTipWidth = 3;
|
constexpr int16_t kBatteryTipWidth = 3;
|
||||||
|
|
||||||
setUiFont(fontStyle_);
|
setUiFont(fontStyle_);
|
||||||
display_->setTextColor(TFT_WHITE);
|
display_->setTextColor(textColor_);
|
||||||
display_->setTextDatum(ML_DATUM);
|
display_->setTextDatum(ML_DATUM);
|
||||||
const String wpmLabel = String(wpm) + " WPM";
|
const String wpmLabel = String(wpm) + " WPM";
|
||||||
display_->drawString(wpmLabel, kMargin, kStatusHeight / 2);
|
display_->drawString(wpmLabel, kMargin, kStatusHeight / 2);
|
||||||
|
|
@ -184,11 +184,14 @@ void Board::drawStatus(uint16_t wpm) {
|
||||||
const String label = percentage < 0 ? "--" : String(percentage) + "%";
|
const String label = percentage < 0 ? "--" : String(percentage) + "%";
|
||||||
display_->setTextDatum(MR_DATUM);
|
display_->setTextDatum(MR_DATUM);
|
||||||
display_->drawString(label, batteryX - 5, kStatusHeight / 2);
|
display_->drawString(label, batteryX - 5, kStatusHeight / 2);
|
||||||
display_->drawRoundRect(batteryX, batteryY, kBatteryWidth, kBatteryHeight, 2, TFT_WHITE);
|
display_->drawRoundRect(batteryX, batteryY, kBatteryWidth, kBatteryHeight, 2,
|
||||||
display_->fillRect(batteryX + kBatteryWidth, batteryY + 4, kBatteryTipWidth, 4, TFT_WHITE);
|
textColor_);
|
||||||
|
display_->fillRect(batteryX + kBatteryWidth, batteryY + 4, kBatteryTipWidth, 4,
|
||||||
|
textColor_);
|
||||||
if (percentage > 0) {
|
if (percentage > 0) {
|
||||||
const int16_t fillWidth = (kBatteryWidth - 4) * percentage / 100;
|
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;
|
const int16_t left = kMargin + display_->textWidth(wpmLabel) + 8;
|
||||||
|
|
@ -225,7 +228,7 @@ void Board::drawFooter() {
|
||||||
display_->fillRect(0, display_->height() - kFooterHeight, display_->width(),
|
display_->fillRect(0, display_->height() - kFooterHeight, display_->width(),
|
||||||
kFooterHeight, TFT_BLACK);
|
kFooterHeight, TFT_BLACK);
|
||||||
setUiFont(fontStyle_);
|
setUiFont(fontStyle_);
|
||||||
display_->setTextColor(TFT_WHITE);
|
display_->setTextColor(textColor_);
|
||||||
const String details = String(wordCount_) + " words - " +
|
const String details = String(wordCount_) + " words - " +
|
||||||
String(progressPercent_) + "%";
|
String(progressPercent_) + "%";
|
||||||
const String separator = " - ";
|
const String separator = " - ";
|
||||||
|
|
@ -242,6 +245,15 @@ void Board::drawFooter() {
|
||||||
displayedProgressPercent_ = progressPercent_;
|
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,
|
void Board::drawWord(const String& word, uint16_t wpm, uint8_t styleFlags,
|
||||||
uint8_t wordColorFlag) {
|
uint8_t wordColorFlag) {
|
||||||
if (display_ == nullptr) return;
|
if (display_ == nullptr) return;
|
||||||
|
|
@ -272,7 +284,7 @@ void Board::drawWord(const String& word, uint16_t wpm, uint8_t styleFlags,
|
||||||
renderedSize -= config::kFontSizeStep;
|
renderedSize -= config::kFontSizeStep;
|
||||||
setReaderFont(fontStyle_, renderedSize, styleFlags);
|
setReaderFont(fontStyle_, renderedSize, styleFlags);
|
||||||
}
|
}
|
||||||
display_->setTextColor(wordColorFlag == 1 ? palette::kOtherGrey : TFT_WHITE);
|
display_->setTextColor(wordColorFlag == 1 ? dialogueColor_ : textColor_);
|
||||||
display_->setTextDatum(MC_DATUM);
|
display_->setTextDatum(MC_DATUM);
|
||||||
|
|
||||||
const int16_t centerX = display_->width() / 2;
|
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::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 (display_ == nullptr) return;
|
||||||
|
|
||||||
if (landscape_) {
|
if (landscape_) {
|
||||||
|
|
@ -323,7 +335,7 @@ void Board::drawMenu(uint8_t selectedOption, bool colorsEnabled) {
|
||||||
displayedProgressPercent_ = 255;
|
displayedProgressPercent_ = 255;
|
||||||
previousWordBounds_.valid = false;
|
previousWordBounds_.valid = false;
|
||||||
setUiFont(fontStyle_);
|
setUiFont(fontStyle_);
|
||||||
display_->setTextColor(TFT_WHITE);
|
display_->setTextColor(textColor_);
|
||||||
display_->setTextDatum(TC_DATUM);
|
display_->setTextDatum(TC_DATUM);
|
||||||
display_->drawString("MENU", display_->width() / 2, 18);
|
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 kFirstRowY = 54;
|
||||||
constexpr int16_t kRowSpacing = 56;
|
constexpr int16_t kRowSpacing = 56;
|
||||||
const char* labels[] = {"Back", "Book selection", "Chapter selection",
|
const char* labels[] = {"Back", "Book selection", "Chapter selection",
|
||||||
"Brightness", "Font",
|
"Brightness", "Font", "Colors"};
|
||||||
colorsEnabled ? "Colors: On" : "Colors: Off"};
|
|
||||||
display_->setTextDatum(ML_DATUM);
|
display_->setTextDatum(ML_DATUM);
|
||||||
for (uint8_t index = 0; index < 6; ++index) {
|
for (uint8_t index = 0; index < 6; ++index) {
|
||||||
const int16_t y = kFirstRowY + index * kRowSpacing;
|
const int16_t y = kFirstRowY + index * kRowSpacing;
|
||||||
if (index == selectedOption) {
|
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);
|
display_->setTextColor(TFT_BLACK);
|
||||||
} else {
|
} else {
|
||||||
display_->drawRoundRect(10, y, display_->width() - 20, kRowHeight, 5, TFT_WHITE);
|
display_->drawRoundRect(10, y, display_->width() - 20, kRowHeight, 5,
|
||||||
display_->setTextColor(TFT_WHITE);
|
textColor_);
|
||||||
|
display_->setTextColor(textColor_);
|
||||||
}
|
}
|
||||||
display_->drawString(labels[index], 24, y + kRowHeight / 2);
|
display_->drawString(labels[index], 24, y + kRowHeight / 2);
|
||||||
}
|
}
|
||||||
display_->setTextColor(TFT_WHITE);
|
display_->setTextColor(textColor_);
|
||||||
display_->setTextDatum(BC_DATUM);
|
display_->setTextDatum(BC_DATUM);
|
||||||
display_->drawString("PREV/NEXT choose", display_->width() / 2,
|
display_->drawString("PREV/NEXT choose", display_->width() / 2,
|
||||||
display_->height() - 36);
|
display_->height() - 36);
|
||||||
|
|
@ -353,6 +366,85 @@ void Board::drawMenu(uint8_t selectedOption, bool colorsEnabled) {
|
||||||
display_->height() - 8);
|
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) {
|
void Board::drawPicker(const String& title, const String& value) {
|
||||||
if (display_ == nullptr) return;
|
if (display_ == nullptr) return;
|
||||||
setLandscape();
|
setLandscape();
|
||||||
|
|
@ -361,7 +453,7 @@ void Board::drawPicker(const String& title, const String& value) {
|
||||||
statusDrawn_ = false;
|
statusDrawn_ = false;
|
||||||
previousWordBounds_.valid = false;
|
previousWordBounds_.valid = false;
|
||||||
setUiFont(fontStyle_);
|
setUiFont(fontStyle_);
|
||||||
display_->setTextColor(TFT_WHITE);
|
display_->setTextColor(textColor_);
|
||||||
display_->setTextDatum(TC_DATUM);
|
display_->setTextDatum(TC_DATUM);
|
||||||
display_->drawString(title, display_->width() / 2, 16);
|
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);
|
display_->width() / 2, display_->height() / 2 - 8);
|
||||||
|
|
||||||
setUiFont(fontStyle_);
|
setUiFont(fontStyle_);
|
||||||
|
display_->setTextColor(textColor_);
|
||||||
display_->setTextDatum(BC_DATUM);
|
display_->setTextDatum(BC_DATUM);
|
||||||
display_->drawString("PREV/NEXT choose", display_->width() / 2,
|
display_->drawString("PREV/NEXT choose", display_->width() / 2,
|
||||||
display_->height() - 36);
|
display_->height() - 36);
|
||||||
|
|
@ -390,18 +483,21 @@ void Board::drawFontStylePicker(uint8_t style, uint8_t size) {
|
||||||
statusDrawn_ = false;
|
statusDrawn_ = false;
|
||||||
previousWordBounds_.valid = false;
|
previousWordBounds_.valid = false;
|
||||||
setUiFont(style);
|
setUiFont(style);
|
||||||
display_->setTextColor(TFT_WHITE);
|
display_->setTextColor(textColor_);
|
||||||
display_->setTextDatum(TC_DATUM);
|
display_->setTextDatum(TC_DATUM);
|
||||||
display_->drawString("FONT STYLE", display_->width() / 2, 16);
|
display_->drawString("FONT STYLE", display_->width() / 2, 16);
|
||||||
|
|
||||||
const char* names[] = {"Sans-serif", "Serif", "Monospace"};
|
const char* names[] = {"Sans-serif", "Serif", "Monospace"};
|
||||||
setReaderFont(style, size);
|
setReaderFont(style, size);
|
||||||
|
display_->setTextColor(textColor_);
|
||||||
display_->setTextDatum(MC_DATUM);
|
display_->setTextDatum(MC_DATUM);
|
||||||
display_->drawString("Sample", display_->width() / 2, display_->height() / 2 - 8);
|
display_->drawString("Sample", display_->width() / 2, display_->height() / 2 - 8);
|
||||||
|
|
||||||
setUiFont(style);
|
setUiFont(style);
|
||||||
|
display_->setTextColor(textColor_);
|
||||||
display_->setTextDatum(TC_DATUM);
|
display_->setTextDatum(TC_DATUM);
|
||||||
display_->drawString(names[constrain(style, 0, 2)], display_->width() / 2, 48);
|
display_->drawString(names[constrain(style, 0, 2)], display_->width() / 2, 48);
|
||||||
|
display_->setTextColor(textColor_);
|
||||||
display_->setTextDatum(BC_DATUM);
|
display_->setTextDatum(BC_DATUM);
|
||||||
display_->drawString("PREV/NEXT choose", display_->width() / 2,
|
display_->drawString("PREV/NEXT choose", display_->width() / 2,
|
||||||
display_->height() - 36);
|
display_->height() - 36);
|
||||||
|
|
@ -417,16 +513,18 @@ void Board::drawFontSizePicker(uint8_t style, uint8_t size) {
|
||||||
statusDrawn_ = false;
|
statusDrawn_ = false;
|
||||||
previousWordBounds_.valid = false;
|
previousWordBounds_.valid = false;
|
||||||
setUiFont(style);
|
setUiFont(style);
|
||||||
display_->setTextColor(TFT_WHITE);
|
display_->setTextColor(textColor_);
|
||||||
display_->setTextDatum(TC_DATUM);
|
display_->setTextDatum(TC_DATUM);
|
||||||
display_->drawString("FONT SIZE", display_->width() / 2, 16);
|
display_->drawString("FONT SIZE", display_->width() / 2, 16);
|
||||||
|
|
||||||
setReaderFont(style, size);
|
setReaderFont(style, size);
|
||||||
|
display_->setTextColor(textColor_);
|
||||||
display_->setTextDatum(MC_DATUM);
|
display_->setTextDatum(MC_DATUM);
|
||||||
display_->drawString("size", display_->width() / 2,
|
display_->drawString("size", display_->width() / 2,
|
||||||
display_->height() / 2 - 8);
|
display_->height() / 2 - 8);
|
||||||
|
|
||||||
setUiFont(style);
|
setUiFont(style);
|
||||||
|
display_->setTextColor(textColor_);
|
||||||
display_->setTextDatum(BC_DATUM);
|
display_->setTextDatum(BC_DATUM);
|
||||||
display_->drawString("PREV/NEXT choose", display_->width() / 2,
|
display_->drawString("PREV/NEXT choose", display_->width() / 2,
|
||||||
display_->height() - 36);
|
display_->height() - 36);
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,8 @@ SavedState Settings::load() const {
|
||||||
state.brightness = preferences.getUChar("brightness", 5);
|
state.brightness = preferences.getUChar("brightness", 5);
|
||||||
state.fontStyle = preferences.getUChar("font_style", 0);
|
state.fontStyle = preferences.getUChar("font_style", 0);
|
||||||
state.fontSize = preferences.getUChar("font_size", 20);
|
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();
|
preferences.end();
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
@ -46,9 +47,13 @@ void Settings::save(const SavedState& state) const {
|
||||||
if (!preferences.isKey("font_size") || preferences.getUChar("font_size") != state.fontSize) {
|
if (!preferences.isKey("font_size") || preferences.getUChar("font_size") != state.fontSize) {
|
||||||
preferences.putUChar("font_size", state.fontSize);
|
preferences.putUChar("font_size", state.fontSize);
|
||||||
}
|
}
|
||||||
if (!preferences.isKey("colors") ||
|
if (!preferences.isKey("text_color") ||
|
||||||
preferences.getBool("colors") != state.colorsEnabled) {
|
preferences.getUChar("text_color") != state.textColor) {
|
||||||
preferences.putBool("colors", state.colorsEnabled);
|
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();
|
preferences.end();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
#include "Board.h"
|
#include "Board.h"
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
#include "Library.h"
|
#include "Library.h"
|
||||||
|
#include "Palette.h"
|
||||||
#include "Reader.h"
|
#include "Reader.h"
|
||||||
#include "Settings.h"
|
#include "Settings.h"
|
||||||
|
|
||||||
|
|
@ -35,6 +36,9 @@ enum class Screen {
|
||||||
kBrightnessPicker,
|
kBrightnessPicker,
|
||||||
kFontStylePicker,
|
kFontStylePicker,
|
||||||
kFontSizePicker,
|
kFontSizePicker,
|
||||||
|
kColorsMenu,
|
||||||
|
kTextColorPicker,
|
||||||
|
kDialogueColorPicker,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class ReaderView {
|
enum class ReaderView {
|
||||||
|
|
@ -49,7 +53,9 @@ String pendingChapterPath;
|
||||||
uint8_t pendingBrightness = config::kNormalBrightness;
|
uint8_t pendingBrightness = config::kNormalBrightness;
|
||||||
uint8_t pendingFontStyle = config::kDefaultFontStyle;
|
uint8_t pendingFontStyle = config::kDefaultFontStyle;
|
||||||
uint8_t pendingFontSize = config::kDefaultFontSize;
|
uint8_t pendingFontSize = config::kDefaultFontSize;
|
||||||
|
uint8_t pendingColor = 0;
|
||||||
uint8_t menuSelection = 0;
|
uint8_t menuSelection = 0;
|
||||||
|
uint8_t colorsMenuSelection = 0;
|
||||||
|
|
||||||
struct Button {
|
struct Button {
|
||||||
explicit Button(uint8_t buttonPin) : pin(buttonPin) {}
|
explicit Button(uint8_t buttonPin) : pin(buttonPin) {}
|
||||||
|
|
@ -97,14 +103,18 @@ void setReadingContext(uint8_t progressPercent) {
|
||||||
|
|
||||||
void showCurrentWord() {
|
void showCurrentWord() {
|
||||||
setReadingContext(reader.progressPercent());
|
setReadingContext(reader.progressPercent());
|
||||||
|
board.setReadingColors(palette::colorForChoice(state.textColor),
|
||||||
|
palette::colorForChoice(state.dialogueColor));
|
||||||
board.drawWord(reader.currentWord(), state.wpm, reader.currentStyleFlags(),
|
board.drawWord(reader.currentWord(), state.wpm, reader.currentStyleFlags(),
|
||||||
state.colorsEnabled ? reader.currentColorFlag() : 0);
|
reader.currentColorFlag());
|
||||||
}
|
}
|
||||||
|
|
||||||
void wakeDisplay();
|
void wakeDisplay();
|
||||||
void configureTouchLandscape();
|
void configureTouchLandscape();
|
||||||
|
|
||||||
void showReaderScreen() {
|
void showReaderScreen() {
|
||||||
|
board.setReadingColors(palette::colorForChoice(state.textColor),
|
||||||
|
palette::colorForChoice(state.dialogueColor));
|
||||||
switch (readerView) {
|
switch (readerView) {
|
||||||
case ReaderView::kChapterTitle:
|
case ReaderView::kChapterTitle:
|
||||||
setReadingContext(0);
|
setReadingContext(0);
|
||||||
|
|
@ -161,6 +171,19 @@ void drawPicker() {
|
||||||
case Screen::kFontSizePicker:
|
case Screen::kFontSizePicker:
|
||||||
board.drawFontSizePicker(pendingFontStyle, pendingFontSize);
|
board.drawFontSizePicker(pendingFontStyle, pendingFontSize);
|
||||||
break;
|
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:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -173,7 +196,22 @@ void openMenu() {
|
||||||
menuSelection = 0;
|
menuSelection = 0;
|
||||||
screen = Screen::kMenu;
|
screen = Screen::kMenu;
|
||||||
wakeDisplay();
|
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) {
|
void openPicker(Screen picker) {
|
||||||
|
|
@ -210,6 +248,11 @@ void changePicker(int direction) {
|
||||||
static_cast<int>(pendingFontSize) + direction * config::kFontSizeStep,
|
static_cast<int>(pendingFontSize) + direction * config::kFontSizeStep,
|
||||||
config::kMinimumFontSize, config::kMaximumFontSize);
|
config::kMinimumFontSize, config::kMaximumFontSize);
|
||||||
break;
|
break;
|
||||||
|
case Screen::kTextColorPicker:
|
||||||
|
case Screen::kDialogueColorPicker:
|
||||||
|
pendingColor = (pendingColor + palette::kChoiceCount + direction) %
|
||||||
|
palette::kChoiceCount;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -217,6 +260,22 @@ void changePicker(int direction) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void applyPicker() {
|
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) {
|
if (screen == Screen::kFontStylePicker) {
|
||||||
screen = Screen::kFontSizePicker;
|
screen = Screen::kFontSizePicker;
|
||||||
drawPicker();
|
drawPicker();
|
||||||
|
|
@ -333,14 +392,20 @@ void confirmMenuSelection() {
|
||||||
case 2: openPicker(Screen::kChapterPicker); break;
|
case 2: openPicker(Screen::kChapterPicker); break;
|
||||||
case 3: openPicker(Screen::kBrightnessPicker); break;
|
case 3: openPicker(Screen::kBrightnessPicker); break;
|
||||||
case 4: openPicker(Screen::kFontStylePicker); break;
|
case 4: openPicker(Screen::kFontStylePicker); break;
|
||||||
case 5:
|
case 5: openColorsMenu(); break;
|
||||||
state.colorsEnabled = !state.colorsEnabled;
|
|
||||||
persistState();
|
|
||||||
board.drawMenu(menuSelection, state.colorsEnabled);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (screen == Screen::kColorsMenu) {
|
||||||
|
if (colorsMenuSelection == 2) {
|
||||||
|
screen = Screen::kMenu;
|
||||||
|
board.drawMenu(menuSelection);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
openColorPicker(colorsMenuSelection == 0 ? Screen::kTextColorPicker
|
||||||
|
: Screen::kDialogueColorPicker);
|
||||||
|
return;
|
||||||
|
}
|
||||||
applyPicker();
|
applyPicker();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -397,7 +462,12 @@ void handleInput() {
|
||||||
wakeDisplay();
|
wakeDisplay();
|
||||||
if (screen == Screen::kMenu) {
|
if (screen == Screen::kMenu) {
|
||||||
menuSelection = (menuSelection + 5) % 6;
|
menuSelection = (menuSelection + 5) % 6;
|
||||||
board.drawMenu(menuSelection, state.colorsEnabled);
|
board.drawMenu(menuSelection);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (screen == Screen::kColorsMenu) {
|
||||||
|
colorsMenuSelection = (colorsMenuSelection + 2) % 3;
|
||||||
|
drawPicker();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (screen != Screen::kReader) {
|
if (screen != Screen::kReader) {
|
||||||
|
|
@ -417,7 +487,12 @@ void handleInput() {
|
||||||
wakeDisplay();
|
wakeDisplay();
|
||||||
if (screen == Screen::kMenu) {
|
if (screen == Screen::kMenu) {
|
||||||
menuSelection = (menuSelection + 1) % 6;
|
menuSelection = (menuSelection + 1) % 6;
|
||||||
board.drawMenu(menuSelection, state.colorsEnabled);
|
board.drawMenu(menuSelection);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (screen == Screen::kColorsMenu) {
|
||||||
|
colorsMenuSelection = (colorsMenuSelection + 1) % 3;
|
||||||
|
drawPicker();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (screen != Screen::kReader) {
|
if (screen != Screen::kReader) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue