mirror of
https://github.com/crosspoint-reader/crosspoint-reader.git
synced 2026-08-13 14:21:57 -07:00
Compare commits
9
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
205fa71c16 | ||
|
|
268c27f7d0 | ||
|
|
b1d53ba810 | ||
|
|
e970abae89 | ||
|
|
9e3cea2e54 | ||
|
|
86eb1cbde2 | ||
|
|
2cac597143 | ||
|
|
7212129318 | ||
|
|
9b090266cf |
No files matched your search
@@ -1 +1 @@
|
||||
/home/urit/Documents/CrossPoint/crosspoint-reader/.skills/SKILL.md
|
||||
.skills/SKILL.md
|
||||
@@ -36,7 +36,7 @@ CrossPoint is open-source e-reader firmware - community-built, fully hackable, f
|
||||
- OPDS browser with saved servers (up to 8), search, pagination, and direct download
|
||||
- OTA update checks and installs from GitHub releases
|
||||
|
||||
- **Customization**: multiple themes (Classic, Lyra, Lyra Extended, RoundedRaff), sleep screen modes, front/side button remapping, status bar controls, power-button behavior, refresh cadence, and more.
|
||||
- **Customization**: multiple themes (Classic, Lyra, Lyra Extended, RoundedRaff), sleep screen modes including transparent overlays, front/side button remapping, status bar controls, power-button behavior, refresh cadence, and more.
|
||||
|
||||
- **Localization**: 24 UI languages and counting. RTL support.
|
||||
|
||||
@@ -136,7 +136,7 @@ Conversion runs the firmware repo's `lib/EpdFont/scripts/fontconvert_sdcard.py`
|
||||
- [Web server endpoints](./docs/webserver-endpoints.md)
|
||||
- [Project scope](./SCOPE.md)
|
||||
- [Contributing docs](./docs/contributing/README.md)
|
||||
- [Touch and UI development](./docs/contributing/touch-and-ui.md) - FreeInkUI components for new screens, the touch bridge for existing ones, and build envs for the non-Xteink touch devices
|
||||
- [Touch and UI development](./docs/contributing/touch-and-ui.md) - how to build new screens on the FreeInkUI activity bases (UiListActivity and friends), plus build envs for the non-Xteink touch devices
|
||||
|
||||
---
|
||||
|
||||
|
||||
+14
-4
@@ -186,7 +186,7 @@ The Settings screen allows you to configure the device's behavior. There are a f
|
||||
- "None" - A blank screen
|
||||
- "Cover + Custom" - The book cover image while actively reading, falls back to "Custom" behavior otherwise
|
||||
- "Quick resume" - The text of the last page read will be displayed on the sleep screen and a moon icon is shown on the edge of the screen. Waking up the device will return to the same page of the opened book. This is useful for quickly resuming reading without waiting for the device to fully wake up and load the book.
|
||||
|
||||
- "Transparent" - A transparent overlay image drawn over the current screen; see [Sleep Screen](#37-sleep-screen) below for more information
|
||||
- **Sleep Screen Cover Mode**: How to display the book cover when "Cover" sleep screen is selected:
|
||||
|
||||
- "Fit" (default) - Scale the image down to fit centered on the screen, padding with white borders as necessary
|
||||
@@ -501,6 +501,7 @@ The **Sleep Screen** setting controls what is displayed when the device goes to
|
||||
| **Custom** | A custom image from the SD card (see below). Falls back to **Dark** if no custom image is found. |
|
||||
| **Cover** | The cover of the currently open book. Falls back to **Dark** if no book is open. |
|
||||
| **Cover + Custom** | The cover of the currently open book, shown only while actively reading. Falls back to **Custom** behavior when not reading. |
|
||||
| **Transparent** | A BMP overlay drawn over the current screen. Supports alpha transparency in 32-bit BGRA images and treats white as transparent in regular BMPs. Falls back to **Dark** if no valid overlay image is found. |
|
||||
| **None** | A blank screen. |
|
||||
|
||||
#### Cover settings
|
||||
@@ -515,12 +516,21 @@ When using **Cover** or **Cover + Custom**, two additional settings apply:
|
||||
To use custom sleep images, set the sleep screen mode to **Custom** or **Cover + Custom**, then place images on the SD card:
|
||||
|
||||
- **Multiple Images (recommended):** Create a `.sleep` directory in the root of the SD card and place any number of `.bmp` images inside. One will be randomly selected each time the device sleeps. (A directory named `sleep` is also accepted as a fallback.)
|
||||
- **Single Image:** Place a file named `sleep.bmp` in the root directory. This is used as a fallback if no valid images are found in the `.sleep`/`sleep` directory.
|
||||
- **Single Image:** Place a file named `sleep.bmp` in the root directory. This takes priority over the `.sleep`/`sleep` directories.
|
||||
|
||||
#### Transparent overlay images
|
||||
|
||||
To use transparent sleep overlays, set the sleep screen mode to **Transparent**, then place BMP files on the SD card:
|
||||
|
||||
- **Multiple Images (recommended):** Create a `.sleep-overlay` directory in the root of the SD card and place any number of valid overlay `.bmp` images inside. One will be randomly selected each time the device sleeps. A directory named `sleep-overlay` is also accepted as a fallback.
|
||||
- **Single Image:** Place a file named `sleep-overlay.bmp` in the root directory. This takes priority over the `.sleep-overlay`/`sleep-overlay` directories.
|
||||
|
||||
Transparent overlay files are intentionally separate from normal sleep images. Regular BMP formats supported by CrossPoint are accepted; white pixels leave the existing screen unchanged. For per-pixel alpha transparency, use a 32-bit BGRA BMP with both visible and non-opaque pixels. Opaque white pixels in these alpha images erase the content behind them.
|
||||
|
||||
> [!TIP]
|
||||
> For best results:
|
||||
>
|
||||
> - Use uncompressed BMP files with 24-bit color depth
|
||||
> - For non-transparent **Custom** mode, use uncompressed BMP files with 24-bit color depth.
|
||||
> - For **Transparent** mode, use a regular BMP for white-as-transparent artwork or an uncompressed 32-bit BGRA BMP for per-pixel alpha.
|
||||
> - X4: Use a resolution of 480x800 pixels to match the device's screen resolution.
|
||||
> - X3: Use a resolution of 528x792 pixels to match the device's screen resolution.
|
||||
|
||||
|
||||
@@ -1,23 +1,88 @@
|
||||
# Touch and UI Development
|
||||
|
||||
CrossPoint now runs on touch devices (Seeed Sticky, M5Paper, LilyGo T5) alongside the button-only Xteink X3/X4. Every screen must work with both input styles. There are two supported ways to get there:
|
||||
CrossPoint runs on touch devices (Seeed Sticky, M5Paper, LilyGo T5) alongside the button-only Xteink X3/X4. Every screen must work with both input styles.
|
||||
|
||||
1. **New screens: build them with FreeInkUI components.** Touch hit-testing, tap highlighting, long-press, and button focus navigation come with the component; you never hand-roll coordinate math.
|
||||
2. **Existing screens and in-flight features: use the MappedInputManager touch bridge.** A small set of helpers adds tap/hold/swipe support to hand-rolled rendering without restructuring the activity.
|
||||
**There is one supported way to build a new screen: FreeInkUI, hosted through the firmware base classes below.** Touch hit-testing, tap highlighting, long-press, swipe scrolling, and button focus navigation all come from the shared stack; you never hand-roll coordinate math.
|
||||
|
||||
If you are starting a new activity, use FreeInkUI. If you have a feature branch with a hand-rolled screen already working on buttons, use the bridge; do not rewrite mid-flight.
|
||||
The old bridge helpers (`rowTouch`, `colTouch`, `wasTapInRect`, manual rect `contains()` checks) are legacy. They survive only for the two remaining hand-rolled surfaces (the theme-driven home screen and the reader page) and must not appear in new code. PRs that add new uses will be asked to convert.
|
||||
|
||||
---
|
||||
|
||||
## Path 1: New screens with FreeInkUI
|
||||
## Picking the right host
|
||||
|
||||
FreeInkUI (`freeink-sdk/libs/ui/FreeInkUI`, namespace `freeink::ui`) is an immediate-mode component library. The core idea:
|
||||
| Your screen is... | Use | In-tree reference |
|
||||
|---|---|---|
|
||||
| A single list of rows | subclass `UiListActivity` | [`LanguageSelectActivity`](../../src/activities/settings/LanguageSelectActivity.cpp) (minimal), [`RecentBooksActivity`](../../src/activities/home/RecentBooksActivity.cpp) (long-press) |
|
||||
| Tabbed lists | subclass `UiTabListActivity` | [`SettingsActivity`](../../src/activities/settings/SettingsActivity.cpp) |
|
||||
| Custom FUI layout (sliders, prompts, state machines) | inherit `UiAppHost` directly | [`EpubReaderPercentSelectionActivity`](../../src/activities/reader/EpubReaderPercentSelectionActivity.cpp), [`WifiSelectionActivity`](../../src/activities/network/WifiSelectionActivity.cpp) |
|
||||
| A modal picker or confirm inside a legacy activity | `OptionPopup` (or push `ConfirmationActivity`) | [`OtaUpdateActivity`](../../src/activities/settings/OtaUpdateActivity.cpp) |
|
||||
| A Yes/No prompt inside a FUI screen | build `fui::optionDialog` into the screen | `WifiSelectionActivity::buildPromptDialog` |
|
||||
|
||||
- While a component renders, it registers its tappable areas ("hit rects") into the frame's interaction buffer via `Frame::hit(rect, action, value, inputMask, state)`.
|
||||
- Each loop you build an `InputSnapshot` from the input manager and route it against that buffer. If a tap (or mapped button press) lands in a registered rect, the routed `ActionEvent` tells you which action fired and with what value.
|
||||
- Components query `frame.stateFor(action, value)` while painting, so touch-down highlight and focus states render correctly without any per-activity code.
|
||||
All four paths route input through the same SDK interaction table, so touch and physical buttons fire the same actions with no per-screen coordinate code.
|
||||
|
||||
You get touch, hold highlighting, long-press, minimum touch-target sizing, and orientation-aware coordinates for free. The same action IDs fire from physical buttons, so one code path serves both input styles.
|
||||
## The hosting stack
|
||||
|
||||
`UiAppHost` ([src/components/UiAppHost.h](../../src/components/UiAppHost.h)) owns what every FUI screen shares: the font-bound render target, the `FreeInkApp`, and the `uiReady` handshake that lets the loop task route touch snapshots against the interaction table the render task rebuilds. Never re-implement that handshake; it is a cross-task protocol and lives in exactly one place.
|
||||
|
||||
`UiListActivity` ([src/activities/UiListActivity.h](../../src/activities/UiListActivity.h)) layers the list protocol on top: swipes scroll the viewport without moving the selection, buttons move the selection and pull the viewport along (`fui::ListNav`), plus the render skeleton (header chrome, app, footer hints). `UiTabListActivity` is the tab-bar sibling; its selection ring puts the tab bar at position 0 and the rows after it.
|
||||
|
||||
### New list screen skeleton
|
||||
|
||||
Subclasses supply the data; the base owns the loop. The whole contract:
|
||||
|
||||
```cpp
|
||||
class MyListActivity final : public UiListActivity {
|
||||
public:
|
||||
MyListActivity(GfxRenderer& r, MappedInputManager& in) : UiListActivity("MyList", r, in) {}
|
||||
|
||||
private:
|
||||
std::vector<Entry> entries; // the activity's data, loaded in onEnter
|
||||
std::vector<fui::ListItem> rows; // activity-owned row cache: rebuilt only when entries changes, reused every render
|
||||
|
||||
int listCount() const override { return entries.size(); }
|
||||
const char* headerTitle() const override { return tr(STR_MY_TITLE); }
|
||||
|
||||
void onEnter() override {
|
||||
UiListActivity::onEnter();
|
||||
entries = /* ... load from wherever ... */;
|
||||
rebuildRows();
|
||||
}
|
||||
|
||||
// Called whenever entries changes (here, only onEnter; a mutable list would
|
||||
// also call this after any add/remove). NOT called from buildScreen().
|
||||
void rebuildRows() {
|
||||
rows.clear();
|
||||
rows.reserve(entries.size());
|
||||
// ... one fui::ListItem per entry (label, actionValue = index) ...
|
||||
}
|
||||
|
||||
void buildScreen(UiScreen& screen) override {
|
||||
// set content margin from the theme safe area, then:
|
||||
fui::ListProps props;
|
||||
props.items = rows.data();
|
||||
props.count = rows.size();
|
||||
props.action = ACTION_ROW;
|
||||
props.inputMask = fui::InputTouch; // physical buttons stay in the base loop
|
||||
syncListViewport(screen, props); // selection/viewport handoff, always right before list()
|
||||
screen.list(props);
|
||||
}
|
||||
|
||||
void activateIndex(int index) override {
|
||||
app.clearTapFlash(); // leaving the screen: a lingering flash would gray the next render
|
||||
// ... open the thing ...
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
See [`FileBrowserActivity`](../../src/activities/home/FileBrowserActivity.cpp)'s `rebuildRowItems()` for this pattern applied to a directory listing that can run into the hundreds of entries, and `LanguageSelectActivity.cpp` for the rest of the skeleton (content-margin math, footer, etc.) — note that file still builds its row vector locally inside `buildScreen()`; match `FileBrowserActivity`, not that file, for the row cache. Optional overrides: `onRowLongPress(index)`, `drawFooter()`, `handleButtons()` for extra physical-button handling, and `ACTION_USER`-and-up action ids for non-row elements (register handlers in `onEnter` after the base's).
|
||||
|
||||
### Rules that apply to every FUI screen
|
||||
|
||||
- Register actions with `app.on(...)` in `onEnter` (after `resetUi()` for direct `UiAppHost` users). Handlers receive a `void* user` you cast back to the activity.
|
||||
- Handlers that leave the current screen call `app.clearTapFlash()` first.
|
||||
- Theme tokens are shared and bound by `resetUi()`; never call `app.setTheme` yourself. Metrics flow from the active UITheme through [`UIThemeTokens.h`](../../src/components/UIThemeTokens.h), including the per-board bezel insets that keep scrollbars visible.
|
||||
- `TextStyle.maxLines` defaults to 1 and truncates with an ellipsis. Set `maxLines` explicitly on any dialog headline or message that can wrap.
|
||||
- Everything stays allocation-free in steady state. A local `std::vector` inside `buildScreen()` is **not** allocation-free even with `reserve()` first: it starts at zero capacity on every call, `reserve()` allocates, and the destructor frees that storage before the call returns — real allocator work and fragmentation risk on every repaint (cursor move, tap flash, ...), not just on data changes. Build `ListItem` rows into activity-owned storage instead, reserved once when the underlying data loads (`onEnter()`/a `load*()` — see the skeleton above and `FileBrowserActivity::rebuildRowItems()`), and reused unchanged by every `buildScreen()` call. Use a fixed-capacity array (e.g. `ListItem rows[MAX]`, as `OptionPopup` and `KOReaderSyncActivity`'s action rows do) when the count is small and bounded. Do not hold FUI `props` across renders — only the row storage they point into.
|
||||
|
||||
### Component inventory
|
||||
|
||||
@@ -33,106 +98,11 @@ All under `freeink-sdk/libs/ui/FreeInkUI/include/components/`:
|
||||
| Media | `book-card`, `cover-grid`, `cover-carousel`, `metric-card` |
|
||||
| Text | `text-field`, `text-area` |
|
||||
|
||||
### Integration skeleton
|
||||
|
||||
The in-tree reference is [`src/activities/util/KeyboardEntryActivity.cpp`](../../src/activities/util/KeyboardEntryActivity.cpp); it drives the FreeInkUI keyboard component inside a normal Activity. The shape:
|
||||
|
||||
```cpp
|
||||
#include <FreeInkUIGfxRenderer.h>
|
||||
namespace fui = freeink::ui;
|
||||
|
||||
// Render step: draw the component and (re)register its hit rects.
|
||||
fui::GfxRendererTarget target(renderer); // adapts GfxRenderer to FreeInkUI's DrawTarget
|
||||
target.setFont(fui::GfxRendererTarget::FONT_BODY, UI_12_FONT_ID);
|
||||
const fui::DeviceContext device = target.deviceContext(); // orientation, safe area, touch sizing
|
||||
|
||||
fui::Frame<48> frame(target, device, fui::InputSnapshot{}, interactions); // 48 = max hit rects
|
||||
fui::KeyboardProps props;
|
||||
props.layout = ¤tLayout();
|
||||
props.keyAction = ACTION_KEY; // one action id; the key is the event value
|
||||
props.inputMask = fui::InputTouch | fui::InputLongPress;
|
||||
fui::keyboard(frame, kbRect, props); // draws AND registers hit rects
|
||||
```
|
||||
|
||||
```cpp
|
||||
// Input step (each loop): feed touch state, act on routed events.
|
||||
int tx, ty, tapX, tapY, hx, hy;
|
||||
const bool pressedDown = mappedInput.wasScreenTouchDown(tx, ty);
|
||||
const bool tapped = mappedInput.wasScreenTapped(tapX, tapY);
|
||||
const bool inContact = mappedInput.isScreenTouchHeld(hx, hy);
|
||||
|
||||
const auto result = touchRouter.update(interactions, pressedDown, tx, ty,
|
||||
tapped, tapX, tapY, inContact, millis());
|
||||
if (result.event) {
|
||||
activateValue(result.event.value, result.event.longPress);
|
||||
requestUpdate();
|
||||
} else if (result.activeChanged) {
|
||||
requestUpdate(); // repaint touch-down highlight
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- `Frame<N>` is templated on the max number of hit rects; the buffer is stack/static, no heap.
|
||||
- `fui::TouchHoldRouter` (see `components/keyboard/keyboard.h`) synthesizes long-press while the finger is still down and swallows the eventual release. Use it whenever a component distinguishes tap from long-press.
|
||||
- For simpler screens without long-press, `FreeInkApp` / `Screen<N>` (`FreeInkApp.h`) is the ergonomic layer: it owns the interaction buffer, offers a top-to-bottom builder (`takeTop`, footer helpers, etc.), and its `render(input)` call routes the snapshot and dispatches registered action callbacks in one step. `snapshotFrom(input, device)` in `FreeInkUIInputManager.h` builds the `InputSnapshot` with orientation-aware touch mapping.
|
||||
- Everything stays allocation-free and works on the button-only devices unchanged: physical buttons route through the same interaction table via each hit rect's `inputMask`.
|
||||
One exception to "always go through a host": `KeyboardEntryActivity` drives the keyboard component with a raw `fui::Frame` and `TouchHoldRouter` because per-key hold repeat needs its own routing. If you think your screen needs that, raise it in the PR first.
|
||||
|
||||
---
|
||||
|
||||
## Path 2: Adding touch to existing hand-rolled screens
|
||||
|
||||
For activities that draw their own rows, menus, and buttons, `MappedInputManager` ([src/MappedInputManager.h](../../src/MappedInputManager.h)) exposes bridge helpers. They all return **logical screen coordinates** (orientation already applied via `GfxRenderer::tapToLogical`); never touch raw normalized panel coordinates or the SDK `InputManager` directly.
|
||||
|
||||
| Helper | Use for |
|
||||
|---|---|
|
||||
| `wasScreenTapped(x, y)` | A completed tap (press + release), with logical coords |
|
||||
| `wasScreenTouchDown(x, y)` | Touch-down (held > 90 ms, not yet released): draw selection highlight |
|
||||
| `isScreenTouchHeld(x, y)` | Live contact position while the finger is down (drag tracking) |
|
||||
| `wasTapInRect(x, y, w, h)` | One-off hit test on a rectangle (a single button, a banner) |
|
||||
| `wasListItemTapped(index, count, selected, listTop, listHeight, hasSubtitle)` | Taps on a standard UITheme list; does the row/paging math for you |
|
||||
| `wasListItemTouchedDown(...)` | Same geometry, touch-down phase (highlight before activate) |
|
||||
| `rowTouch(row, top, rowStep, rowCount, xStart, xEnd, rowHeight)` | Any custom band of equal-height rows; returns `RowTouch::None/Down/Tap` |
|
||||
| `colTouch(col, left, colStep, colCount, yStart, yEnd, colWidth)` | Horizontal button bands (dialogs, prompts) |
|
||||
| `wasSwipe()` | Raw swipe direction if you need one beyond the global gestures |
|
||||
| `hasTouch()` | True when the device has a touch panel (rarely needed; helpers simply never fire without one) |
|
||||
|
||||
### Pattern A: standard themed list
|
||||
|
||||
One call per loop; UITheme owns the row geometry ([EpubReaderBookmarksActivity.cpp:123](../../src/activities/reader/EpubReaderBookmarksActivity.cpp)):
|
||||
|
||||
```cpp
|
||||
int tapped = -1;
|
||||
if (mappedInput.wasListItemTapped(tapped, bookmarks.size(), selectorIndex, listY, listHeight, true)) {
|
||||
selectorIndex = tapped;
|
||||
openBookmark();
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern B: custom rows with hold highlight
|
||||
|
||||
For non-theme row layouts, use `rowTouch` and distinguish `Down` (highlight) from `Tap` (activate), as in [EpubReaderFootnotesActivity.cpp](../../src/activities/reader/EpubReaderFootnotesActivity.cpp):
|
||||
|
||||
```cpp
|
||||
int row = -1;
|
||||
const auto touch = mappedInput.rowTouch(row, listTop, lineHeight, visibleCount,
|
||||
contentX, contentX + contentWidth);
|
||||
if (touch != MappedInputManager::RowTouch::None) {
|
||||
const int touched = scrollOffset + row;
|
||||
if (touch == MappedInputManager::RowTouch::Down) {
|
||||
if (selectedIndex != touched) { selectedIndex = touched; requestUpdate(); }
|
||||
} else { // RowTouch::Tap
|
||||
selectedIndex = touched;
|
||||
activateSelection();
|
||||
}
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
The `Down` state exists because e-ink repaints are slow: highlight on touch-down gives immediate feedback, activation happens on release.
|
||||
|
||||
### Global gestures: do not reimplement these
|
||||
## Global gestures: do not reimplement these
|
||||
|
||||
Three gestures are handled once, for every screen. Activities must not add their own edge-swipe handling:
|
||||
|
||||
@@ -142,14 +112,25 @@ Three gestures are handled once, for every screen. Activities must not add their
|
||||
| Home | Up-swipe starting in the bottom 14% | `ActivityManager::loop()`; pops to Home (activities can override via `handleHomeGesture()`) |
|
||||
| Menu | Down-swipe starting in the top 14% | Activities that have a menu check `wasMenuGesture()` themselves (the reader does this) |
|
||||
|
||||
Because the back gesture arrives as `Button::Back`, most button-era activities gain back-swipe support with zero changes. That is the bar to aim for: bridge helpers should make touch an additive layer over the button flow, not a second input state machine.
|
||||
Because the back gesture arrives as `Button::Back`, most button-era activities gain back-swipe support with zero changes.
|
||||
|
||||
### Bridge rules
|
||||
---
|
||||
|
||||
- Handle touch in `loop()` next to the existing button handling, one helper call per interaction zone, and `return` after consuming an event (mirrors the button pattern).
|
||||
- Never call the SDK `InputManager` or read GPIO directly; the HAL rule from the main guide applies to touch too.
|
||||
- Coordinates from the helpers are logical and orientation-correct on all four rotations; test at least Portrait and one Landscape mode before PR.
|
||||
- Nothing to clean up in `onExit()`; the helpers are stateless from the activity's point of view.
|
||||
## Legacy bridge helpers (do not use in new code)
|
||||
|
||||
`MappedInputManager` still exposes raw touch accessors. The FUI stack consumes them internally (via `touchSnapshotFrom` in [`UiAppHelpers.h`](../../src/components/UiAppHelpers.h)); activities should not.
|
||||
|
||||
| Helper | Status |
|
||||
|---|---|
|
||||
| `wasScreenTapped` / `wasScreenTouchDown` / `isScreenTouchHeld` | Consumed by the FUI snapshot builder. Direct use only in the two legacy surfaces |
|
||||
| `wasTapInRect(x, y, w, h)` | Legacy one-off hit test |
|
||||
| `rowTouch` / `colTouch` | Legacy row/column band math. Sole remaining user: `HomeActivity` (theme-driven layout) |
|
||||
| `wasSwipe()` | Raw swipe direction, for behavior beyond the global gestures (reader page turns) |
|
||||
| `hasTouch()` | Still fine anywhere: gate touch-only chrome (on-screen Cancel/OK pairs) on it |
|
||||
|
||||
The `wasListItemTapped` / `wasListItemTouchedDown` helpers from the button-era bridge have been removed; every themed list is a `UiListActivity` now. If you are porting a branch that still calls them, convert the screen to `UiListActivity` rather than resurrecting the helpers.
|
||||
|
||||
As with all input: never call the SDK `InputManager` or read GPIO directly. The HAL rule from the main guide applies to touch too.
|
||||
|
||||
---
|
||||
|
||||
|
||||
+1
-1
Submodule freeink-sdk updated: a485dc46ef...83d49345d8.
@@ -1,6 +1,7 @@
|
||||
#include "GfxRenderer.h"
|
||||
|
||||
#include <BidiUtils.h>
|
||||
#include <BoardConfig.h>
|
||||
#include <BuildScratch.h>
|
||||
#include <FontDecompressor.h>
|
||||
#include <HalGPIO.h>
|
||||
@@ -726,11 +727,14 @@ void GfxRenderer::drawRect(const int x, const int y, const int width, const int
|
||||
// Border is inside the rectangle
|
||||
void GfxRenderer::drawRect(const int x, const int y, const int width, const int height, const int lineWidth,
|
||||
const bool state) const {
|
||||
// Keep the border inside [x, x+width) like the thin overload: the previous
|
||||
// right/bottom edges at x+width / y+height sat one pixel outside the rect,
|
||||
// so stroked boxes looked shifted against fills computed from the rect.
|
||||
for (int i = 0; i < lineWidth; i++) {
|
||||
drawLine(x + i, y + i, x + width - i, y + i, state);
|
||||
drawLine(x + width - i, y + i, x + width - i, y + height - i, state);
|
||||
drawLine(x + width - i, y + height - i, x + i, y + height - i, state);
|
||||
drawLine(x + i, y + height - i, x + i, y + i, state);
|
||||
drawLine(x + i, y + i, x + width - 1 - i, y + i, state);
|
||||
drawLine(x + width - 1 - i, y + i, x + width - 1 - i, y + height - 1 - i, state);
|
||||
drawLine(x + width - 1 - i, y + height - 1 - i, x + i, y + height - 1 - i, state);
|
||||
drawLine(x + i, y + height - 1 - i, x + i, y + i, state);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2220,30 +2224,33 @@ void GfxRenderer::cleanupGrayscaleWithFrameBuffer() const {
|
||||
}
|
||||
|
||||
void GfxRenderer::getOrientedViewableTRBL(int* outTop, int* outRight, int* outBottom, int* outLeft) const {
|
||||
// Board truth: the bezel insets live in the active profile (panel-native
|
||||
// portrait frame); this only rotates them into the current orientation.
|
||||
const BoardConfig::ViewableInsets& vi = BoardConfig::ACTIVE.viewableInsets;
|
||||
switch (orientation) {
|
||||
case Portrait:
|
||||
*outTop = VIEWABLE_MARGIN_TOP;
|
||||
*outRight = VIEWABLE_MARGIN_RIGHT;
|
||||
*outBottom = VIEWABLE_MARGIN_BOTTOM;
|
||||
*outLeft = VIEWABLE_MARGIN_LEFT;
|
||||
*outTop = vi.top;
|
||||
*outRight = vi.right;
|
||||
*outBottom = vi.bottom;
|
||||
*outLeft = vi.left;
|
||||
break;
|
||||
case LandscapeClockwise:
|
||||
*outTop = VIEWABLE_MARGIN_LEFT;
|
||||
*outRight = VIEWABLE_MARGIN_TOP;
|
||||
*outBottom = VIEWABLE_MARGIN_RIGHT;
|
||||
*outLeft = VIEWABLE_MARGIN_BOTTOM;
|
||||
*outTop = vi.left;
|
||||
*outRight = vi.top;
|
||||
*outBottom = vi.right;
|
||||
*outLeft = vi.bottom;
|
||||
break;
|
||||
case PortraitInverted:
|
||||
*outTop = VIEWABLE_MARGIN_BOTTOM;
|
||||
*outRight = VIEWABLE_MARGIN_LEFT;
|
||||
*outBottom = VIEWABLE_MARGIN_TOP;
|
||||
*outLeft = VIEWABLE_MARGIN_RIGHT;
|
||||
*outTop = vi.bottom;
|
||||
*outRight = vi.left;
|
||||
*outBottom = vi.top;
|
||||
*outLeft = vi.right;
|
||||
break;
|
||||
case LandscapeCounterClockwise:
|
||||
*outTop = VIEWABLE_MARGIN_RIGHT;
|
||||
*outRight = VIEWABLE_MARGIN_BOTTOM;
|
||||
*outBottom = VIEWABLE_MARGIN_LEFT;
|
||||
*outLeft = VIEWABLE_MARGIN_TOP;
|
||||
*outTop = vi.right;
|
||||
*outRight = vi.bottom;
|
||||
*outBottom = vi.left;
|
||||
*outLeft = vi.top;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -109,11 +109,6 @@ class GfxRenderer {
|
||||
: display(halDisplay), renderMode(BW), orientation(Portrait), fadingFix(false) {}
|
||||
~GfxRenderer() { freeBwBufferChunks(); }
|
||||
|
||||
static constexpr int VIEWABLE_MARGIN_TOP = 9;
|
||||
static constexpr int VIEWABLE_MARGIN_RIGHT = 3;
|
||||
static constexpr int VIEWABLE_MARGIN_BOTTOM = 3;
|
||||
static constexpr int VIEWABLE_MARGIN_LEFT = 3;
|
||||
|
||||
// Setup
|
||||
void begin(); // must be called right after display.begin()
|
||||
void insertFont(int fontId, EpdFontFamily font);
|
||||
|
||||
@@ -278,6 +278,7 @@ STR_BOOKMARK_REMOVED: "S'ha eliminat el punt de llibre."
|
||||
STR_OPDS_BROWSER: "Navegador OPDS"
|
||||
STR_COVER_CUSTOM: "Portada + Personalitzat"
|
||||
STR_QUICK_RESUME: "Represa ràpida"
|
||||
STR_TRANSPARENT: "Transparent"
|
||||
STR_MENU_RECENT_BOOKS: "Llibres recents"
|
||||
STR_REMOVE_FROM_RECENTS: "Vols suprimir-lo de Llibres recents?"
|
||||
STR_NO_RECENT_BOOKS: "No hi ha llibres recents"
|
||||
|
||||
@@ -299,12 +299,14 @@ STR_THEME_LYRA_EXTENDED: "Lyra Extended"
|
||||
STR_SUNLIGHT_FADING_FIX: "Sunlight Fading Fix"
|
||||
STR_REMAP_FRONT_BUTTONS: "Remap Front Buttons"
|
||||
STR_BOOKMARKS: "Bookmarks"
|
||||
STR_NO_BOOKMARKS: "No bookmarks"
|
||||
STR_BOOKMARK_ADDED: "Bookmark added."
|
||||
STR_BOOKMARK_REMOVED: "Bookmark removed."
|
||||
STR_OPDS_BROWSER: "OPDS Browser"
|
||||
STR_SEARCH: "Search"
|
||||
STR_COVER_CUSTOM: "Cover + Custom"
|
||||
STR_QUICK_RESUME: "Quick Resume"
|
||||
STR_TRANSPARENT: "Transparent"
|
||||
STR_MENU_RECENT_BOOKS: "Recent Books"
|
||||
STR_REMOVE_FROM_RECENTS: "Remove from Recent Books?"
|
||||
STR_NO_RECENT_BOOKS: "No recent books"
|
||||
|
||||
@@ -0,0 +1,328 @@
|
||||
_language_name: "Orangutan"
|
||||
_language_code: "OOK"
|
||||
_order: "34"
|
||||
|
||||
STR_CROSSPOINT: "CrossPoint"
|
||||
STR_BOOTING: "OOK"
|
||||
STR_SLEEPING: "Oooook"
|
||||
STR_ENTERING_SLEEP: "Ook..."
|
||||
STR_BROWSE_FILES: "Ook? OOK"
|
||||
STR_FILE_TRANSFER: "Oook!"
|
||||
STR_SETTINGS_TITLE: "OOK!"
|
||||
STR_CONTINUE_READING: "Oooook ook"
|
||||
STR_RESUME: "OOk!"
|
||||
STR_NO_OPEN_BOOK: "OOK?"
|
||||
STR_START_READING: "Ook ook"
|
||||
STR_NO_FILES_FOUND: "Oook?"
|
||||
STR_SELECT_CHAPTER: "Ook? Oook OOK!"
|
||||
STR_NO_CHAPTERS: "Ook..."
|
||||
STR_END_OF_BOOK: "Oooook."
|
||||
STR_EMPTY_CHAPTER: "OOk?"
|
||||
STR_INDEXING: "Oook..."
|
||||
STR_LOADING: "Ooook..."
|
||||
STR_LOADING_POPUP: "Oook"
|
||||
STR_LOOKUP: "Ook? Oook! OOK"
|
||||
STR_WIFI_NETWORKS: "Wi-Fi OOK"
|
||||
STR_NO_NETWORKS: "Oook?"
|
||||
STR_NETWORKS_FOUND: "%zu OOK"
|
||||
STR_SCANNING: "Oook..."
|
||||
STR_FINDING_SAVED_WIFI: "Oook Wi-Fi..."
|
||||
STR_CONNECTING: "Ooook..."
|
||||
STR_CONNECTING_SAVED_WIFI: "OOk Wi-Fi..."
|
||||
STR_SHOW_NETWORKS: "OOK"
|
||||
STR_CONNECTED: "Oook!"
|
||||
STR_CONNECTION_FAILED: "OOk?"
|
||||
STR_FORGET_NETWORK: "Oook?"
|
||||
STR_SAVE_PASSWORD: "OOK?"
|
||||
STR_PRESS_OK_SCAN: "OOk OK"
|
||||
STR_JOIN_NETWORK: "Oook!"
|
||||
STR_CREATE_HOTSPOT: "Ooook"
|
||||
STR_JOIN_DESC: "Oook Wi-Fi"
|
||||
STR_HOTSPOT_DESC: "Oooook Wi-Fi"
|
||||
STR_STARTING_HOTSPOT: "Ooook..."
|
||||
STR_HOTSPOT_MODE: "OOk!"
|
||||
STR_CONNECT_WIFI_HINT: "Oook Wi-Fi"
|
||||
STR_OPEN_URL_HINT: "Oook URL"
|
||||
STR_SCAN_QR_HINT: "Oook QR:"
|
||||
STR_CALIBRE_WIRELESS: "Calibre OOK"
|
||||
STR_NETWORK_LEGEND: "* = OOK | + = OOk"
|
||||
STR_MAC_ADDRESS: "MAC!"
|
||||
STR_CHECKING_WIFI: "OOk Wi-Fi..."
|
||||
STR_ENTER_WIFI_PASSWORD: "Oook Wi-Fi"
|
||||
STR_ADD_HIDDEN_NETWORK: "Oook..."
|
||||
STR_ENTER_WIFI_SSID: "OOk (SSID)"
|
||||
STR_TO_PREFIX: "Oo "
|
||||
STR_CALIBRE_RECEIVING: "Oook: "
|
||||
STR_CALIBRE_RECEIVED: "OOK!: "
|
||||
STR_CAT_DISPLAY: "OOK"
|
||||
STR_CAT_READER: "OOk"
|
||||
STR_CAT_CONTROLS: "Ook!"
|
||||
STR_CAT_SYSTEM: "Oook"
|
||||
STR_SLEEP_SCREEN: "Oooook"
|
||||
STR_NIGHT_MODE: "Ook!"
|
||||
STR_QUICK_RESUME_TIMEOUT: "Oook ook"
|
||||
STR_SLEEP_COVER_MODE: "Ook ook"
|
||||
STR_HIDE_BATTERY: "OOk %"
|
||||
STR_EXTRA_SPACING: "Oook ook"
|
||||
STR_TEXT_AA: "Ook-ook"
|
||||
STR_IMAGES: "OOk!"
|
||||
STR_IMAGES_DISPLAY: "Oook"
|
||||
STR_IMAGES_PLACEHOLDER: "Ooook"
|
||||
STR_IMAGES_SUPPRESS: "OOK"
|
||||
STR_EOB_HOME: "Oook"
|
||||
STR_EOB_CONTINUE_WITH: "Oook ook"
|
||||
STR_SHORT_PWR_BTN: "Oooook OOK"
|
||||
STR_ORIENTATION: "Ook? Oooook OOK"
|
||||
STR_SIDE_BTN_LAYOUT: "Oook ook"
|
||||
STR_TOUCH_READER_CONTROLS: "OOk! OOK"
|
||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Oook OOK"
|
||||
STR_LONG_PRESS_BEHAVIOR: "Oooook OOK"
|
||||
STR_LONG_PRESS_BEHAVIOR_OFF: "OFF"
|
||||
STR_LONG_PRESS_BEHAVIOR_SKIP: "Oook!"
|
||||
STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "Ook? OOK"
|
||||
STR_LONG_PRESS_MENU: "Oooook!"
|
||||
STR_FONT_PREVIEW_TEXT: "Ook ook ook."
|
||||
STR_FONT_FAMILY: "Oook OOK"
|
||||
STR_FONT_SIZE: "OOk OOK"
|
||||
STR_TEXT_SETTINGS: "Oooook OOK: Ook!"
|
||||
STR_FONT: "OOk!"
|
||||
STR_SIZE: "OOK"
|
||||
STR_LAYOUT: "Oook"
|
||||
STR_ALIGNMENT: "Oooook"
|
||||
STR_STYLE: "OOk!"
|
||||
STR_NOT_IN_PREVIEW: "Oook?"
|
||||
STR_LINE_SPACING: "Oook ook"
|
||||
STR_SCREEN_MARGIN: "OOk ook"
|
||||
STR_PARA_ALIGNMENT: "Ook OOK"
|
||||
STR_HYPHENATION: "Ooook"
|
||||
STR_TIME_TO_SLEEP: "Oook..."
|
||||
STR_SHOW_HIDDEN_FILES: "OOk?"
|
||||
STR_REMOVE_READ_FROM_RECENTS: "Oook OOK"
|
||||
STR_MOVE_FINISHED_TO_READ: "Ook! OOK"
|
||||
STR_REFRESH_FREQ: "Oook!"
|
||||
STR_KOREADER_SYNC: "KOReader OOK"
|
||||
STR_CHECK_UPDATES: "Oook!"
|
||||
STR_LANGUAGE: "OOk?"
|
||||
STR_CLEAR_READING_CACHE: "Oook OOK"
|
||||
STR_USERNAME: "Oook"
|
||||
STR_PASSWORD: "OOk!"
|
||||
STR_SYNC_SERVER_URL: "Oook URL"
|
||||
STR_DOCUMENT_MATCHING: "Oook OOK"
|
||||
STR_SEND_METADATA: "Ook! OOK"
|
||||
STR_SYNC_BEHAVIOR: "OOk ook"
|
||||
STR_ASK_EVERY_TIME: "Oook?"
|
||||
STR_SMART_SYNC: "Oook!"
|
||||
STR_AUTHENTICATE: "Oooook"
|
||||
STR_SIGN_UP: "OOk!"
|
||||
STR_KOREADER_USERNAME: "KOReader OOK"
|
||||
STR_KOREADER_PASSWORD: "KOReader OOk"
|
||||
STR_FILENAME: "Oook"
|
||||
STR_KOREADER_AUTH: "KOReader OOK"
|
||||
STR_DONE: "Oook"
|
||||
STR_DARK: "OOk!"
|
||||
STR_LIGHT: "Oook"
|
||||
STR_CUSTOM: "Ooook"
|
||||
STR_COVER: "OOk?"
|
||||
STR_NONE_OPT: "OOk!"
|
||||
STR_FIT: "OOk"
|
||||
STR_CROP: "OOK!"
|
||||
STR_NEVER: "Oook!"
|
||||
STR_IN_READER: "Ook? OOK"
|
||||
STR_ALWAYS: "Oooook"
|
||||
STR_IGNORE: "OOk?"
|
||||
STR_SLEEP: "Oook!"
|
||||
STR_PAGE_TURN: "Oook OOK"
|
||||
STR_FORCE_REFRESH: "Ook! OOK"
|
||||
STR_PORTRAIT: "Oook"
|
||||
STR_LANDSCAPE_CW: "Oook OOK"
|
||||
STR_INVERTED: "OOk!"
|
||||
STR_ORIENTATION_INVERTED: "Ook 180°"
|
||||
STR_LANDSCAPE_CCW: "OOk OOK"
|
||||
STR_PREV_NEXT: "OOk/OOK"
|
||||
STR_NEXT_PREV: "OOK/OOk"
|
||||
STR_KOSYNC: "KOSync"
|
||||
STR_BOOKMARK_OPTION: "Oook!"
|
||||
STR_DICTIONARY: "Oooook"
|
||||
STR_DISABLED: "OOk!"
|
||||
STR_NOTO_SERIF: "Noto OOK"
|
||||
STR_NOTO_SANS: "Noto OOk"
|
||||
STR_TIGHT: "OOk!"
|
||||
STR_NORMAL: "Oook"
|
||||
STR_WIDE: "OOk!"
|
||||
STR_JUSTIFY: "Ooook!"
|
||||
STR_ALIGN_LEFT: "OOk"
|
||||
STR_CENTER: "Oook"
|
||||
STR_ALIGN_RIGHT: "OOK!"
|
||||
STR_PAGES_1: "1 OOK"
|
||||
STR_PAGES_5: "5 OOk"
|
||||
STR_PAGES_10: "10 OOK"
|
||||
STR_PAGES_15: "15 OOk"
|
||||
STR_PAGES_30: "30 OooK"
|
||||
|
||||
STR_UPDATE: "Oook!"
|
||||
STR_CURRENT_VERSION: "OOK: "
|
||||
STR_NEW_VERSION: "OOk: "
|
||||
STR_DOWNLOADING: "Oooook..."
|
||||
STR_UNNAMED: "OOk?"
|
||||
STR_HOLD_OPEN_TO_DELETE: "Oook OOK"
|
||||
STR_NEXT_PAGE: "OOk »"
|
||||
STR_PREV_PAGE: "« OOK"
|
||||
STR_NETWORK_PREFIX: "OOk: "
|
||||
STR_IP_ADDRESS_PREFIX: "IP: "
|
||||
STR_SD_CARD: "SD OOK"
|
||||
STR_BACK: "« OOK"
|
||||
STR_EXIT: "« OOk"
|
||||
STR_HOME: "« Oook"
|
||||
STR_SELECT: "OOk!"
|
||||
STR_SELECTED: "Oooook"
|
||||
STR_TOGGLE: "Oook!"
|
||||
STR_TOGGLE_BOOKMARK: "Ook? OOK! Oooook"
|
||||
STR_CONFIRM: "Oooook"
|
||||
STR_CANCEL: "OOk?"
|
||||
STR_CONNECT: "Oooook"
|
||||
STR_OPEN: "Oook"
|
||||
STR_DOWNLOAD: "Oooook"
|
||||
STR_RETRY: "Oook!"
|
||||
STR_YES: "OOk"
|
||||
STR_NO: "Oo"
|
||||
STR_SHOW: "Oook"
|
||||
STR_HIDE: "OOk!"
|
||||
STR_STATE_ON: "OK"
|
||||
STR_STATE_OFF: "OOk"
|
||||
STR_NOT_SET: "Oook"
|
||||
STR_DIR_LEFT: "OOk!"
|
||||
STR_DIR_RIGHT: "OOK!"
|
||||
STR_DIR_UP: "OK"
|
||||
STR_DIR_DOWN: "OOk!"
|
||||
STR_OK_BUTTON: "OK"
|
||||
STR_KEY_SHIFT: "OOk!"
|
||||
STR_KEY_MODE_SYMBOLS: "?123"
|
||||
STR_KEY_MODE_ABC: "abc"
|
||||
|
||||
STR_SLEEP_COVER_FILTER: "Oook OOK"
|
||||
STR_FILTER_CONTRAST: "Oooook"
|
||||
STR_CUSTOMISE_STATUS_BAR: "Oook OOK"
|
||||
STR_CHAPTER_PAGE_COUNT: "OOk OOK"
|
||||
STR_BOOK_PROGRESS_PERCENTAGE: "Oook OOK"
|
||||
STR_PROGRESS_BAR: "Oook OOK"
|
||||
STR_PROGRESS_BAR_THICKNESS: "Oook OOK"
|
||||
STR_PROGRESS_BAR_THIN: "OOk!"
|
||||
STR_PROGRESS_BAR_MEDIUM: "Oook"
|
||||
STR_PROGRESS_BAR_THICK: "Oook!"
|
||||
STR_BOOK: "Oook"
|
||||
STR_CHAPTER: "OOk!"
|
||||
STR_EXAMPLE_CHAPTER: "OOk 21"
|
||||
STR_EXAMPLE_BOOK: "Oook OOK"
|
||||
STR_PREVIEW: "Oook!"
|
||||
STR_TITLE: "OOk?"
|
||||
STR_BATTERY: "Oook"
|
||||
STR_XTC_STATUS_BAR: "XTC OOK"
|
||||
STR_BOTTOM: "Oooook"
|
||||
STR_TOP: "OOk"
|
||||
STR_CLOCK: "Oook!"
|
||||
STR_CLOCK_UTC_OFFSET: "Oook OOK"
|
||||
STR_CLOCK_FORMAT: "OOk OOK"
|
||||
STR_CLOCK_FORMAT_24H: "24-OOK"
|
||||
STR_CLOCK_FORMAT_12H: "12-OOk"
|
||||
STR_CURRENT_TIME: "OOk:"
|
||||
STR_NEXT_FIELD: "Oook"
|
||||
STR_CLOCK_SYNC: "OOk OOK"
|
||||
STR_CLOCK_SYNC_NOW: "Oook OOK"
|
||||
STR_CLOCK_SYNCING: "Oooook..."
|
||||
STR_CLOCK_SYNC_OK: "Oook!"
|
||||
STR_CLOCK_SYNC_FAIL: "OOk?"
|
||||
STR_CLOCK_SYNC_NO_WIFI: "Wi-Fi OOK"
|
||||
STR_CLOCK_SYNCED: "Oooook"
|
||||
STR_UI_THEME: "OOk OOK"
|
||||
STR_THEME_CLASSIC: "Oook!"
|
||||
STR_THEME_LYRA: "OOk!"
|
||||
STR_THEME_ROUNDEDRAFF: "Oook"
|
||||
STR_THEME_LYRA_EXTENDED: "Oook OOK"
|
||||
STR_SUNLIGHT_FADING_FIX: "Oook OOK"
|
||||
STR_REMAP_FRONT_BUTTONS: "Oook OOK"
|
||||
|
||||
STR_BOOKMARKS: "Ook! OOK Oooook"
|
||||
STR_NO_BOOKMARKS: "OOk?"
|
||||
STR_BOOKMARK_ADDED: "Ook!"
|
||||
STR_BOOKMARK_REMOVED: "Oook!"
|
||||
STR_OPDS_BROWSER: "OPDS OOK"
|
||||
STR_SEARCH: "Oook!"
|
||||
STR_COVER_CUSTOM: "OOk + OOK"
|
||||
STR_QUICK_RESUME: "Oook OOK"
|
||||
STR_MENU_RECENT_BOOKS: "Oook OOK"
|
||||
STR_REMOVE_FROM_RECENTS: "OOk?"
|
||||
STR_NO_RECENT_BOOKS: "Oook?"
|
||||
STR_CALIBRE_DESC: "Calibre OOK"
|
||||
STR_FORGET_AND_REMOVE: "Oook?"
|
||||
STR_FORGET_BUTTON: "Oook!"
|
||||
STR_CALIBRE_STARTING: "Calibre OOK"
|
||||
STR_CALIBRE_SETUP: "OOk!"
|
||||
STR_CALIBRE_STATUS: "Oook"
|
||||
STR_CLEAR_BUTTON: "Oook!"
|
||||
STR_DEFAULT_VALUE: "OOk OOK"
|
||||
STR_UNASSIGNED: "OOk?"
|
||||
STR_ALREADY_ASSIGNED: "Oook OOK"
|
||||
STR_GO_TO_PERCENT: "Oook! Ook? %"
|
||||
STR_GO_HOME_BUTTON: "Oook OOK! Oooook"
|
||||
STR_SYNC_PROGRESS: "Oooook OOK! Ook!"
|
||||
STR_DELETE_CACHE: "Oook OOK? Oooook!"
|
||||
STR_DELETE: "Oooook"
|
||||
STR_CONFIRM_DELETE_BOOKMARK: "Oook?"
|
||||
STR_DISPLAY_QR: "Oooook OOK QR!"
|
||||
STR_CHAPTER_PREFIX: "OOk: "
|
||||
STR_BOOK_PREFIX: "Oook: "
|
||||
STR_SYNCING_TIME: "Oook..."
|
||||
STR_FETCH_PROGRESS: "OOk..."
|
||||
STR_UPLOAD_PROGRESS: "Oooook..."
|
||||
STR_KOREADER_SETUP_HINT: "KOReader OOK"
|
||||
STR_PROGRESS_FOUND: "Oook!"
|
||||
STR_REMOTE_LABEL: "OOk:"
|
||||
STR_LOCAL_LABEL: "Oook:"
|
||||
STR_PAGE_OVERALL_FORMAT: "OOk %d, %.2f%%"
|
||||
STR_PAGE_TOTAL_OVERALL_FORMAT: "Oook %d/%d, %.2f%%"
|
||||
STR_DEVICE_FROM_FORMAT: "OOk: %s"
|
||||
STR_APPLY_REMOTE: "Oook OOK"
|
||||
STR_UPLOAD_LOCAL: "OOk OOK"
|
||||
STR_UPLOAD_PROMPT: "Oook?"
|
||||
STR_UPLOAD_SUCCESS: "Oooook!"
|
||||
STR_ALREADY_SYNCED: "Oook!"
|
||||
STR_SECTION_PREFIX: "OOk "
|
||||
STR_UPLOAD: "Oooook"
|
||||
STR_BOOK_S_STYLE: "Oook OOK"
|
||||
STR_EMBEDDED_STYLE: "OOk OOK"
|
||||
STR_FOCUS_READING: "Oook OOK"
|
||||
STR_OPDS_SERVER_URL: "OPDS OOK"
|
||||
STR_PWR_BTN_FOOTNOTE_BACK: "Oook OOK"
|
||||
STR_BACK_SHORT_TO_FILE_BROWSER: "OOk OOK"
|
||||
STR_SET_SLEEP_COVER: "Oook OOK"
|
||||
STR_FOOTNOTES: "Ook! Oook OOK"
|
||||
STR_NO_FOOTNOTES: "Oook?"
|
||||
STR_LINK: "[Ook]"
|
||||
STR_SCREENSHOT_BUTTON: "Oook! OOK? Ook!"
|
||||
STR_SLEEP_TIMER_VALUE_FORMAT: "%u OOK"
|
||||
STR_SLEEP_NEVER: "Oook!"
|
||||
STR_ADD_SERVER: "Oook!"
|
||||
STR_SERVER_NAME: "OOk OOK"
|
||||
STR_DELETE_SERVER: "Oook OOK"
|
||||
STR_OPDS_SERVERS: "OPDS OOK"
|
||||
STR_OPDS_DOWNLOAD_FOLDER: "Oook OOK"
|
||||
STR_OPDS_FILENAME_FORMAT: "OOk OOK"
|
||||
STR_FMT_AUTHOR_TITLE: "OOk - OOK"
|
||||
STR_FMT_TITLE_AUTHOR: "OOK - OOk"
|
||||
STR_FMT_TITLE: "Oook"
|
||||
STR_OPDS_SD_ROOT: "SD OOK"
|
||||
STR_AUTO_TURN_ENABLED: "OOk: "
|
||||
STR_AUTO_TURN_PAGES_PER_MIN: "Oooook OOK (Ook!)"
|
||||
STR_MANAGE_FONTS: "Oook OOK"
|
||||
STR_FONT_BROWSER: "OOk OOK"
|
||||
STR_LOADING_FONT_LIST: "Oooook..."
|
||||
STR_NO_FONTS_AVAILABLE: "Oook?"
|
||||
STR_FONT_INSTALLED: "Oook!"
|
||||
STR_INSTALLED: "OOk!"
|
||||
STR_DOWNLOAD_ALL: "Oook OOK"
|
||||
STR_UPDATE_ALL: "OOk OOK"
|
||||
STR_UPDATE_AVAILABLE: "Oook!"
|
||||
STR_TILT_PAGE_TURN: "Oook OOK"
|
||||
STR_SD_FIRMWARE_UPDATE: "SD Oooook Ooook Ooooook"
|
||||
STR_STEP_HINT_FRONT: "Ooook Ooooook:"
|
||||
STR_STEP_HINT_SIDE: "OOK Oook:"
|
||||
@@ -303,6 +303,7 @@ STR_OPDS_BROWSER: "Navegador OPDS"
|
||||
STR_SEARCH: "Buscar"
|
||||
STR_COVER_CUSTOM: "Portada + Pers."
|
||||
STR_QUICK_RESUME: "Reanudación rápida"
|
||||
STR_TRANSPARENT: "Transparente"
|
||||
STR_MENU_RECENT_BOOKS: "Libros recientes"
|
||||
STR_REMOVE_FROM_RECENTS: "¿Eliminar de Libros recientes?"
|
||||
STR_NO_RECENT_BOOKS: "No hay libros recientes"
|
||||
|
||||
@@ -283,6 +283,7 @@ STR_BOOKMARK_REMOVED: "S'ha eliminat el punt de llibre."
|
||||
STR_OPDS_BROWSER: "Navegador OPDS"
|
||||
STR_COVER_CUSTOM: "Portada + Personalitzat"
|
||||
STR_QUICK_RESUME: "Represa ràpida"
|
||||
STR_TRANSPARENT: "Transparent"
|
||||
STR_MENU_RECENT_BOOKS: "Llibres recents"
|
||||
STR_REMOVE_FROM_RECENTS: "Vols eliminar-lo de Llibres recents?"
|
||||
STR_NO_RECENT_BOOKS: "No hi ha llibres recents"
|
||||
|
||||
@@ -167,12 +167,18 @@ bool HalGPIO::wasTouchTap(float& nx, float& ny) const { return inputMgr.wasTouch
|
||||
|
||||
bool HalGPIO::wasTouchDown(float& nx, float& ny) const { return inputMgr.wasTouchPressedAt(nx, ny); }
|
||||
|
||||
bool HalGPIO::wasTouchReleased() const { return inputMgr.wasTouchReleased(); }
|
||||
|
||||
bool HalGPIO::isTouchTapCandidate(float& nx, float& ny, unsigned long& heldMs) const {
|
||||
return inputMgr.isTouchTapCandidate(nx, ny, heldMs);
|
||||
}
|
||||
|
||||
bool HalGPIO::isTouchHeldAt(float& nx, float& ny) const { return inputMgr.isTouchHeldAt(nx, ny); }
|
||||
|
||||
bool HalGPIO::wasTouchLongPress(float& nx, float& ny) const { return inputMgr.wasTouchLongPress(nx, ny); }
|
||||
|
||||
void HalGPIO::suppressTouchContact() { inputMgr.suppressTouchContact(); }
|
||||
|
||||
unsigned long HalGPIO::lastTouchHeldMs() const { return inputMgr.lastTouchHeldMs(); }
|
||||
|
||||
bool HalGPIO::wasSwipe(float& nxStart, float& nyStart, float& nxEnd, float& nyEnd) const {
|
||||
@@ -185,6 +191,11 @@ void HalGPIO::setSharedConfirmPowerShortPressEmitsPower(const bool enabled) {
|
||||
InputManager::setSharedConfirmPowerShortPressEmitsPower(enabled);
|
||||
}
|
||||
|
||||
bool HalGPIO::hasEdgeSideButtons() const {
|
||||
return BoardConfig::ACTIVE.board == BoardConfig::Board::XteinkX3 ||
|
||||
BoardConfig::ACTIVE.board == BoardConfig::Board::XteinkX4Pro;
|
||||
}
|
||||
|
||||
bool HalGPIO::isXteinkDevice() const {
|
||||
return BoardConfig::ACTIVE.board == BoardConfig::Board::XteinkX3 ||
|
||||
BoardConfig::ACTIVE.board == BoardConfig::Board::XteinkX3Uc8279 ||
|
||||
|
||||
@@ -60,6 +60,12 @@ class HalGPIO {
|
||||
inline bool deviceIsX4() const { return _deviceType == DeviceType::X4; }
|
||||
bool isXteinkDevice() const;
|
||||
|
||||
// True when the board's page buttons sit on the left/right screen edges
|
||||
// (X3, X4 Pro) rather than an off-screen vertical rocker. Drives side-hint
|
||||
// placement and the flipped large-step direction in selection activities.
|
||||
// Keyed off the active BoardConfig profile, not the X3/X4 runtime detection.
|
||||
bool hasEdgeSideButtons() const;
|
||||
|
||||
// Start button GPIO and setup SPI for screen and SD card
|
||||
void begin();
|
||||
|
||||
@@ -75,8 +81,20 @@ class HalGPIO {
|
||||
bool hasTouch() const;
|
||||
bool wasTouchTap(float& nx, float& ny) const;
|
||||
bool wasTouchDown(float& nx, float& ny) const;
|
||||
// Raw release edge, reported even when the contact was not a tap (swipe end,
|
||||
// drag-off). Snapshot builders forward it so interaction routing can clear
|
||||
// pressed state.
|
||||
bool wasTouchReleased() const;
|
||||
bool isTouchTapCandidate(float& nx, float& ny, unsigned long& heldMs) const;
|
||||
bool isTouchHeldAt(float& nx, float& ny) const;
|
||||
// One-shot long-press, fired by the SDK classifier while the finger is still
|
||||
// down (stationary contact held past its threshold). Position = touch-down
|
||||
// point. Callers that act on it should suppressTouchContact() so the lift
|
||||
// cannot also tap.
|
||||
bool wasTouchLongPress(float& nx, float& ny) const;
|
||||
// Ignore the remainder of the current contact (its continued hold and its
|
||||
// release edge). Self-clears once the contact ends.
|
||||
void suppressTouchContact();
|
||||
unsigned long lastTouchHeldMs() const;
|
||||
bool wasSwipe(float& nxStart, float& nyStart, float& nxEnd, float& nyEnd) const;
|
||||
bool wasTouchActivity() const;
|
||||
|
||||
+25
-7
@@ -11,9 +11,13 @@
|
||||
#include "esp_private/panic_internal.h"
|
||||
|
||||
#define MAX_PANIC_STACK_DEPTH 32
|
||||
#define PANIC_CAPTURE_MAGIC 0x50414E49u
|
||||
|
||||
RTC_NOINIT_ATTR char panicMessage[256];
|
||||
RTC_NOINIT_ATTR HalSystem::StackFrame panicStack[MAX_PANIC_STACK_DEPTH];
|
||||
// RTC_NOINIT is uninitialized on cold boot, so only this exact marker proves a
|
||||
// panic diagnostic was captured before the reset.
|
||||
RTC_NOINIT_ATTR volatile uint32_t panicCaptureMarker;
|
||||
|
||||
extern "C" {
|
||||
|
||||
@@ -29,6 +33,7 @@ void IRAM_ATTR __wrap_panic_abort(const char* message) {
|
||||
panicMessage[i] = message[i];
|
||||
}
|
||||
panicMessage[i] = '\0';
|
||||
panicCaptureMarker = PANIC_CAPTURE_MAGIC;
|
||||
|
||||
__real_panic_abort(message);
|
||||
}
|
||||
@@ -68,6 +73,7 @@ void IRAM_ATTR __wrap_panic_print_backtrace(const void* frame, int core) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
panicCaptureMarker = PANIC_CAPTURE_MAGIC;
|
||||
|
||||
__real_panic_print_backtrace(frame, core);
|
||||
#endif
|
||||
@@ -77,9 +83,8 @@ void IRAM_ATTR __wrap_panic_print_backtrace(const void* frame, int core) {
|
||||
namespace HalSystem {
|
||||
|
||||
void begin() {
|
||||
// This is mostly for the first boot, we need to initialize the panic info and logs to empty state
|
||||
// If we reboot from a panic state, we want to keep the panic info until we successfully dump it to the SD card, use
|
||||
// `clearPanic()` to clear it after dumping
|
||||
// On a panic reboot, preserve diagnostics until checkPanic() has tried to write them to the SD card.
|
||||
// Ordinary boots clear any stale retained diagnostics.
|
||||
if (!isRebootFromPanic()) {
|
||||
clearPanic();
|
||||
} else {
|
||||
@@ -98,9 +103,16 @@ void checkPanic() {
|
||||
auto panicInfo = getPanicInfo(true);
|
||||
auto file = Storage.open("/crash_report.txt", O_WRITE | O_CREAT | O_TRUNC);
|
||||
if (file) {
|
||||
file.write(panicInfo.c_str(), panicInfo.size());
|
||||
const size_t written = file.write(panicInfo.c_str(), panicInfo.size());
|
||||
file.close();
|
||||
LOG_INF("SYS", "Dumped panic info to SD card");
|
||||
if (written == panicInfo.size()) {
|
||||
// Keep the crash data for CrashActivity, but mark it consumed so a
|
||||
// later watchdog reset cannot be mistaken for this panic.
|
||||
panicCaptureMarker = 0;
|
||||
LOG_INF("SYS", "Dumped panic info to SD card");
|
||||
} else {
|
||||
LOG_ERR("SYS", "Failed to write complete crash report (%zu of %zu bytes)", written, panicInfo.size());
|
||||
}
|
||||
} else {
|
||||
LOG_ERR("SYS", "Failed to open crash_report.txt for writing");
|
||||
}
|
||||
@@ -108,6 +120,7 @@ void checkPanic() {
|
||||
}
|
||||
|
||||
void clearPanic() {
|
||||
panicCaptureMarker = 0;
|
||||
panicMessage[0] = '\0';
|
||||
for (size_t i = 0; i < MAX_PANIC_STACK_DEPTH; i++) {
|
||||
panicStack[i].sp = 0;
|
||||
@@ -148,8 +161,13 @@ std::string getPanicInfo(bool full) {
|
||||
|
||||
bool isRebootFromPanic() {
|
||||
const auto resetReason = esp_reset_reason();
|
||||
return resetReason == ESP_RST_PANIC || resetReason == ESP_RST_CPU_LOCKUP || resetReason == ESP_RST_INT_WDT ||
|
||||
resetReason == ESP_RST_TASK_WDT || resetReason == ESP_RST_WDT;
|
||||
if (resetReason == ESP_RST_PANIC || resetReason == ESP_RST_CPU_LOCKUP) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const bool watchdogReset =
|
||||
resetReason == ESP_RST_INT_WDT || resetReason == ESP_RST_TASK_WDT || resetReason == ESP_RST_WDT;
|
||||
return watchdogReset && panicCaptureMarker == PANIC_CAPTURE_MAGIC;
|
||||
}
|
||||
|
||||
} // namespace HalSystem
|
||||
@@ -21,6 +21,7 @@ class CrossPointSettings : public PersistableStore<CrossPointSettings> {
|
||||
COVER_CUSTOM = 4,
|
||||
BLANK = 5,
|
||||
QUICK_RESUME = 6,
|
||||
TRANSPARENT_CUSTOM = 7,
|
||||
SLEEP_SCREEN_MODE_COUNT
|
||||
};
|
||||
enum SLEEP_SCREEN_COVER_MODE { FIT = 0, CROP = 1, SLEEP_SCREEN_COVER_MODE_COUNT };
|
||||
|
||||
+50
-11
@@ -1,23 +1,43 @@
|
||||
#include "CrossPointState.h"
|
||||
|
||||
#include <Logging.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
bool CrossPointState::isRecentSleep(uint16_t idx, uint8_t checkCount) const {
|
||||
const uint8_t effectiveCount = std::min(checkCount, recentSleepFill);
|
||||
namespace {
|
||||
|
||||
bool isRecentIndex(const uint16_t* recentImages, uint8_t recentPos, uint8_t recentFill, uint16_t idx,
|
||||
uint8_t checkCount) {
|
||||
const uint8_t effectiveCount = std::min(checkCount, recentFill);
|
||||
for (uint8_t i = 0; i < effectiveCount; i++) {
|
||||
const uint8_t slot = (recentSleepPos + SLEEP_RECENT_COUNT - 1 - i) % SLEEP_RECENT_COUNT;
|
||||
if (recentSleepImages[slot] == idx) return true;
|
||||
const uint8_t slot =
|
||||
(recentPos + CrossPointState::SLEEP_RECENT_COUNT - 1 - i) % CrossPointState::SLEEP_RECENT_COUNT;
|
||||
if (recentImages[slot] == idx) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void pushRecentIndex(uint16_t* recentImages, uint8_t& recentPos, uint8_t& recentFill, uint16_t idx) {
|
||||
recentImages[recentPos] = idx;
|
||||
recentPos = (recentPos + 1) % CrossPointState::SLEEP_RECENT_COUNT;
|
||||
if (recentFill < CrossPointState::SLEEP_RECENT_COUNT) recentFill++;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool CrossPointState::isRecentSleep(uint16_t idx, uint8_t checkCount) const {
|
||||
return isRecentIndex(recentSleepImages, recentSleepPos, recentSleepFill, idx, checkCount);
|
||||
}
|
||||
|
||||
bool CrossPointState::isRecentOverlaySleep(uint16_t idx, uint8_t checkCount) const {
|
||||
return isRecentIndex(recentOverlaySleepImages, recentOverlaySleepPos, recentOverlaySleepFill, idx, checkCount);
|
||||
}
|
||||
|
||||
void CrossPointState::pushRecentSleep(uint16_t idx) {
|
||||
recentSleepImages[recentSleepPos] = idx;
|
||||
recentSleepPos = (recentSleepPos + 1) % SLEEP_RECENT_COUNT;
|
||||
if (recentSleepFill < SLEEP_RECENT_COUNT) recentSleepFill++;
|
||||
pushRecentIndex(recentSleepImages, recentSleepPos, recentSleepFill, idx);
|
||||
}
|
||||
|
||||
void CrossPointState::pushRecentOverlaySleep(uint16_t idx) {
|
||||
pushRecentIndex(recentOverlaySleepImages, recentOverlaySleepPos, recentOverlaySleepFill, idx);
|
||||
}
|
||||
|
||||
void CrossPointState::toJson(JsonDocument& doc) const {
|
||||
@@ -26,6 +46,10 @@ void CrossPointState::toJson(JsonDocument& doc) const {
|
||||
for (int i = 0; i < SLEEP_RECENT_COUNT; i++) recentArr.add(recentSleepImages[i]);
|
||||
doc["recentSleepPos"] = recentSleepPos;
|
||||
doc["recentSleepFill"] = recentSleepFill;
|
||||
JsonArray recentOverlayArr = doc["recentOverlaySleepImages"].to<JsonArray>();
|
||||
for (int i = 0; i < SLEEP_RECENT_COUNT; i++) recentOverlayArr.add(recentOverlaySleepImages[i]);
|
||||
doc["recentOverlaySleepPos"] = recentOverlaySleepPos;
|
||||
doc["recentOverlaySleepFill"] = recentOverlaySleepFill;
|
||||
doc["readerActivityLoadCount"] = readerActivityLoadCount;
|
||||
doc["lastSleepFromReader"] = lastSleepFromReader;
|
||||
doc["showBootScreen"] = showBootScreen;
|
||||
@@ -33,6 +57,7 @@ void CrossPointState::toJson(JsonDocument& doc) const {
|
||||
|
||||
bool CrossPointState::fromJson(JsonVariantConst doc) {
|
||||
openEpubPath = doc["openEpubPath"] | "";
|
||||
|
||||
memset(recentSleepImages, 0, sizeof(recentSleepImages));
|
||||
JsonArrayConst recentArr = doc["recentSleepImages"];
|
||||
const int actualCount =
|
||||
@@ -42,8 +67,22 @@ bool CrossPointState::fromJson(JsonVariantConst doc) {
|
||||
if (recentSleepPos >= SLEEP_RECENT_COUNT) recentSleepPos = actualCount > 0 ? recentSleepPos % SLEEP_RECENT_COUNT : 0;
|
||||
recentSleepFill = doc["recentSleepFill"] | static_cast<uint8_t>(0);
|
||||
recentSleepFill = static_cast<uint8_t>(std::min(static_cast<int>(recentSleepFill), actualCount));
|
||||
// Migrate legacy single-image field from old state.json (pre-recency-buffer).
|
||||
// Only seeds the buffer if the new buffer is empty (fresh migration, not a resave).
|
||||
|
||||
memset(recentOverlaySleepImages, 0, sizeof(recentOverlaySleepImages));
|
||||
JsonArrayConst recentOverlayArr = doc["recentOverlaySleepImages"];
|
||||
const int actualOverlayCount = recentOverlayArr.isNull() ? 0
|
||||
: std::min(static_cast<int>(recentOverlayArr.size()),
|
||||
static_cast<int>(SLEEP_RECENT_COUNT));
|
||||
for (int i = 0; i < actualOverlayCount; i++) {
|
||||
recentOverlaySleepImages[i] = recentOverlayArr[i] | static_cast<uint16_t>(0);
|
||||
}
|
||||
recentOverlaySleepPos = doc["recentOverlaySleepPos"] | static_cast<uint8_t>(0);
|
||||
if (recentOverlaySleepPos >= SLEEP_RECENT_COUNT) {
|
||||
recentOverlaySleepPos = actualOverlayCount > 0 ? recentOverlaySleepPos % CrossPointState::SLEEP_RECENT_COUNT : 0;
|
||||
}
|
||||
recentOverlaySleepFill = doc["recentOverlaySleepFill"] | static_cast<uint8_t>(0);
|
||||
recentOverlaySleepFill = static_cast<uint8_t>(std::min(static_cast<int>(recentOverlaySleepFill), actualOverlayCount));
|
||||
|
||||
if (recentSleepFill == 0 && !doc["lastSleepImage"].isNull()) {
|
||||
const uint8_t legacy = doc["lastSleepImage"] | static_cast<uint8_t>(UINT8_MAX);
|
||||
if (legacy != UINT8_MAX) pushRecentSleep(static_cast<uint16_t>(legacy));
|
||||
|
||||
@@ -14,9 +14,12 @@ class CrossPointState : public PersistableStore<CrossPointState> {
|
||||
static constexpr uint8_t SLEEP_RECENT_COUNT = 16;
|
||||
|
||||
std::string openEpubPath;
|
||||
uint16_t recentSleepImages[SLEEP_RECENT_COUNT] = {}; // circular buffer of recent wallpaper indices
|
||||
uint8_t recentSleepPos = 0; // next write slot
|
||||
uint8_t recentSleepFill = 0; // valid entries (0..SLEEP_RECENT_COUNT)
|
||||
uint16_t recentSleepImages[SLEEP_RECENT_COUNT] = {};
|
||||
uint8_t recentSleepPos = 0;
|
||||
uint8_t recentSleepFill = 0;
|
||||
uint16_t recentOverlaySleepImages[SLEEP_RECENT_COUNT] = {};
|
||||
uint8_t recentOverlaySleepPos = 0;
|
||||
uint8_t recentOverlaySleepFill = 0;
|
||||
uint8_t readerActivityLoadCount = 0;
|
||||
bool lastSleepFromReader = false;
|
||||
bool showBootScreen = true;
|
||||
@@ -25,12 +28,11 @@ class CrossPointState : public PersistableStore<CrossPointState> {
|
||||
void toJson(JsonDocument& doc) const;
|
||||
bool fromJson(JsonVariantConst doc);
|
||||
|
||||
// Returns true if idx was shown within the last checkCount picks.
|
||||
// Walks backwards from the most recently written slot.
|
||||
bool isRecentSleep(uint16_t idx, uint8_t checkCount) const;
|
||||
bool isRecentOverlaySleep(uint16_t idx, uint8_t checkCount) const;
|
||||
|
||||
void pushRecentSleep(uint16_t idx);
|
||||
void pushRecentOverlaySleep(uint16_t idx);
|
||||
};
|
||||
|
||||
// Helper macro to access state
|
||||
#define APP_STATE CrossPointState::getInstance()
|
||||
+49
-81
@@ -1,5 +1,7 @@
|
||||
#include "MappedInputManager.h"
|
||||
|
||||
#include <BoardConfig.h>
|
||||
#include <FreeInkUICore.h>
|
||||
#include <GfxRenderer.h>
|
||||
|
||||
#include <algorithm>
|
||||
@@ -8,6 +10,8 @@
|
||||
#include "CrossPointSettings.h"
|
||||
#include "components/UITheme.h"
|
||||
|
||||
namespace fui = freeink::ui;
|
||||
|
||||
bool MappedInputManager::isNavDirectionSwapped() const {
|
||||
// Key the swap on the orientation the screen is *actually* rendered at, not the persisted reader
|
||||
// setting. The reader (and its modal menus) render rotated, so navigation/labels flip there; the
|
||||
@@ -116,9 +120,6 @@ bool MappedInputManager::mapButton(const Button button, bool (HalGPIO::*fn)(uint
|
||||
}
|
||||
|
||||
namespace {
|
||||
constexpr float LEFT_EDGE_BACK_GESTURE_FRAC_X = 0.25f;
|
||||
constexpr float BOTTOM_EDGE_BACK_GESTURE_FRAC_Y = 0.14f;
|
||||
constexpr float TOP_EDGE_MENU_GESTURE_FRAC_Y = 0.14f;
|
||||
constexpr unsigned long TOUCH_DOWN_SELECT_DELAY_MS = 90;
|
||||
constexpr unsigned long TOUCH_HELD_OVERRIDE_WINDOW_MS = 250;
|
||||
} // namespace
|
||||
@@ -150,6 +151,17 @@ bool MappedInputManager::wasScreenTouchDown(int& x, int& y) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MappedInputManager::wasScreenLongPress(int& x, int& y) const {
|
||||
float nx = 0.0f;
|
||||
float ny = 0.0f;
|
||||
if (!gpio.wasTouchLongPress(nx, ny)) return false;
|
||||
// Consuming the long-press implies acting on it: suppress the rest of the
|
||||
// contact so the finger lift can't also tap whatever the action opened.
|
||||
gpio.suppressTouchContact();
|
||||
renderer.tapToLogical(nx, ny, x, y);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MappedInputManager::isScreenTouchHeld(int& x, int& y) const {
|
||||
// Live contact position while the finger is down (no tap-slop gate) — drag tracking.
|
||||
float nx = 0.0f;
|
||||
@@ -159,49 +171,14 @@ bool MappedInputManager::isScreenTouchHeld(int& x, int& y) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MappedInputManager::wasScreenTouchReleased() const { return gpio.wasTouchReleased(); }
|
||||
|
||||
bool MappedInputManager::wasTapInRect(const int x, const int y, const int width, const int height) const {
|
||||
int tx = 0;
|
||||
int ty = 0;
|
||||
return wasScreenTapped(tx, ty) && tx >= x && tx < x + width && ty >= y && ty < y + height;
|
||||
}
|
||||
|
||||
bool MappedInputManager::listItemFromPoint(const int x, const int y, int& index, const int itemCount,
|
||||
const int selectedIndex, const int listTop, const int listHeight,
|
||||
const bool hasSubtitle) const {
|
||||
(void)x;
|
||||
if (itemCount <= 0) return false;
|
||||
if (y < listTop || y >= listTop + listHeight) return false;
|
||||
|
||||
const auto& theme = UITheme::getInstance().getTheme();
|
||||
const int rowStep = theme.getListRowStep(hasSubtitle);
|
||||
if (rowStep <= 0) return false;
|
||||
|
||||
const int pageItems = theme.getListPageItems(listHeight, hasSubtitle);
|
||||
if (pageItems <= 0) return false;
|
||||
const int pageStart = std::max(0, selectedIndex / pageItems) * pageItems;
|
||||
const int row = (y - listTop) / rowStep;
|
||||
const int tapped = pageStart + row;
|
||||
if (row < 0 || row >= pageItems || tapped >= itemCount) return false;
|
||||
index = tapped;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MappedInputManager::wasListItemTapped(int& index, const int itemCount, const int selectedIndex, const int listTop,
|
||||
const int listHeight, const bool hasSubtitle) const {
|
||||
int tx = 0;
|
||||
int ty = 0;
|
||||
return wasScreenTapped(tx, ty) &&
|
||||
listItemFromPoint(tx, ty, index, itemCount, selectedIndex, listTop, listHeight, hasSubtitle);
|
||||
}
|
||||
|
||||
bool MappedInputManager::wasListItemTouchedDown(int& index, const int itemCount, const int selectedIndex,
|
||||
const int listTop, const int listHeight, const bool hasSubtitle) const {
|
||||
int tx = 0;
|
||||
int ty = 0;
|
||||
return wasScreenTouchDown(tx, ty) &&
|
||||
listItemFromPoint(tx, ty, index, itemCount, selectedIndex, listTop, listHeight, hasSubtitle);
|
||||
}
|
||||
|
||||
MappedInputManager::RowTouch MappedInputManager::rowTouch(int& row, const int top, const int rowStep,
|
||||
const int rowCount, const int xStart, const int xEnd,
|
||||
const int rowHeight) const {
|
||||
@@ -257,57 +234,48 @@ MappedInputManager::SwipeDir MappedInputManager::wasSwipe() const {
|
||||
int ex = 0;
|
||||
int ey = 0;
|
||||
if (!decodeSwipe(sx, sy, ex, ey)) return SwipeDir::None;
|
||||
const int dx = ex - sx;
|
||||
const int dy = ey - sy;
|
||||
if (std::abs(dx) >= std::abs(dy)) {
|
||||
return dx < 0 ? SwipeDir::Left : SwipeDir::Right;
|
||||
switch (fui::swipeDirection(sx, sy, ex, ey)) {
|
||||
case fui::SwipeDir::Left:
|
||||
return SwipeDir::Left;
|
||||
case fui::SwipeDir::Right:
|
||||
return SwipeDir::Right;
|
||||
case fui::SwipeDir::Up:
|
||||
return SwipeDir::Up;
|
||||
case fui::SwipeDir::Down:
|
||||
return SwipeDir::Down;
|
||||
default:
|
||||
return SwipeDir::None;
|
||||
}
|
||||
return dy < 0 ? SwipeDir::Up : SwipeDir::Down;
|
||||
}
|
||||
|
||||
// Edge classification (which swipe counts as an edge gesture) lives in the
|
||||
// SDK; only the MEANING of each edge — back, menu, home, light panel, and the
|
||||
// home-key remap — is decided here.
|
||||
bool MappedInputManager::wasEdgeSwipe(const freeink::ui::ScreenEdge edge) const {
|
||||
int sx = 0;
|
||||
int sy = 0;
|
||||
int ex = 0;
|
||||
int ey = 0;
|
||||
if (!decodeSwipe(sx, sy, ex, ey)) return false;
|
||||
const bool hit = fui::edgeSwipe(edge, sx, sy, ex, ey, renderer.getScreenWidth(), renderer.getScreenHeight());
|
||||
if (hit) rememberTouchHeldTime();
|
||||
return hit;
|
||||
}
|
||||
|
||||
bool MappedInputManager::wasBackGesture() const {
|
||||
// Back = left-to-right swipe starting near the left edge. Edge-anchored so that
|
||||
// mid-screen horizontal swipes stay available to activities that consume
|
||||
// SwipeDir::Left/Right (e.g. percent selection, image viewer).
|
||||
int sx = 0;
|
||||
int sy = 0;
|
||||
int ex = 0;
|
||||
int ey = 0;
|
||||
if (!decodeSwipe(sx, sy, ex, ey)) return false;
|
||||
const bool hit = sx <= renderer.getScreenWidth() * LEFT_EDGE_BACK_GESTURE_FRAC_X && ex > sx &&
|
||||
std::abs(ex - sx) > std::abs(ey - sy);
|
||||
if (hit) rememberTouchHeldTime();
|
||||
return hit;
|
||||
return wasEdgeSwipe(fui::ScreenEdge::Left);
|
||||
}
|
||||
|
||||
bool MappedInputManager::wasMenuGesture() const {
|
||||
// Downward swipe starting at the top edge (mirror of the bottom-edge home gesture).
|
||||
int sx = 0;
|
||||
int sy = 0;
|
||||
int ex = 0;
|
||||
int ey = 0;
|
||||
if (!decodeSwipe(sx, sy, ex, ey)) return false;
|
||||
const int topEdgeBottom = static_cast<int>(renderer.getScreenHeight() * TOP_EDGE_MENU_GESTURE_FRAC_Y);
|
||||
const bool hit = sy <= topEdgeBottom && ey > sy && std::abs(ey - sy) > std::abs(ex - sx);
|
||||
if (hit) rememberTouchHeldTime();
|
||||
return hit;
|
||||
}
|
||||
bool MappedInputManager::wasTopEdgeDownSwipe() const { return wasEdgeSwipe(fui::ScreenEdge::Top); }
|
||||
|
||||
bool MappedInputManager::wasHomeGesture() const {
|
||||
int sx = 0;
|
||||
int sy = 0;
|
||||
int ex = 0;
|
||||
int ey = 0;
|
||||
if (decodeSwipe(sx, sy, ex, ey)) {
|
||||
const int bottomEdgeTop =
|
||||
renderer.getScreenHeight() - static_cast<int>(renderer.getScreenHeight() * BOTTOM_EDGE_BACK_GESTURE_FRAC_Y);
|
||||
if (sy >= bottomEdgeTop && ey < sy && std::abs(ey - sy) > std::abs(ex - sx)) {
|
||||
rememberTouchHeldTime();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool MappedInputManager::wasBottomEdgeUpSwipe() const { return wasEdgeSwipe(fui::ScreenEdge::Bottom); }
|
||||
|
||||
bool MappedInputManager::wasMenuGesture() const { return wasTopEdgeDownSwipe(); }
|
||||
|
||||
bool MappedInputManager::wasHomeGesture() const { return wasBottomEdgeUpSwipe(); }
|
||||
|
||||
bool MappedInputManager::wasPressed(const Button button) const {
|
||||
if (button == Button::Back && wasBackGesture()) return true;
|
||||
|
||||
@@ -3,6 +3,11 @@
|
||||
#include <HalGPIO.h>
|
||||
|
||||
class GfxRenderer;
|
||||
namespace freeink {
|
||||
namespace ui {
|
||||
enum class ScreenEdge : uint8_t;
|
||||
}
|
||||
} // namespace freeink
|
||||
|
||||
class MappedInputManager {
|
||||
public:
|
||||
@@ -41,12 +46,19 @@ class MappedInputManager {
|
||||
bool hasTouch() const;
|
||||
bool wasScreenTapped(int& x, int& y) const;
|
||||
bool wasScreenTouchDown(int& x, int& y) const;
|
||||
// One-shot long-press from the SDK touch classifier, fired WHILE the finger
|
||||
// is still down (stationary contact held past the SDK threshold). Consuming
|
||||
// it suppresses the remainder of the contact — its continued hold and its
|
||||
// release edge — so the ensuing finger lift can't also tap-dismiss the popup
|
||||
// the long-press opened. The SDK owns that latch and self-clears it once the
|
||||
// contact ends.
|
||||
bool wasScreenLongPress(int& x, int& y) const;
|
||||
bool isScreenTouchHeld(int& x, int& y) const;
|
||||
// Raw release edge, also true when the contact ended in a swipe or drag-off
|
||||
// (which wasScreenTapped never reports). InputSnapshot builders forward it
|
||||
// off-target so FreeInkUI routing clears its pressed-element state.
|
||||
bool wasScreenTouchReleased() const;
|
||||
bool wasTapInRect(int x, int y, int width, int height) const;
|
||||
bool wasListItemTapped(int& index, int itemCount, int selectedIndex, int listTop, int listHeight,
|
||||
bool hasSubtitle) const;
|
||||
bool wasListItemTouchedDown(int& index, int itemCount, int selectedIndex, int listTop, int listHeight,
|
||||
bool hasSubtitle) const;
|
||||
|
||||
// Combined touch interaction for a band of equal rows with caller-supplied
|
||||
// geometry — the shared hit-test for lists the theme helpers above do not
|
||||
@@ -61,6 +73,10 @@ class MappedInputManager {
|
||||
RowTouch colTouch(int& col, int left, int colStep, int colCount, int yStart, int yEnd, int colWidth = 0) const;
|
||||
|
||||
SwipeDir wasSwipe() const;
|
||||
// Back = left-to-right swipe anchored at the left edge. Public so swipe-mode
|
||||
// page turns (reader) can exclude it from a plain SwipeDir::Right.
|
||||
bool wasBackGesture() const;
|
||||
// Home = bottom-edge up-swipe; reader menu = top-edge down-swipe.
|
||||
bool wasHomeGesture() const;
|
||||
bool wasMenuGesture() const;
|
||||
bool wasAnyPressed() const;
|
||||
@@ -93,11 +109,13 @@ class MappedInputManager {
|
||||
Button mapScreenDirection(Button button) const;
|
||||
Labels mapFrontLabels(const char* back, const char* confirm, const char* left, const char* right) const;
|
||||
bool mapButton(Button button, bool (HalGPIO::*fn)(uint8_t) const) const;
|
||||
bool wasBackGesture() const;
|
||||
// SDK edge classification (fui::edgeSwipe) + the shared decode/held-time
|
||||
// bookkeeping; the wrappers below give each edge its board meaning.
|
||||
bool wasEdgeSwipe(freeink::ui::ScreenEdge edge) const;
|
||||
bool wasTopEdgeDownSwipe() const;
|
||||
bool wasBottomEdgeUpSwipe() const;
|
||||
// Fetch the pending swipe (if any) and map both endpoints to logical screen coords
|
||||
bool decodeSwipe(int& sx, int& sy, int& ex, int& ey) const;
|
||||
bool listItemFromPoint(int x, int y, int& index, int itemCount, int selectedIndex, int listTop, int listHeight,
|
||||
bool hasSubtitle) const;
|
||||
void rememberTouchHeldTime() const;
|
||||
|
||||
mutable bool touchHeldOverrideValid = false;
|
||||
|
||||
Loaded 20 of 109 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user