130 lines
4.8 KiB
C++
130 lines
4.8 KiB
C++
#pragma once
|
|
#include <FreeInkUIGfxRenderer.h>
|
|
#include <GfxRenderer.h>
|
|
|
|
#include <atomic>
|
|
#include <cstdint>
|
|
#include <string>
|
|
#include <utility>
|
|
|
|
#include "activities/Activity.h"
|
|
#include "util/ButtonNavigator.h"
|
|
|
|
enum class InputType { Text, Password, Url };
|
|
|
|
// Text entry on the FreeInkUI keyboard component: the SDK layout tables and
|
|
// keyboard() do the key rendering and hit-rect registration, InteractionBuffer
|
|
// routes taps/long-presses, and this activity owns the text field, cursor
|
|
// editing, and the URL snippet layouts.
|
|
class KeyboardEntryActivity : public Activity {
|
|
public:
|
|
explicit KeyboardEntryActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
|
std::string title = "Enter Text", std::string initialText = "",
|
|
const size_t maxLength = 0, InputType inputType = InputType::Text)
|
|
: Activity("KeyboardEntry", renderer, mappedInput),
|
|
title(std::move(title)),
|
|
text(std::move(initialText)),
|
|
maxLength(maxLength),
|
|
inputType(inputType) {}
|
|
|
|
void onEnter() override;
|
|
void onExit() override;
|
|
void loop() override;
|
|
void render(RenderLock&&) override;
|
|
|
|
private:
|
|
std::string title;
|
|
std::string text;
|
|
size_t maxLength;
|
|
InputType inputType;
|
|
bool passwordVisible = false;
|
|
|
|
ButtonNavigator buttonNavigator;
|
|
|
|
// Keyboard layers. The letter/symbol layers come from the SDK's builtin
|
|
// layouts (with the always-visible number row); the URL layers are
|
|
// app-defined tables in the .cpp.
|
|
freeink::ui::KeyboardLayoutId layoutId = freeink::ui::KeyboardLayoutId::QwertyEn;
|
|
bool shifted = false;
|
|
bool symbols = false;
|
|
bool urlPanel = false; // URL snippet panel replaces the letter layer
|
|
|
|
// Key hit rects registered by the keyboard component during render();
|
|
// loop() routes touch snapshots against them. 5-row EN layout registers 41
|
|
// keys, so 48 leaves headroom.
|
|
freeink::ui::InteractionBuffer<48> interactions;
|
|
|
|
// GPIO selection over the current layout grid (row/col in layout terms;
|
|
// the bottom action row is just the last row).
|
|
int selRow = 0;
|
|
int selCol = 0;
|
|
|
|
bool confirmHeld = false;
|
|
bool confirmLongHandled = false;
|
|
|
|
bool cursorMode = false;
|
|
bool togglePos = false;
|
|
size_t cursorPos = 0; // byte offset into text (always on a code point boundary)
|
|
bool upHeld = false;
|
|
bool upLongHandled = false;
|
|
bool downHeld = false;
|
|
bool downLongHandled = false;
|
|
bool rightHeld = false;
|
|
bool rightLongHandled = false;
|
|
size_t savedCursorPos = 0;
|
|
size_t rightStartCursorPos = 0;
|
|
|
|
// Tap/hold routing (threshold long-press, release swallow, slide re-arm)
|
|
// lives in the SDK; loop() feeds it the level-triggered touch state.
|
|
freeink::ui::TouchHoldRouter touchRouter;
|
|
|
|
// loop() runs on the main task while render() rebuilds the interaction
|
|
// table on the render task; routing against a half-built table would read
|
|
// torn entries, so taps are dropped during the rebuild window. atomic (not
|
|
// volatile) so the flag also orders the table writes on dual-core targets.
|
|
std::atomic<bool> interactionsReady{false};
|
|
|
|
int delPressCount = 0;
|
|
bool hintVisible = false;
|
|
unsigned long hintShowTime = 0;
|
|
|
|
void onComplete(std::string text);
|
|
void onCancel();
|
|
bool cursorPositionFromPoint(int x, int y, size_t& position) const;
|
|
std::string displayTextForCurrentState() const;
|
|
// Advance of s[start, end) measured in place by temporarily null-terminating
|
|
// at `end` — avoids a substr temporary per measurement.
|
|
int measureRange(std::string& s, int start, int end) const;
|
|
// Largest line end in (start, s.length()] whose advance fits maxWidth.
|
|
// Binary search over the monotonic prefix advance; always advances at least
|
|
// one byte so an oversized glyph cannot stall the wrap loop.
|
|
int lineBreakEnd(std::string& s, int start, int maxWidth) const;
|
|
|
|
const freeink::ui::KeyboardLayout& currentLayout() const;
|
|
const freeink::ui::KeyboardKey* selectedKey() const;
|
|
int selectedLogicalIndex() const;
|
|
void clampSelection();
|
|
void moveSelectionRow(int delta);
|
|
void moveSelectionCol(int delta);
|
|
bool syncSelectionToValue(int16_t value);
|
|
// Handles one key activation (by stable key id). Returns true when the
|
|
// screen needs a repaint; OK/cancel finish the activity instead.
|
|
bool activateValue(int16_t value, bool longPress);
|
|
bool clearAllOrAltOnSelected();
|
|
|
|
void insertUtf8(const char* out);
|
|
bool backspaceUtf8();
|
|
static size_t utf8Prev(const std::string& s, size_t pos);
|
|
static size_t utf8Next(const std::string& s, size_t pos);
|
|
|
|
freeink::ui::Rect keyboardRect() const;
|
|
|
|
static constexpr uint16_t LONG_PRESS_MS = 500;
|
|
static constexpr uint16_t DEL_LONG_PRESS_MS = 1500;
|
|
static constexpr uint16_t TOUCH_LONG_PRESS_MS = 350;
|
|
static constexpr uint16_t TOUCH_DEL_LONG_PRESS_MS = 900;
|
|
|
|
// App-specific key id: toggles the URL snippet panel (URL fields only).
|
|
static constexpr int16_t URL_PANEL_KEY = -3;
|
|
};
|