mirror of
https://github.com/alexhopeoconnor/firmware.git
synced 2026-10-19 09:51:30 +10:00
Rounded selections and hidden navigation bar
This commit is contained in:
@@ -12,6 +12,7 @@
|
||||
#include "graphics/TimeFormatters.h"
|
||||
#include "graphics/images.h"
|
||||
#include "main.h"
|
||||
#include "modules/CannedMessageModule.h"
|
||||
#include "target_specific.h"
|
||||
#include <OLEDDisplay.h>
|
||||
#include <RTC.h>
|
||||
@@ -1422,7 +1423,9 @@ void UIRenderer::drawNavigationBar(OLEDDisplay *display, OLEDDisplayUiState *sta
|
||||
const int totalWidth = (pageEnd - pageStart) * iconSize + (pageEnd - pageStart - 1) * spacing;
|
||||
const int xStart = (SCREEN_WIDTH - totalWidth) / 2;
|
||||
|
||||
bool navBarVisible = millis() - lastFrameChangeTime <= ICON_DISPLAY_DURATION_MS;
|
||||
// Suppress nav overlay while canned-message freetext composer is active.
|
||||
const bool hideForFreeText = (cannedMessageModule && cannedMessageModule->isFreeTextUIActive());
|
||||
bool navBarVisible = !hideForFreeText && (millis() - lastFrameChangeTime <= ICON_DISPLAY_DURATION_MS);
|
||||
int y = navBarVisible ? (SCREEN_HEIGHT - iconSize - 1) : SCREEN_HEIGHT;
|
||||
|
||||
#if defined(USE_EINK)
|
||||
@@ -1452,6 +1455,10 @@ void UIRenderer::drawNavigationBar(OLEDDisplay *display, OLEDDisplayUiState *sta
|
||||
navBarPrevVisible = navBarVisible;
|
||||
#endif
|
||||
|
||||
if (!navBarVisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Pre-calculate bounding rect
|
||||
const int rectX = xStart - 2 - bigOffset;
|
||||
const int rectWidth = totalWidth + 4 + (bigOffset * 2);
|
||||
|
||||
@@ -72,6 +72,7 @@ class CannedMessageModule : public SinglePortModule, public Observable<const UIF
|
||||
bool shouldDraw();
|
||||
bool hasMessages();
|
||||
void resetSearch();
|
||||
bool isFreeTextUIActive() const { return runState == CANNED_MESSAGE_RUN_STATE_FREETEXT; }
|
||||
void updateDestinationSelectionList();
|
||||
void drawDestinationSelectionScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
||||
bool isCharInputAllowed() const;
|
||||
@@ -202,8 +203,7 @@ class CannedMessageModule : public SinglePortModule, public Observable<const UIF
|
||||
void updateFreeTextCompletion();
|
||||
bool cycleFreeTextCompletion(int8_t step);
|
||||
bool acceptFreeTextCompletion(bool appendSpace);
|
||||
void drawFreeTextCompletionRow(OLEDDisplay *display, int16_t x, int16_t viewportTop, int16_t viewportBottom, int rowHeight,
|
||||
int linesCount, int scrollRows, const String &completionPrefix);
|
||||
void drawFreeTextCompletionRow(OLEDDisplay *display, int16_t x, int16_t rowY, const String &completionPrefix);
|
||||
void drawFreeTextScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y, char *buffer);
|
||||
void drawCannedMessageListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y, char *buffer);
|
||||
|
||||
|
||||
@@ -595,17 +595,47 @@ bool CannedMessageModule::acceptFreeTextCompletion(bool appendSpace)
|
||||
}
|
||||
|
||||
// Draw horizontally scrollable completion chips under the freetext input area.
|
||||
void CannedMessageModule::drawFreeTextCompletionRow(OLEDDisplay *display, int16_t x, int16_t viewportTop, int16_t viewportBottom,
|
||||
int rowHeight, int linesCount, int scrollRows, const String &completionPrefix)
|
||||
void CannedMessageModule::drawFreeTextCompletionRow(OLEDDisplay *display, int16_t x, int16_t rowY, const String &completionPrefix)
|
||||
{
|
||||
const int completionRowY = viewportTop + ((linesCount - scrollRows) * rowHeight);
|
||||
if (completionRowY < (viewportTop - rowHeight) || completionRowY > viewportBottom) {
|
||||
const int completionRowY = rowY;
|
||||
if (completionRowY < 0 || completionRowY >= display->getHeight()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int spaceWidth = display->getStringWidth(" ");
|
||||
const int separatorWidth = spaceWidth * 2;
|
||||
const int separatorWidth = spaceWidth;
|
||||
const int viewportWidth = display->getWidth();
|
||||
const int chipPaddingX = 3;
|
||||
const int chipRadius = 2;
|
||||
const int chipHeight = FONT_HEIGHT_SMALL;
|
||||
|
||||
// Draw a filled rounded rectangle using rects + circles (works across OLED/EInk backends).
|
||||
auto drawRoundedFill = [display](int x0, int y0, int w0, int h0, int radius) {
|
||||
if (w0 <= 0 || h0 <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
int r = std::max(0, radius);
|
||||
r = std::min(r, std::min(w0 / 2, h0 / 2));
|
||||
if (r == 0) {
|
||||
display->fillRect(x0, y0, w0, h0);
|
||||
return;
|
||||
}
|
||||
|
||||
const int centerW = w0 - (r * 2);
|
||||
const int sideH = h0 - (r * 2);
|
||||
if (centerW > 0) {
|
||||
display->fillRect(x0 + r, y0, centerW, h0);
|
||||
}
|
||||
if (sideH > 0) {
|
||||
display->fillRect(x0, y0 + r, r, sideH);
|
||||
display->fillRect(x0 + w0 - r, y0 + r, r, sideH);
|
||||
}
|
||||
display->fillCircle(x0 + r, y0 + r, r);
|
||||
display->fillCircle(x0 + w0 - r - 1, y0 + r, r);
|
||||
display->fillCircle(x0 + r, y0 + h0 - r - 1, r);
|
||||
display->fillCircle(x0 + w0 - r - 1, y0 + h0 - r - 1, r);
|
||||
};
|
||||
|
||||
struct ChoiceLayout {
|
||||
uint8_t idx;
|
||||
@@ -631,13 +661,14 @@ void CannedMessageModule::drawFreeTextCompletionRow(OLEDDisplay *display, int16_
|
||||
}
|
||||
|
||||
int tokenWidth = display->getStringWidth(candidate);
|
||||
choices.push_back({i, candidate, runningX, tokenWidth});
|
||||
int chipWidth = tokenWidth + (chipPaddingX * 2);
|
||||
choices.push_back({i, candidate, runningX, chipWidth});
|
||||
if (i == this->freeTextCompletionIndex) {
|
||||
selectedStart = runningX;
|
||||
selectedEnd = runningX + tokenWidth;
|
||||
selectedEnd = runningX + chipWidth;
|
||||
}
|
||||
|
||||
runningX += tokenWidth;
|
||||
runningX += chipWidth;
|
||||
hasChoices = true;
|
||||
}
|
||||
|
||||
@@ -653,26 +684,36 @@ void CannedMessageModule::drawFreeTextCompletionRow(OLEDDisplay *display, int16_
|
||||
}
|
||||
}
|
||||
|
||||
// Center the whole row when all chips fit on screen; otherwise keep scroll behavior.
|
||||
const int centeredOffsetX = (runningX < viewportWidth) ? ((viewportWidth - runningX) / 2) : 0;
|
||||
const int drawBaseX = x + centeredOffsetX;
|
||||
|
||||
for (const auto &choice : choices) {
|
||||
int drawX = x + (choice.startX - choiceScrollX);
|
||||
if ((drawX + choice.width) < x || drawX > (x + viewportWidth)) {
|
||||
int boxX = drawBaseX + (choice.startX - choiceScrollX);
|
||||
if ((boxX + choice.width) < x || boxX > (x + viewportWidth)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const int textX = boxX + chipPaddingX;
|
||||
if (choice.idx == this->freeTextCompletionIndex) {
|
||||
#ifdef USE_EINK
|
||||
display->drawRect(drawX - 1, completionRowY, choice.width + 2, FONT_HEIGHT_SMALL - 1);
|
||||
display->drawString(drawX, completionRowY, choice.word);
|
||||
#else
|
||||
display->fillRect(drawX - 1, completionRowY, choice.width + 2, FONT_HEIGHT_SMALL);
|
||||
display->setColor(BLACK);
|
||||
display->drawString(drawX, completionRowY, choice.word);
|
||||
// Selected completion: filled rounded chip.
|
||||
display->setColor(WHITE);
|
||||
#endif
|
||||
drawRoundedFill(boxX, completionRowY, choice.width, chipHeight, chipRadius);
|
||||
display->setColor(BLACK);
|
||||
display->drawString(textX, completionRowY, choice.word);
|
||||
} else {
|
||||
display->drawString(drawX, completionRowY, choice.word);
|
||||
// Unselected completion: hollow rounded chip.
|
||||
display->setColor(WHITE);
|
||||
drawRoundedFill(boxX, completionRowY, choice.width, chipHeight, chipRadius);
|
||||
if (choice.width > 2 && chipHeight > 2) {
|
||||
display->setColor(BLACK);
|
||||
drawRoundedFill(boxX + 1, completionRowY + 1, choice.width - 2, chipHeight - 2, chipRadius - 1);
|
||||
}
|
||||
display->setColor(WHITE);
|
||||
display->drawString(textX, completionRowY, choice.word);
|
||||
}
|
||||
}
|
||||
display->setColor(WHITE);
|
||||
}
|
||||
|
||||
// Insert visual cursor (and optional completion suffix hint) into rendered text.
|
||||
@@ -1174,7 +1215,14 @@ void CannedMessageModule::drawFreeTextScreen(OLEDDisplay *display, OLEDDisplayUi
|
||||
const int rowHeight = std::max(8, FONT_HEIGHT_SMALL - 3);
|
||||
const int viewportTop = inputY;
|
||||
const int viewportBottom = y + display->getHeight();
|
||||
const int viewportHeight = std::max(1, viewportBottom - viewportTop);
|
||||
String completionPrefix = this->getFreeTextPrefix();
|
||||
const bool showCompletionRow =
|
||||
(this->cursor == this->freetext.length() && this->freeTextCompletionCount > 1 && completionPrefix.length() >= 2);
|
||||
|
||||
// Reserve enough space for the completion row so chip/text rendering is not clipped.
|
||||
const int completionRowHeight = FONT_HEIGHT_SMALL + 1;
|
||||
const int textViewportBottom = showCompletionRow ? std::max(viewportTop, viewportBottom - completionRowHeight) : viewportBottom;
|
||||
const int viewportHeight = std::max(1, textViewportBottom - viewportTop);
|
||||
const int viewportRows = std::max(1, viewportHeight / rowHeight);
|
||||
|
||||
int cursorRow = static_cast<int>(lines.size()) - 1;
|
||||
@@ -1197,12 +1245,9 @@ void CannedMessageModule::drawFreeTextScreen(OLEDDisplay *display, OLEDDisplayUi
|
||||
}
|
||||
}
|
||||
|
||||
String completionPrefix = this->getFreeTextPrefix();
|
||||
const bool showCompletionRow =
|
||||
(this->cursor == this->freetext.length() && this->freeTextCompletionCount > 1 && completionPrefix.length() >= 2);
|
||||
const int totalRows = static_cast<int>(lines.size()) + (showCompletionRow ? 1 : 0);
|
||||
const int totalRows = static_cast<int>(lines.size());
|
||||
int scrollRows = std::max(0, totalRows - viewportRows);
|
||||
int targetRow = cursorRow + (showCompletionRow ? 1 : 0);
|
||||
int targetRow = cursorRow;
|
||||
if (targetRow < scrollRows) {
|
||||
scrollRows = targetRow;
|
||||
}
|
||||
@@ -1216,7 +1261,7 @@ void CannedMessageModule::drawFreeTextScreen(OLEDDisplay *display, OLEDDisplayUi
|
||||
// Draw wrapped text rows with vertical scrolling.
|
||||
for (int rowIdx = 0; rowIdx < static_cast<int>(lines.size()); ++rowIdx) {
|
||||
int yLine = viewportTop + ((rowIdx - scrollRows) * rowHeight);
|
||||
if (yLine < viewportTop || (yLine + rowHeight) > (viewportBottom + rowHeight)) {
|
||||
if (yLine < viewportTop || yLine >= textViewportBottom) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1232,8 +1277,7 @@ void CannedMessageModule::drawFreeTextScreen(OLEDDisplay *display, OLEDDisplayUi
|
||||
}
|
||||
|
||||
if (showCompletionRow) {
|
||||
this->drawFreeTextCompletionRow(display, x, viewportTop, viewportBottom, rowHeight, static_cast<int>(lines.size()),
|
||||
scrollRows, completionPrefix);
|
||||
this->drawFreeTextCompletionRow(display, x, viewportBottom - completionRowHeight, completionPrefix);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user