Compare commits

...
Author SHA1 Message Date
Justin Mitchell 9cfdaa46a1 Reformat touch reader controls enum definition 2026-08-13 15:51:38 -04:00
Justin Mitchell 58b2f6d41e Move tap for reader menu setting to Controls category
Changed the category of the tapForReaderMenu setting from STR_CAT_READER to STR_CAT_CONTROLS for better organization in the settings UI.
2026-08-13 15:26:33 -04:00
Justin Mitchell 6bf0dd746a Add inverted tap mode for touch reader controls
Adds a new touch control mode that swaps the left/right tap zones, allowing users to navigate forward by tapping the left side and backward by tapping the right side. The inverted behavior is applied when the TOUCH_READER_INVERTED_TAP setting is selected.
2026-08-13 15:21:52 -04:00
Justin Mitchell c0b2d3c2ad Add toggle to disable tap-for-reader-menu gesture
Introduces a new setting 'tapForReaderMenu' that allows users to disable the center-third tap gesture for opening the reader menu. This option is only shown on devices with a capacitive Home key, since those devices can still access the menu via long-press. On devices without a Home key, the tap gesture remains the primary access method and cannot be disabled.
2026-08-13 15:14:41 -04:00
Justin Mitchell 8a4efefe25 Add Reader Menu option for long-press action on homekey boards
Adds a new long-press menu option that opens the reader menu. This option is conditionally available only on boards with a Home key. The new option allows users to configure long-press to open the reader menu directly, which is useful for devices with capacitive Home keys that provide long-press actions.
2026-08-13 03:25:38 -04:00
Justin Mitchell b26bc5d531 Avoid restart on WiFi exit for touch devices
On devices with capacitive touch, external power rails for touch and frontlight are not cycled during software reset. Instead of restarting, cleanly shut down WiFi and SNTP in place to preserve peripheral state. Also handle Home gesture alongside Back button for exit responsiveness in the web server activity.
2026-08-13 03:20:14 -04:00
Justin Mitchell 2b3379569a Add power button confirm action for capacitive touch
Adds PWR_CONFIRM option to short power button settings, allowing the power button to act as a confirm button on capacitive touch devices. Includes special handling for X4 Pro's frontlight double-click window to avoid conflicts.
2026-08-13 03:14:02 -04:00
Justin Mitchell 9ebb824dfd Add Home key long-press action support in reader
Implements long-press detection for the Home key on devices without a front Confirm button. The long-press triggers the user-configured action (bookmark, KOSync, dictionary, or disabled) similar to the Confirm button long-press behavior.
2026-08-13 03:10:11 -04:00
Justin Mitchell 8891d49669 Prevent light sleep when frontlight is active
Add frontlight state check to sleep exclusion conditions to avoid visible flashing caused by LEDC PWM suspension during light sleep cycles. This prevents the ESP from entering light sleep when the frontlight is on, similar to existing WiFi and USB exclusions.
2026-08-13 00:05:01 -04:00
Justin Mitchell 3c3e695340 Add X4 Pro support for recovery mode detection
X4 Pro uses a different button (DOWN instead of UP) and requires shorter debounce time (20ms vs 500ms) for recovery mode detection. The DOWN button is used to avoid GPIO0 boot-strap pin conflicts on X4 Pro hardware.
2026-08-12 23:47:17 -04:00
Justin Mitchell 2a3f08ddcd Add vertical bounds to reader menu tap zone
The tap zone for opening the reader menu now checks both horizontal and vertical bounds, restricting it to the center third of the screen in both dimensions. Previously only horizontal bounds were checked, allowing taps anywhere vertically in the middle third horizontally to open the menu.
2026-08-12 23:30:47 -04:00
Justin Mitchell 4ff215f4e1 Merge branch 'develop' into feat-x4-papermono-support
# Conflicts:
#	src/activities/settings/SettingsActivity.cpp
2026-08-12 23:16:56 -04:00
Justin Mitchell 9361115e46 Skip power button hold verification for X4 Pro
X4 Pro wakes on any power button press, so it should bypass the hold-duration verification like boards without a power button. This ensures the wake is treated as valid immediately.
2026-08-12 23:16:22 -04:00
Justin Mitchell 9b1fb712de fix: Guard GPIO13 power control for Xteink C3 boards only (#2998) 2026-08-12 22:06:52 -04:00
Justin Mitchell e715532f90 fix: settings UI not updating after language change & line height padding (#2997) 2026-08-12 21:32:03 -04:00
Justin Mitchell 1020f66723 Merge remote-tracking branch 'origin/develop' into feat-x4-papermono-support 2026-08-12 21:13:26 -04:00
Brian Pugh 70faa29dac perf: light-sleep idle + refresh downclock — ~3.2x active reading time (X3-measured) (#2525)
## Summary

Reduces active-reading power draw ~3.2x, measured end-to-end on an X3
with a [Nordic
PPK2](https://www.nordicsemi.com/Products/Development-hardware/Power-Profiler-Kit-2)
at the battery terminals (slim build, USB cable removed, 3.8v). Three
independent changes:

1. **Light-sleep between input polls when idle** (the big one): after 1
s of inactivity the main loop replaces its busy `delay(50)` with 50 ms
timer-paced `esp_light_sleep_start()` slices. Poll cadence and input
latency are unchanged — the CPU sleeps between polls instead of spinning
at 10 MHz. Guards: never sleeps while a `HalPowerManager::Lock` is held
(render in flight), WiFi is up, or USB is connected (light sleep kills
the CDC link).
2. **Two-stage idle backoff**: downclock at 500 ms, light sleep at 1 s,
replacing the flat 3 s full-speed window. Renders re-raise the clock via
`Lock` the moment they start, so the full-speed tail only ever served
loop bookkeeping. (The 3 s value in #852 was a placeholder;
responsiveness verified on device against stock.)
3. **Refresh BUSY-wait downclock**: drops the CPU to 10 MHz while
`pollBusy()` polls the BUSY pin (0.3–2 s per refresh), via new optional
hooks in the SDK — **depends on Free-Ink/freeink-sdk/pull/8**. Plus a 1
Hz throttle on the X3's I2C-based USB detection (was polled every loop).

(1+2) reduced average static-page-current from 9.68mA to 3.45mA.
Reducing the battery-monitor polling frequency further reduced it to
2.78mA.

Before merging, we'll need to update the submodule reference.

## Measured results (PPK2, X3)

| Metric | Before | After |
|---|---|---|
| Idle on a static page | 9.68 mA | **2.78 mA** |
| Fast page turn (incl. post-turn behavior) | ~100 mC | **28.98 mC** |
| Post-turn 160 MHz tail | 21.2 mA x 3 s | eliminated |
| Session average @ 30 s/page | ~11.6 mA | **~3.6 mA (~3.2x reading
time)** |
| Deep sleep | 12.8 µA | unchanged |

## Typical-day profiles

Assuming someone spends 30s per page.

- **Light**: 1 h reading, 10 lock/wake cycles, remaining ~23 h deep
sleep
- **Heavy**: 2 h reading, 20 lock/wake cycles, remaining ~22 h deep
sleep
- Per-day cost components: reading 11.6 → 3.6 mA (session avg),
lock+wake cycle
0.113 → 0.099 mAh each (sleep-screen mode), deep sleep 12.8 µA
(unchanged,
  ~0.29 mAh/day)
- Excluded: WiFi sessions, cold chapter indexing, self-discharge

### Days per charge (650mAh)

| Profile | Before | After | Ratio |
|---|---|---|---|
| Light (1 h/day, 10 locks) | 13.0 mAh/day → ~50 days | 4.9 mAh/day →
~133 days | 2.7Ă— |
| Heavy (2 h/day, 20 locks) | 25.7 mAh/day → ~25 days | 9.5 mAh/day →
~68 days | 2.7Ă— |
| Pure active reading | 11.6 mA → ~56 h | 3.6 mA → ~180 h | 3.2× |

### Caveats

- "After" saturates in reality: Li-ion self-discharge (~2–3%/month) is
not modeled
  and becomes the limiter once firmware draw is this low.
- Faster page-turners trend toward ~2.8Ă—, slower readers slightly above
3.2Ă—.
- Frequent locking dilutes the ratio (wake = full cold boot, barely
touched by the
power branch): at 10–20 locks/day, cycles are ~20% of the "after"
budget.
---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**YES**_

---------
2026-08-12 20:57:45 -04:00
Justin Mitchell 144f5740db fix: install full penv_setup dependency set for espressif32 builds
platform-espressif32's own penv bootstrap isn't reliably installing
its required python packages in this runner image, so pin the full
list (littlefs-python, fatfs-ng, and friends) explicitly instead of
discovering missing modules one build at a time.
2026-08-12 18:50:17 -04:00
Justin Mitchell 7d15decbc6 fix: install littlefs-python for PlatformIO CI builds
pioarduino's espressif32 builder imports littlefs unconditionally,
which was never installed alongside platformio-core, breaking every
build and cppcheck job.
2026-08-12 18:42:25 -04:00
Justin Mitchell a30d59ef97 Downgrade CI Python version from 3.14 to 3.13
Update all GitHub Actions workflows to use Python 3.13 instead of 3.14, likely due to 3.14 not being available or stable yet.
2026-08-12 18:22:02 -04:00
Justin Mitchell af3fe731ca Add grayscale base display handling for e-ink panels
Introduce displayBaseWithRefreshCycle to handle B/W base layer display differently for panels that combine grayscale bases vs those that don't. Panels with combined bases defer activation to avoid visible flashing when grayscale pass re-drives text. Also add cleanup call to flush deferred activations for combined-base panels.
2026-08-12 18:10:30 -04:00
Justin Mitchell e9eb7b8d6d Add combined grayscale base rendering for Paper Mono
Implements a new rendering path for Paper Mono displays that defers base activation so grayscale planes can be combined with the base in a single waveform. This prevents visible flashing that occurs when the base is displayed separately and the gray pass re-drives the text body. The feature is controlled by combinesGrayscaleBase() checks throughout the rendering pipeline.
2026-08-12 18:04:02 -04:00
Justin Mitchell a97e850279 Add matrix build for multiple board variants
Consolidates duplicate build jobs into a single matrix-based job that builds firmware for default, sticky, x4pro, and papermono boards. Adds a new release workflow that builds all board variants with release-specific environments when tags are pushed. Artifacts are named to match OtaUpdater's expected format (firmware.bin for C3, firmware-<board>.bin for others).
2026-08-12 17:59:47 -04:00
Leopoldo Pla SempereandJulia Nguyen cb995809ba fix: backport File Transfer guard from CrossInk (#2990)
Co-authored-by: Julia Nguyen <julia@uxj.io>
2026-08-13 00:19:03 +03:00
Uri Tauber f16451c729 feat: render HTML dictionary definitions via EPUB engine (#2836) 2026-08-12 17:17:25 -04:00
Justin Mitchell 4af91daaee Remove manual row height scaling for wrapped labels
The list() function now handles per-row sizing for wrapped items automatically, so manual scaling of row height by line count is no longer needed. Updated comments to reflect that wrapped labels grow only their own row while dense height is preserved for other rows.
2026-08-12 17:01:05 -04:00
Justin Mitchell 595edde489 Add back-to-file-browser setting to settings list
Include STR_BACK_SHORT_TO_FILE_BROWSER in the settings condition check alongside the existing sunlight fading fix setting.
2026-08-12 16:39:44 -04:00
Justin Mitchell 788e1d5901 Merge branch 'feat-x4-papermono-support' of https://github.com/crosspoint-reader/crosspoint-reader into feat-x4-papermono-support 2026-08-12 16:35:13 -04:00
Justin Mitchell 1c4ded42ff Scale list row height by label line count
When a list label can wrap to multiple lines, scale the entire row height by the label's maxLines instead of flooring against the raw font line-height. This preserves the theme's proportional padding for dense rows and prevents wrapped text from bleeding into the next row. Applied to UiListActivity, UiTabListActivity, and WifiSelectionActivity (list/tab/wifi selection screens) replacing the previous wrappedMin floor logic with rowHeight *= maxLines for labelText.maxLines > 1.
2026-08-12 16:34:29 -04:00
Leopoldo Pla SempereandPablo Cabeza 268c27f7d0 feat: add transparent sleep overlays (#2937)
Co-authored-by: Pablo Cabeza <josepablocg@gmail.com>
2026-08-12 23:21:43 +03:00
Justin Mitchell 6c2be7bb1e Reformat multi-line expressions for consistency
Adjust line breaks in WifiSelectionActivity and FrontlightPanelActivity to improve code formatting consistency. No functional changes.
2026-08-12 16:04:56 -04:00
Justin Mitchell a6db9c7bab Persist frontlight 'on' only if user changed
Add a lightOnChanged flag to FrontlightPanelActivity (src/activities/util/FrontlightPanelActivity.h) and initialize it in onEnter. Set the flag whenever the user toggles the light or adjustments implicitly turn it on. onExit now only updates SETTINGS.frontlightOn when lightOnChanged is true; brightness/warmth continue to be persisted as before. This avoids overwriting the saved "was on" preference with the live hardware state when the user didn't interact with the control.
2026-08-12 16:04:21 -04:00
Justin Mitchell 30a3137a69 Ensure list rows accommodate wrapped labels
Floor list row heights to the label's required lines where labels may wrap. UiListActivity, UiTabListActivity and WifiSelectionActivity now compute a wrappedMin = lineHeight * maxLines and increase rowHeight if needed to prevent the second wrapped line from bleeding into the next row. Also update SettingsActivity's language selection flow to save settings and rebuild the settings lists on return so row labels reflect the new language immediately.
2026-08-12 15:37:16 -04:00
Uri Tauber b1d53ba810 feat: add orangutan translation (#2992)
Translated by Dr Horace Worblehat, B. Thau, D.M.
2026-08-12 22:18:39 +03:00
Julia e970abae89 feat: skip boot screen on startup (#2989) 2026-08-12 21:45:21 +03:00
Justin Mitchell f55580888f Add XteinkX3Uc8279 board to GPIO configuration
Include XteinkX3Uc8279 board in the active board check condition for GPIO handling.
2026-08-12 14:34:40 -04:00
Justin Mitchell 96dd6f6d6e Add bounds check before accessing recent books list
Clear rowItems before bounds checking to prevent accessing stale row indices. The interaction table can deliver row indices captured before list modifications, so validate the index is within current list bounds before proceeding.
2026-08-12 14:30:59 -04:00
Justin Mitchell 6953859576 Merge remote-tracking branch 'origin/develop' into feat-x4-papermono-support
# Conflicts:
#	freeink-sdk
#	src/MappedInputManager.cpp
#	src/MappedInputManager.h
#	src/activities/reader/EpubReaderMenuActivity.cpp
#	src/activities/reader/EpubReaderMenuActivity.h
#	src/activities/reader/ReaderUtils.h
#	src/activities/settings/OpdsServerListActivity.cpp
#	src/activities/settings/OpdsSettingsActivity.cpp
#	src/components/OptionPopup.h
#	src/components/UiAppHelpers.h
#	src/components/UiAppHost.cpp
#	src/components/UiAppHost.h
#	src/components/themes/BaseTheme.cpp
2026-08-12 13:51:39 -04:00
Justin Mitchell 9e3cea2e54 feat: convert all lists and tabs to FUI (#2957) 2026-08-12 20:40:09 +03:00
Justin Mitchell 35be6872a2 Remove duplicate screenInverted setting 2026-08-12 00:45:51 -04:00
Justin Mitchell 4baa19f05a Remove setInverted method from HalDisplay
Removes the setInverted method that was wrapping einkDisplay.setInverted. Callers should use the underlying einkDisplay method directly if needed.
2026-08-12 00:29:51 -04:00
Justin Mitchell fd281a5351 Remove duplicate night mode translation string 2026-08-12 00:21:11 -04:00
Justin Mitchell 55fe65e06b Merge remote-tracking branch 'origin/develop' into feat-x4-papermono-support 2026-08-12 00:19:27 -04:00
Justin Mitchell 72404c20af Update freeink-sdk submodule
Update freeink-sdk submodule from c1d96ba to 65d9eba.
2026-08-12 00:14:23 -04:00
Justin Mitchell ddc4c6a250 Add frontlight restore setting and X4 Pro toggle
Add optional setting to restore frontlight on/off state after wake (brightness/warmth always restored). Implement double-click power button to toggle frontlight on X4 Pro. Silent reboots preserve light state regardless of setting to avoid unexpected darkness.
2026-08-12 00:12:54 -04:00
Justin Mitchell b76b999266 Add frontlight control and display inversion support
Implements HalFrontlight wrapper around SDK frontlight manager with brightness, warmth, and on/off controls. Adds display inversion methods to HalDisplay. Includes special power management for PaperMono device using M5PM1 PMIC for shutdown. Adds corresponding UI strings for night mode and frontlight settings.
2026-08-12 00:10:53 -04:00
Justin Mitchell 2780802830 Add capacitive Home key support for X4 Pro
Implements Home key detection through the touch controller for X4 Pro hardware. The Home gesture now checks for a capacitive Home key tap if available, falling back to the bottom-edge swipe gesture on devices without the key. Also adds X4 Pro device detection and display controller probing.
2026-08-11 23:48:37 -04:00
Justin Mitchell 6b5600cd3b Return to reader menu when cancelling sub-screens
When users cancel (via Back button/gesture) from menu-launched sub-screens like text settings or footnotes, return them to the reader menu instead of the reading surface. This provides more intuitive navigation flow. Home gesture exits are unaffected as ActivityManager handles the stack.
2026-08-11 23:04:05 -04:00
Justin Mitchell cccdea96f4 Reformat array initializers for consistency
Adjust whitespace and line breaks in array initializer lists across settings activities to improve code formatting consistency. No functional changes.
2026-08-11 22:50:54 -04:00
Justin Mitchell 3c83f45f93 Cache list row data to avoid per-render allocations
Prebuild and cache ListItem/label buffers for list screens to eliminate repeated per-render heap allocations. Many activities were changed to build rowItems/rowLabels/rowValues once (on construction, onEnter(), or when data changes) and reuse them during paint: OpdsBookBrowser, RecentBooks, NetworkModeSelection, EndOfBookOptions, EpubReaderFootnotes, EpubReaderMenu, XtcReaderChapterSelection, FontDownload, KOReaderSettings, LanguageSelect, OpdsServerList, OpdsSettings, Settings, StatusBarSettings, and TextSettings. Added rebuildRowItems()/rowsDirty_ flags and fixed-capacity arrays where appropriate (NetworkModeSelection, KOReader, etc.). Behavior unchanged; this reduces CPU and heap churn during repaints.
2026-08-11 22:44:00 -04:00
Justin Mitchell ae478fb179 Clarify FUI row-cache guidance
Updates the touch/UI contribution guide to recommend activity-owned row caches for FUI list screens. It adds a concrete rebuildRows pattern, points contributors to FileBrowserActivity for the real-world implementation, and explains why per-render std::vector allocations are avoidable and harmful to steady-state rendering.
2026-08-11 22:13:48 -04:00
Justin Mitchell ee4db8e1fd Preserve denser list rows on non-touch devices
Restore the pre-FreeInkUI list density for non-touch hardware by selecting denser theme metrics where appropriate.

- Added optional hasSubtitle parameter to UiListActivity::syncListViewport and UiTabListActivity::syncTabListViewport so callers can choose subtitle-aware row heights.
- When mappedInput/gpio reports no touch, activities now override props.rowHeight with UITheme metrics (listRowHeight or listWithSubtitleRowHeight) so lists show more rows on X3/X4 devices.
- Updated callers across activities (RecentBooks, Network/WiFi screens, bookmarks, font/opds lists, reader actions, etc.) and added necessary includes.

This keeps touch-target sizing for touch devices while restoring denser layouts on non-touch hardware.
2026-08-11 22:08:19 -04:00
Justin Mitchell 3702925755 Cache list rows/strings for list screens
Introduce cached row buffers to avoid composing strings and allocating ListItems on every repaint. Added networkStatuses/networkRowItems + rebuildNetworkRowItems() (WifiSelectionActivity.cpp/.h), bookmarkSubtitles/bookmarkRowItems + rebuildBookmarkRowItems() (EpubReaderBookmarksActivity.cpp/.h), and tocLabels/tocRowItems + buildTocRowItems() (EpubReaderChapterSelectionActivity.cpp/.h). buildScreen() now reuses these cached arrays; caches are rebuilt when underlying data changes (scan results, bookmark delete, onEnter for TOC). Reduces per-frame allocations and CPU work during cursor moves/taps.
2026-08-11 18:09:14 -04:00
Justin Mitchell a4edce3961 Cache file-list rows to avoid per-render allocs
Add cached per-row buffers (rowNames, rowExtensions, rowItems) and rebuildRowItems() to FileBrowserActivity. loadFiles() now populates/clears the cache and error paths call rebuildRowItems(); buildScreen() reuses cached rowItems and invalidates them on theme change. Caches are cleared in onExit(). Changes in src/activities/home/FileBrowserActivity.h and .cpp. This prevents allocating hundreds of strings/ListItem objects on every repaint for large directories, reducing heap churn and improving UI responsiveness.
2026-08-11 17:58:06 -04:00
Justin Mitchell 07b27ff6df Restore popup borders in option dialogs
Explicitly apply themed popup border styles in the Wi‑Fi prompt and generic option popup dialogs. This restores the missing frame around option dialogs by setting the same border color, thickness, and corner radius used by the theme metrics, matching the prior OptionPopup rendering and keeping the UI consistent.
2026-08-11 16:50:54 -04:00
Thiago Kenji Okada 86eb1cbde2 fix: properly link AGENTS.md (#2976) 2026-08-11 22:24:24 +03:00
Uri Tauber 2cac597143 fix symlink AGENTS.md 2026-08-11 08:24:13 +03:00
Julia 7212129318 fix: ignore empty watchdog reports (#2968)
## Summary

* **What is the goal of this PR?**
* Prevent reset-only watchdog events from opening the crash-report flow
with an empty panic reason.

* **What changes are included?**
* Record a marker in RTC memory only when panic diagnostics are
captured.
  * Continue reporting explicit panics and CPU lockups.
* Require that marker before treating watchdog resets as crashes, and
clear it with the existing panic state.


## Scope Check

CrossPoint is intentionally narrow. See
[SCOPE.md](../blob/master/SCOPE.md) and
[ROADMAP.md](../blob/master/ROADMAP.md).
Please confirm:

- [x] I have read SCOPE.md and ROADMAP.md.
- [x] This PR is **not** a new built-in theme (themes are temporarily
closed pending the move to SD-loaded themes).
- [x] This PR is **not** a new external network connector (sync engine,
cloud storage, remote file access, etc.).
- [x] This PR is **not** an interactive app, writing tool,
RSS/news/browser, media playback, or PDF feature.
- [x] The stock firmware does not already handle this well, **and** no
other popular CrossPoint fork already does
      (or, if one does, I explain why CrossPoint still needs it below).
- [x] If this PR touches `freeink-sdk/`, `lib/hal/`, the bootloader,
OTA, or recovery code, I have coordinated with
      the relevant maintainer.

**If this PR was opened against the previous (broader) scope and was
already in flight under Phase 0, link the
relevant Discussion or issue so reviewers can see the history.**

## Additional Context

* This is a focused port of CrossInk commit `c96d4275`.
* The change adds 4 bytes of RTC no-init state; it does not affect
storage formats, normal boot routing, or real panic reporting.
* Hardware follow-up: on an X3/X4, confirm a reset-only watchdog boots
normally without an empty crash report, while a genuine panic still
reaches the crash-report screen.
---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**<YES>**_
2026-08-11 03:02:56 +00:00
Justin Mitchell 9b090266cf fix: MAC address reading when WiFi is off (#2960) 2026-08-10 15:38:58 -04:00
Justin Mitchell 3198b293f3 Clarify list data flow with improved comments
Rename 'items' to 'entries' to distinguish activity data from transient render items. Add comments explaining that 'entries' holds loaded data while 'rows' is the per-render vector of ListItems passed to the UI component.
2026-08-10 15:33:28 -04:00
Justin Mitchell bba8c21b1a Use UI font for titles in all builds
Remove OMIT_FONTS conditional and always use UI_FONT_ID for titles instead of NOTOSANS_14_FONT_ID. UI fonts provide better script coverage (Hebrew, CJK) for book and directory titles, matching the font used in develop's drawHeader.
2026-08-10 15:26:41 -04:00
Justin Mitchell 6dea6b31f4 Deprecate touch bridge helpers in favor of FreeInkUI
Mandate FreeInkUI components for all new screens. The old touch bridge helpers (rowTouch, colTouch, wasTapInRect, manual rect contains checks) are now legacy-only and must not appear in new code. Document the four supported host patterns: UiListActivity, UiTabListActivity, UiAppHost, and OptionPopup.
2026-08-10 15:16:36 -04:00
Justin Mitchell fc6d11fdd8 Fix input handling race conditions and edge cases
Add assertions for tab navigation bounds checking. Fix confirm button handling to only swallow release when button is actually pressed, preventing touch input from being incorrectly consumed. Add fromButton parameter to distinguish physical button presses from touch taps. Hide action hints when recent books list is empty. Add atomic synchronization for end-of-book options menu to prevent race conditions. Handle case where section is dropped during pending page turn.
2026-08-10 14:29:09 -04:00
Justin Mitchell daf7937b68 Refactor UiListActivity to inherit from UiAppHost
Extract common UI app hosting logic into UiAppHost base class. UiListActivity now inherits from UiAppHost instead of duplicating the render target, FreeInkApp, and uiReady handshake logic. Simplifies touch routing by delegating to parent class and renames routeTouch to routeListTouch to avoid name hiding.
2026-08-10 13:54:09 -04:00
Justin Mitchell 502335693d Replace custom touch handling with ConfirmPopup
Refactor OtaUpdateActivity to use the standard ConfirmPopup component instead of custom touch rectangle handling and button rendering. Also configure WifiSelectionActivity to use vertical options layout with multi-line text wrapping to prevent label truncation.
2026-08-10 13:19:37 -04:00
William Floyd e9c3a166f5 feat: StarDict .syn synonym lookup support (#2696) 2026-08-10 20:01:28 +03:00
Justin Mitchell d720891b2f feat: Adds dark mode with image polarity preservation (#2705) 2026-08-10 19:58:22 +03:00
e4aae7baef feat: retain custom sleep screen during wake (#2943)
Co-authored-by: Bridger Brundy <bridgerborpcc@gmail.com>
Co-authored-by: Julia <julia@uxj.io>
2026-08-10 17:02:20 +03:00
Uri Tauber 7f8b005ae1 chore: update AI agents instructions 2026-08-10 16:06:51 +03:00
Serena 81028d5849 fix(ui): wrap button hint labels that overflow their button (#2928) 2026-08-10 12:05:27 +03:00
Justin Mitchell 0423276d2c fix: accept any 2xx from KOSync-compatible servers (#2945) 2026-08-10 09:26:37 +03:00
Justin Mitchell 9e6578f61c Replace custom prompt rendering with dialog component
Refactor WiFi save and forget prompts to use the FreeInkApp dialog system instead of custom touch handling and rendering. Moves prompt logic into event handlers that interact with the dialog action system, simplifying the code and improving consistency with the UI framework.
2026-08-10 00:48:06 -04:00
Justin Mitchell 36085d80db Convert all lists and tabs to FUI 2026-08-09 23:31:47 -04:00
Leopoldo Pla Sempere 596f0d3f71 fix: invalidate stale footnote caches (#2935) 2026-08-09 19:27:48 +03:00
hooligan333andErica Jensen 6af9a04934 perf: cache cumulative spine sizes in RAM (#2441)
Co-authored-by: Erica Jensen <erica@mailershaven.com>
2026-08-09 17:31:42 +03:00
François Chaix 1582e70ef4 fix(epub): bump FOOTNOTE_HREF_LEN 96→256 for long calibre paths (#2722) 2026-08-09 17:23:40 +03:00
Uri Tauber d4733bfc65 chore: split GH build action (#2901)
## Summary

Build action take too much time.
Lets see if splitting it to two seperate action would speed things up.

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**< PARTIALLY >**_
2026-08-09 03:55:33 -04:00
182 changed files with 10246 additions and 4618 deletions

No files matched your search

+43 -16
View File
@@ -18,7 +18,7 @@ jobs:
- uses: actions/setup-python@v6
with:
python-version: '3.14'
python-version: '3.13'
- name: Install clang-format-21
run: |
@@ -42,7 +42,7 @@ jobs:
- uses: actions/setup-python@v6
with:
python-version: '3.14'
python-version: '3.13'
- name: Install uv
uses: astral-sh/setup-uv@v7
@@ -51,7 +51,15 @@ jobs:
enable-cache: false
- name: Install PlatformIO Core
run: uv pip install --system -U https://github.com/pioarduino/platformio-core/archive/refs/tags/v6.1.19.zip
run: |
uv pip install --system -U https://github.com/pioarduino/platformio-core/archive/refs/tags/v6.1.19.zip
# platform-espressif32's builder/main.py imports these unconditionally; its own
# penv bootstrap (builder/penv_setup.py) is supposed to install them automatically
# but isn't picking them up reliably in this runner image, so pin them explicitly.
uv pip install --system -U "littlefs-python>=0.16.0" "fatfs-ng>=0.1.14" "pyyaml>=6.0.2" \
"rich-click>=1.8.6" "zopfli>=0.2.2" "intelhex>=2.3.0" "rich>=14.0.0" "urllib3<2" \
"cryptography>=45.0.3" "certifi>=2025.8.3" "ecdsa>=0.19.1" "bitstring>=4.3.1" \
"reedsolo>=1.5.3,<1.8" "esp-idf-size>=2.0.0" "esp-coredump>=1.14.0"
- name: Cache PlatformIO packages
uses: actions/cache@v4
@@ -64,7 +72,23 @@ jobs:
run: pio check --fail-on-defect low --fail-on-defect medium --fail-on-defect high
build:
name: Build ${{ matrix.env }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# Artifact names match the release asset names OtaUpdater requests:
# plain firmware.bin for the C3 X4/X3 binary, firmware-<board>.bin for
# the S3 boards.
include:
- env: default
asset: firmware.bin
- env: sticky
asset: firmware-sticky.bin
- env: x4pro
asset: firmware-x4pro.bin
- env: papermono
asset: firmware-papermono.bin
steps:
- uses: actions/checkout@v6
with:
@@ -72,7 +96,7 @@ jobs:
- uses: actions/setup-python@v6
with:
python-version: '3.14'
python-version: '3.13'
- name: Install uv
uses: astral-sh/setup-uv@v7
@@ -81,7 +105,15 @@ jobs:
enable-cache: false
- name: Install PlatformIO Core
run: uv pip install --system -U https://github.com/pioarduino/platformio-core/archive/refs/tags/v6.1.19.zip
run: |
uv pip install --system -U https://github.com/pioarduino/platformio-core/archive/refs/tags/v6.1.19.zip
# platform-espressif32's builder/main.py imports these unconditionally; its own
# penv bootstrap (builder/penv_setup.py) is supposed to install them automatically
# but isn't picking them up reliably in this runner image, so pin them explicitly.
uv pip install --system -U "littlefs-python>=0.16.0" "fatfs-ng>=0.1.14" "pyyaml>=6.0.2" \
"rich-click>=1.8.6" "zopfli>=0.2.2" "intelhex>=2.3.0" "rich>=14.0.0" "urllib3<2" \
"cryptography>=45.0.3" "certifi>=2025.8.3" "ecdsa>=0.19.1" "bitstring>=4.3.1" \
"reedsolo>=1.5.3,<1.8" "esp-idf-size>=2.0.0" "esp-coredump>=1.14.0"
- name: Cache PlatformIO packages
uses: actions/cache@v4
@@ -90,17 +122,12 @@ jobs:
key: pio-${{ runner.os }}-${{ hashFiles('platformio.ini') }}
restore-keys: pio-${{ runner.os }}-
- name: Build CrossPoint
# Both envs in ONE pio invocation: a second `pio run` can wipe the whole
# .pio/build tree (project-checksum mismatch after the S3 toolchain
# installs into the restored cache), deleting the default firmware.bin
# before the artifact upload.
- name: Build ${{ matrix.env }} firmware
run: |
set -euo pipefail
pio run -e default -e sticky | tee pio.log
pio run -e ${{ matrix.env }} | tee pio.log
- name: Extract firmware stats
run: |
set -euo pipefail
ram_line="$(grep -E "RAM:\\s" -m1 pio.log || true)"
@@ -108,16 +135,16 @@ jobs:
echo "ram_line=${ram_line}" >> "$GITHUB_OUTPUT"
echo "flash_line=${flash_line}" >> "$GITHUB_OUTPUT"
{
echo "## Firmware build stats"
echo "## ${{ matrix.env }} firmware build stats"
if [ -n "$ram_line" ]; then echo "- ${ram_line}"; else echo "- RAM: not found"; fi
if [ -n "$flash_line" ]; then echo "- ${flash_line}"; else echo "- Flash: not found"; fi
} >> "$GITHUB_STEP_SUMMARY"
- name: Upload firmware.bin artifact
- name: Upload firmware artifact
uses: actions/upload-artifact@v6
with:
name: firmware.bin
path: .pio/build/default/firmware.bin
name: ${{ matrix.asset }}
path: .pio/build/${{ matrix.env }}/firmware.bin
if-no-files-found: error
unit-tests:
+1 -1
View File
@@ -24,7 +24,7 @@ jobs:
- uses: actions/setup-python@v6
with:
python-version: '3.14'
python-version: '3.13'
- name: Install font tools
run: pip install freetype-py fonttools pyyaml
+45 -9
View File
@@ -1,12 +1,33 @@
name: Compile Release
# Builds every board's release firmware on tag push, with the .bin named
# exactly as OtaUpdater requests it from the GitHub release: plain
# firmware.bin for the C3 X4/X3 binary, firmware-<board>.bin for every other
# board. Attach the artifacts' .bin files to the release as-is.
on:
push:
tags:
- '*'
permissions:
contents: read
jobs:
build-release:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- pio_env: gh_release
asset: firmware.bin
- pio_env: sticky-gh_release
asset: firmware-sticky.bin
- pio_env: x4pro-gh_release
asset: firmware-x4pro.bin
- pio_env: papermono-gh_release
asset: firmware-papermono.bin
steps:
- uses: actions/checkout@v6
with:
@@ -14,7 +35,7 @@ jobs:
- uses: actions/setup-python@v6
with:
python-version: '3.14'
python-version: '3.13'
- name: Install uv
uses: astral-sh/setup-uv@v7
@@ -23,7 +44,15 @@ jobs:
enable-cache: false
- name: Install PlatformIO Core
run: uv pip install --system -U https://github.com/pioarduino/platformio-core/archive/refs/tags/v6.1.19.zip
run: |
uv pip install --system -U https://github.com/pioarduino/platformio-core/archive/refs/tags/v6.1.19.zip
# platform-espressif32's builder/main.py imports these unconditionally; its own
# penv bootstrap (builder/penv_setup.py) is supposed to install them automatically
# but isn't picking them up reliably in this runner image, so pin them explicitly.
uv pip install --system -U "littlefs-python>=0.16.0" "fatfs-ng>=0.1.14" "pyyaml>=6.0.2" \
"rich-click>=1.8.6" "zopfli>=0.2.2" "intelhex>=2.3.0" "rich>=14.0.0" "urllib3<2" \
"cryptography>=45.0.3" "certifi>=2025.8.3" "ecdsa>=0.19.1" "bitstring>=4.3.1" \
"reedsolo>=1.5.3,<1.8" "esp-idf-size>=2.0.0" "esp-coredump>=1.14.0"
- name: Cache PlatformIO packages
uses: actions/cache@v4
@@ -33,15 +62,22 @@ jobs:
restore-keys: pio-${{ runner.os }}-
- name: Build CrossPoint
run: pio run -e gh_release
run: pio run -e ${{ matrix.pio_env }} -j1
- name: Name release asset
run: |
if [ "${{ matrix.asset }}" != "firmware.bin" ]; then
cp ".pio/build/${{ matrix.pio_env }}/firmware.bin" \
".pio/build/${{ matrix.pio_env }}/${{ matrix.asset }}"
fi
- name: Upload Artifacts
uses: actions/upload-artifact@v4
with:
name: CrossPoint-${{ github.ref_name }}
name: ${{ matrix.asset }}
path: |
.pio/build/gh_release/bootloader.bin
.pio/build/gh_release/firmware.bin
.pio/build/gh_release/firmware.elf
.pio/build/gh_release/firmware.map
.pio/build/gh_release/partitions.bin
.pio/build/${{ matrix.pio_env }}/bootloader.bin
.pio/build/${{ matrix.pio_env }}/${{ matrix.asset }}
.pio/build/${{ matrix.pio_env }}/firmware.elf
.pio/build/${{ matrix.pio_env }}/firmware.map
.pio/build/${{ matrix.pio_env }}/partitions.bin
+10 -2
View File
@@ -14,7 +14,7 @@ jobs:
- uses: actions/setup-python@v6
with:
python-version: '3.14'
python-version: '3.13'
- name: Install uv
uses: astral-sh/setup-uv@v7
@@ -23,7 +23,15 @@ jobs:
enable-cache: false
- name: Install PlatformIO Core
run: uv pip install --system -U https://github.com/pioarduino/platformio-core/archive/refs/tags/v6.1.19.zip
run: |
uv pip install --system -U https://github.com/pioarduino/platformio-core/archive/refs/tags/v6.1.19.zip
# platform-espressif32's builder/main.py imports these unconditionally; its own
# penv bootstrap (builder/penv_setup.py) is supposed to install them automatically
# but isn't picking them up reliably in this runner image, so pin them explicitly.
uv pip install --system -U "littlefs-python>=0.16.0" "fatfs-ng>=0.1.14" "pyyaml>=6.0.2" \
"rich-click>=1.8.6" "zopfli>=0.2.2" "intelhex>=2.3.0" "rich>=14.0.0" "urllib3<2" \
"cryptography>=45.0.3" "certifi>=2025.8.3" "ecdsa>=0.19.1" "bitstring>=4.3.1" \
"reedsolo>=1.5.3,<1.8" "esp-idf-size>=2.0.0" "esp-coredump>=1.14.0"
- name: Cache PlatformIO packages
uses: actions/cache@v4
+1
View File
@@ -31,3 +31,4 @@ lib/EpdFont/scripts/output/
/dependencies.lock
/sdkconfig.*
sdkconfig.sticky
flake.*
+179 -68
View File
File diff suppressed because it is too large. Load diff
Symlink
+1
View File
@@ -0,0 +1 @@
.skills/SKILL.md
+2 -2
View File
@@ -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
View File
@@ -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.
+94 -113
View File
@@ -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, M5Stack PaperMono, LilyGo T5, Xteink X4 Pro) 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 of these 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 = &currentLayout();
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.
---
+8 -4
View File
@@ -10,9 +10,10 @@ A dictionary folder must contain:
- `.idx` — word index (required, **must be uncompressed** — a `.idx.gz` will not work; decompress it on your computer with `gzip -d` first)
- `.dict` or `.dict.dz` — definition data (`.dict.dz` is supported as-is; entries are decompressed on the fly during lookup)
- `.syn` — synonym index (optional; maps alternate spellings and irregular forms to their headword)
- `.ifo` — metadata (optional)
Not supported: `.syn` synonym files (ignored), dictionaries with 64-bit index offsets (`idxoffsetbits=64` in the `.ifo` — rare, and rejected with an error), and HTML-formatted definitions render as raw markup rather than styled text.
Not supported: dictionaries with 64-bit index offsets (`idxoffsetbits=64` in the `.ifo` — rare, and rejected with an error).
## Setting Up a Dictionary
@@ -35,18 +36,21 @@ One word on the page becomes highlighted:
2. Press **Confirm** to look up the highlighted word.
3. Press **Back** to return to the reader.
On the very first lookup with a dictionary (and again if the dictionary file changes), the reader shows *"Indexing dictionary…"* while it builds a small `.qidx` sidecar file next to the `.idx`. This takes a few seconds for large dictionaries and makes all subsequent lookups fast. The sidecar can be deleted safely at any time — it will simply be rebuilt.
On the very first lookup with a dictionary (and again whenever the `.idx` or `.syn` source file changes), the reader shows *"Indexing dictionary…"* while it builds small sidecar files next to them — a `.qidx` for the word index, and a `.sidx` when a `.syn` synonym file is present. Each sidecar is rebuilt independently, only when its own source changes. This takes a few seconds for large dictionaries and makes all subsequent lookups fast. The sidecars can be deleted safely at any time — they will simply be rebuilt.
### How Lookup Works
1. **Direct match** — the word is found as-is (case-insensitive) in the dictionary index. Surrounding punctuation is ignored.
2. **Stemming** — on a miss, common English word forms are retried automatically: possessives and plurals (`dogs` → `dog`, `stories` → `story`) and verb endings (`walked` → `walk`, `running` → `run`, `making` → `make`).
3. **Not found** — a short popup appears and you return to word selection.
2. **Synonyms** — on a miss, if the dictionary ships a `.syn` file, alternate spellings and irregular forms recorded there are resolved to their headword (e.g. `oxen` → `ox`, `colour` → `color`). This step is skipped if the `.sidx` sidecar could not be built (e.g. transient low memory during indexing); the dictionary otherwise stays usable, and the build is retried the next time it is opened.
3. **Stemming** — still no match: common English word forms are retried automatically: possessives and plurals (`dogs` → `dog`, `stories` → `story`) and verb endings (`walked` → `walk`, `running` → `run`, `making` → `make`).
4. **Not found** — a short popup appears and you return to word selection.
## The Definition Screen
When a word is found, the definition screen shows the matched headword at the top and the definition text below, with a page counter for long definitions.
HTML dictionaries that declare `sametypesequence=h` use the EPUB text-layout engine for semantic formatting such as headings, bold, italics, lists, and line breaks. Images and CSS styling are ignored. Definitions that are too large or cannot be laid out within the available memory fall back to plain text.
- **Left/Right** or side **Up/Down** — previous / next page
- **Back** — return to word selection
+10 -3
View File
@@ -90,12 +90,19 @@ if (parsedSize != fileSize) {
## `section.bin`
### Version 35
### Version 37
Each file in `sections/*.bin` stores one laid-out spine section. The header is
also the cache-busting key: if any layout-affecting setting differs from the
current reader settings, the section is discarded and rebuilt.
Version 37 increases the fixed-size footnote href field from 96 to 256 bytes.
This changes each serialized footnote record from 128 to 288 bytes, so older
section caches must be discarded and rebuilt.
Version 36 invalidates cached word positions after ruby and CJK justification
layout changes.
Version 35 adds a header offset and a `uint32_t` entry per page for the
visible-text offset LUT. The other section LUTs remain unchanged.
@@ -135,10 +142,10 @@ import std.mem;
import std.string;
import std.core;
#define EXPECTED_VERSION 35
#define EXPECTED_VERSION 37
#define MAX_STRING_LENGTH 65535
#define FOOTNOTE_NUMBER_LEN 32
#define FOOTNOTE_HREF_LEN 96
#define FOOTNOTE_HREF_LEN 256
struct String {
u32 length [[hidden, comment("String byte length")]];
+6 -1
View File
@@ -802,7 +802,12 @@ int Epub::getSpineItemsCount() const {
return bookMetadataCache->getSpineCount();
}
size_t Epub::getCumulativeSpineItemSize(const int spineIndex) const { return getSpineItem(spineIndex).cumulativeSize; }
size_t Epub::getCumulativeSpineItemSize(const int spineIndex) const {
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) {
return 0;
}
return bookMetadataCache->getCumulativeSize(spineIndex);
}
BookMetadataCache::SpineEntry Epub::getSpineItem(const int spineIndex) const {
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) {
+19
View File
@@ -481,11 +481,30 @@ bool BookMetadataCache::load() {
serialization::readString(bookFile, coreMetadata.coverItemHref);
serialization::readString(bookFile, coreMetadata.textReferenceHref);
// Cache cumulative spine sizes in RAM. The progress bar (every render) and percent
// jumps otherwise pay 2 seeks + a heap-allocating SpineEntry read per access. Spine
// entries are stored contiguously in index order immediately after the LUTs, so read
// them in a single sequential pass.
cumulativeSizes.clear();
cumulativeSizes.reserve(spineCount);
const uint32_t lutSize = (static_cast<uint32_t>(spineCount) + tocCount) * sizeof(uint32_t);
bookFile.seek(lutOffset + lutSize);
for (uint16_t i = 0; i < spineCount; i++) {
cumulativeSizes.push_back(readSpineEntry(bookFile).cumulativeSize);
}
loaded = true;
LOG_DBG("BMC", "Loaded cache data: %d spine, %d TOC entries", spineCount, tocCount);
return true;
}
uint32_t BookMetadataCache::getCumulativeSize(const int index) const {
if (index < 0 || index >= static_cast<int>(cumulativeSizes.size())) {
return 0;
}
return cumulativeSizes[index];
}
BookMetadataCache::SpineEntry BookMetadataCache::getSpineEntry(const int index) {
if (!loaded) {
LOG_ERR("BMC", "getSpineEntry called but cache not loaded");
+9
View File
@@ -7,6 +7,7 @@
#include <deque>
#include <memory>
#include <string>
#include <vector>
class BookMetadataCache {
public:
@@ -62,6 +63,11 @@ class BookMetadataCache {
// wrapper serves whichever pass is active (spine, then toc).
std::unique_ptr<serialization::BufferedFileWriter> passOut;
// Cumulative spine sizes, cached in RAM at load() so progress/percent lookups are
// O(1) instead of 2 seeks + a heap-allocating SpineEntry read per access (4 bytes
// per spine item; <1KB for typical books).
std::vector<uint32_t> cumulativeSizes;
// Index for fast href→spineIndex lookup (used only for large EPUBs)
struct SpineHrefIndexEntry {
uint64_t hrefHash; // FNV-1a 64-bit hash
@@ -113,6 +119,9 @@ class BookMetadataCache {
bool load();
SpineEntry getSpineEntry(int index);
TocEntry getTocEntry(int index);
// Cumulative byte size up to and including the given spine item (0 if out of range
// or not loaded). Backed by the in-RAM cumulativeSizes cache populated in load().
uint32_t getCumulativeSize(int index) const;
int getSpineCount() const { return spineCount; }
int getTocCount() const { return tocCount; }
bool isLoaded() const { return loaded; }
+4 -1
View File
@@ -3,7 +3,10 @@
#include <cstring>
#define FOOTNOTE_NUMBER_LEN 32
#define FOOTNOTE_HREF_LEN 96
#define FOOTNOTE_HREF_LEN 256
// ponytail: bumped from 96 to 256; calibre-generated EPUBs with long filenames
// and URL-encoded characters routinely exceed 96 chars (e.g.
// "Author-Title_split_NNN.html#_ftnN" encoded is ~150 chars).
struct FootnoteEntry {
char number[FOOTNOTE_NUMBER_LEN];
+3 -1
View File
@@ -31,7 +31,9 @@ namespace {
// match. Keeps <br>-per-paragraph books (common CJK formatting) from
// re-adding container spacing at every paragraph.
// v35: Persist a uint32_t visible-text start offset for every page.
constexpr uint8_t SECTION_FILE_VERSION = 36;
// v36: Ruby and CJK justification layout changes invalidate cached word positions.
// v37: Footnote href records grew from 96 to 256 bytes.
constexpr uint8_t SECTION_FILE_VERSION = 37;
// Written into the version field while a build is in progress; patched to
// SECTION_FILE_VERSION only when the build is finalized. An abandoned /
// crash-interrupted .bin therefore carries version 0, which loadSectionFile rejects
+2
View File
@@ -357,6 +357,7 @@ void ImageBlock::render(GfxRenderer& renderer, const int x, const int y) {
// Try to render from cache first
std::string cachePath = getCachePath(imagePath);
if (renderFromCache(renderer, cachePath, x, y, width, height)) {
renderer.preserveImagePolarity(x, y, width, height);
return; // Successfully rendered from cache
}
@@ -419,6 +420,7 @@ void ImageBlock::render(GfxRenderer& renderer, const int x, const int y) {
return;
}
renderer.preserveImagePolarity(x, y, width, height);
LOG_DBG("IMG", "Decode successful");
}
+74 -20
View File
@@ -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);
}
}
@@ -1354,6 +1358,12 @@ void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, con
free(outputRow);
free(rowBytes);
const int sourceWidth = bitmap.getWidth() - cropPixX * 2;
const int sourceHeight = bitmap.getHeight() - cropPixY * 2;
const int renderedWidth = isScaled ? static_cast<int>(std::floor((sourceWidth - 1) * scale)) + 1 : sourceWidth;
const int renderedHeight = isScaled ? static_cast<int>(std::floor((sourceHeight - 1) * scale)) + 1 : sourceHeight;
preserveImagePolarity(x, y, renderedWidth, renderedHeight);
}
void GfxRenderer::drawBitmap1Bit(const Bitmap& bitmap, const int x, const int y, const int maxWidth,
@@ -1423,6 +1433,45 @@ void GfxRenderer::drawBitmap1Bit(const Bitmap& bitmap, const int x, const int y,
free(outputRow);
free(rowBytes);
const int renderedWidth =
isScaled ? static_cast<int>(std::floor((bitmap.getWidth() - 1) * scale)) + 1 : bitmap.getWidth();
const int renderedHeight =
isScaled ? static_cast<int>(std::floor((bitmap.getHeight() - 1) * scale)) + 1 : bitmap.getHeight();
preserveImagePolarity(x, y, renderedWidth, renderedHeight);
}
void GfxRenderer::preserveImagePolarity(const int x, const int y, const int width, const int height) const {
if (renderMode != BW || !display.isInverted() || _stripActive || !frameBuffer || width <= 0 || height <= 0) {
return;
}
int ax, ay, bx, by;
rotateCoordinates(orientation, x, y, &ax, &ay, panelWidth, panelHeight);
rotateCoordinates(orientation, x + width - 1, y + height - 1, &bx, &by, panelWidth, panelHeight);
int left = std::max(0, std::min(ax, bx));
int right = std::min(static_cast<int>(panelWidth) - 1, std::max(ax, bx));
int top = std::max(0, std::min(ay, by));
int bottom = std::min(static_cast<int>(panelHeight) - 1, std::max(ay, by));
if (left > right || top > bottom) return;
for (int row = top; row <= bottom; row++) {
uint8_t* rowData = frameBuffer + static_cast<uint32_t>(row) * panelWidthBytes;
int col = left;
while (col <= right && (col & 7) != 0) {
rowData[col >> 3] ^= static_cast<uint8_t>(0x80U >> (col & 7));
col++;
}
while (col + 7 <= right) {
rowData[col >> 3] ^= 0xFF;
col += 8;
}
while (col <= right) {
rowData[col >> 3] ^= static_cast<uint8_t>(0x80U >> (col & 7));
col++;
}
}
}
void GfxRenderer::fillPolygon(const int* xPoints, const int* yPoints, int numPoints, bool state) const {
@@ -2089,6 +2138,8 @@ void GfxRenderer::writeGrayscalePlaneStrip(bool lsbPlane, const uint8_t* scratch
bool GfxRenderer::supportsStripGrayscale() const { return display.supportsStripGrayscale(); }
bool GfxRenderer::combinesGrayscaleBase() const { return display.combinesGrayscaleBase(); }
void GfxRenderer::freeBwBufferChunks() {
for (auto& bwBufferChunk : bwBufferChunks) {
if (bwBufferChunk) {
@@ -2175,30 +2226,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;
}
}
Loaded 20 of 182 files, more files were not shown because too many files have changed in this diff. Show more