Merge pull request #85 from jpirnay/rebase-keyboardpr1644
feat: Take new keyboard features on board (upstream 1644 by pablohc)
This commit is contained in:
@@ -44,6 +44,18 @@ STR_CONNECT_WIFI_HINT: "Connect your device to this WiFi network"
|
|||||||
STR_OPEN_URL_HINT: "Open this URL in your browser"
|
STR_OPEN_URL_HINT: "Open this URL in your browser"
|
||||||
STR_OR_HTTP_PREFIX: "or http://"
|
STR_OR_HTTP_PREFIX: "or http://"
|
||||||
STR_SCAN_QR_HINT: "or scan QR code with your phone:"
|
STR_SCAN_QR_HINT: "or scan QR code with your phone:"
|
||||||
|
STR_KEYBOARD_HELP_URL_LINE_1: "URL mode: Space toggles URL snippets."
|
||||||
|
STR_KEYBOARD_HELP_URL_LINE_2: "Use a 3×3 grid for protocols, hosts, and domains."
|
||||||
|
STR_KEYBOARD_HELP_URL_LINE_3: "Press ABC to exit URL mode."
|
||||||
|
STR_KEYBOARD_HELP_CURSOR_LINE_1: "Cursor mode: long-press Up, short Down to exit."
|
||||||
|
STR_KEYBOARD_HELP_CURSOR_LINE_2: "Left/Right moves the cursor; Confirm toggles [abc]/[***]."
|
||||||
|
STR_KEYBOARD_HELP_CURSOR_LINE_3: "Block cursor highlights the selected character."
|
||||||
|
STR_KEYBOARD_HELP_PASSWORD_LINE_1: "Password mode hides text with * and reveals one char."
|
||||||
|
STR_KEYBOARD_HELP_PASSWORD_LINE_2: "Use cursor mode past end to toggle [abc]/[***]."
|
||||||
|
STR_KEYBOARD_HELP_PASSWORD_LINE_3: "Long-press Del to clear all text."
|
||||||
|
STR_KEYBOARD_HELP_DEFAULT_LINE_1: "Long-press Up for cursor mode; Left/Right moves cursor."
|
||||||
|
STR_KEYBOARD_HELP_DEFAULT_LINE_2: "Hold Confirm for alternate character input in ABC mode."
|
||||||
|
STR_KEYBOARD_HELP_DEFAULT_LINE_3: "Shift toggles case; Space inserts a normal space."
|
||||||
STR_RSSI: "RSSI"
|
STR_RSSI: "RSSI"
|
||||||
STR_NO_SIGNAL: "No signal"
|
STR_NO_SIGNAL: "No signal"
|
||||||
STR_SIGNAL_QUALITY_POOR: "Poor"
|
STR_SIGNAL_QUALITY_POOR: "Poor"
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ void GlobalBookmarksActivity::renameSelected() {
|
|||||||
entry.bookmarks[bookmarkIndex].name.empty() ? getRowTitle(selectorIndex) : entry.bookmarks[bookmarkIndex].name;
|
entry.bookmarks[bookmarkIndex].name.empty() ? getRowTitle(selectorIndex) : entry.bookmarks[bookmarkIndex].name;
|
||||||
|
|
||||||
startActivityForResult(std::make_unique<KeyboardEntryActivity>(renderer, mappedInput, tr(STR_RENAME), initial,
|
startActivityForResult(std::make_unique<KeyboardEntryActivity>(renderer, mappedInput, tr(STR_RENAME), initial,
|
||||||
BookmarkStore::MAX_NAME_LENGTH, false),
|
BookmarkStore::MAX_NAME_LENGTH, InputType::Text),
|
||||||
[this, bookIndex, bookmarkIndex](const ActivityResult& result) {
|
[this, bookIndex, bookmarkIndex](const ActivityResult& result) {
|
||||||
if (!result.isCancelled) {
|
if (!result.isCancelled) {
|
||||||
const auto& kr = std::get<KeyboardResult>(result.data);
|
const auto& kr = std::get<KeyboardResult>(result.data);
|
||||||
|
|||||||
@@ -220,20 +220,18 @@ void WifiSelectionActivity::selectNetwork(const int index) {
|
|||||||
// Show password entry
|
// Show password entry
|
||||||
state = WifiSelectionState::PASSWORD_ENTRY;
|
state = WifiSelectionState::PASSWORD_ENTRY;
|
||||||
// Don't allow screen updates while changing activity
|
// Don't allow screen updates while changing activity
|
||||||
startActivityForResult(
|
startActivityForResult(std::make_unique<KeyboardEntryActivity>(renderer, mappedInput, tr(STR_ENTER_WIFI_PASSWORD),
|
||||||
std::make_unique<KeyboardEntryActivity>(renderer, mappedInput, tr(STR_ENTER_WIFI_PASSWORD),
|
"", // No initial text
|
||||||
"", // No initial text
|
64, // Max password length
|
||||||
64, // Max password length
|
InputType::Password),
|
||||||
false // Show password by default (hard keyboard to use)
|
[this](const ActivityResult& result) {
|
||||||
),
|
if (result.isCancelled) {
|
||||||
[this](const ActivityResult& result) {
|
state = WifiSelectionState::NETWORK_LIST;
|
||||||
if (result.isCancelled) {
|
} else {
|
||||||
state = WifiSelectionState::NETWORK_LIST;
|
enteredPassword = std::get<KeyboardResult>(result.data).text;
|
||||||
} else {
|
// state will be updated in next loop iteration
|
||||||
enteredPassword = std::get<KeyboardResult>(result.data).text;
|
}
|
||||||
// state will be updated in next loop iteration
|
});
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
// Connect directly for open networks
|
// Connect directly for open networks
|
||||||
attemptConnection();
|
attemptConnection();
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ void StarredPagesActivity::startRename() {
|
|||||||
const std::string initial =
|
const std::string initial =
|
||||||
all[renamingIndex].name.empty() ? getDefaultLabel(renamingIndex) : all[renamingIndex].name;
|
all[renamingIndex].name.empty() ? getDefaultLabel(renamingIndex) : all[renamingIndex].name;
|
||||||
startActivityForResult(std::make_unique<KeyboardEntryActivity>(renderer, mappedInput, tr(STR_RENAME), initial,
|
startActivityForResult(std::make_unique<KeyboardEntryActivity>(renderer, mappedInput, tr(STR_RENAME), initial,
|
||||||
BookmarkStore::MAX_NAME_LENGTH, false),
|
BookmarkStore::MAX_NAME_LENGTH, InputType::Text),
|
||||||
[this, renamingIndex](const ActivityResult& result) {
|
[this, renamingIndex](const ActivityResult& result) {
|
||||||
if (!result.isCancelled) {
|
if (!result.isCancelled) {
|
||||||
const auto& kr = std::get<KeyboardResult>(result.data);
|
const auto& kr = std::get<KeyboardResult>(result.data);
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ void CalibreSettingsActivity::handleSelection() {
|
|||||||
if (selectedIndex == 0) {
|
if (selectedIndex == 0) {
|
||||||
// OPDS Server URL
|
// OPDS Server URL
|
||||||
startActivityForResult(std::make_unique<KeyboardEntryActivity>(renderer, mappedInput, tr(STR_CALIBRE_WEB_URL),
|
startActivityForResult(std::make_unique<KeyboardEntryActivity>(renderer, mappedInput, tr(STR_CALIBRE_WEB_URL),
|
||||||
SETTINGS.opdsServerUrl, 127, false),
|
SETTINGS.opdsServerUrl, 127, InputType::Url),
|
||||||
[this](const ActivityResult& result) {
|
[this](const ActivityResult& result) {
|
||||||
if (!result.isCancelled) {
|
if (!result.isCancelled) {
|
||||||
const auto& kb = std::get<KeyboardResult>(result.data);
|
const auto& kb = std::get<KeyboardResult>(result.data);
|
||||||
@@ -64,7 +64,7 @@ void CalibreSettingsActivity::handleSelection() {
|
|||||||
} else if (selectedIndex == 1) {
|
} else if (selectedIndex == 1) {
|
||||||
// Username
|
// Username
|
||||||
startActivityForResult(std::make_unique<KeyboardEntryActivity>(renderer, mappedInput, tr(STR_USERNAME),
|
startActivityForResult(std::make_unique<KeyboardEntryActivity>(renderer, mappedInput, tr(STR_USERNAME),
|
||||||
SETTINGS.opdsUsername, 63, false),
|
SETTINGS.opdsUsername, 63, InputType::Text),
|
||||||
[this](const ActivityResult& result) {
|
[this](const ActivityResult& result) {
|
||||||
if (!result.isCancelled) {
|
if (!result.isCancelled) {
|
||||||
const auto& kb = std::get<KeyboardResult>(result.data);
|
const auto& kb = std::get<KeyboardResult>(result.data);
|
||||||
@@ -76,7 +76,7 @@ void CalibreSettingsActivity::handleSelection() {
|
|||||||
} else if (selectedIndex == 2) {
|
} else if (selectedIndex == 2) {
|
||||||
// Password
|
// Password
|
||||||
startActivityForResult(std::make_unique<KeyboardEntryActivity>(renderer, mappedInput, tr(STR_PASSWORD),
|
startActivityForResult(std::make_unique<KeyboardEntryActivity>(renderer, mappedInput, tr(STR_PASSWORD),
|
||||||
SETTINGS.opdsPassword, 63, false),
|
SETTINGS.opdsPassword, 63, InputType::Password),
|
||||||
[this](const ActivityResult& result) {
|
[this](const ActivityResult& result) {
|
||||||
if (!result.isCancelled) {
|
if (!result.isCancelled) {
|
||||||
const auto& kb = std::get<KeyboardResult>(result.data);
|
const auto& kb = std::get<KeyboardResult>(result.data);
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ void KOReaderSettingsActivity::onActionSelected(int index) {
|
|||||||
|
|
||||||
if (item.nameId == StrId::STR_USERNAME) {
|
if (item.nameId == StrId::STR_USERNAME) {
|
||||||
startActivityForResult(std::make_unique<KeyboardEntryActivity>(renderer, mappedInput, tr(STR_KOREADER_USERNAME),
|
startActivityForResult(std::make_unique<KeyboardEntryActivity>(renderer, mappedInput, tr(STR_KOREADER_USERNAME),
|
||||||
KOREADER_STORE.getUsername(), 64, false),
|
KOREADER_STORE.getUsername(), 64, InputType::Text),
|
||||||
[this](const ActivityResult& result) {
|
[this](const ActivityResult& result) {
|
||||||
if (!result.isCancelled) {
|
if (!result.isCancelled) {
|
||||||
const auto& kb = std::get<KeyboardResult>(result.data);
|
const auto& kb = std::get<KeyboardResult>(result.data);
|
||||||
@@ -72,28 +72,30 @@ void KOReaderSettingsActivity::onActionSelected(int index) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (item.nameId == StrId::STR_PASSWORD) {
|
} else if (item.nameId == StrId::STR_PASSWORD) {
|
||||||
startActivityForResult(std::make_unique<KeyboardEntryActivity>(renderer, mappedInput, tr(STR_KOREADER_PASSWORD),
|
|
||||||
KOREADER_STORE.getPassword(), 64, true),
|
|
||||||
[this](const ActivityResult& result) {
|
|
||||||
if (!result.isCancelled) {
|
|
||||||
const auto& kb = std::get<KeyboardResult>(result.data);
|
|
||||||
KOREADER_STORE.setCredentials(KOREADER_STORE.getUsername(), kb.text);
|
|
||||||
KOREADER_STORE.saveToFile();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (item.nameId == StrId::STR_SYNC_SERVER_URL) {
|
|
||||||
const std::string currentUrl = KOREADER_STORE.getServerUrl();
|
|
||||||
const std::string prefillUrl = currentUrl.empty() ? "https://" : currentUrl;
|
|
||||||
startActivityForResult(
|
startActivityForResult(
|
||||||
std::make_unique<KeyboardEntryActivity>(renderer, mappedInput, tr(STR_SYNC_SERVER_URL), prefillUrl, 128, false),
|
std::make_unique<KeyboardEntryActivity>(renderer, mappedInput, tr(STR_KOREADER_PASSWORD),
|
||||||
|
KOREADER_STORE.getPassword(), 64, InputType::Password),
|
||||||
[this](const ActivityResult& result) {
|
[this](const ActivityResult& result) {
|
||||||
if (!result.isCancelled) {
|
if (!result.isCancelled) {
|
||||||
const auto& kb = std::get<KeyboardResult>(result.data);
|
const auto& kb = std::get<KeyboardResult>(result.data);
|
||||||
const std::string urlToSave = (kb.text == "https://" || kb.text == "http://") ? "" : kb.text;
|
KOREADER_STORE.setCredentials(KOREADER_STORE.getUsername(), kb.text);
|
||||||
KOREADER_STORE.setServerUrl(urlToSave);
|
|
||||||
KOREADER_STORE.saveToFile();
|
KOREADER_STORE.saveToFile();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} else if (item.nameId == StrId::STR_SYNC_SERVER_URL) {
|
||||||
|
const std::string currentUrl = KOREADER_STORE.getServerUrl();
|
||||||
|
const std::string prefillUrl = currentUrl.empty() ? "https://" : currentUrl;
|
||||||
|
startActivityForResult(std::make_unique<KeyboardEntryActivity>(renderer, mappedInput, tr(STR_SYNC_SERVER_URL),
|
||||||
|
prefillUrl, 128, InputType::Url),
|
||||||
|
[this](const ActivityResult& result) {
|
||||||
|
if (!result.isCancelled) {
|
||||||
|
const auto& kb = std::get<KeyboardResult>(result.data);
|
||||||
|
const std::string urlToSave =
|
||||||
|
(kb.text == "https://" || kb.text == "http://") ? "" : kb.text;
|
||||||
|
KOREADER_STORE.setServerUrl(urlToSave);
|
||||||
|
KOREADER_STORE.saveToFile();
|
||||||
|
}
|
||||||
|
});
|
||||||
} else if (item.nameId == StrId::STR_AUTHENTICATE) {
|
} else if (item.nameId == StrId::STR_AUTHENTICATE) {
|
||||||
if (!KOREADER_STORE.hasCredentials()) return;
|
if (!KOREADER_STORE.hasCredentials()) return;
|
||||||
startActivityForResult(
|
startActivityForResult(
|
||||||
|
|||||||
@@ -2,32 +2,49 @@
|
|||||||
|
|
||||||
#include <I18n.h>
|
#include <I18n.h>
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "MappedInputManager.h"
|
#include "MappedInputManager.h"
|
||||||
#include "components/UITheme.h"
|
#include "components/UITheme.h"
|
||||||
#include "fontIds.h"
|
#include "fontIds.h"
|
||||||
|
|
||||||
|
const char* KeyboardEntryActivity::shiftLabel(int shiftState) {
|
||||||
|
return shiftState ? tr(STR_SHIFT_CAPS) : tr(STR_SHIFT);
|
||||||
|
}
|
||||||
|
|
||||||
void KeyboardEntryActivity::onEnter() {
|
void KeyboardEntryActivity::onEnter() {
|
||||||
Activity::onEnter();
|
Activity::onEnter();
|
||||||
|
cursorPos = text.length();
|
||||||
|
symMode = false;
|
||||||
|
urlMode = false;
|
||||||
|
cursorMode = false;
|
||||||
|
passwordVisible = false;
|
||||||
|
shiftState = 0;
|
||||||
|
selectedRow = 0;
|
||||||
|
selectedCol = 0;
|
||||||
|
confirmHeld = false;
|
||||||
|
confirmLongHandled = false;
|
||||||
|
upHeld = false;
|
||||||
|
upLongHandled = false;
|
||||||
|
downHeld = false;
|
||||||
|
downLongHandled = false;
|
||||||
requestUpdate();
|
requestUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
void KeyboardEntryActivity::onExit() { Activity::onExit(); }
|
void KeyboardEntryActivity::onExit() { Activity::onExit(); }
|
||||||
|
|
||||||
int KeyboardEntryActivity::getContentRowCount() const { return ABC_ROWS; }
|
int KeyboardEntryActivity::getContentRowCount() const {
|
||||||
|
if (urlMode) return 3;
|
||||||
|
return ABC_ROWS;
|
||||||
|
}
|
||||||
|
|
||||||
|
int KeyboardEntryActivity::getContentColCount() const {
|
||||||
|
if (urlMode) return 3;
|
||||||
|
return COLS;
|
||||||
|
}
|
||||||
|
|
||||||
int KeyboardEntryActivity::getTotalRowCount() const { return getContentRowCount() + 1; }
|
int KeyboardEntryActivity::getTotalRowCount() const { return getContentRowCount() + 1; }
|
||||||
|
|
||||||
int KeyboardEntryActivity::getBottomKeyCount() const { return isPassword ? 6 : 5; }
|
|
||||||
|
|
||||||
bool KeyboardEntryActivity::isBottomRow(const int row) const { return row == getContentRowCount(); }
|
bool KeyboardEntryActivity::isBottomRow(const int row) const { return row == getContentRowCount(); }
|
||||||
|
|
||||||
const char* KeyboardEntryActivity::getShiftLabel() const {
|
|
||||||
const StrId labelId = shiftState > 0 ? StrId::STR_SHIFT_CAPS : StrId::STR_SHIFT;
|
|
||||||
return I18n::getInstance().get(labelId);
|
|
||||||
}
|
|
||||||
|
|
||||||
char KeyboardEntryActivity::getSelectedChar() const {
|
char KeyboardEntryActivity::getSelectedChar() const {
|
||||||
const KeyDef(*layout)[COLS] = symMode ? symLayout : abcLayout;
|
const KeyDef(*layout)[COLS] = symMode ? symLayout : abcLayout;
|
||||||
|
|
||||||
@@ -35,8 +52,7 @@ char KeyboardEntryActivity::getSelectedChar() const {
|
|||||||
if (selectedCol < 0 || selectedCol >= COLS) return '\0';
|
if (selectedCol < 0 || selectedCol >= COLS) return '\0';
|
||||||
|
|
||||||
const KeyDef& key = layout[selectedRow][selectedCol];
|
const KeyDef& key = layout[selectedRow][selectedCol];
|
||||||
const bool useSecondary = !symMode && shiftState > 0 && key.secondary != '\0';
|
return (shiftState > 0 && key.secondary != '\0') ? key.secondary : key.primary;
|
||||||
return useSecondary ? key.secondary : key.primary;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
char KeyboardEntryActivity::getAlternativeChar() const {
|
char KeyboardEntryActivity::getAlternativeChar() const {
|
||||||
@@ -57,72 +73,64 @@ char KeyboardEntryActivity::getAlternativeChar() const {
|
|||||||
bool KeyboardEntryActivity::insertChar(char c) {
|
bool KeyboardEntryActivity::insertChar(char c) {
|
||||||
if (c == '\0') return true;
|
if (c == '\0') return true;
|
||||||
if (maxLength != 0 && text.length() >= maxLength) return true;
|
if (maxLength != 0 && text.length() >= maxLength) return true;
|
||||||
|
if (cursorPos > text.length()) cursorPos = text.length();
|
||||||
|
|
||||||
text += c;
|
text.insert(cursorPos, 1, c);
|
||||||
|
cursorPos++;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
SpecialKeyType KeyboardEntryActivity::getBottomSpecialKey(int index) const {
|
void KeyboardEntryActivity::insertString(const std::string& str) {
|
||||||
if (isPassword) {
|
if (str.empty()) return;
|
||||||
switch (index) {
|
if (maxLength != 0 && text.length() + str.length() > maxLength) return;
|
||||||
case 0:
|
if (cursorPos > text.length()) cursorPos = text.length();
|
||||||
return SpecShift;
|
|
||||||
case 1:
|
|
||||||
return SpecMode;
|
|
||||||
case 2:
|
|
||||||
return SpecReveal;
|
|
||||||
case 3:
|
|
||||||
return SpecSpace;
|
|
||||||
case 4:
|
|
||||||
return SpecDel;
|
|
||||||
case 5:
|
|
||||||
default:
|
|
||||||
return SpecOk;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (index) {
|
text.insert(cursorPos, str);
|
||||||
case 0:
|
cursorPos += str.length();
|
||||||
return SpecShift;
|
|
||||||
case 1:
|
|
||||||
return SpecMode;
|
|
||||||
case 2:
|
|
||||||
return SpecSpace;
|
|
||||||
case 3:
|
|
||||||
return SpecDel;
|
|
||||||
case 4:
|
|
||||||
default:
|
|
||||||
return SpecOk;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool KeyboardEntryActivity::handleKeyPress() {
|
bool KeyboardEntryActivity::handleKeyPress() {
|
||||||
if (isBottomRow(selectedRow)) {
|
if (isBottomRow(selectedRow)) {
|
||||||
switch (getBottomSpecialKey(selectedCol)) {
|
switch (static_cast<SpecialKeyType>(selectedCol)) {
|
||||||
case SpecShift:
|
case SpecShift:
|
||||||
|
if (urlMode) return true;
|
||||||
if (symMode) return true;
|
if (symMode) return true;
|
||||||
shiftState = (shiftState + 1) % 2;
|
shiftState = (shiftState + 1) % 2;
|
||||||
return true;
|
return true;
|
||||||
case SpecMode: {
|
case SpecMode: {
|
||||||
|
if (urlMode) {
|
||||||
|
urlMode = false;
|
||||||
|
symMode = false;
|
||||||
|
requestUpdate();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
symMode = !symMode;
|
symMode = !symMode;
|
||||||
int maxRow = getTotalRowCount() - 1;
|
int maxRow = getTotalRowCount() - 1;
|
||||||
if (selectedRow > maxRow) selectedRow = maxRow;
|
if (selectedRow > maxRow) selectedRow = maxRow;
|
||||||
if (isBottomRow(selectedRow)) {
|
if (isBottomRow(selectedRow)) {
|
||||||
int bottomCount = getBottomKeyCount();
|
if (selectedCol >= BOTTOM_KEY_COUNT) selectedCol = BOTTOM_KEY_COUNT - 1;
|
||||||
if (selectedCol >= bottomCount) selectedCol = bottomCount - 1;
|
|
||||||
} else {
|
} else {
|
||||||
if (selectedCol >= COLS) selectedCol = COLS - 1;
|
if (selectedCol >= getContentColCount()) selectedCol = getContentColCount() - 1;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
case SpecReveal:
|
|
||||||
passwordVisible = !passwordVisible;
|
|
||||||
return true;
|
|
||||||
case SpecSpace:
|
case SpecSpace:
|
||||||
return insertChar(' ');
|
if (inputType == InputType::Url) {
|
||||||
|
urlMode = !urlMode;
|
||||||
|
if (urlMode) {
|
||||||
|
symMode = false;
|
||||||
|
}
|
||||||
|
selectedRow = getTotalRowCount() - 1;
|
||||||
|
selectedCol = SpecSpace;
|
||||||
|
requestUpdate();
|
||||||
|
} else {
|
||||||
|
return insertChar(' ');
|
||||||
|
}
|
||||||
|
return true;
|
||||||
case SpecDel:
|
case SpecDel:
|
||||||
if (!text.empty()) {
|
if (cursorPos > 0 && !text.empty()) {
|
||||||
text.pop_back();
|
text.erase(cursorPos - 1, 1);
|
||||||
|
cursorPos--;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
case SpecOk:
|
case SpecOk:
|
||||||
@@ -133,46 +141,118 @@ bool KeyboardEntryActivity::handleKeyPress() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (urlMode) {
|
||||||
|
const int idx = selectedCol + selectedRow * 3;
|
||||||
|
if (idx < URL_SNIPPET_COUNT) {
|
||||||
|
insertString(urlSnippets[idx]);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return insertChar(getSelectedChar());
|
return insertChar(getSelectedChar());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void KeyboardEntryActivity::mapColContentBottom(int& col, bool goingUp) const {
|
||||||
|
if (urlMode) {
|
||||||
|
col = goingUp ? col - 1 : col + 1;
|
||||||
|
if (col < 0) col = 0;
|
||||||
|
if (col >= 3) col = 2;
|
||||||
|
} else {
|
||||||
|
col = goingUp ? col * 2 : col / 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void KeyboardEntryActivity::loop() {
|
void KeyboardEntryActivity::loop() {
|
||||||
const int totalRows = getTotalRowCount();
|
const int totalRows = getTotalRowCount();
|
||||||
|
|
||||||
buttonNavigator.onPressAndContinuous({MappedInputManager::Button::Up}, [this, totalRows] {
|
if (!cursorMode && mappedInput.wasPressed(MappedInputManager::Button::Up)) {
|
||||||
bool wasBottom = isBottomRow(selectedRow);
|
upHeld = true;
|
||||||
selectedRow = ButtonNavigator::previousIndex(selectedRow, totalRows);
|
upLongHandled = false;
|
||||||
if (wasBottom && !isBottomRow(selectedRow)) {
|
}
|
||||||
selectedCol = selectedCol * COLS / getBottomKeyCount();
|
|
||||||
} else if (!wasBottom && isBottomRow(selectedRow)) {
|
|
||||||
selectedCol = selectedCol * getBottomKeyCount() / COLS;
|
|
||||||
}
|
|
||||||
int maxCol = isBottomRow(selectedRow) ? getBottomKeyCount() - 1 : COLS - 1;
|
|
||||||
if (selectedCol > maxCol) selectedCol = maxCol;
|
|
||||||
requestUpdate();
|
|
||||||
});
|
|
||||||
|
|
||||||
buttonNavigator.onPressAndContinuous({MappedInputManager::Button::Down}, [this, totalRows] {
|
if (upHeld && !upLongHandled && mappedInput.isPressed(MappedInputManager::Button::Up) &&
|
||||||
bool wasBottom = isBottomRow(selectedRow);
|
mappedInput.getHeldTime() > LONG_PRESS_MS) {
|
||||||
selectedRow = ButtonNavigator::nextIndex(selectedRow, totalRows);
|
cursorMode = true;
|
||||||
if (wasBottom && !isBottomRow(selectedRow)) {
|
upLongHandled = true;
|
||||||
selectedCol = selectedCol * COLS / getBottomKeyCount();
|
|
||||||
} else if (!wasBottom && isBottomRow(selectedRow)) {
|
|
||||||
selectedCol = selectedCol * getBottomKeyCount() / COLS;
|
|
||||||
}
|
|
||||||
int maxCol = isBottomRow(selectedRow) ? getBottomKeyCount() - 1 : COLS - 1;
|
|
||||||
if (selectedCol > maxCol) selectedCol = maxCol;
|
|
||||||
requestUpdate();
|
requestUpdate();
|
||||||
});
|
}
|
||||||
|
|
||||||
|
if (mappedInput.wasReleased(MappedInputManager::Button::Up)) {
|
||||||
|
if (upHeld && !upLongHandled && !cursorMode) {
|
||||||
|
bool wasBottom = isBottomRow(selectedRow);
|
||||||
|
const int contentCols = getContentColCount();
|
||||||
|
selectedRow = ButtonNavigator::previousIndex(selectedRow, totalRows);
|
||||||
|
if (wasBottom && !isBottomRow(selectedRow)) {
|
||||||
|
mapColContentBottom(selectedCol, true);
|
||||||
|
} else if (!wasBottom && isBottomRow(selectedRow)) {
|
||||||
|
mapColContentBottom(selectedCol, false);
|
||||||
|
}
|
||||||
|
int maxCol = isBottomRow(selectedRow) ? BOTTOM_KEY_COUNT - 1 : contentCols - 1;
|
||||||
|
if (selectedCol > maxCol) selectedCol = maxCol;
|
||||||
|
requestUpdate();
|
||||||
|
}
|
||||||
|
upHeld = false;
|
||||||
|
upLongHandled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mappedInput.wasPressed(MappedInputManager::Button::Down)) {
|
||||||
|
downHeld = true;
|
||||||
|
if (cursorMode) {
|
||||||
|
if (cursorPos > text.length()) {
|
||||||
|
cursorPos = text.length();
|
||||||
|
}
|
||||||
|
passwordVisible = false;
|
||||||
|
cursorMode = false;
|
||||||
|
downLongHandled = true;
|
||||||
|
requestUpdate();
|
||||||
|
} else {
|
||||||
|
downLongHandled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mappedInput.wasReleased(MappedInputManager::Button::Down)) {
|
||||||
|
if (downHeld && !downLongHandled && !cursorMode) {
|
||||||
|
bool wasBottom = isBottomRow(selectedRow);
|
||||||
|
const int contentCols = getContentColCount();
|
||||||
|
selectedRow = ButtonNavigator::nextIndex(selectedRow, totalRows);
|
||||||
|
if (wasBottom && !isBottomRow(selectedRow)) {
|
||||||
|
mapColContentBottom(selectedCol, true);
|
||||||
|
} else if (!wasBottom && isBottomRow(selectedRow)) {
|
||||||
|
mapColContentBottom(selectedCol, false);
|
||||||
|
}
|
||||||
|
int maxCol = isBottomRow(selectedRow) ? BOTTOM_KEY_COUNT - 1 : contentCols - 1;
|
||||||
|
if (selectedCol > maxCol) selectedCol = maxCol;
|
||||||
|
requestUpdate();
|
||||||
|
}
|
||||||
|
downHeld = false;
|
||||||
|
downLongHandled = false;
|
||||||
|
}
|
||||||
|
|
||||||
buttonNavigator.onPressAndContinuous({MappedInputManager::Button::Left}, [this] {
|
buttonNavigator.onPressAndContinuous({MappedInputManager::Button::Left}, [this] {
|
||||||
int maxCol = isBottomRow(selectedRow) ? getBottomKeyCount() - 1 : COLS - 1;
|
if (cursorMode) {
|
||||||
|
if (cursorPos > 0) {
|
||||||
|
cursorPos--;
|
||||||
|
requestUpdate();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int maxCol = isBottomRow(selectedRow) ? BOTTOM_KEY_COUNT - 1 : getContentColCount() - 1;
|
||||||
selectedCol = ButtonNavigator::previousIndex(selectedCol, maxCol + 1);
|
selectedCol = ButtonNavigator::previousIndex(selectedCol, maxCol + 1);
|
||||||
requestUpdate();
|
requestUpdate();
|
||||||
});
|
});
|
||||||
|
|
||||||
buttonNavigator.onPressAndContinuous({MappedInputManager::Button::Right}, [this] {
|
buttonNavigator.onPressAndContinuous({MappedInputManager::Button::Right}, [this] {
|
||||||
int maxCol = isBottomRow(selectedRow) ? getBottomKeyCount() - 1 : COLS - 1;
|
if (cursorMode) {
|
||||||
|
if (cursorPos < text.length()) {
|
||||||
|
cursorPos++;
|
||||||
|
requestUpdate();
|
||||||
|
} else if (cursorPos == text.length() && inputType == InputType::Password) {
|
||||||
|
cursorPos = text.length() + 1;
|
||||||
|
requestUpdate();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int maxCol = isBottomRow(selectedRow) ? BOTTOM_KEY_COUNT - 1 : getContentColCount() - 1;
|
||||||
selectedCol = ButtonNavigator::nextIndex(selectedCol, maxCol + 1);
|
selectedCol = ButtonNavigator::nextIndex(selectedCol, maxCol + 1);
|
||||||
requestUpdate();
|
requestUpdate();
|
||||||
});
|
});
|
||||||
@@ -183,7 +263,15 @@ void KeyboardEntryActivity::loop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (confirmHeld && !confirmLongHandled && mappedInput.isPressed(MappedInputManager::Button::Confirm) &&
|
if (confirmHeld && !confirmLongHandled && mappedInput.isPressed(MappedInputManager::Button::Confirm) &&
|
||||||
mappedInput.getHeldTime() > LONG_PRESS_MS) {
|
mappedInput.getHeldTime() > DEL_LONG_PRESS_MS && isBottomRow(selectedRow) && selectedCol == SpecDel) {
|
||||||
|
text.clear();
|
||||||
|
cursorPos = 0;
|
||||||
|
confirmLongHandled = true;
|
||||||
|
requestUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (confirmHeld && !confirmLongHandled && mappedInput.isPressed(MappedInputManager::Button::Confirm) &&
|
||||||
|
mappedInput.getHeldTime() > LONG_PRESS_MS && !cursorMode && inputType == InputType::Text && !urlMode) {
|
||||||
char alt = getAlternativeChar();
|
char alt = getAlternativeChar();
|
||||||
if (alt != '\0') {
|
if (alt != '\0') {
|
||||||
insertChar(alt);
|
insertChar(alt);
|
||||||
@@ -193,10 +281,14 @@ void KeyboardEntryActivity::loop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
|
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
|
||||||
if (confirmHeld && !confirmLongHandled) {
|
if (confirmHeld && !confirmLongHandled && !cursorMode) {
|
||||||
if (handleKeyPress()) {
|
if (handleKeyPress()) {
|
||||||
requestUpdate();
|
requestUpdate();
|
||||||
}
|
}
|
||||||
|
} else if (confirmHeld && !confirmLongHandled && cursorMode && inputType == InputType::Password &&
|
||||||
|
cursorPos > text.length()) {
|
||||||
|
passwordVisible = !passwordVisible;
|
||||||
|
requestUpdate();
|
||||||
}
|
}
|
||||||
confirmHeld = false;
|
confirmHeld = false;
|
||||||
confirmLongHandled = false;
|
confirmLongHandled = false;
|
||||||
@@ -210,41 +302,158 @@ void KeyboardEntryActivity::loop() {
|
|||||||
void KeyboardEntryActivity::render(RenderLock&&) {
|
void KeyboardEntryActivity::render(RenderLock&&) {
|
||||||
renderer.clearScreen();
|
renderer.clearScreen();
|
||||||
|
|
||||||
const auto pageWidth = renderer.getScreenWidth();
|
|
||||||
const auto pageHeight = renderer.getScreenHeight();
|
|
||||||
const auto& metrics = UITheme::getInstance().getMetrics();
|
|
||||||
const Rect contentRect = UITheme::getContentRect(renderer, true, true);
|
const Rect contentRect = UITheme::getContentRect(renderer, true, true);
|
||||||
|
const int pageWidth = contentRect.width;
|
||||||
|
const int contentX = contentRect.x;
|
||||||
|
const int contentY = contentRect.y;
|
||||||
|
const auto& metrics = UITheme::getInstance().getMetrics();
|
||||||
|
|
||||||
GUI.drawHeader(renderer, Rect{contentRect.x, metrics.topPadding, contentRect.width, metrics.headerHeight},
|
GUI.drawHeader(renderer, Rect{contentX, contentY + metrics.topPadding, pageWidth, metrics.headerHeight},
|
||||||
title.c_str());
|
title.c_str());
|
||||||
|
|
||||||
const int lineHeight = renderer.getLineHeight(UI_12_FONT_ID);
|
const int lineHeight = renderer.getLineHeight(UI_12_FONT_ID);
|
||||||
const int inputStartY = metrics.topPadding + metrics.headerHeight + metrics.verticalSpacing +
|
const int inputStartY = contentY + metrics.topPadding + metrics.headerHeight + metrics.verticalSpacing +
|
||||||
metrics.verticalSpacing * 4 + metrics.keyboardVerticalOffset;
|
metrics.verticalSpacing * 4 + metrics.keyboardVerticalOffset;
|
||||||
int inputHeight = 0;
|
int inputHeight = lineHeight;
|
||||||
|
|
||||||
std::string displayText;
|
std::string displayText;
|
||||||
if (isPassword && !passwordVisible) {
|
if (inputType == InputType::Password && !passwordVisible) {
|
||||||
displayText = std::string(text.length(), '*');
|
size_t revealPos;
|
||||||
|
if (cursorMode) {
|
||||||
|
revealPos = text.length(); // no reveal in displayText; block draws actual char directly
|
||||||
|
} else {
|
||||||
|
revealPos = (text.length() > 0 && cursorPos > 0) ? cursorPos - 1 : 0;
|
||||||
|
}
|
||||||
|
displayText = text;
|
||||||
|
for (size_t i = 0; i < displayText.length(); i++) {
|
||||||
|
if (i != revealPos) {
|
||||||
|
displayText[i] = '*';
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
displayText = text;
|
displayText = text;
|
||||||
}
|
}
|
||||||
|
|
||||||
displayText += "_";
|
const bool isPassword = (inputType == InputType::Password);
|
||||||
|
const int margin = metrics.contentSidePadding;
|
||||||
|
const int extraMargin = 10;
|
||||||
|
const int effectiveMargin = margin + extraMargin;
|
||||||
|
const int toggleGap = isPassword ? 8 : 0;
|
||||||
|
const int toggleReserve = isPassword ? std::max(renderer.getTextWidth(UI_12_FONT_ID, "[abc]"),
|
||||||
|
renderer.getTextWidth(UI_12_FONT_ID, "[***]")) +
|
||||||
|
toggleGap
|
||||||
|
: 0;
|
||||||
|
const int textAreaWidth = pageWidth - 2 * effectiveMargin - toggleReserve;
|
||||||
|
const int maxLineWidth = textAreaWidth;
|
||||||
|
const bool centerText = metrics.keyboardCenteredText;
|
||||||
|
|
||||||
|
const int keyHeight = metrics.keyboardKeyHeight;
|
||||||
|
const int bottomKeyHeight = metrics.keyboardBottomKeyHeight;
|
||||||
|
const int keySpacing = metrics.keyboardKeySpacing;
|
||||||
|
const int contentCols = getContentColCount();
|
||||||
|
const int keyWidth = (pageWidth * 95 / 100 - (contentCols - 1) * keySpacing) / contentCols;
|
||||||
|
const int leftMargin = contentX + (pageWidth - (contentCols * keyWidth + (contentCols - 1) * keySpacing)) / 2;
|
||||||
|
|
||||||
|
const int bottomRowGap = metrics.keyboardBottomKeySpacing > 0 ? 4 : 0;
|
||||||
|
const int keyboardStartY = metrics.keyboardBottomAligned
|
||||||
|
? contentY + contentRect.height - metrics.verticalSpacing -
|
||||||
|
(keyHeight + keySpacing) * getContentRowCount() - bottomKeyHeight -
|
||||||
|
bottomRowGap + metrics.keyboardVerticalOffset
|
||||||
|
: inputStartY + inputHeight + lineHeight + metrics.verticalSpacing;
|
||||||
|
|
||||||
|
const char* helpTexts[3];
|
||||||
|
if (inputType == InputType::Url) {
|
||||||
|
helpTexts[0] = tr(STR_KEYBOARD_HELP_URL_LINE_1);
|
||||||
|
helpTexts[1] = tr(STR_KEYBOARD_HELP_URL_LINE_2);
|
||||||
|
helpTexts[2] = tr(STR_KEYBOARD_HELP_URL_LINE_3);
|
||||||
|
} else if (cursorMode) {
|
||||||
|
helpTexts[0] = tr(STR_KEYBOARD_HELP_CURSOR_LINE_1);
|
||||||
|
helpTexts[1] = tr(STR_KEYBOARD_HELP_CURSOR_LINE_2);
|
||||||
|
helpTexts[2] = tr(STR_KEYBOARD_HELP_CURSOR_LINE_3);
|
||||||
|
} else if (inputType == InputType::Password) {
|
||||||
|
helpTexts[0] = tr(STR_KEYBOARD_HELP_PASSWORD_LINE_1);
|
||||||
|
helpTexts[1] = tr(STR_KEYBOARD_HELP_PASSWORD_LINE_2);
|
||||||
|
helpTexts[2] = tr(STR_KEYBOARD_HELP_PASSWORD_LINE_3);
|
||||||
|
} else {
|
||||||
|
helpTexts[0] = tr(STR_KEYBOARD_HELP_DEFAULT_LINE_1);
|
||||||
|
helpTexts[1] = tr(STR_KEYBOARD_HELP_DEFAULT_LINE_2);
|
||||||
|
helpTexts[2] = tr(STR_KEYBOARD_HELP_DEFAULT_LINE_3);
|
||||||
|
}
|
||||||
|
|
||||||
|
const int helpGap = keyboardStartY - (inputStartY + inputHeight);
|
||||||
|
const int helpLineHeight = renderer.getLineHeight(SMALL_FONT_ID);
|
||||||
|
const int maxHelpLines = std::max(0, helpGap / helpLineHeight);
|
||||||
|
const int helpLineCount = std::min(3, maxHelpLines);
|
||||||
|
const int helpBodyHeight = helpLineCount * helpLineHeight;
|
||||||
|
const int helpTop = inputStartY + inputHeight + std::max(0, (helpGap - helpBodyHeight) / 2);
|
||||||
|
const int helpLineWidth = pageWidth - 2 * metrics.contentSidePadding;
|
||||||
|
for (int i = 0; i < helpLineCount; ++i) {
|
||||||
|
const char* helpText = helpTexts[i];
|
||||||
|
const std::string line = renderer.truncatedText(SMALL_FONT_ID, helpText, helpLineWidth, EpdFontFamily::ITALIC);
|
||||||
|
renderer.drawText(SMALL_FONT_ID, contentX + metrics.contentSidePadding, helpTop + i * helpLineHeight, line.c_str(),
|
||||||
|
true, EpdFontFamily::ITALIC);
|
||||||
|
}
|
||||||
|
|
||||||
|
int cursorCharWidth;
|
||||||
|
if (cursorPos < text.length()) {
|
||||||
|
cursorCharWidth = renderer.getTextWidth(UI_12_FONT_ID, text.substr(cursorPos, 1).c_str());
|
||||||
|
} else {
|
||||||
|
cursorCharWidth = 6;
|
||||||
|
}
|
||||||
|
|
||||||
int lineStartIdx = 0;
|
int lineStartIdx = 0;
|
||||||
int lineEndIdx = displayText.length();
|
int lineEndIdx = displayText.length();
|
||||||
int textWidth = 0;
|
int textWidth = 0;
|
||||||
|
int cursorPixelX = contentX + metrics.contentSidePadding;
|
||||||
|
int cursorLineY = inputStartY;
|
||||||
|
bool cursorDrawn = false;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
std::string lineText = displayText.substr(lineStartIdx, lineEndIdx - lineStartIdx);
|
std::string lineText = displayText.substr(lineStartIdx, lineEndIdx - lineStartIdx);
|
||||||
textWidth = renderer.getTextWidth(UI_12_FONT_ID, lineText.c_str());
|
textWidth = renderer.getTextWidth(UI_12_FONT_ID, lineText.c_str());
|
||||||
if (textWidth <= contentRect.width - 2 * metrics.contentSidePadding) {
|
if (textWidth <= maxLineWidth) {
|
||||||
if (metrics.keyboardCenteredText) {
|
const bool isLastLine = (lineEndIdx == static_cast<int>(displayText.length()));
|
||||||
const int centeredX = contentRect.x + (contentRect.width - textWidth) / 2;
|
bool isCursorLine = false;
|
||||||
renderer.drawText(UI_12_FONT_ID, centeredX, inputStartY + inputHeight, lineText.c_str());
|
if (!cursorDrawn && cursorPos >= lineStartIdx &&
|
||||||
|
(isLastLine ? cursorPos <= lineEndIdx : cursorPos < lineEndIdx)) {
|
||||||
|
std::string beforeCursor;
|
||||||
|
if (isPassword && !passwordVisible && cursorMode) {
|
||||||
|
beforeCursor = std::string(cursorPos - lineStartIdx, '*');
|
||||||
|
} else {
|
||||||
|
beforeCursor = displayText.substr(lineStartIdx, cursorPos - lineStartIdx);
|
||||||
|
}
|
||||||
|
int beforeWidth = renderer.getTextWidth(UI_12_FONT_ID, beforeCursor.c_str());
|
||||||
|
if (centerText) {
|
||||||
|
cursorPixelX = contentX + effectiveMargin + (maxLineWidth - textWidth) / 2 + beforeWidth;
|
||||||
|
} else {
|
||||||
|
cursorPixelX = contentX + effectiveMargin + beforeWidth;
|
||||||
|
}
|
||||||
|
if (cursorPos == static_cast<size_t>(lineEndIdx) && cursorPos == displayText.length()) {
|
||||||
|
cursorPixelX += 2;
|
||||||
|
}
|
||||||
|
cursorLineY = inputStartY + inputHeight;
|
||||||
|
cursorDrawn = true;
|
||||||
|
isCursorLine = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int lineStartX =
|
||||||
|
contentX + (centerText ? effectiveMargin + (maxLineWidth - textWidth) / 2 : effectiveMargin);
|
||||||
|
if (isCursorLine && cursorMode && isPassword && !passwordVisible) {
|
||||||
|
// Draw text in 3 parts to avoid block cursor overflowing onto next char.
|
||||||
|
// displayText uses '*' for all chars; actual char may be wider than '*'.
|
||||||
|
// Part 1: chars before cursor position
|
||||||
|
const std::string part1 = displayText.substr(lineStartIdx, cursorPos - lineStartIdx);
|
||||||
|
renderer.drawText(UI_12_FONT_ID, lineStartX, inputStartY + inputHeight, part1.c_str());
|
||||||
|
// Part 2: skip cursor slot (block + actual char drawn later)
|
||||||
|
// Part 3: chars after cursor position (skip char under cursor), starting at cursorPixelX + cursorCharWidth
|
||||||
|
const int afterStart = static_cast<int>(cursorPos) + (cursorPos < text.length() ? 1 : 0);
|
||||||
|
const int afterEnd = lineEndIdx;
|
||||||
|
if (afterStart < afterEnd) {
|
||||||
|
const std::string part3 = displayText.substr(afterStart, afterEnd - afterStart);
|
||||||
|
renderer.drawText(UI_12_FONT_ID, cursorPixelX + cursorCharWidth, inputStartY + inputHeight, part3.c_str());
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
renderer.drawText(UI_12_FONT_ID, contentRect.x + metrics.contentSidePadding, inputStartY + inputHeight,
|
renderer.drawText(UI_12_FONT_ID, lineStartX, inputStartY + inputHeight, lineText.c_str());
|
||||||
lineText.c_str());
|
|
||||||
}
|
}
|
||||||
if (lineEndIdx == displayText.length()) {
|
if (lineEndIdx == displayText.length()) {
|
||||||
break;
|
break;
|
||||||
@@ -258,75 +467,159 @@ void KeyboardEntryActivity::render(RenderLock&&) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GUI.drawTextField(renderer, Rect{contentRect.x, inputStartY, contentRect.width, inputHeight}, textWidth);
|
const int fieldWidth = (inputHeight > 0) ? maxLineWidth : textWidth;
|
||||||
|
const int lineMargin = margin + extraMargin - 5;
|
||||||
|
GUI.drawTextField(renderer, Rect{contentX, inputStartY, pageWidth, inputHeight}, fieldWidth, cursorMode, lineMargin,
|
||||||
|
pageWidth - 2 * lineMargin);
|
||||||
|
|
||||||
const int keyHeight = metrics.keyboardKeyHeight;
|
if (cursorMode && cursorPos <= displayText.length()) {
|
||||||
const int keySpacing = metrics.keyboardKeySpacing;
|
static constexpr int blockPadding = 1;
|
||||||
const int keyWidth = (pageWidth * 95 / 100 - (COLS - 1) * keySpacing) / COLS;
|
renderer.fillRect(cursorPixelX - blockPadding, cursorLineY, cursorCharWidth + blockPadding * 2, lineHeight, true);
|
||||||
const int leftMargin = (pageWidth - (COLS * keyWidth + (COLS - 1) * keySpacing)) / 2;
|
if (cursorPos < text.length()) {
|
||||||
|
const char buf[2] = {text[cursorPos], '\0'};
|
||||||
|
renderer.drawText(UI_12_FONT_ID, cursorPixelX, cursorLineY, buf, false);
|
||||||
|
}
|
||||||
|
} else if (!cursorMode && cursorPos <= displayText.length()) {
|
||||||
|
static constexpr int serifW = 3;
|
||||||
|
const int cX = cursorPixelX;
|
||||||
|
const int cY = cursorLineY;
|
||||||
|
const int cBottom = cursorLineY + lineHeight - 1;
|
||||||
|
renderer.fillRect(cX, cY, 2, lineHeight, true);
|
||||||
|
renderer.drawLine(cX - serifW, cY, cX - 1, cY, 2, true);
|
||||||
|
renderer.drawLine(cX + 1, cY, cX + serifW, cY, 2, true);
|
||||||
|
renderer.drawLine(cX - serifW, cBottom, cX - 1, cBottom, 2, true);
|
||||||
|
renderer.drawLine(cX + 1, cBottom, cX + serifW, cBottom, 2, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPassword) {
|
||||||
|
const char* toggleLabel = passwordVisible ? "[***]" : "[abc]";
|
||||||
|
const int toggleWidth = renderer.getTextWidth(UI_12_FONT_ID, toggleLabel);
|
||||||
|
const int toggleX = contentX + pageWidth - effectiveMargin - toggleWidth;
|
||||||
|
const int toggleY = inputStartY + inputHeight;
|
||||||
|
const bool toggleSelected = cursorMode && cursorPos > text.length();
|
||||||
|
|
||||||
|
if (toggleSelected) {
|
||||||
|
renderer.fillRect(toggleX - 2, toggleY, toggleWidth + 5, lineHeight + 3, true);
|
||||||
|
renderer.drawText(UI_12_FONT_ID, toggleX, toggleY, toggleLabel, false);
|
||||||
|
} else {
|
||||||
|
renderer.drawText(UI_12_FONT_ID, toggleX, toggleY, toggleLabel, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const int bkSpacing = metrics.keyboardBottomKeySpacing;
|
||||||
|
const int contentTotalWidth =
|
||||||
|
COLS * ((pageWidth * 95 / 100 - (COLS - 1) * keySpacing) / COLS) + (COLS - 1) * keySpacing;
|
||||||
|
const int bottomKeyWidth = (contentTotalWidth - (BOTTOM_KEY_COUNT - 1) * bkSpacing) / BOTTOM_KEY_COUNT;
|
||||||
|
const int bottomLeftMargin =
|
||||||
|
contentX + (pageWidth - (BOTTOM_KEY_COUNT * bottomKeyWidth + (BOTTOM_KEY_COUNT - 1) * bkSpacing)) / 2;
|
||||||
|
|
||||||
|
int urlLeftMargin = leftMargin;
|
||||||
|
if (urlMode) {
|
||||||
|
const int urlTotalWidth = 3 * keyWidth + 2 * keySpacing;
|
||||||
|
const int urlCenterX = bottomLeftMargin + SpecSpace * (bottomKeyWidth + bkSpacing) + bottomKeyWidth / 2;
|
||||||
|
urlLeftMargin = urlCenterX - urlTotalWidth / 2;
|
||||||
|
}
|
||||||
|
|
||||||
const int keyboardStartY = metrics.keyboardBottomAligned
|
|
||||||
? pageHeight - metrics.buttonHintsHeight - metrics.verticalSpacing -
|
|
||||||
(keyHeight + keySpacing) * getTotalRowCount() + metrics.keyboardVerticalOffset
|
|
||||||
: inputStartY + inputHeight + lineHeight + metrics.verticalSpacing;
|
|
||||||
const KeyDef(*layout)[COLS] = symMode ? symLayout : abcLayout;
|
const KeyDef(*layout)[COLS] = symMode ? symLayout : abcLayout;
|
||||||
const int contentRows = getContentRowCount();
|
const int contentRows = getContentRowCount();
|
||||||
|
|
||||||
for (int row = 0; row < contentRows; row++) {
|
for (int row = 0; row < contentRows; row++) {
|
||||||
const int rowY = keyboardStartY + row * (keyHeight + keySpacing);
|
const int rowY = keyboardStartY + row * (keyHeight + keySpacing);
|
||||||
|
const int rowLeftMargin = urlMode ? urlLeftMargin : leftMargin;
|
||||||
|
|
||||||
for (int col = 0; col < COLS; col++) {
|
for (int col = 0; col < contentCols; col++) {
|
||||||
const KeyDef& key = layout[row][col];
|
const int keyX = rowLeftMargin + col * (keyWidth + keySpacing);
|
||||||
const int keyX = leftMargin + col * (keyWidth + keySpacing);
|
|
||||||
const bool isSelected = row == selectedRow && col == selectedCol;
|
const bool isSelected = row == selectedRow && col == selectedCol;
|
||||||
|
const bool activeKeySelected = isSelected && !cursorMode;
|
||||||
|
|
||||||
char primaryChar = key.primary;
|
if (urlMode) {
|
||||||
char secondaryChar = key.secondary;
|
const int snippetIdx = col + row * 3;
|
||||||
|
if (snippetIdx < URL_SNIPPET_COUNT) {
|
||||||
|
GUI.drawKeyboardKey(renderer, Rect{keyX, rowY, keyWidth, keyHeight}, urlSnippets[snippetIdx],
|
||||||
|
activeKeySelected, nullptr);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const KeyDef& key = layout[row][col];
|
||||||
|
|
||||||
if (!symMode && shiftState > 0 && key.secondary != '\0') {
|
char primaryChar = key.primary;
|
||||||
primaryChar = key.secondary;
|
char secondaryChar = key.secondary;
|
||||||
secondaryChar = key.primary;
|
|
||||||
|
if (!symMode && shiftState > 0 && key.secondary != '\0') {
|
||||||
|
primaryChar = key.secondary;
|
||||||
|
secondaryChar = key.primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char primaryBuf[2] = {primaryChar, '\0'};
|
||||||
|
const char secondaryBuf[2] = {secondaryChar, '\0'};
|
||||||
|
const bool showSecondary = !symMode && row == 0 && secondaryChar != '\0';
|
||||||
|
GUI.drawKeyboardKey(renderer, Rect{keyX, rowY, keyWidth, keyHeight}, primaryBuf, activeKeySelected,
|
||||||
|
showSecondary ? secondaryBuf : nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
const char primaryBuf[2] = {primaryChar, '\0'};
|
|
||||||
const char secondaryBuf[2] = {secondaryChar, '\0'};
|
|
||||||
|
|
||||||
const bool showSecondary = !symMode && row == 0 && secondaryChar != '\0';
|
|
||||||
GUI.drawKeyboardKey(renderer, Rect{keyX, rowY, keyWidth, keyHeight}, primaryBuf, isSelected,
|
|
||||||
showSecondary ? secondaryBuf : nullptr);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const int bottomRowGap = metrics.keyboardBottomKeySpacing > 0 ? 4 : 0;
|
|
||||||
const int bottomRowY = keyboardStartY + contentRows * (keyHeight + keySpacing) + bottomRowGap;
|
const int bottomRowY = keyboardStartY + contentRows * (keyHeight + keySpacing) + bottomRowGap;
|
||||||
const int bkSpacing = metrics.keyboardBottomKeySpacing;
|
|
||||||
const int contentTotalWidth = COLS * keyWidth + (COLS - 1) * keySpacing;
|
|
||||||
const int bottomKeyCount = getBottomKeyCount();
|
|
||||||
const int bottomKeyWidth = (contentTotalWidth - (bottomKeyCount - 1) * bkSpacing) / bottomKeyCount;
|
|
||||||
const bool bottomSelected = isBottomRow(selectedRow);
|
const bool bottomSelected = isBottomRow(selectedRow);
|
||||||
|
|
||||||
struct BottomKeyInfo {
|
struct BottomKeyInfo {
|
||||||
KeyboardKeyType themeType;
|
KeyboardKeyType themeType;
|
||||||
const char* label;
|
const char* label;
|
||||||
};
|
};
|
||||||
|
const BottomKeyInfo bottomKeys[BOTTOM_KEY_COUNT] = {
|
||||||
|
{KeyboardKeyType::Shift, (symMode || urlMode) ? shiftLabel(0) : shiftLabel(shiftState)},
|
||||||
|
{KeyboardKeyType::Mode, urlMode ? "abc" : (symMode ? "abc" : "#@!")},
|
||||||
|
{inputType == InputType::Url ? KeyboardKeyType::Mode : KeyboardKeyType::Space,
|
||||||
|
inputType == InputType::Url ? "URL" : nullptr},
|
||||||
|
{KeyboardKeyType::Del, nullptr},
|
||||||
|
{KeyboardKeyType::Ok, tr(STR_OK_BUTTON)},
|
||||||
|
};
|
||||||
|
|
||||||
std::vector<BottomKeyInfo> bottomKeys;
|
for (int i = 0; i < BOTTOM_KEY_COUNT; i++) {
|
||||||
bottomKeys.reserve(6);
|
const int keyX = bottomLeftMargin + i * (bottomKeyWidth + bkSpacing);
|
||||||
bottomKeys.push_back({KeyboardKeyType::Shift, getShiftLabel()});
|
|
||||||
bottomKeys.push_back({KeyboardKeyType::Mode, symMode ? tr(STR_ABC) : "#@!"});
|
|
||||||
if (isPassword) {
|
|
||||||
bottomKeys.push_back({KeyboardKeyType::Reveal, passwordVisible ? tr(STR_HIDE) : tr(STR_SHOW)});
|
|
||||||
}
|
|
||||||
bottomKeys.push_back({KeyboardKeyType::Space, nullptr});
|
|
||||||
bottomKeys.push_back({KeyboardKeyType::Del, nullptr});
|
|
||||||
bottomKeys.push_back({KeyboardKeyType::Ok, tr(STR_OK_BUTTON)});
|
|
||||||
|
|
||||||
for (int i = 0; i < bottomKeyCount; i++) {
|
|
||||||
const int keyX = leftMargin + i * (bottomKeyWidth + bkSpacing);
|
|
||||||
const bool isSelected = bottomSelected && i == selectedCol;
|
const bool isSelected = bottomSelected && i == selectedCol;
|
||||||
|
|
||||||
GUI.drawKeyboardKey(renderer, Rect{keyX, bottomRowY, bottomKeyWidth, keyHeight}, bottomKeys[i].label, isSelected,
|
const bool activeKeySelected = isSelected && !cursorMode;
|
||||||
nullptr, bottomKeys[i].themeType);
|
GUI.drawKeyboardKey(renderer, Rect{keyX, bottomRowY, bottomKeyWidth, bottomKeyHeight}, bottomKeys[i].label,
|
||||||
|
activeKeySelected, nullptr, bottomKeys[i].themeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cursorMode) {
|
||||||
|
int selKeyX, selKeyY, selKeyW, selKeyH;
|
||||||
|
if (isBottomRow(selectedRow)) {
|
||||||
|
selKeyX = bottomLeftMargin + selectedCol * (bottomKeyWidth + bkSpacing);
|
||||||
|
selKeyY = bottomRowY;
|
||||||
|
selKeyW = bottomKeyWidth;
|
||||||
|
selKeyH = bottomKeyHeight;
|
||||||
|
} else {
|
||||||
|
const int rowLM = urlMode ? urlLeftMargin : leftMargin;
|
||||||
|
selKeyX = rowLM + selectedCol * (keyWidth + keySpacing);
|
||||||
|
selKeyY = keyboardStartY + selectedRow * (keyHeight + keySpacing);
|
||||||
|
selKeyW = keyWidth;
|
||||||
|
selKeyH = keyHeight;
|
||||||
|
}
|
||||||
|
if (isBottomRow(selectedRow)) {
|
||||||
|
GUI.drawKeyboardKey(renderer, Rect{selKeyX, selKeyY, selKeyW, selKeyH}, bottomKeys[selectedCol].label, true,
|
||||||
|
nullptr, bottomKeys[selectedCol].themeType, true);
|
||||||
|
} else if (urlMode) {
|
||||||
|
const int idx = selectedCol + selectedRow * 3;
|
||||||
|
if (idx < URL_SNIPPET_COUNT) {
|
||||||
|
GUI.drawKeyboardKey(renderer, Rect{selKeyX, selKeyY, selKeyW, selKeyH}, urlSnippets[idx], true, nullptr,
|
||||||
|
KeyboardKeyType::Normal, true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const KeyDef& selKey = layout[selectedRow][selectedCol];
|
||||||
|
char selPrimary = selKey.primary;
|
||||||
|
char selSecondary = selKey.secondary;
|
||||||
|
if (!symMode && shiftState > 0 && selKey.secondary != '\0') {
|
||||||
|
selPrimary = selKey.secondary;
|
||||||
|
selSecondary = selKey.primary;
|
||||||
|
}
|
||||||
|
const char selPrimaryBuf[2] = {selPrimary, '\0'};
|
||||||
|
const char selSecondaryBuf[2] = {selSecondary, '\0'};
|
||||||
|
const bool selShowSecondary = !symMode && selectedRow == 0 && selSecondary != '\0';
|
||||||
|
GUI.drawKeyboardKey(renderer, Rect{selKeyX, selKeyY, selKeyW, selKeyH}, selPrimaryBuf, true,
|
||||||
|
selShowSecondary ? selSecondaryBuf : nullptr, KeyboardKeyType::Normal, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_SELECT), tr(STR_DIR_LEFT), tr(STR_DIR_RIGHT));
|
const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_SELECT), tr(STR_DIR_LEFT), tr(STR_DIR_RIGHT));
|
||||||
|
|||||||
@@ -14,18 +14,20 @@ struct KeyDef {
|
|||||||
char secondary;
|
char secondary;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum SpecialKeyType { SpecShift, SpecMode, SpecReveal, SpecSpace, SpecDel, SpecOk };
|
enum SpecialKeyType { SpecShift, SpecMode, SpecSpace, SpecDel, SpecOk };
|
||||||
|
|
||||||
|
enum class InputType { Text, Password, Url };
|
||||||
|
|
||||||
class KeyboardEntryActivity : public Activity {
|
class KeyboardEntryActivity : public Activity {
|
||||||
public:
|
public:
|
||||||
explicit KeyboardEntryActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
explicit KeyboardEntryActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
||||||
std::string title = "Enter Text", std::string initialText = "",
|
std::string title = "Enter Text", std::string initialText = "",
|
||||||
const size_t maxLength = 0, const bool isPassword = false)
|
const size_t maxLength = 0, InputType inputType = InputType::Text)
|
||||||
: Activity("KeyboardEntry", renderer, mappedInput),
|
: Activity("KeyboardEntry", renderer, mappedInput),
|
||||||
title(std::move(title)),
|
title(std::move(title)),
|
||||||
text(std::move(initialText)),
|
text(std::move(initialText)),
|
||||||
maxLength(maxLength),
|
maxLength(maxLength),
|
||||||
isPassword(isPassword) {}
|
inputType(inputType) {}
|
||||||
|
|
||||||
void onEnter() override;
|
void onEnter() override;
|
||||||
void onExit() override;
|
void onExit() override;
|
||||||
@@ -36,7 +38,7 @@ class KeyboardEntryActivity : public Activity {
|
|||||||
std::string title;
|
std::string title;
|
||||||
std::string text;
|
std::string text;
|
||||||
size_t maxLength;
|
size_t maxLength;
|
||||||
bool isPassword;
|
InputType inputType;
|
||||||
bool passwordVisible = false;
|
bool passwordVisible = false;
|
||||||
|
|
||||||
ButtonNavigator buttonNavigator;
|
ButtonNavigator buttonNavigator;
|
||||||
@@ -48,14 +50,29 @@ class KeyboardEntryActivity : public Activity {
|
|||||||
bool confirmHeld = false;
|
bool confirmHeld = false;
|
||||||
bool confirmLongHandled = false;
|
bool confirmLongHandled = false;
|
||||||
|
|
||||||
|
bool cursorMode = false;
|
||||||
|
size_t cursorPos = 0;
|
||||||
|
bool upHeld = false;
|
||||||
|
bool upLongHandled = false;
|
||||||
|
bool downHeld = false;
|
||||||
|
bool downLongHandled = false;
|
||||||
|
|
||||||
|
bool urlMode = false;
|
||||||
|
static const char* shiftLabel(int shiftState);
|
||||||
|
static constexpr int URL_SNIPPET_COUNT = 9;
|
||||||
|
static constexpr const char* const urlSnippets[URL_SNIPPET_COUNT] = {
|
||||||
|
"https://", "www.", ".com", "http://", "192.168.", ".org", "/opds", ":8080", ".net"};
|
||||||
|
|
||||||
void onComplete(std::string text);
|
void onComplete(std::string text);
|
||||||
void onCancel();
|
void onCancel();
|
||||||
|
|
||||||
static constexpr uint16_t LONG_PRESS_MS = 500;
|
static constexpr uint16_t LONG_PRESS_MS = 500;
|
||||||
|
static constexpr uint16_t DEL_LONG_PRESS_MS = 1500;
|
||||||
|
|
||||||
static constexpr int COLS = 10;
|
static constexpr int COLS = 10;
|
||||||
static constexpr int ABC_ROWS = 4;
|
static constexpr int ABC_ROWS = 4;
|
||||||
static constexpr int SYM_ROWS = 4;
|
static constexpr int SYM_ROWS = 4;
|
||||||
|
static constexpr int BOTTOM_KEY_COUNT = 5;
|
||||||
|
|
||||||
static constexpr KeyDef abcLayout[ABC_ROWS][COLS] = {
|
static constexpr KeyDef abcLayout[ABC_ROWS][COLS] = {
|
||||||
{{'0', ')'},
|
{{'0', ')'},
|
||||||
@@ -143,15 +160,16 @@ class KeyboardEntryActivity : public Activity {
|
|||||||
{'`', '\0'}},
|
{'`', '\0'}},
|
||||||
};
|
};
|
||||||
|
|
||||||
const char* getShiftLabel() const;
|
static const char* const shiftString[2];
|
||||||
|
|
||||||
int getContentRowCount() const;
|
int getContentRowCount() const;
|
||||||
|
int getContentColCount() const;
|
||||||
int getTotalRowCount() const;
|
int getTotalRowCount() const;
|
||||||
int getBottomKeyCount() const;
|
|
||||||
SpecialKeyType getBottomSpecialKey(int index) const;
|
|
||||||
bool isBottomRow(int row) const;
|
bool isBottomRow(int row) const;
|
||||||
char getSelectedChar() const;
|
char getSelectedChar() const;
|
||||||
char getAlternativeChar() const;
|
char getAlternativeChar() const;
|
||||||
bool handleKeyPress();
|
bool handleKeyPress();
|
||||||
bool insertChar(char c);
|
bool insertChar(char c);
|
||||||
|
void insertString(const std::string& str);
|
||||||
|
void mapColContentBottom(int& col, bool goingUp) const;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -114,71 +114,71 @@ void WeatherSettingsActivity::handleSelection() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void WeatherSettingsActivity::launchCitySearch() {
|
void WeatherSettingsActivity::launchCitySearch() {
|
||||||
startActivityForResult(
|
startActivityForResult(std::make_unique<KeyboardEntryActivity>(renderer, mappedInput, tr(STR_WEATHER_SEARCH_CITY), "",
|
||||||
std::make_unique<KeyboardEntryActivity>(renderer, mappedInput, tr(STR_WEATHER_SEARCH_CITY), "", 64, false),
|
64, InputType::Text),
|
||||||
[this](const ActivityResult& result) {
|
[this](const ActivityResult& result) {
|
||||||
if (result.isCancelled) return;
|
if (result.isCancelled) return;
|
||||||
|
|
||||||
const auto& kb = std::get<KeyboardResult>(result.data);
|
const auto& kb = std::get<KeyboardResult>(result.data);
|
||||||
if (kb.text.empty()) return;
|
if (kb.text.empty()) return;
|
||||||
|
|
||||||
// Need WiFi for geocoding
|
// Need WiFi for geocoding
|
||||||
if (WiFi.status() != WL_CONNECTED || WiFi.localIP() == IPAddress(0, 0, 0, 0)) {
|
if (WiFi.status() != WL_CONNECTED || WiFi.localIP() == IPAddress(0, 0, 0, 0)) {
|
||||||
startActivityForResult(std::make_unique<WifiSelectionActivity>(renderer, mappedInput),
|
startActivityForResult(std::make_unique<WifiSelectionActivity>(renderer, mappedInput),
|
||||||
[this, query = kb.text](const ActivityResult& wifiResult) {
|
[this, query = kb.text](const ActivityResult& wifiResult) {
|
||||||
if (wifiResult.isCancelled) return;
|
if (wifiResult.isCancelled) return;
|
||||||
searchResults = WeatherClient::searchCity(query);
|
searchResults = WeatherClient::searchCity(query);
|
||||||
showingSearchResults = !searchResults.empty();
|
showingSearchResults = !searchResults.empty();
|
||||||
selectedIndex = 0;
|
selectedIndex = 0;
|
||||||
requestUpdate();
|
requestUpdate();
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
searchResults = WeatherClient::searchCity(kb.text);
|
searchResults = WeatherClient::searchCity(kb.text);
|
||||||
showingSearchResults = !searchResults.empty();
|
showingSearchResults = !searchResults.empty();
|
||||||
selectedIndex = 0;
|
selectedIndex = 0;
|
||||||
requestUpdate();
|
requestUpdate();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void WeatherSettingsActivity::launchLatitudeEntry() {
|
void WeatherSettingsActivity::launchLatitudeEntry() {
|
||||||
std::string current = std::to_string(WEATHER_SETTINGS.getLatitude());
|
std::string current = std::to_string(WEATHER_SETTINGS.getLatitude());
|
||||||
startActivityForResult(
|
startActivityForResult(std::make_unique<KeyboardEntryActivity>(renderer, mappedInput, tr(STR_WEATHER_LATITUDE),
|
||||||
std::make_unique<KeyboardEntryActivity>(renderer, mappedInput, tr(STR_WEATHER_LATITUDE), current, 12, false),
|
current, 12, InputType::Text),
|
||||||
[this](const ActivityResult& result) {
|
[this](const ActivityResult& result) {
|
||||||
if (!result.isCancelled) {
|
if (!result.isCancelled) {
|
||||||
const auto& kb = std::get<KeyboardResult>(result.data);
|
const auto& kb = std::get<KeyboardResult>(result.data);
|
||||||
char* end = nullptr;
|
char* end = nullptr;
|
||||||
const float lat = strtof(kb.text.c_str(), &end);
|
const float lat = strtof(kb.text.c_str(), &end);
|
||||||
if (end != kb.text.c_str() && *end == '\0' && lat >= -90.0f && lat <= 90.0f) {
|
if (end != kb.text.c_str() && *end == '\0' && lat >= -90.0f && lat <= 90.0f) {
|
||||||
if (lat != WEATHER_SETTINGS.getLatitude()) {
|
if (lat != WEATHER_SETTINGS.getLatitude()) {
|
||||||
WEATHER_SETTINGS.setLocation(lat, WEATHER_SETTINGS.getLongitude(), "");
|
WEATHER_SETTINGS.setLocation(lat, WEATHER_SETTINGS.getLongitude(), "");
|
||||||
}
|
}
|
||||||
WEATHER_SETTINGS.saveToFile();
|
WEATHER_SETTINGS.saveToFile();
|
||||||
requestUpdate();
|
requestUpdate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void WeatherSettingsActivity::launchLongitudeEntry() {
|
void WeatherSettingsActivity::launchLongitudeEntry() {
|
||||||
std::string current = std::to_string(WEATHER_SETTINGS.getLongitude());
|
std::string current = std::to_string(WEATHER_SETTINGS.getLongitude());
|
||||||
startActivityForResult(
|
startActivityForResult(std::make_unique<KeyboardEntryActivity>(renderer, mappedInput, tr(STR_WEATHER_LONGITUDE),
|
||||||
std::make_unique<KeyboardEntryActivity>(renderer, mappedInput, tr(STR_WEATHER_LONGITUDE), current, 12, false),
|
current, 12, InputType::Text),
|
||||||
[this](const ActivityResult& result) {
|
[this](const ActivityResult& result) {
|
||||||
if (!result.isCancelled) {
|
if (!result.isCancelled) {
|
||||||
const auto& kb = std::get<KeyboardResult>(result.data);
|
const auto& kb = std::get<KeyboardResult>(result.data);
|
||||||
char* end = nullptr;
|
char* end = nullptr;
|
||||||
const float lon = strtof(kb.text.c_str(), &end);
|
const float lon = strtof(kb.text.c_str(), &end);
|
||||||
if (end != kb.text.c_str() && *end == '\0' && lon >= -180.0f && lon <= 180.0f) {
|
if (end != kb.text.c_str() && *end == '\0' && lon >= -180.0f && lon <= 180.0f) {
|
||||||
if (lon != WEATHER_SETTINGS.getLongitude()) {
|
if (lon != WEATHER_SETTINGS.getLongitude()) {
|
||||||
WEATHER_SETTINGS.setLocation(WEATHER_SETTINGS.getLatitude(), lon, "");
|
WEATHER_SETTINGS.setLocation(WEATHER_SETTINGS.getLatitude(), lon, "");
|
||||||
}
|
}
|
||||||
WEATHER_SETTINGS.saveToFile();
|
WEATHER_SETTINGS.saveToFile();
|
||||||
requestUpdate();
|
requestUpdate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void WeatherSettingsActivity::toggleTempUnit() {
|
void WeatherSettingsActivity::toggleTempUnit() {
|
||||||
|
|||||||
@@ -878,40 +878,50 @@ void BaseTheme::drawHelpText(const GfxRenderer& renderer, Rect rect, const char*
|
|||||||
renderer.drawCenteredText(SMALL_FONT_ID, rect.y, truncatedLabel.c_str());
|
renderer.drawCenteredText(SMALL_FONT_ID, rect.y, truncatedLabel.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
void BaseTheme::drawTextField(const GfxRenderer& renderer, Rect rect, const int textWidth) const {
|
void BaseTheme::drawTextField(const GfxRenderer& renderer, Rect rect, const int textWidth, bool cursorMode,
|
||||||
|
int contentStartX, int contentWidth) const {
|
||||||
(void)textWidth;
|
(void)textWidth;
|
||||||
|
(void)contentStartX;
|
||||||
|
(void)contentWidth;
|
||||||
const int lineHeight = renderer.getLineHeight(UI_12_FONT_ID);
|
const int lineHeight = renderer.getLineHeight(UI_12_FONT_ID);
|
||||||
const int bracketHeight = lineHeight;
|
const int bracketHeight = lineHeight;
|
||||||
const int fieldLeft = rect.x + 10;
|
const int fieldLeft = rect.x + 15;
|
||||||
const int fieldRight = rect.x + rect.width - 15;
|
const int fieldRight = rect.x + rect.width - 15;
|
||||||
const int topY = rect.y;
|
const int topY = rect.y - 7;
|
||||||
const int bottomY = rect.y + rect.height + lineHeight;
|
const int bottomY = rect.y + rect.height + lineHeight + 7;
|
||||||
const int tickLen = bracketHeight / 2;
|
const int tickLen = bracketHeight / 2;
|
||||||
|
const int thickness = cursorMode ? 3 : 1;
|
||||||
|
|
||||||
renderer.drawLine(fieldLeft, topY, fieldLeft, bottomY);
|
renderer.fillRect(fieldLeft, topY, thickness, bottomY - topY + 1, true);
|
||||||
renderer.drawLine(fieldLeft, topY, fieldLeft + tickLen, topY);
|
renderer.drawLine(fieldLeft, topY, fieldLeft + tickLen, topY, thickness, true);
|
||||||
renderer.drawLine(fieldLeft, bottomY, fieldLeft + tickLen, bottomY);
|
renderer.drawLine(fieldLeft, bottomY, fieldLeft + tickLen, bottomY, thickness, true);
|
||||||
|
|
||||||
renderer.drawLine(fieldRight, topY, fieldRight, bottomY);
|
renderer.fillRect(fieldRight - thickness + 1, topY, thickness, bottomY - topY + 1, true);
|
||||||
renderer.drawLine(fieldRight, topY, fieldRight - tickLen, topY);
|
renderer.drawLine(fieldRight, topY, fieldRight - tickLen, topY, thickness, true);
|
||||||
renderer.drawLine(fieldRight, bottomY, fieldRight - tickLen, bottomY);
|
renderer.drawLine(fieldRight, bottomY, fieldRight - tickLen, bottomY, thickness, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BaseTheme::drawKeyboardKey(const GfxRenderer& renderer, Rect rect, const char* label, const bool isSelected,
|
void BaseTheme::drawKeyboardKey(const GfxRenderer& renderer, Rect rect, const char* label, const bool isSelected,
|
||||||
const char* secondaryLabel, const KeyboardKeyType keyType) const {
|
const char* secondaryLabel, const KeyboardKeyType keyType,
|
||||||
|
const bool inactiveSelection) const {
|
||||||
if (isSelected) {
|
if (isSelected) {
|
||||||
renderer.fillRect(rect.x, rect.y, rect.width, rect.height, true);
|
if (inactiveSelection) {
|
||||||
} else if (keyType == KeyboardKeyType::Shift || keyType == KeyboardKeyType::Mode ||
|
renderer.drawRect(rect.x, rect.y, rect.width, rect.height, 2, true);
|
||||||
keyType == KeyboardKeyType::Reveal || keyType == KeyboardKeyType::Space ||
|
} else {
|
||||||
keyType == KeyboardKeyType::Del || keyType == KeyboardKeyType::Ok) {
|
renderer.fillRect(rect.x, rect.y, rect.width, rect.height, true);
|
||||||
|
}
|
||||||
|
} else if (keyType == KeyboardKeyType::Shift || keyType == KeyboardKeyType::Mode || keyType == KeyboardKeyType::Del ||
|
||||||
|
keyType == KeyboardKeyType::Space || keyType == KeyboardKeyType::Ok) {
|
||||||
renderer.drawRect(rect.x, rect.y, rect.width, rect.height);
|
renderer.drawRect(rect.x, rect.y, rect.width, rect.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const bool invert = isSelected && !inactiveSelection;
|
||||||
|
|
||||||
if (keyType == KeyboardKeyType::Space) {
|
if (keyType == KeyboardKeyType::Space) {
|
||||||
const int lineHalfWidth = rect.width * 3 / 10;
|
const int lineHalfWidth = rect.width * 3 / 10;
|
||||||
const int centerX = rect.x + rect.width / 2;
|
const int centerX = rect.x + rect.width / 2;
|
||||||
const int lineY = rect.y + rect.height / 2 + 3;
|
const int lineY = rect.y + rect.height / 2 + 3;
|
||||||
renderer.drawLine(centerX - lineHalfWidth, lineY, centerX + lineHalfWidth, lineY, 3, !isSelected);
|
renderer.drawLine(centerX - lineHalfWidth, lineY, centerX + lineHalfWidth, lineY, 3, !invert);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -920,27 +930,24 @@ void BaseTheme::drawKeyboardKey(const GfxRenderer& renderer, Rect rect, const ch
|
|||||||
const int centerY = rect.y + rect.height / 2;
|
const int centerY = rect.y + rect.height / 2;
|
||||||
const int arrowLen = rect.width / 4;
|
const int arrowLen = rect.width / 4;
|
||||||
const int arrowHead = arrowLen / 2;
|
const int arrowHead = arrowLen / 2;
|
||||||
renderer.drawLine(centerX - arrowLen / 2, centerY, centerX + arrowLen / 2, centerY, 3, !isSelected);
|
renderer.drawLine(centerX - arrowLen / 2, centerY, centerX + arrowLen / 2, centerY, 3, !invert);
|
||||||
renderer.drawLine(centerX - arrowLen / 2, centerY, centerX - arrowLen / 2 + arrowHead, centerY - arrowHead, 3,
|
renderer.drawLine(centerX - arrowLen / 2, centerY, centerX - arrowLen / 2 + arrowHead, centerY - arrowHead, 3,
|
||||||
!isSelected);
|
!invert);
|
||||||
renderer.drawLine(centerX - arrowLen / 2, centerY, centerX - arrowLen / 2 + arrowHead, centerY + arrowHead, 3,
|
renderer.drawLine(centerX - arrowLen / 2, centerY, centerX - arrowLen / 2 + arrowHead, centerY + arrowHead, 3,
|
||||||
!isSelected);
|
!invert);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool hasSecondary = secondaryLabel != nullptr && secondaryLabel[0] != '\0';
|
const bool hasSecondary = secondaryLabel != nullptr && secondaryLabel[0] != '\0';
|
||||||
const int primaryOffset = 0;
|
const int primaryOffset = 0;
|
||||||
const int fontId = (keyType == KeyboardKeyType::Shift || keyType == KeyboardKeyType::Mode ||
|
const int itemWidth = renderer.getTextWidth(UI_12_FONT_ID, label);
|
||||||
keyType == KeyboardKeyType::Reveal || keyType == KeyboardKeyType::Ok)
|
|
||||||
? UI_10_FONT_ID
|
|
||||||
: UI_12_FONT_ID;
|
|
||||||
const int itemWidth = renderer.getTextWidth(fontId, label);
|
|
||||||
const int textX = rect.x + (rect.width - itemWidth) / 2;
|
const int textX = rect.x + (rect.width - itemWidth) / 2;
|
||||||
const int textY = rect.y + (rect.height - renderer.getLineHeight(fontId)) / 2 + primaryOffset;
|
const int textY = rect.y + (rect.height - renderer.getLineHeight(UI_12_FONT_ID)) / 2 + primaryOffset;
|
||||||
renderer.drawText(fontId, textX, textY, label, !isSelected);
|
|
||||||
|
|
||||||
if (hasSecondary) {
|
if (hasSecondary) {
|
||||||
const int secWidth = renderer.getTextWidth(SMALL_FONT_ID, secondaryLabel);
|
const int secWidth = renderer.getTextWidth(SMALL_FONT_ID, secondaryLabel);
|
||||||
renderer.drawText(SMALL_FONT_ID, rect.x + rect.width - secWidth - 1, rect.y - 3, secondaryLabel, !isSelected);
|
renderer.drawText(SMALL_FONT_ID, rect.x + rect.width - secWidth - 1, rect.y, secondaryLabel, !invert);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderer.drawText(UI_12_FONT_ID, textX, textY, label, !invert);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ struct ThemeMetrics {
|
|||||||
int keyboardKeyWidth;
|
int keyboardKeyWidth;
|
||||||
int keyboardKeyHeight;
|
int keyboardKeyHeight;
|
||||||
int keyboardKeySpacing;
|
int keyboardKeySpacing;
|
||||||
|
int keyboardBottomKeyHeight;
|
||||||
int keyboardBottomKeySpacing;
|
int keyboardBottomKeySpacing;
|
||||||
bool keyboardBottomAligned;
|
bool keyboardBottomAligned;
|
||||||
bool keyboardCenteredText;
|
bool keyboardCenteredText;
|
||||||
@@ -100,12 +101,13 @@ constexpr ThemeMetrics values = {.batteryWidth = 15,
|
|||||||
.statusBarHorizontalMargin = 5,
|
.statusBarHorizontalMargin = 5,
|
||||||
.statusBarVerticalMargin = 19,
|
.statusBarVerticalMargin = 19,
|
||||||
.keyboardKeyWidth = 22,
|
.keyboardKeyWidth = 22,
|
||||||
.keyboardKeyHeight = 30,
|
.keyboardKeyHeight = 40,
|
||||||
.keyboardKeySpacing = 10,
|
.keyboardKeySpacing = 0,
|
||||||
.keyboardBottomKeySpacing = 0,
|
.keyboardBottomKeyHeight = 35,
|
||||||
|
.keyboardBottomKeySpacing = 5,
|
||||||
.keyboardBottomAligned = true,
|
.keyboardBottomAligned = true,
|
||||||
.keyboardCenteredText = false,
|
.keyboardCenteredText = false,
|
||||||
.keyboardVerticalOffset = -10};
|
.keyboardVerticalOffset = -13};
|
||||||
}
|
}
|
||||||
|
|
||||||
class BaseTheme {
|
class BaseTheme {
|
||||||
@@ -147,10 +149,11 @@ class BaseTheme {
|
|||||||
const int pageCount, std::string title, const int paddingBottom = 0,
|
const int pageCount, std::string title, const int paddingBottom = 0,
|
||||||
const int textYOffset = 0, const bool isStarred = false) const;
|
const int textYOffset = 0, const bool isStarred = false) const;
|
||||||
virtual void drawHelpText(const GfxRenderer& renderer, Rect rect, const char* label) const;
|
virtual void drawHelpText(const GfxRenderer& renderer, Rect rect, const char* label) const;
|
||||||
virtual void drawTextField(const GfxRenderer& renderer, Rect rect, const int textWidth) const;
|
virtual void drawTextField(const GfxRenderer& renderer, Rect rect, const int textWidth, bool cursorMode = false,
|
||||||
|
int contentStartX = 0, int contentWidth = 0) const;
|
||||||
virtual void drawKeyboardKey(const GfxRenderer& renderer, Rect rect, const char* label, const bool isSelected,
|
virtual void drawKeyboardKey(const GfxRenderer& renderer, Rect rect, const char* label, const bool isSelected,
|
||||||
const char* secondaryLabel = nullptr,
|
const char* secondaryLabel = nullptr, KeyboardKeyType keyType = KeyboardKeyType::Normal,
|
||||||
KeyboardKeyType keyType = KeyboardKeyType::Normal) const;
|
bool inactiveSelection = false) const;
|
||||||
virtual bool showsFileIcons() const { return false; }
|
virtual bool showsFileIcons() const { return false; }
|
||||||
|
|
||||||
// Shared constants and helpers for battery drawing (used by all themes)
|
// Shared constants and helpers for battery drawing (used by all themes)
|
||||||
|
|||||||
@@ -34,12 +34,13 @@ constexpr ThemeMetrics values = {.batteryWidth = 16,
|
|||||||
.statusBarHorizontalMargin = 5,
|
.statusBarHorizontalMargin = 5,
|
||||||
.statusBarVerticalMargin = 19,
|
.statusBarVerticalMargin = 19,
|
||||||
.keyboardKeyWidth = 31,
|
.keyboardKeyWidth = 31,
|
||||||
.keyboardKeyHeight = 50,
|
.keyboardKeyHeight = 40,
|
||||||
.keyboardKeySpacing = 0,
|
.keyboardKeySpacing = 0,
|
||||||
|
.keyboardBottomKeyHeight = 35,
|
||||||
.keyboardBottomKeySpacing = 5,
|
.keyboardBottomKeySpacing = 5,
|
||||||
.keyboardBottomAligned = true,
|
.keyboardBottomAligned = true,
|
||||||
.keyboardCenteredText = true,
|
.keyboardCenteredText = false,
|
||||||
.keyboardVerticalOffset = 0};
|
.keyboardVerticalOffset = -7};
|
||||||
}
|
}
|
||||||
|
|
||||||
class Lyra3CoversTheme : public LyraTheme {
|
class Lyra3CoversTheme : public LyraTheme {
|
||||||
|
|||||||
@@ -790,23 +790,34 @@ void LyraTheme::fillPopupProgress(const GfxRenderer& renderer, const Rect& layou
|
|||||||
renderer.displayBuffer(HalDisplay::FAST_REFRESH);
|
renderer.displayBuffer(HalDisplay::FAST_REFRESH);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LyraTheme::drawTextField(const GfxRenderer& renderer, Rect rect, const int textWidth) const {
|
void LyraTheme::drawTextField(const GfxRenderer& renderer, Rect rect, const int textWidth, bool cursorMode,
|
||||||
|
int contentStartX, int contentWidth) const {
|
||||||
int lineY = rect.y + rect.height + renderer.getLineHeight(UI_12_FONT_ID) + LyraMetrics::values.verticalSpacing;
|
int lineY = rect.y + rect.height + renderer.getLineHeight(UI_12_FONT_ID) + LyraMetrics::values.verticalSpacing;
|
||||||
int lineW = textWidth + hPaddingInSelection * 2;
|
const int thickness = cursorMode ? 3 : 2;
|
||||||
renderer.drawLine(rect.x + (rect.width - lineW) / 2, lineY, rect.x + (rect.width + lineW) / 2, lineY, 3);
|
if (contentWidth > 0) {
|
||||||
|
renderer.drawLine(rect.x + contentStartX, lineY, rect.x + contentStartX + contentWidth, lineY, thickness, true);
|
||||||
|
} else {
|
||||||
|
int lineW = textWidth + hPaddingInSelection * 2;
|
||||||
|
renderer.drawLine(rect.x + (rect.width - lineW) / 2, lineY, rect.x + (rect.width + lineW) / 2, lineY, thickness,
|
||||||
|
true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void LyraTheme::drawKeyboardKey(const GfxRenderer& renderer, Rect rect, const char* label, const bool isSelected,
|
void LyraTheme::drawKeyboardKey(const GfxRenderer& renderer, Rect rect, const char* label, const bool isSelected,
|
||||||
const char* secondaryLabel, const KeyboardKeyType keyType) const {
|
const char* secondaryLabel, const KeyboardKeyType keyType,
|
||||||
|
const bool inactiveSelection) const {
|
||||||
if (isSelected) {
|
if (isSelected) {
|
||||||
renderer.fillRoundedRect(rect.x, rect.y, rect.width, rect.height, cornerRadius, Color::Black);
|
if (inactiveSelection) {
|
||||||
} else if (keyType == KeyboardKeyType::Shift || keyType == KeyboardKeyType::Mode ||
|
renderer.fillRoundedRect(rect.x, rect.y, rect.width, rect.height, cornerRadius, Color::LightGray);
|
||||||
keyType == KeyboardKeyType::Reveal || keyType == KeyboardKeyType::Del ||
|
} else {
|
||||||
|
renderer.fillRoundedRect(rect.x, rect.y, rect.width, rect.height, cornerRadius, Color::Black);
|
||||||
|
}
|
||||||
|
} else if (keyType == KeyboardKeyType::Shift || keyType == KeyboardKeyType::Mode || keyType == KeyboardKeyType::Del ||
|
||||||
keyType == KeyboardKeyType::Space || keyType == KeyboardKeyType::Ok) {
|
keyType == KeyboardKeyType::Space || keyType == KeyboardKeyType::Ok) {
|
||||||
renderer.drawRoundedRect(rect.x, rect.y, rect.width, rect.height, 1, cornerRadius, true);
|
renderer.drawRoundedRect(rect.x, rect.y, rect.width, rect.height, 1, cornerRadius, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool invert = isSelected;
|
const bool invert = isSelected && !inactiveSelection;
|
||||||
|
|
||||||
if (keyType == KeyboardKeyType::Space) {
|
if (keyType == KeyboardKeyType::Space) {
|
||||||
const int lineHalfWidth = rect.width * 3 / 10;
|
const int lineHalfWidth = rect.width * 3 / 10;
|
||||||
|
|||||||
@@ -34,10 +34,11 @@ constexpr ThemeMetrics values = {.batteryWidth = 16,
|
|||||||
.keyboardKeyWidth = 31,
|
.keyboardKeyWidth = 31,
|
||||||
.keyboardKeyHeight = 40,
|
.keyboardKeyHeight = 40,
|
||||||
.keyboardKeySpacing = 0,
|
.keyboardKeySpacing = 0,
|
||||||
|
.keyboardBottomKeyHeight = 35,
|
||||||
.keyboardBottomKeySpacing = 5,
|
.keyboardBottomKeySpacing = 5,
|
||||||
.keyboardBottomAligned = true,
|
.keyboardBottomAligned = true,
|
||||||
.keyboardCenteredText = true,
|
.keyboardCenteredText = false,
|
||||||
.keyboardVerticalOffset = -12};
|
.keyboardVerticalOffset = -7};
|
||||||
}
|
}
|
||||||
|
|
||||||
class LyraTheme : public BaseTheme {
|
class LyraTheme : public BaseTheme {
|
||||||
@@ -68,10 +69,11 @@ class LyraTheme : public BaseTheme {
|
|||||||
void drawEmptyRecents(const GfxRenderer& renderer, const Rect rect) const;
|
void drawEmptyRecents(const GfxRenderer& renderer, const Rect rect) const;
|
||||||
Rect drawPopup(const GfxRenderer& renderer, const char* message) const override;
|
Rect drawPopup(const GfxRenderer& renderer, const char* message) const override;
|
||||||
void fillPopupProgress(const GfxRenderer& renderer, const Rect& layout, const int progress) const override;
|
void fillPopupProgress(const GfxRenderer& renderer, const Rect& layout, const int progress) const override;
|
||||||
void drawTextField(const GfxRenderer& renderer, Rect rect, const int textWidth) const override;
|
void drawTextField(const GfxRenderer& renderer, Rect rect, const int textWidth, bool cursorMode = false,
|
||||||
|
int contentStartX = 0, int contentWidth = 0) const override;
|
||||||
void drawKeyboardKey(const GfxRenderer& renderer, Rect rect, const char* label, const bool isSelected,
|
void drawKeyboardKey(const GfxRenderer& renderer, Rect rect, const char* label, const bool isSelected,
|
||||||
const char* secondaryLabel = nullptr,
|
const char* secondaryLabel = nullptr, KeyboardKeyType keyType = KeyboardKeyType::Normal,
|
||||||
KeyboardKeyType keyType = KeyboardKeyType::Normal) const override;
|
bool inactiveSelection = false) const override;
|
||||||
bool showsFileIcons() const override { return true; }
|
bool showsFileIcons() const override { return true; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|||||||
Reference in New Issue
Block a user