mirror of
https://github.com/crosspoint-reader/crosspoint-reader.git
synced 2026-08-13 14:21:57 -07:00
203 lines
8.7 KiB
C++
203 lines
8.7 KiB
C++
#pragma once
|
|
#include <FreeInkApp.h>
|
|
#include <FreeInkUIGfxRenderer.h>
|
|
#include <FreeInkUIIcon.h>
|
|
#include <I18n.h>
|
|
|
|
#include <atomic>
|
|
|
|
#include "MappedInputManager.h"
|
|
#include "components/UIScale.h"
|
|
#include "components/UITheme.h"
|
|
#include "components/UIThemeTokens.h"
|
|
#include "components/icons/customListIcons.h"
|
|
#include "components/icons/listIcons.h"
|
|
|
|
// Shared glue for activities hosting a FreeInkApp: the font-bound render
|
|
// target and the touch snapshot FreeInkApp routing consumes.
|
|
|
|
// One app-wide ThemeTokens instance shared by every FreeInkApp via
|
|
// setThemeRef, so per-app copies (~1.5KB each, and one per stacked activity)
|
|
// aren't pure heap waste. Refreshed on every screen entry, so theme or font
|
|
// changes between activities re-derive it; live theme changes (Settings)
|
|
// refresh it and every referencing app repaints in the new look.
|
|
//
|
|
// Backed by a small pool + an atomic cell (FreeInkApp::setThemeRef() takes a
|
|
// pointer to the cell, not to a ThemeTokens instance directly) rather than a
|
|
// single instance overwritten in place: refreshSharedUiThemeTokens() below
|
|
// always builds the new tokens into whichever pool slot the cell does NOT
|
|
// currently reference, then does one atomic store. Every app sharing the
|
|
// cell picks up the change on its next theme() call, and nothing ever
|
|
// dereferences an instance mid-overwrite — unlike a plain
|
|
// `sharedTokens = uiThemeTokens(target);` in-place assignment, which a
|
|
// render task reading theme().rowHeight/etc. field-by-field on another task
|
|
// could observe as a torn mix of old and new fields.
|
|
inline std::atomic<const freeink::ui::ThemeTokens*>& sharedUiThemeCell() {
|
|
static std::atomic<const freeink::ui::ThemeTokens*> cell{nullptr};
|
|
return cell;
|
|
}
|
|
|
|
// Rebuilds the shared tokens for `target` and atomically publishes them via
|
|
// sharedUiThemeCell(). Returns the freshly-published instance for callers
|
|
// that also want to read it back immediately (e.g. BaseTheme::drawHeader(),
|
|
// which derives the same tokens as a render-path scratch value instead of
|
|
// stack-allocating its own copy).
|
|
inline const freeink::ui::ThemeTokens& refreshSharedUiThemeTokens(const freeink::ui::GfxRendererTarget& target) {
|
|
static freeink::ui::ThemeTokens pool[2];
|
|
auto& cell = sharedUiThemeCell();
|
|
const auto* current = cell.load(std::memory_order_relaxed);
|
|
freeink::ui::ThemeTokens* next = (current == &pool[0]) ? &pool[1] : &pool[0];
|
|
*next = uiThemeTokens(target);
|
|
cell.store(next, std::memory_order_release);
|
|
return *next;
|
|
}
|
|
|
|
// Refresh the shared tokens from the active UITheme + this target's fonts and
|
|
// point the app at them. Replaces the old per-app `app.setTheme(...)` copies.
|
|
template <typename App>
|
|
inline void applySharedUiTheme(App& app, const freeink::ui::GfxRendererTarget& target) {
|
|
refreshSharedUiThemeTokens(target);
|
|
app.setThemeRef(&sharedUiThemeCell());
|
|
}
|
|
|
|
// Bind the uiScale fonts before FreeInkApp's constructor derives its theme
|
|
// metrics from the body font's line height.
|
|
inline freeink::ui::GfxRendererTarget makeUiTarget(const GfxRenderer& renderer) {
|
|
freeink::ui::GfxRendererTarget target(renderer);
|
|
const auto spec = uiScaleSpec();
|
|
target.setFont(freeink::ui::GfxRendererTarget::FONT_SMALL, spec.smallFontId);
|
|
target.setFont(freeink::ui::GfxRendererTarget::FONT_BODY, spec.bodyFontId);
|
|
target.setFont(freeink::ui::GfxRendererTarget::FONT_TITLE, spec.titleFontId);
|
|
return target;
|
|
}
|
|
|
|
// Tap release with coords, plus the raw release the tap classifier never
|
|
// reports (swipe end, drag-off) delivered off-target: nothing dispatches,
|
|
// but routing drops its pressed-element state instead of ghosting it onto
|
|
// the next render.
|
|
// Firmware UIIcon -> FreeInkUI bitmap for list rows (SDK-format icons only;
|
|
// the legacy drawIcon assets use a different bit layout). Two crisp sizes:
|
|
// 24 for single-line rows, 32 for label+subtitle rows.
|
|
inline freeink::ui::BitmapRef listIconFor(const UIIcon icon, const int size = 24) {
|
|
if (size >= 32) {
|
|
switch (icon) {
|
|
case UIIcon::Folder:
|
|
return freeink::ui::bitmapFromIcon(icon_folder_32);
|
|
case UIIcon::Text:
|
|
return freeink::ui::bitmapFromIcon(icon_file_text_32);
|
|
case UIIcon::Image:
|
|
return freeink::ui::bitmapFromIcon(icon_image_32);
|
|
case UIIcon::Book:
|
|
return freeink::ui::bitmapFromIcon(icon_book_32);
|
|
case UIIcon::File:
|
|
return freeink::ui::bitmapFromIcon(icon_file_32);
|
|
case UIIcon::Wifi:
|
|
return freeink::ui::bitmapFromIcon(icon_wifi_32);
|
|
case UIIcon::Library:
|
|
return freeink::ui::bitmapFromIcon(icon_library_32);
|
|
case UIIcon::Hotspot:
|
|
return freeink::ui::bitmapFromIcon(icon_radio_tower_32);
|
|
case UIIcon::Bookmark:
|
|
return freeink::ui::bitmapFromIcon(icon_bookmark_32);
|
|
default:
|
|
return {};
|
|
}
|
|
}
|
|
switch (icon) {
|
|
case UIIcon::Folder:
|
|
return freeink::ui::bitmapFromIcon(icon_folder_24);
|
|
case UIIcon::Text:
|
|
return freeink::ui::bitmapFromIcon(icon_file_text_24);
|
|
case UIIcon::Image:
|
|
return freeink::ui::bitmapFromIcon(icon_image_24);
|
|
case UIIcon::Book:
|
|
return freeink::ui::bitmapFromIcon(icon_book_24);
|
|
case UIIcon::File:
|
|
return freeink::ui::bitmapFromIcon(icon_file_24);
|
|
case UIIcon::Wifi:
|
|
return freeink::ui::bitmapFromIcon(icon_wifi_24);
|
|
case UIIcon::Library:
|
|
return freeink::ui::bitmapFromIcon(icon_library_24);
|
|
case UIIcon::Hotspot:
|
|
return freeink::ui::bitmapFromIcon(icon_radio_tower_24);
|
|
case UIIcon::Bookmark:
|
|
return freeink::ui::bitmapFromIcon(icon_bookmark_24);
|
|
default:
|
|
return {};
|
|
}
|
|
}
|
|
|
|
// Bottom-anchored Cancel / OK pair for slider dialogs on touch devices, where
|
|
// the physical Back/Confirm buttons (and their auto-hidden hints) may not
|
|
// exist. Callers gate on hasTouch(): button boards keep the hint chrome and
|
|
// need no on-screen pair. Consumes the bottom of the screen's content band.
|
|
template <typename Screen>
|
|
inline void addDialogCancelOk(Screen& screen, const freeink::ui::ActionId cancelAction,
|
|
const freeink::ui::ActionId okAction) {
|
|
const auto& theme = screen.theme();
|
|
const int16_t sideInset = static_cast<int16_t>(theme.spaceLg * 2);
|
|
const freeink::ui::Rect band =
|
|
screen.takeBottom(theme.rowHeight, theme.spaceLg).inset(freeink::ui::Insets{0, sideInset, 0, sideInset});
|
|
const int16_t gap = theme.spaceLg;
|
|
const int16_t buttonWidth = static_cast<int16_t>((band.width - gap) / 2);
|
|
|
|
freeink::ui::ButtonProps cancel;
|
|
cancel.label = tr(STR_CANCEL);
|
|
cancel.action = cancelAction;
|
|
cancel.inputMask = freeink::ui::InputTouch;
|
|
cancel.text = theme.bodyText;
|
|
freeink::ui::ButtonProps ok = cancel;
|
|
ok.label = tr(STR_OK_BUTTON);
|
|
ok.action = okAction;
|
|
freeink::ui::button(screen.frame(), freeink::ui::Rect{band.x, band.y, buttonWidth, band.height}, cancel);
|
|
freeink::ui::button(
|
|
screen.frame(),
|
|
freeink::ui::Rect{static_cast<int16_t>(band.x + band.width - buttonWidth), band.y, buttonWidth, band.height}, ok);
|
|
}
|
|
|
|
// withLongPress: the SDK touch classifier fires the long-press WHILE the
|
|
// finger is still down (matching the physical-button hold-to-act feel) and
|
|
// suppresses the remainder of the contact, so the finger lift can't also
|
|
// tap-dismiss the popup the long-press opens. Delivered as a touchReleased +
|
|
// longPress snapshot at the contact point; only rows masked InputLongPress
|
|
// receive it. Mirrors the SDK's long-press-aware fui::snapshotFrom, but maps
|
|
// coordinates through the renderer's LIVE orientation (the reader rotates at
|
|
// runtime), which the DeviceContext-based SDK adapter does not track.
|
|
inline freeink::ui::InputSnapshot touchSnapshotFrom(const MappedInputManager& mappedInput,
|
|
const bool withLongPress = false) {
|
|
int tx = 0;
|
|
int ty = 0;
|
|
if (withLongPress && mappedInput.wasScreenLongPress(tx, ty)) {
|
|
freeink::ui::InputSnapshot snap{};
|
|
snap.touchReleased = true;
|
|
snap.longPress = true;
|
|
snap.touchX = static_cast<int16_t>(tx);
|
|
snap.touchY = static_cast<int16_t>(ty);
|
|
return snap;
|
|
}
|
|
|
|
freeink::ui::InputSnapshot snap{};
|
|
// Live contact position: only InputDrag-masked elements (sliders) react, so
|
|
// carrying it in every snapshot is free for ordinary screens.
|
|
if (mappedInput.isScreenTouchHeld(tx, ty)) {
|
|
snap.touchHeld = true;
|
|
snap.touchX = static_cast<int16_t>(tx);
|
|
snap.touchY = static_cast<int16_t>(ty);
|
|
}
|
|
if (mappedInput.wasScreenTouchDown(tx, ty)) {
|
|
snap.touchPressed = true;
|
|
snap.touchX = static_cast<int16_t>(tx);
|
|
snap.touchY = static_cast<int16_t>(ty);
|
|
}
|
|
if (mappedInput.wasScreenTapped(tx, ty)) {
|
|
snap.touchReleased = true;
|
|
snap.touchX = static_cast<int16_t>(tx);
|
|
snap.touchY = static_cast<int16_t>(ty);
|
|
} else if (mappedInput.wasScreenTouchReleased()) {
|
|
snap.touchReleased = true;
|
|
snap.touchX = -1;
|
|
snap.touchY = -1;
|
|
}
|
|
return snap;
|
|
}
|