Improve Indicator transport touch UI
This commit is contained in:
1 parent
1629725d7d
commit
7791f3f8c9
10 files changed
+212
-38
No files matched your search
@@ -86,16 +86,17 @@ static void drawCompanionTransportChoice(DisplayDriver& display,
|
||||
}
|
||||
|
||||
const bool large_transport_text = display.height() >= 96;
|
||||
display.setTextSize(large_transport_text ? 2 : 1);
|
||||
display.setTextSize(large_transport_text ? 3 : 1);
|
||||
const bool show_status_label = height >= 44;
|
||||
const int label_y = show_status_label
|
||||
? y + (large_transport_text ? 9 : 12)
|
||||
? y + (large_transport_text ? 17 : 12)
|
||||
: y + (height - 8) / 2;
|
||||
display.drawTextCentered(x + width / 2, label_y, label);
|
||||
if (show_status_label && (active || selected)) {
|
||||
display.setTextSize(large_transport_text ? 2 : 1);
|
||||
display.drawTextCentered(
|
||||
x + width / 2,
|
||||
y + height - (large_transport_text ? 23 : 17),
|
||||
y + height - (large_transport_text ? 25 : 17),
|
||||
active ? "ACTIVE"
|
||||
: (large_transport_text ? "NEXT" : "NEXT BOOT"));
|
||||
}
|
||||
@@ -375,13 +376,27 @@ public:
|
||||
|
||||
if (_page == HomePage::FIRST) {
|
||||
display.setColor(UIColor::primary_txt);
|
||||
#ifdef UI_DEDICATED_PAIRING_BLOCK
|
||||
const bool expanded_home =
|
||||
mesh::ui::usesExpandedCompanionHomeTypography(
|
||||
display.width(), display.height(),
|
||||
display.renderWidth(), display.renderHeight());
|
||||
sprintf(tmp, expanded_home ? "INBOX %d" : "INBOX: %d",
|
||||
_task->getPreviewCount());
|
||||
display.setTextSize(expanded_home ? 3 : 2);
|
||||
if (expanded_home && display.getTextWidth(tmp) > display.width() - 4) {
|
||||
display.setTextSize(2);
|
||||
}
|
||||
#else
|
||||
display.setTextSize(2);
|
||||
sprintf(tmp, "INBOX: %d", _task->getPreviewCount());
|
||||
#endif
|
||||
display.drawTextCentered(display.width() / 2, 22, tmp);
|
||||
#ifdef UI_DEDICATED_PAIRING_BLOCK
|
||||
const mesh::ui::CompanionHomeLayout layout =
|
||||
mesh::ui::makeLargeCompanionHomeLayout(display.width(),
|
||||
display.height());
|
||||
display.height(),
|
||||
expanded_home);
|
||||
|
||||
// These are distinct repaint regions. In particular, removing or
|
||||
// replacing a pairing PIN cannot leave old glyphs behind, and long
|
||||
@@ -390,10 +405,11 @@ public:
|
||||
mesh::ui::clearDisplayRegion(display, layout.info);
|
||||
mesh::ui::clearDisplayRegion(display, layout.pairing);
|
||||
|
||||
display.setTextSize(1);
|
||||
display.setTextSize(expanded_home ? 2 : 1);
|
||||
display.setColor(UIColor::secondary_txt);
|
||||
mesh::ui::drawTextCenteredEllipsized(
|
||||
display, layout.info, layout.instruction_y, "tap center: inbox");
|
||||
display, layout.info, layout.instruction_y,
|
||||
expanded_home ? "tap inbox" : "tap center: inbox");
|
||||
|
||||
#ifdef WIFI_SSID
|
||||
if (isCompanionWiFiEnabled()) {
|
||||
@@ -403,6 +419,10 @@ public:
|
||||
} else {
|
||||
strcpy(tmp, "WiFi: OFF");
|
||||
}
|
||||
display.setTextSize(expanded_home ? 2 : 1);
|
||||
if (display.getTextWidth(tmp) > layout.info.width) {
|
||||
display.setTextSize(1);
|
||||
}
|
||||
mesh::ui::drawTextCenteredEllipsized(
|
||||
display, layout.info, layout.network_y, tmp);
|
||||
#endif
|
||||
@@ -415,11 +435,11 @@ public:
|
||||
int pairing_value_size = 2;
|
||||
char pairing_pin[16];
|
||||
if (bluetooth_connected) {
|
||||
pairing_label = "BLUETOOTH";
|
||||
pairing_label = expanded_home ? "BLE" : "BLUETOOTH";
|
||||
pairing_value = "CONNECTED";
|
||||
} else if (mesh::ui::shouldDisplayBluetoothPairingPin(
|
||||
bluetooth_enabled, bluetooth_connected, bluetooth_pin)) {
|
||||
pairing_label = "BLUETOOTH PIN";
|
||||
pairing_label = expanded_home ? "BLE PIN" : "BLUETOOTH PIN";
|
||||
snprintf(pairing_pin, sizeof(pairing_pin), "%06u",
|
||||
(unsigned int)bluetooth_pin);
|
||||
pairing_value = pairing_pin;
|
||||
@@ -430,7 +450,7 @@ public:
|
||||
display.setColor(UIColor::title_bkg);
|
||||
mesh::ui::clearDisplayRegion(display, layout.pairing);
|
||||
display.setColor(UIColor::title_txt);
|
||||
display.setTextSize(1);
|
||||
display.setTextSize(expanded_home ? 2 : 1);
|
||||
mesh::ui::drawTextCenteredEllipsized(
|
||||
display, layout.pairing, layout.pairing_label_y, pairing_label);
|
||||
display.setTextSize(pairing_value_size);
|
||||
@@ -591,10 +611,10 @@ public:
|
||||
mesh::ui::makeCompanionTransportSelectorLayout(
|
||||
display.width(), display.height());
|
||||
|
||||
// Size 2 is already a substantial increase. Keep this page independent
|
||||
// of the additional native-480 text boost so ACTIVE still fits inside
|
||||
// either half-screen choice; the compact status strip above was drawn
|
||||
// before this point and is unaffected.
|
||||
// Keep this page independent of the additional native-480 text boost:
|
||||
// the size-3 choice names and size-2 status already consume nearly the
|
||||
// full half-screen width, and should remain equally readable in the
|
||||
// memory-saving 320 profile.
|
||||
display.setCompactText(true);
|
||||
if (layout.show_title) {
|
||||
display.setColor(UIColor::primary_txt);
|
||||
@@ -605,14 +625,14 @@ public:
|
||||
drawCompanionTransportChoice(
|
||||
display, layout.wifi.x, layout.wifi.y,
|
||||
layout.wifi.width, layout.wifi.height,
|
||||
"Wi-Fi", wifi_active, wifi_selected);
|
||||
"WiFi", wifi_active, wifi_selected);
|
||||
drawCompanionTransportChoice(
|
||||
display, layout.bluetooth.x, layout.bluetooth.y,
|
||||
layout.bluetooth.width, layout.bluetooth.height,
|
||||
"BLE", !wifi_active, !wifi_selected);
|
||||
|
||||
display.setColor(UIColor::secondary_txt);
|
||||
display.setTextSize(display.height() >= 96 ? 2 : 1);
|
||||
display.setTextSize(1);
|
||||
#ifdef HAS_TOUCH
|
||||
display.drawTextCentered(
|
||||
display.width() / 2, layout.prompt_y, "tap a box");
|
||||
|
||||
@@ -30,6 +30,17 @@ struct CompactCompanionPairingLayout {
|
||||
int pairing_value_y;
|
||||
};
|
||||
|
||||
// Full-size Indicator builds keep the established 160x160 logical coordinate
|
||||
// system even when they obtain a native 480x480 render buffer. Expose that
|
||||
// distinction to the home page so it can spend the extra pixel density on
|
||||
// larger, deliberately reflowed text without changing the 320 BLE profile or
|
||||
// unrelated displays that happen to have a tall viewport.
|
||||
inline bool usesExpandedCompanionHomeTypography(
|
||||
int width, int height, int render_width, int render_height) {
|
||||
return width == 160 && height == 160
|
||||
&& render_width == 480 && render_height == 480;
|
||||
}
|
||||
|
||||
// The common Companion OLED/TFT viewport is 128x64. Its normal instruction
|
||||
// and network rows occupy the same lower area needed by the Bluetooth status,
|
||||
// so an active PIN/connection replaces those rows with this compact block.
|
||||
@@ -56,7 +67,19 @@ inline CompactCompanionPairingLayout makeCompactCompanionPairingLayout(
|
||||
// fifths of the display height. The Indicator renders a 160x160 logical UI,
|
||||
// for which this produces a 48-pixel-tall block centered at y=128.
|
||||
inline CompanionHomeLayout makeLargeCompanionHomeLayout(int width,
|
||||
int height) {
|
||||
int height,
|
||||
bool expanded = false) {
|
||||
if (expanded && width == 160 && height == 160) {
|
||||
return {
|
||||
{2, 51, 156, 46},
|
||||
{8, 100, 144, 58},
|
||||
52,
|
||||
76,
|
||||
102,
|
||||
126,
|
||||
};
|
||||
}
|
||||
|
||||
// Ordinary one-line status needs almost the full logical width when the
|
||||
// Indicator's recovered mono font is active. The PIN block gets the larger
|
||||
// inset so it remains visibly distinct from the page background.
|
||||
|
||||
@@ -23,10 +23,10 @@ inline CompanionTransportSelectorLayout makeCompanionTransportSelectorLayout(
|
||||
const bool tall_display = height >= 96;
|
||||
const int margin = 2;
|
||||
const int gap = 4;
|
||||
const int box_y = tall_display ? 40 : 20;
|
||||
const int prompt_y = height - (tall_display ? 22 : 11);
|
||||
int box_height = prompt_y - box_y - 8;
|
||||
if (box_height > 72) box_height = 72;
|
||||
const int box_y = tall_display ? 36 : 20;
|
||||
const int prompt_y = height - (tall_display ? 20 : 11);
|
||||
int box_height = prompt_y - box_y - (tall_display ? 6 : 8);
|
||||
if (box_height > 98) box_height = 98;
|
||||
if (box_height < 20) box_height = 20;
|
||||
const int box_width = (width - margin * 2 - gap) / 2;
|
||||
const int bluetooth_x = margin + box_width + gap;
|
||||
|
||||
@@ -89,9 +89,13 @@ public:
|
||||
_active = false;
|
||||
_release_samples = 0;
|
||||
|
||||
// A contact seen only once has no measurable direction. Ignoring it is
|
||||
// safer than treating the endpoint of a fast swipe as an opposite tap.
|
||||
if (_touch_samples < 2 || width <= 0 || height <= 0) {
|
||||
// A contact seen only once has no measurable direction. Keep ignoring it
|
||||
// on ordinary pages so the endpoint of a fast swipe cannot become an
|
||||
// opposite tap. An explicit split selector is different: its bounded
|
||||
// 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)) {
|
||||
return TouchAction::None;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -53,7 +53,7 @@ does not reflect the GoogleTest count -- run the built binary directly
|
||||
| `test_mqtt_prefs_atomic_store` | `src/helpers/MQTTPrefsAtomicStore.h` | transactional MQTT writes and legacy `/node_prefs` handoff; exact short-write detection; begin/finish/rename failure cleanup; original-file preservation |
|
||||
| `test_mqtt_prefs_json_import` | `src/helpers/MQTTPrefsJsonImport.h`, `src/helpers/MQTTPrefsSerializer.h` | one-time observer `/mqtt.json` v1 import grammar, strict schema/repair rules, binary-first precedence, future/artifact preservation, display-tail encoding, and atomic commit failure routing |
|
||||
| `test_display_viewport` | `src/helpers/ui/DisplayViewport.h`, `src/helpers/ui/DisplayFrameSignature.h` | portrait logical-to-physical mapping, span coverage, fitted-width conversion, text scaling, and visible-frame signatures |
|
||||
| `test_display_driver` | `src/helpers/ui/DisplayDriver.h`, `DisplayTextLayout.h`, `CompanionHomeLayout.h` | UTF-8-safe ellipsis/wrapping; maximum SSID and long setup-address bounds; Indicator home info/pairing separation and 4/5-height PIN placement; compact 128x64 pairing replacement; stale-region clearing |
|
||||
| `test_display_driver` | `src/helpers/ui/DisplayDriver.h`, `DisplayTextLayout.h`, `CompanionHomeLayout.h` | UTF-8-safe ellipsis/wrapping; maximum SSID and long setup-address bounds; Indicator 320/native-480 home info/pairing separation and enlarged-text placement; compact 128x64 pairing replacement; stale-region clearing |
|
||||
| `test_indicator_font_stage_v2_protocol` | `src/helpers/IndicatorFontStageV2Protocol.h` | fail-closed STAGEV2 negotiation and legacy fallback; exact cumulative ACK parsing; 512-byte block boundaries, short final blocks, and real font-asset transfer geometry |
|
||||
| `test_radio_activity_window` | `src/helpers/RadioActivityWindow.h` | rolling minute buckets, derived rates, expiry, warm-up, silence, saturation, and `millis()` rollover |
|
||||
| `test_observer_dashboard` | `src/helpers/ui/ObserverDashboard.h` | landscape and portrait dashboard layout bounds, compact formatting, graph scaling, row signatures, and partial repaint policy |
|
||||
|
||||
@@ -20,8 +20,8 @@ int main() {
|
||||
if (!layout.show_title) return 1;
|
||||
if (layout.wifi.x != 2 || layout.wifi.width != 76) return 2;
|
||||
if (layout.bluetooth.x != 82 || layout.bluetooth.width != 76) return 3;
|
||||
if (layout.wifi.y != 40 || layout.wifi.height != 72) return 4;
|
||||
if (layout.title_y != 17 || layout.prompt_y != 138) return 5;
|
||||
if (layout.wifi.y != 36 || layout.wifi.height != 98) return 4;
|
||||
if (layout.title_y != 17 || layout.prompt_y != 140) return 5;
|
||||
|
||||
const auto small = mesh::ui::makeCompanionTransportSelectorLayout(128, 64);
|
||||
if (small.show_title) return 6;
|
||||
@@ -56,15 +56,21 @@ int main() {
|
||||
)
|
||||
self.assertEqual(run_result.returncode, 0, run_result.stderr)
|
||||
|
||||
logical_glyph_width = 6 * 2
|
||||
logical_glyph_height = 8 * 2
|
||||
for text in ("Wi-Fi", "BLE", "ACTIVE", "NEXT"):
|
||||
self.assertLessEqual(len(text) * logical_glyph_width, 76)
|
||||
choice_glyph_width = 6 * 3
|
||||
choice_glyph_height = 8 * 3
|
||||
for text in ("WiFi", "BLE"):
|
||||
self.assertLessEqual(len(text) * choice_glyph_width, 76)
|
||||
status_glyph_width = 6 * 2
|
||||
status_glyph_height = 8 * 2
|
||||
for text in ("ACTIVE", "NEXT"):
|
||||
self.assertLessEqual(len(text) * status_glyph_width, 76)
|
||||
title_glyph_width = 6 * 2
|
||||
for text in ("TRANSPORT", "tap a box"):
|
||||
self.assertLessEqual(len(text) * logical_glyph_width, 160)
|
||||
self.assertLessEqual(17 + logical_glyph_height, 40)
|
||||
self.assertLessEqual(89 + logical_glyph_height, 112)
|
||||
self.assertLessEqual(138 + logical_glyph_height, 160)
|
||||
self.assertLessEqual(len(text) * title_glyph_width, 160)
|
||||
self.assertLessEqual(17 + 8 * 2, 36)
|
||||
self.assertLessEqual(53 + choice_glyph_height, 134)
|
||||
self.assertLessEqual(109 + status_glyph_height, 134)
|
||||
self.assertLessEqual(140 + 8, 160)
|
||||
|
||||
def test_touch_split_selector_distinguishes_taps_from_swipes(self):
|
||||
source = r'''
|
||||
@@ -113,6 +119,35 @@ int main() {
|
||||
input.update(true, 130, 68, 160, 160, false, &selector);
|
||||
if (release(input, &selector) != TouchAction::Next) return 6;
|
||||
|
||||
// A quick selector tap may occupy only one 25 ms poll. Its bounded target
|
||||
// remains unambiguous after the normal two-sample release debounce.
|
||||
input.update(true, 30, 120, 160, 160, false, &selector);
|
||||
if (release(input, &selector) != TouchAction::SelectLeft) return 7;
|
||||
|
||||
input.update(true, 130, 120, 160, 160, false, &selector);
|
||||
if (release(input, &selector) != TouchAction::SelectRight) return 8;
|
||||
|
||||
// The gap, title, and exclusive bottom edge remain inert even for a quick
|
||||
// contact, while the last pixel inside the taller box is selectable.
|
||||
input.update(true, 80, 120, 160, 160, false, &selector);
|
||||
if (release(input, &selector) != TouchAction::None) return 9;
|
||||
|
||||
input.update(true, 30, layout.wifi.y - 1, 160, 160, false, &selector);
|
||||
if (release(input, &selector) != TouchAction::None) return 10;
|
||||
|
||||
input.update(true, 30, layout.wifi.y + layout.wifi.height,
|
||||
160, 160, false, &selector);
|
||||
if (release(input, &selector) != TouchAction::None) return 11;
|
||||
|
||||
input.update(true, 30, layout.wifi.y + layout.wifi.height - 1,
|
||||
160, 160, false, &selector);
|
||||
if (release(input, &selector) != TouchAction::SelectLeft) return 12;
|
||||
|
||||
// Ordinary pages keep the two-sample protection against a detached swipe
|
||||
// endpoint being interpreted as a tap.
|
||||
input.update(true, 30, 70, 160, 160, false, nullptr);
|
||||
if (release(input, nullptr) != TouchAction::None) return 13;
|
||||
|
||||
return 0;
|
||||
}
|
||||
'''
|
||||
@@ -144,11 +179,11 @@ int main() {
|
||||
source = UI.read_text(encoding="utf-8")
|
||||
self.assertIn("#ifdef COMPANION_EXCLUSIVE_WIFI_BLE", source)
|
||||
self.assertIn("TRANSPORT,", source)
|
||||
self.assertIn('"Wi-Fi", wifi_active, wifi_selected', source)
|
||||
self.assertIn('"WiFi", wifi_active, wifi_selected', source)
|
||||
self.assertIn('"BLE", !wifi_active, !wifi_selected', source)
|
||||
self.assertIn("display.fillRect(x, y, width, height);", source)
|
||||
self.assertIn('"ACTIVE"', source)
|
||||
self.assertIn('large_transport_text ? 2 : 1', source)
|
||||
self.assertIn('large_transport_text ? 3 : 1', source)
|
||||
self.assertIn("const bool show_status_label = height >= 44;", source)
|
||||
self.assertIn('active ? "ACTIVE"', source)
|
||||
self.assertIn('large_transport_text ? "NEXT" : "NEXT BOOT"', source)
|
||||
|
||||
@@ -181,6 +181,36 @@ TEST(DisplayDriver, IndicatorHomeSeparatesInfoAndPairingGeometry) {
|
||||
layout.pairing, layout.pairing_value_y, 24));
|
||||
}
|
||||
|
||||
TEST(DisplayDriver, NativeIndicatorExpandsHomeTypographyRegions) {
|
||||
EXPECT_TRUE(mesh::ui::usesExpandedCompanionHomeTypography(
|
||||
160, 160, 480, 480));
|
||||
EXPECT_FALSE(mesh::ui::usesExpandedCompanionHomeTypography(
|
||||
160, 160, 320, 320));
|
||||
EXPECT_FALSE(mesh::ui::usesExpandedCompanionHomeTypography(
|
||||
128, 64, 480, 480));
|
||||
|
||||
const mesh::ui::CompanionHomeLayout layout =
|
||||
mesh::ui::makeLargeCompanionHomeLayout(160, 160, true);
|
||||
EXPECT_EQ(2, layout.info.x);
|
||||
EXPECT_EQ(51, layout.info.y);
|
||||
EXPECT_EQ(156, layout.info.width);
|
||||
EXPECT_EQ(46, layout.info.height);
|
||||
EXPECT_EQ(8, layout.pairing.x);
|
||||
EXPECT_EQ(100, layout.pairing.y);
|
||||
EXPECT_EQ(144, layout.pairing.width);
|
||||
EXPECT_EQ(58, layout.pairing.height);
|
||||
EXPECT_FALSE(mesh::ui::displayRegionsOverlap(layout.info,
|
||||
layout.pairing));
|
||||
EXPECT_TRUE(mesh::ui::displayRegionContainsLine(
|
||||
layout.info, layout.instruction_y, 20));
|
||||
EXPECT_TRUE(mesh::ui::displayRegionContainsLine(
|
||||
layout.info, layout.network_y, 20));
|
||||
EXPECT_TRUE(mesh::ui::displayRegionContainsLine(
|
||||
layout.pairing, layout.pairing_label_y, 20));
|
||||
EXPECT_TRUE(mesh::ui::displayRegionContainsLine(
|
||||
layout.pairing, layout.pairing_value_y, 30));
|
||||
}
|
||||
|
||||
TEST(DisplayDriver, IndicatorPairingValuesFitAtRenderedTextSizes) {
|
||||
// Model the fallback font's fixed six-logical-pixel cell. It is the wider
|
||||
// of the two paths for CONNECTED; the profile test reads the recovered
|
||||
|
||||
@@ -103,8 +103,13 @@ class IndicatorDisplayProfileTest(unittest.TestCase):
|
||||
requested_size * coordinate_scale / native_scale
|
||||
* profile_scale
|
||||
)
|
||||
width = sum(advances[ord(char)] for char in text)
|
||||
height = max(heights[ord(char)] for char in text)
|
||||
# Blank glyphs carry no bitmap record in the packed VLW. The
|
||||
# checked-in font is monospaced with the documented 18px cell, so
|
||||
# spaces still advance by one complete cell.
|
||||
width = sum(
|
||||
18 if char == " " else advances[ord(char)] for char in text
|
||||
)
|
||||
height = max(heights[ord(char)] for char in text if char != " ")
|
||||
return (
|
||||
int((width * render_scale + coordinate_scale - 1)
|
||||
// coordinate_scale),
|
||||
@@ -129,6 +134,26 @@ class IndicatorDisplayProfileTest(unittest.TestCase):
|
||||
self.assertLessEqual(width, 144)
|
||||
self.assertLessEqual(height, 30)
|
||||
|
||||
# Native 480 reflows the spacious home page instead of globally
|
||||
# enlarging every dense UI row. Its prominent strings fit at their
|
||||
# requested sizes, while a two-digit inbox count takes the deliberate
|
||||
# size-2 fallback.
|
||||
native_title = dimensions("INBOX 0", 3, 3, 1.2)
|
||||
native_two_digit_title = dimensions("INBOX 10", 3, 3, 1.2)
|
||||
native_two_digit_fallback = dimensions("INBOX 10", 2, 3, 1.2)
|
||||
self.assertLessEqual(native_title[0], 156)
|
||||
self.assertGreater(native_two_digit_title[0], 156)
|
||||
self.assertLessEqual(native_two_digit_fallback[0], 156)
|
||||
|
||||
for text, region_width in (
|
||||
("tap inbox", 156),
|
||||
("WiFi: OFF", 156),
|
||||
("BLE PIN", 144),
|
||||
):
|
||||
width, height = dimensions(text, 2, 3, 1.2)
|
||||
self.assertLessEqual(width, region_width)
|
||||
self.assertLessEqual(height, 20)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -153,6 +153,32 @@ TEST(TouchInput, SplitSelectorMapsTapHalvesToExplicitChoices) {
|
||||
TouchAction::None);
|
||||
}
|
||||
|
||||
TEST(TouchInput, SplitSelectorAcceptsQuickBoundedTapsOnly) {
|
||||
TouchInput input(true, true, 70);
|
||||
const TouchSplitSelector selector{2, 76, 82, 76, 36, 98};
|
||||
|
||||
input.update(true, 30, 100, 160, 160, false, &selector);
|
||||
EXPECT_EQ(input.update(false, -1, -1, 160, 160, false, &selector),
|
||||
TouchAction::None);
|
||||
EXPECT_EQ(input.update(false, -1, -1, 160, 160, false, &selector),
|
||||
TouchAction::SelectLeft);
|
||||
|
||||
input.update(true, 130, 100, 160, 160, false, &selector);
|
||||
EXPECT_EQ(input.update(false, -1, -1, 160, 160, false, &selector),
|
||||
TouchAction::None);
|
||||
EXPECT_EQ(input.update(false, -1, -1, 160, 160, false, &selector),
|
||||
TouchAction::SelectRight);
|
||||
|
||||
input.update(true, 80, 100, 160, 160, false, &selector);
|
||||
EXPECT_EQ(input.update(false, -1, -1, 160, 160, false, &selector),
|
||||
TouchAction::None);
|
||||
EXPECT_EQ(input.update(false, -1, -1, 160, 160, false, &selector),
|
||||
TouchAction::None);
|
||||
|
||||
input.update(true, 30, 100, 160, 160);
|
||||
EXPECT_EQ(release(input, 160, 160), TouchAction::None);
|
||||
}
|
||||
|
||||
TEST(TouchInput, SplitSelectorKeepsSwipesAsNavigation) {
|
||||
TouchInput input(true, true, 70);
|
||||
const TouchSplitSelector selector{8, 68, 84, 68, 38, 72};
|
||||
|
||||
@@ -16,6 +16,10 @@ wakes it without selecting anything.
|
||||
side performs Previous or Next; horizontal swipes remain available anywhere.
|
||||
- A gesture is committed after a stable release. Brief missing touch samples
|
||||
are ignored so one swipe cannot become a second, opposite endpoint tap.
|
||||
- The transport page is the exception for a very quick stationary contact:
|
||||
either tall, outlined WiFi/BLE box can accept one sampled point after the
|
||||
same stable-release debounce. Taps in the title, center gap, or footer stay
|
||||
inert, and a moving contact is still handled as a swipe first.
|
||||
|
||||
## Reading messages
|
||||
|
||||
@@ -111,6 +115,13 @@ Bluetooth controller and host memory. BLE mode does not start infrastructure
|
||||
WiFi, WebConfig, MQTT, OTA networking, or TCP Companion services. USB remains
|
||||
available in both modes for recovery.
|
||||
|
||||
The Indicator transport page presents WiFi and BLE as full-height side-by-side
|
||||
choices. Tapping either choice saves it and reboots only when it differs from
|
||||
the active mode. Its size-3 choice labels render at the same physical size in
|
||||
the 320 and 480 profiles. On a retained native 480 canvas, the first home page
|
||||
also reflows its inbox heading and short status labels at larger sizes; long IP
|
||||
addresses automatically fall back to the smaller row size instead of clipping.
|
||||
|
||||
LoRa remains the primary radio in both LoRa modes. On the ESP-NOW layout,
|
||||
selecting BLE leaves the primary ESP-NOW WiFi radio and its fixed channel
|
||||
running; it disables only infrastructure WiFi. Rendering follows the memory
|
||||
|
||||
Reference in new issue
Block a user