Fix touchscreen navigation and add runtime touch diagnostics
This commit is contained in:
1 parent
23fe7c8a9d
commit
14796e6573
32 files changed
+1785
-123
No files matched your search
@@ -32,6 +32,8 @@ constexpr ColorVal WARNING_TEXT = rgb565(248, 176, 72);
|
||||
// semantic slots by their RGB565 value before selecting a palette entry.
|
||||
constexpr ColorVal POPUP_BACKGROUND = rgb565(20, 48, 70);
|
||||
constexpr ColorVal ACCENT = rgb565(64, 176, 240);
|
||||
constexpr ColorVal TOUCH_OUTLINE = rgb565(255, 255, 0);
|
||||
constexpr ColorVal TOUCH_PRESSED = rgb565(0, 255, 0);
|
||||
|
||||
enum IndexedColor : uint8_t {
|
||||
INDEX_BACKGROUND = 0,
|
||||
@@ -41,6 +43,8 @@ enum IndexedColor : uint8_t {
|
||||
INDEX_WARNING_TEXT = 4,
|
||||
INDEX_POPUP_BACKGROUND = 5,
|
||||
INDEX_ACCENT = 6,
|
||||
INDEX_TOUCH_PRESSED = 8,
|
||||
INDEX_TOUCH_OUTLINE = 11,
|
||||
};
|
||||
|
||||
} // namespace color_theme
|
||||
|
||||
@@ -16,10 +16,14 @@ struct CompanionTransportSelectorLayout {
|
||||
int title_y;
|
||||
int prompt_y;
|
||||
bool show_title;
|
||||
int side_nav_width;
|
||||
};
|
||||
|
||||
inline CompanionTransportSelectorLayout makeCompanionTransportSelectorLayout(
|
||||
int width, int height) {
|
||||
int width, int height, int center_zone_percent = 100) {
|
||||
if (center_zone_percent < 0) center_zone_percent = 0;
|
||||
if (center_zone_percent > 100) center_zone_percent = 100;
|
||||
const int side_nav_width = width * ((100 - center_zone_percent) / 2) / 100;
|
||||
const bool tall_display = height >= 96;
|
||||
const int margin = 2;
|
||||
const int gap = 4;
|
||||
@@ -28,15 +32,17 @@ inline CompanionTransportSelectorLayout makeCompanionTransportSelectorLayout(
|
||||
int box_height = prompt_y - box_y - (tall_display ? 3 : 8);
|
||||
if (box_height > 100) box_height = 100;
|
||||
if (box_height < 20) box_height = 20;
|
||||
const int box_width = (width - margin * 2 - gap) / 2;
|
||||
const int bluetooth_x = margin + box_width + gap;
|
||||
const int available = width - side_nav_width * 2 - margin * 2 - gap;
|
||||
const int box_width = available > 0 ? available / 2 : 0;
|
||||
const int bluetooth_x = side_nav_width + margin + box_width + gap;
|
||||
|
||||
return {
|
||||
{margin, box_y, box_width, box_height},
|
||||
{side_nav_width + margin, box_y, box_width, box_height},
|
||||
{bluetooth_x, box_y, box_width, box_height},
|
||||
tall_display ? 14 : 0,
|
||||
prompt_y,
|
||||
tall_display,
|
||||
side_nav_width,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace mesh {
|
||||
namespace ui {
|
||||
|
||||
// Map the rotated physical panel to the logical UI, exactly once. Both the
|
||||
// native canvas and the zoomed fallback fill this same panel; their internal
|
||||
// render resolution must not change the hit targets. Reject invalid samples
|
||||
// before division (e.g. -1 / 3 would otherwise become a valid edge tap).
|
||||
inline bool panelTouchToLogical(int panel_x, int panel_y,
|
||||
int panel_width, int panel_height,
|
||||
int logical_width, int logical_height,
|
||||
int* x, int* y) {
|
||||
if (x == nullptr || y == nullptr) return false;
|
||||
*x = *y = -1;
|
||||
if (panel_width <= 0 || panel_height <= 0
|
||||
|| logical_width <= 0 || logical_height <= 0
|
||||
|| panel_x < 0 || panel_x >= panel_width
|
||||
|| panel_y < 0 || panel_y >= panel_height) return false;
|
||||
*x = static_cast<int>(static_cast<int64_t>(panel_x) * logical_width / panel_width);
|
||||
*y = static_cast<int>(static_cast<int64_t>(panel_y) * logical_height / panel_height);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
} // namespace mesh
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "LGFXDisplay.h"
|
||||
#include "ColorTheme.h"
|
||||
#include "IndicatorRenderProfile.h"
|
||||
#include "DisplayTouchCoordinates.h"
|
||||
#include <lgfx/utility/lgfx_qrcode.h>
|
||||
|
||||
#ifndef DISPLAY_ROTATION
|
||||
@@ -97,7 +98,9 @@ static const uint32_t UI_PALETTE[16] = {
|
||||
mesh::ui::color_theme::rgb888(mesh::ui::color_theme::POPUP_BACKGROUND),
|
||||
mesh::ui::color_theme::rgb888(mesh::ui::color_theme::ACCENT),
|
||||
0xE53935,
|
||||
0x43A047, 0x1E88E5, 0x8E24AA, 0xFDD835,
|
||||
mesh::ui::color_theme::rgb888(mesh::ui::color_theme::TOUCH_PRESSED),
|
||||
0x1E88E5, 0x8E24AA,
|
||||
mesh::ui::color_theme::rgb888(mesh::ui::color_theme::TOUCH_OUTLINE),
|
||||
0x6D4C41, 0x00ACC1, 0xF06292, 0xFF00FF,
|
||||
};
|
||||
|
||||
@@ -497,16 +500,14 @@ void LGFXDisplay::endFrame() {
|
||||
}
|
||||
|
||||
bool LGFXDisplay::getTouch(int* x, int* y) {
|
||||
if (x == nullptr || y == nullptr) return false;
|
||||
*x = *y = -1;
|
||||
lgfx::v1::touch_point_t point = {};
|
||||
if (display->getTouch(&point) == 0) return false;
|
||||
if (_outputZoom * _coordinateScale != 1.0f) {
|
||||
*x = point.x / (_outputZoom * _coordinateScale);
|
||||
*y = point.y / (_outputZoom * _coordinateScale);
|
||||
} else {
|
||||
*x = point.x;
|
||||
*y = point.y;
|
||||
}
|
||||
return *x >= 0 && *x < width() && *y >= 0 && *y < height();
|
||||
// LovyanGFX has already applied panel rotation and touch calibration. Use
|
||||
// its live output dimensions, not the sprite resolution or build flags.
|
||||
return mesh::ui::panelTouchToLogical(point.x, point.y,
|
||||
display->width(), display->height(), width(), height(), x, y);
|
||||
}
|
||||
|
||||
bool LGFXDisplay::installRuntimeFont(uint8_t* data, size_t size) {
|
||||
@@ -548,6 +549,8 @@ uint32_t LGFXDisplay::renderColor(ColorVal color) const {
|
||||
if (color == UIColor::warning_txt) return INDEX_WARNING_TEXT;
|
||||
if (color == UIColor::popup_bkg) return INDEX_POPUP_BACKGROUND;
|
||||
if (color == UIColor::corp_blue) return INDEX_ACCENT;
|
||||
if (color == TOUCH_OUTLINE) return INDEX_TOUCH_OUTLINE;
|
||||
if (color == TOUCH_PRESSED) return INDEX_TOUCH_PRESSED;
|
||||
return color & 0x0F;
|
||||
#else
|
||||
return color;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "MomentaryButton.h"
|
||||
|
||||
#define MULTI_CLICK_WINDOW_MS 280
|
||||
#define BUTTON_DEBOUNCE_MS 25
|
||||
|
||||
#if defined(NRF52_PLATFORM) && \
|
||||
@@ -31,7 +30,7 @@ MomentaryButton::MomentaryButton(int8_t pin, int long_press_millis, bool reverse
|
||||
_press_active = false;
|
||||
_click_count = 0;
|
||||
_last_click_time = 0;
|
||||
_multi_click_window = multiclick ? MULTI_CLICK_WINDOW_MS : 0;
|
||||
_multi_click_window = multiclick ? MOMENTARY_BUTTON_MULTI_CLICK_MS : 0;
|
||||
_pending_click = false;
|
||||
_candidate_level = prev;
|
||||
_candidate_since = 0;
|
||||
@@ -50,7 +49,7 @@ MomentaryButton::MomentaryButton(int8_t pin, int long_press_millis, int analog_t
|
||||
_press_active = false;
|
||||
_click_count = 0;
|
||||
_last_click_time = 0;
|
||||
_multi_click_window = MULTI_CLICK_WINDOW_MS;
|
||||
_multi_click_window = MOMENTARY_BUTTON_MULTI_CLICK_MS;
|
||||
_pending_click = false;
|
||||
_candidate_level = prev;
|
||||
_candidate_since = 0;
|
||||
|
||||
@@ -9,6 +9,10 @@
|
||||
#define BUTTON_EVENT_TRIPLE_CLICK 4
|
||||
#define BUTTON_EVENT_QUADRUPLE_CLICK 5
|
||||
|
||||
#ifndef MOMENTARY_BUTTON_MULTI_CLICK_MS
|
||||
#define MOMENTARY_BUTTON_MULTI_CLICK_MS 280
|
||||
#endif
|
||||
|
||||
#ifndef MOMENTARY_BUTTON_WAKE_HOLD_MS
|
||||
#define MOMENTARY_BUTTON_WAKE_HOLD_MS 0
|
||||
#endif
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "DisplayDriver.h"
|
||||
#include "TouchInput.h"
|
||||
|
||||
namespace mesh {
|
||||
namespace ui {
|
||||
@@ -52,5 +53,39 @@ inline void drawButtonReaderHint(DisplayDriver& text,
|
||||
}
|
||||
}
|
||||
|
||||
// Enlarge the header exit target without enlarging text. Reserve this same
|
||||
// height for content, touch detection and diagnostic outlines.
|
||||
inline int readerTouchHeaderHeight(int content_height) {
|
||||
return content_height + 8 < 24 ? 24 : content_height + 8;
|
||||
}
|
||||
|
||||
// Touch readers use five equal, full-width hit cells. A 24-unit row is 72
|
||||
// physical pixels high on both Indicator render profiles; keep the same font.
|
||||
inline TouchNavigationBar makeReaderTouchBar(DisplayDriver& text, int bottom,
|
||||
int exit_height = 0) {
|
||||
TouchNavigationBar bar;
|
||||
const int line_height = text.textLineHeight();
|
||||
bar.height = line_height + 4 < 24 ? 24 : line_height + 4;
|
||||
if (bar.height > bottom) bar.height = bottom;
|
||||
bar.top = bottom - bar.height;
|
||||
bar.exit_height = exit_height < 0 ? 0 : exit_height > bar.top ? bar.top : exit_height;
|
||||
return bar;
|
||||
}
|
||||
|
||||
inline void drawReaderTouchBar(DisplayDriver& text, const TouchNavigationBar& bar) {
|
||||
static const char* const labels[] = {"4<<", "2<", ">1", ">>3", "X"};
|
||||
text.setColor(UIColor::window_bkg);
|
||||
text.fillRect(0, bar.top, text.width(), bar.height);
|
||||
text.setColor(UIColor::corp_blue);
|
||||
text.drawRect(0, bar.top, text.width(), 1);
|
||||
const int line_height = text.textLineHeight();
|
||||
const int y = bar.top + (bar.height - line_height) / 2;
|
||||
for (int cell = 0; cell < 5; ++cell) {
|
||||
const int left = text.width() * cell / 5;
|
||||
const int right = text.width() * (cell + 1) / 5;
|
||||
text.drawTextCentered((left + right) / 2, y, labels[cell]);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
} // namespace mesh
|
||||
@@ -0,0 +1,118 @@
|
||||
#pragma once
|
||||
|
||||
#include "ColorTheme.h"
|
||||
#include "TouchInput.h"
|
||||
|
||||
namespace mesh {
|
||||
namespace ui {
|
||||
|
||||
struct TouchDebugArea {
|
||||
int x, y, width, height;
|
||||
TouchAction action;
|
||||
};
|
||||
|
||||
struct TouchDebugAreas {
|
||||
TouchDebugArea areas[10];
|
||||
int count = 0;
|
||||
|
||||
void add(int x, int y, int width, int height, TouchAction action) {
|
||||
if (width > 0 && height > 0 && count < 10)
|
||||
areas[count++] = {x, y, width, height, action};
|
||||
}
|
||||
};
|
||||
|
||||
// Visual (already unmirrored), logical coordinates. The UI supplies the same
|
||||
// current screen geometry to this overlay and TouchInput::update(). Tests
|
||||
// compare every pixel in these rectangles with the actual stationary actions.
|
||||
inline TouchDebugAreas makeTouchDebugAreas(int width, int height,
|
||||
int center_zone_percent, bool bottom_selector,
|
||||
const TouchSplitSelector* split, const TouchNavigationBar* reader) {
|
||||
TouchDebugAreas result;
|
||||
if (width <= 0 || height <= 0) return result;
|
||||
if (reader != nullptr) {
|
||||
result.add(0, 0, width, reader->exit_height, TouchAction::Select);
|
||||
result.add(0, reader->exit_height, width / 2,
|
||||
reader->top - reader->exit_height, TouchAction::Previous);
|
||||
result.add(width / 2, reader->exit_height, width - width / 2,
|
||||
reader->top - reader->exit_height, TouchAction::Next);
|
||||
const TouchAction actions[] = {TouchAction::VerticalPrevious,
|
||||
TouchAction::Previous, TouchAction::Next,
|
||||
TouchAction::VerticalNext, TouchAction::Select};
|
||||
for (int cell = 0; cell < 5; ++cell) {
|
||||
const int left = width * cell / 5;
|
||||
const int right = width * (cell + 1) / 5;
|
||||
result.add(left, reader->top, right - left, reader->height, actions[cell]);
|
||||
}
|
||||
// Retain truthfulness for layouts with space below the footer as well.
|
||||
const int below = reader->top + reader->height;
|
||||
result.add(0, below, width / 2, height - below, TouchAction::Previous);
|
||||
result.add(width / 2, below, width - width / 2, height - below, TouchAction::Next);
|
||||
return result;
|
||||
}
|
||||
|
||||
const int body_bottom = bottom_selector ? height * 3 / 4 : height;
|
||||
if (bottom_selector) {
|
||||
result.add(0, body_bottom, width / 4, height - body_bottom,
|
||||
TouchAction::VerticalPrevious);
|
||||
result.add(width * 3 / 4, body_bottom, width - width * 3 / 4,
|
||||
height - body_bottom, TouchAction::VerticalNext);
|
||||
}
|
||||
if (split != nullptr) {
|
||||
result.add(0, 0, split->side_nav_width, body_bottom, TouchAction::Previous);
|
||||
result.add(width - split->side_nav_width, 0, split->side_nav_width,
|
||||
body_bottom, TouchAction::Next);
|
||||
const int bottom = split->top_y + split->height < body_bottom
|
||||
? split->top_y + split->height : body_bottom;
|
||||
result.add(split->left_x, split->top_y, split->left_width,
|
||||
bottom - split->top_y, TouchAction::SelectLeft);
|
||||
result.add(split->right_x, split->top_y, split->right_width,
|
||||
bottom - split->top_y, TouchAction::SelectRight);
|
||||
return result;
|
||||
}
|
||||
const int side_percent = (100 - center_zone_percent) / 2;
|
||||
const int left = width * side_percent / 100;
|
||||
const int right = width - left;
|
||||
result.add(0, 0, left, body_bottom, TouchAction::Previous);
|
||||
result.add(left, 0, right - left, body_bottom, TouchAction::Select);
|
||||
result.add(right, 0, width - right, body_bottom, TouchAction::Next);
|
||||
return result;
|
||||
}
|
||||
|
||||
inline int touchDebugAreaAt(const TouchDebugAreas& areas, int x, int y) {
|
||||
for (int i = 0; i < areas.count; ++i) {
|
||||
const auto& r = areas.areas[i];
|
||||
if (x >= r.x && x < r.x + r.width && y >= r.y && y < r.y + r.height)
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
inline void drawTouchDebugAreas(DisplayDriver& display, const TouchDebugAreas& areas,
|
||||
int pressed_x = -1, int pressed_y = -1) {
|
||||
const int pressed = touchDebugAreaAt(areas, pressed_x, pressed_y);
|
||||
for (int i = 0; i < areas.count; ++i) {
|
||||
display.setColor(i == pressed ? color_theme::TOUCH_PRESSED : color_theme::TOUCH_OUTLINE);
|
||||
const auto& area = areas.areas[i];
|
||||
const int left = area.x < 0 ? 0 : area.x;
|
||||
const int top = area.y < 0 ? 0 : area.y;
|
||||
const int right = area.x + area.width < display.width()
|
||||
? area.x + area.width - 1 : display.width() - 1;
|
||||
const int bottom = area.y + area.height < display.height()
|
||||
? area.y + area.height - 1 : display.height() - 1;
|
||||
if (left > right || top > bottom) continue;
|
||||
// One logical pixel per dot, two pixels of gap. Use the normal drawing
|
||||
// path so native 480px and scaled 320px canvases show identical bounds.
|
||||
for (int x = left; x <= right; x += 3) {
|
||||
display.fillRect(x, top, 1, 1);
|
||||
display.fillRect(x, bottom, 1, 1);
|
||||
}
|
||||
for (int y = top; y <= bottom; y += 3) {
|
||||
display.fillRect(left, y, 1, 1);
|
||||
display.fillRect(right, y, 1, 1);
|
||||
}
|
||||
display.fillRect(right, bottom, 1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
} // namespace mesh
|
||||
@@ -23,6 +23,16 @@ struct TouchSplitSelector {
|
||||
int right_width;
|
||||
int top_y;
|
||||
int height;
|
||||
// Full-height page-navigation strips, zero for legacy selector layouts.
|
||||
int side_nav_width;
|
||||
};
|
||||
|
||||
// Reader footer has five controls (<<, <, >, >>, X); the top header exits.
|
||||
// All bounds are logical UI units supplied by the screen that draws them.
|
||||
struct TouchNavigationBar {
|
||||
int top = 0;
|
||||
int height = 0;
|
||||
int exit_height = 0;
|
||||
};
|
||||
|
||||
// Converts one-finger touch samples into actions understood by the
|
||||
@@ -31,6 +41,7 @@ struct TouchSplitSelector {
|
||||
class TouchInput {
|
||||
bool _active = false;
|
||||
bool _reverse_swipes;
|
||||
bool _reverse_vertical_swipes;
|
||||
bool _separate_vertical_swipes;
|
||||
bool _mirror_tap_x;
|
||||
uint8_t _center_zone_percent;
|
||||
@@ -50,17 +61,27 @@ class TouchInput {
|
||||
|
||||
TouchAction verticalSwipeAction(bool negative_direction) const {
|
||||
if (!_separate_vertical_swipes) return swipeAction(negative_direction);
|
||||
const bool next = negative_direction != _reverse_swipes;
|
||||
const bool next = negative_direction != _reverse_vertical_swipes;
|
||||
return next ? TouchAction::VerticalNext
|
||||
: TouchAction::VerticalPrevious;
|
||||
}
|
||||
|
||||
public:
|
||||
int centerZonePercent() const { return _center_zone_percent; }
|
||||
bool hasSeparateVerticalSwipes() const { return _separate_vertical_swipes; }
|
||||
|
||||
explicit TouchInput(bool reverse_swipes = false,
|
||||
bool separate_vertical_swipes = false,
|
||||
uint8_t center_zone_percent = 34,
|
||||
bool mirror_tap_x = false)
|
||||
: TouchInput(reverse_swipes, separate_vertical_swipes,
|
||||
center_zone_percent, mirror_tap_x, reverse_swipes) {}
|
||||
|
||||
TouchInput(bool reverse_swipes, bool separate_vertical_swipes,
|
||||
uint8_t center_zone_percent, bool mirror_tap_x,
|
||||
bool reverse_vertical_swipes)
|
||||
: _reverse_swipes(reverse_swipes),
|
||||
_reverse_vertical_swipes(reverse_vertical_swipes),
|
||||
_separate_vertical_swipes(separate_vertical_swipes),
|
||||
_mirror_tap_x(mirror_tap_x),
|
||||
_center_zone_percent(center_zone_percent > 100
|
||||
@@ -69,7 +90,8 @@ public:
|
||||
|
||||
TouchAction update(bool touched, int x, int y, int width, int height,
|
||||
bool bottom_selector = false,
|
||||
const TouchSplitSelector* split_selector = nullptr) {
|
||||
const TouchSplitSelector* split_selector = nullptr,
|
||||
const TouchNavigationBar* navigation_bar = nullptr) {
|
||||
if (touched) {
|
||||
_release_samples = 0;
|
||||
if (!_active) {
|
||||
@@ -98,7 +120,7 @@ public:
|
||||
// boxes are unambiguous stationary targets, and rejecting a quick contact
|
||||
// makes a normal tap disappear when it lands within one polling interval.
|
||||
if (_touch_samples == 0 || width <= 0 || height <= 0
|
||||
|| (_touch_samples < 2 && split_selector == nullptr)) {
|
||||
|| (_touch_samples < 2 && split_selector == nullptr && navigation_bar == nullptr)) {
|
||||
return TouchAction::None;
|
||||
}
|
||||
|
||||
@@ -109,10 +131,19 @@ public:
|
||||
const int horizontal_threshold = width / 8 > 8 ? width / 8 : 8;
|
||||
const int vertical_threshold = height / 8 > 8 ? height / 8 : 8;
|
||||
|
||||
// Explicit header/footer controls are tap targets, not swipe surfaces.
|
||||
// Gestures begun in the body may cross them without activating a button.
|
||||
const bool in_reader_chrome = navigation_bar != nullptr
|
||||
&& (_start_y < navigation_bar->exit_height
|
||||
|| (_start_y >= navigation_bar->top
|
||||
&& _start_y < navigation_bar->top + navigation_bar->height));
|
||||
|
||||
if (abs_dx >= abs_dy && abs_dx >= horizontal_threshold) {
|
||||
if (in_reader_chrome) return TouchAction::None;
|
||||
return swipeAction(dx < 0);
|
||||
}
|
||||
if (abs_dy > abs_dx && abs_dy >= vertical_threshold) {
|
||||
if (in_reader_chrome) return TouchAction::None;
|
||||
return verticalSwipeAction(dy < 0);
|
||||
}
|
||||
|
||||
@@ -122,6 +153,39 @@ public:
|
||||
// after movement has been ruled out so it cannot alter swipe detection.
|
||||
const int tap_x = _mirror_tap_x ? width - 1 - _start_x : _start_x;
|
||||
|
||||
// Explicit reader controls take priority over the older, broad bottom
|
||||
// channel-selector zone. Swipes still win over every stationary target.
|
||||
if (navigation_bar != nullptr) {
|
||||
if (tap_x < 0 || tap_x >= width || _start_y < 0 || _start_y >= height)
|
||||
return TouchAction::None;
|
||||
if (_start_y < navigation_bar->exit_height)
|
||||
return _last_y >= 0 && _last_y < navigation_bar->exit_height
|
||||
? TouchAction::Select : TouchAction::None;
|
||||
if (_start_y >= navigation_bar->top
|
||||
&& _start_y < navigation_bar->top + navigation_bar->height) {
|
||||
if (_last_y < navigation_bar->top
|
||||
|| _last_y >= navigation_bar->top + navigation_bar->height)
|
||||
return TouchAction::None;
|
||||
const int end_x = _mirror_tap_x ? width - 1 - _last_x : _last_x;
|
||||
if (end_x < 0 || end_x >= width) return TouchAction::None;
|
||||
const TouchAction actions[] = {TouchAction::VerticalPrevious,
|
||||
TouchAction::Previous, TouchAction::Next,
|
||||
TouchAction::VerticalNext, TouchAction::Select};
|
||||
for (int cell = 0; cell < 5; ++cell) {
|
||||
const int left = width * cell / 5;
|
||||
const int right = width * (cell + 1) / 5;
|
||||
if (tap_x < right)
|
||||
return end_x >= left && end_x < right ? actions[cell] : TouchAction::None;
|
||||
}
|
||||
}
|
||||
// Missing an arrow must never fall through to the ordinary center tap
|
||||
// (Select), which closes the reader. Body halves move pages instead.
|
||||
if (_touch_samples < 2) return TouchAction::None;
|
||||
return tap_x < width / 2 ? TouchAction::Previous : TouchAction::Next;
|
||||
}
|
||||
// A quick single sample is accepted only inside an explicit target.
|
||||
if (_touch_samples < 2 && split_selector == nullptr) return TouchAction::None;
|
||||
|
||||
// Message screens may reserve the otherwise empty ends of their bottom
|
||||
// status bar as forgiving arrow buttons. Keep the label in the middle
|
||||
// inert so an imprecise arrow tap cannot accidentally close the screen.
|
||||
@@ -140,6 +204,10 @@ public:
|
||||
// selections. Swipe detection above retains priority, so page navigation
|
||||
// gestures cannot accidentally activate either choice.
|
||||
if (split_selector != nullptr) {
|
||||
if (tap_x >= 0 && tap_x < split_selector->side_nav_width)
|
||||
return TouchAction::Previous;
|
||||
if (tap_x >= width - split_selector->side_nav_width && tap_x < width)
|
||||
return TouchAction::Next;
|
||||
if (_start_y < split_selector->top_y
|
||||
|| _start_y >= split_selector->top_y + split_selector->height) {
|
||||
return TouchAction::None;
|
||||
|
||||
Reference in new issue
Block a user