Compare commits
8 Commits
v1.0-beep-
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 3711e37f28 | |||
| 71fde27f05 | |||
| 0a58ed8a79 | |||
| 51938cd76a | |||
| 69d37005b7 | |||
| 652f4a0499 | |||
| 5afe3971fe | |||
| 54a70d09e2 |
16
CHANGELOG.md
16
CHANGELOG.md
@@ -1,5 +1,21 @@
|
||||
# Changelog
|
||||
|
||||
## 2026-10-25 — 3.5" backlight pin correction
|
||||
|
||||
- `cyd_35r`: `TFT_BL` / `TFT_BL_PIN` **GPIO27** (was wrongly 21; 21 is GT911 INT).
|
||||
- `../CYD_PROJECT_GUIDE.md` §A:2.8 vs 3.5 对照(触控 2046/911、背光、外设脚 22/27 vs 22)。
|
||||
|
||||
## 2026-10-18 — Pomodoro presets + custom minutes
|
||||
|
||||
- Duration presets: 1 / 3 / 5 / 10 minutes; `Set` opens numeric keypad (1–99).
|
||||
- Keep `+/-`, Start/Pause/Reset, sound picker, speaker + volume.
|
||||
|
||||
## 2026-10-18 — Pomodoro timer UI
|
||||
|
||||
- Tag baseline `v1.0-beep-wav` (Beep + WAV list UI).
|
||||
- New Pomodoro screen with Start/Pause/Reset and selectable alarm sound.
|
||||
- Keep speaker toggle + volume slider; TF status in header.
|
||||
|
||||
## 2026-10-18 — WAV via DAC2 only (touch-safe)
|
||||
|
||||
- Drop I2S INTERNAL_DAC (it enables GPIO25 and kills touch).
|
||||
|
||||
38
README.md
38
README.md
@@ -1,17 +1,43 @@
|
||||
# CYD TF + Speaker
|
||||
# CYD Pomodoro
|
||||
|
||||
ESP32-2432S028R:LVGL 横屏显示 TF 卡信息,触控开关喇叭并播放音乐。
|
||||
默认板:**ESP32-2432S028R**(2.8"):LVGL 横屏番茄钟,可选内置提示音,触控喇叭播放。
|
||||
|
||||
可选 **3.5" C**:`pio run -e cyd_35r`。
|
||||
|
||||
| | 2.8" `cyd` | 3.5C `cyd_35r` |
|
||||
|--|------------|----------------|
|
||||
| 屏 | ILI9341 · UI 320×240 | ST7796 · UI 480×320 |
|
||||
| 触控 | **XPT2046** SoftSPI | **GT911** I2C |
|
||||
| 背光 | GPIO**21** | GPIO**27** |
|
||||
| 外设首选 | CN1 **IO22 + IO27** | 优先 **IO22**(21=INT,27=BL) |
|
||||
|
||||
完整对照与避坑:[`../CYD_PROJECT_GUIDE.md`](../CYD_PROJECT_GUIDE.md) **§A**。
|
||||
|
||||
## 功能
|
||||
|
||||
- SoftSPI 触摸(已标定,勿改 `src/touch.cpp` 映射)
|
||||
- TF 卡信息(VSPI)
|
||||
- 喇叭 GPIO26,默认音量 **20%**;按钮:Speaker ON/OFF、Play Music
|
||||
- SoftSPI 触摸(2.8,已标定,勿改 `src/touch.cpp` 映射);3.5C 为 GT911 I2C
|
||||
- 番茄钟:预设 1 / 3 / 5 / 10 分钟;`Set` 自定义输入;`+/-` 微调;Start / Pause / Reset
|
||||
- 结束音效:内置 Beep 列表自选(默认 Alarm),点名称可试听
|
||||
- 喇叭 GPIO26 + 音量滑条;TF 卡状态显示在右上角
|
||||
- 基线标签:`v1.0-beep-wav`(改番茄钟前的 Beep + WAV 界面)
|
||||
|
||||
## 构建 / 烧录
|
||||
|
||||
```bash
|
||||
# 2.8"
|
||||
pio run -e cyd -t upload --upload-port COM3
|
||||
# 3.5" 电容板
|
||||
pio run -e cyd_35r -t upload --upload-port COM3
|
||||
```
|
||||
|
||||
或 `firmware/cyd/flash.bat`。
|
||||
或 `firmware/cyd/flash.bat`(2.8 预编译包)。
|
||||
|
||||
浏览器刷写(推荐):在 [`../cydweb`](../cydweb) 运行 `npm run dev`,打开 http://localhost:3000 ,选择 **CYD Pomodoro**。
|
||||
|
||||
更新预编译包:
|
||||
|
||||
```powershell
|
||||
.\firmware\export_from_build.ps1
|
||||
# 仅复制已有构建:
|
||||
.\firmware\export_from_build.ps1 -SkipBuild
|
||||
```
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
当前归档目录:[`cyd/`](cyd/)
|
||||
|
||||
对应板子:**CYD ESP32-2432S028R**(ILI9341)
|
||||
功能:TF 卡信息 + 触屏扬声器控制
|
||||
功能:**番茄钟(Pomodoro)** + 触控喇叭 + TF 状态
|
||||
版本信息见 [`cyd/VERSION.txt`](cyd/VERSION.txt)。
|
||||
|
||||
也可在 [`../../cydweb`](../../cydweb)(CYD Flasher)里通过浏览器串口刷写本目录固件。
|
||||
|
||||
## 直接刷写(无需重新编译)
|
||||
|
||||
1. USB 连接板子
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
name: CYD TF card + touch speaker (cyd)
|
||||
git: 7c6eb9a
|
||||
date: 2026-10-17
|
||||
name: CYD Pomodoro (cyd)
|
||||
git: 54a70d0
|
||||
date: 2026-10-19
|
||||
board: ESP32-2432S028R (ILI9341)
|
||||
features: TF/SD info, SoftSPI touch, GPIO26 speaker
|
||||
features: Pomodoro timer, SoftSPI touch, GPIO26 speaker, TF status
|
||||
flash_size: 4MB
|
||||
offsets:
|
||||
0x1000 bootloader.bin
|
||||
@@ -12,5 +12,5 @@ offsets:
|
||||
sha256:
|
||||
bootloader.bin 3D234A7471F67B013686DABD4DEE7C1FA915C9928463616A94BC9297ACF1ABF8
|
||||
boot_app0.bin F94C5D786A7A8FAB06AC5D10E33BF37711A6697636DC037559EA19CC410A17F0
|
||||
firmware.bin C2BD52279AFB3E40C45358CA05493EF0255F708858A3F5132417AEAC0B6EE25F
|
||||
firmware.bin 4C064140E8F8E0D2DD55D0085F7974FA57CAF5A24DFFACC9B3ECF03B576A033B
|
||||
partitions.bin 148B959CBFF1C38AA8E1D5C0BA9D612C54997B945E56A63F41223EEF650653A1
|
||||
|
||||
Binary file not shown.
55
firmware/cyd/media/preview.html
Normal file
55
firmware/cyd/media/preview.html
Normal file
@@ -0,0 +1,55 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>POMO</title>
|
||||
<style>
|
||||
*{box-sizing:border-box;margin:0;padding:0}
|
||||
html,body{width:320px;height:240px;overflow:hidden;background:#000;margin:0}
|
||||
.bezel{width:320px;height:240px;background:#1a1408}
|
||||
.screen{width:320px;height:240px;background:#0A1016;color:#EAF0F6;font:12px/1.2 system-ui,sans-serif;
|
||||
position:relative;overflow:hidden;}
|
||||
.title{position:absolute;left:12px;top:4px;color:#E85D4C;font-size:20px;font-weight:700}
|
||||
.chip{position:absolute;right:12px;top:8px;color:#7A8B9C}
|
||||
.pm{position:absolute;left:12px;top:32px;width:40px;height:44px;border:0;border-radius:8px;background:#243140;color:#EAF0F6;font-size:18px}
|
||||
.pp{left:auto;right:12px}
|
||||
.timer{position:absolute;left:0;right:0;top:34px;text-align:center;font-size:28px;font-weight:700;color:#4FC3F7}
|
||||
.status{position:absolute;left:0;right:0;top:74px;text-align:center;color:#7A8B9C}
|
||||
.presets{position:absolute;left:12px;right:12px;top:94px;display:flex;gap:6px}
|
||||
.presets button{flex:1;height:30px;border:0;border-radius:6px;background:#15202C;color:#EAF0F6;font-weight:600}
|
||||
.presets button.on{background:#1E3A4A}
|
||||
.snd{position:absolute;left:12px;top:134px;color:#7A8B9C}
|
||||
.sndrow{position:absolute;left:68px;right:12px;top:128px;display:flex;gap:4px;align-items:center}
|
||||
.sndrow button{height:28px;border:0;border-radius:6px;background:#15202C;color:#EAF0F6;min-width:32px}
|
||||
.sndrow .name{flex:1;height:28px;border-radius:6px;background:#15202C;display:grid;place-items:center}
|
||||
.vol{position:absolute;left:12px;top:164px;color:#7A8B9C}
|
||||
.slider{position:absolute;left:52px;right:56px;top:168px;height:14px;border-radius:4px;background:#1C2A38;overflow:hidden}
|
||||
.slider>i{display:block;height:100%;width:40%;background:#3DDC97}
|
||||
.volpct{position:absolute;right:12px;top:164px}
|
||||
.audio{position:absolute;left:12px;top:188px;color:#7A8B9C;font-size:11px}
|
||||
.acts{position:absolute;left:12px;right:12px;bottom:10px;display:flex;gap:8px}
|
||||
.acts button{flex:1;height:32px;border:0;border-radius:8px;font-weight:700;color:#EAF0F6}
|
||||
.start{background:#1A4A6E}.reset{background:#243140}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="bezel"><div class="screen" id="s">
|
||||
<div class="title">POMO</div><div class="chip">TF OK</div>
|
||||
<button class="pm" type="button">-</button>
|
||||
<div class="timer">05:00</div>
|
||||
<button class="pm pp" type="button">+</button>
|
||||
<div class="status">5m - READY</div>
|
||||
<div class="presets">
|
||||
<button type="button">1</button><button type="button">3</button>
|
||||
<button class="on" type="button">5</button><button type="button">10</button>
|
||||
<button type="button">Set</button>
|
||||
</div>
|
||||
<div class="snd">SOUND</div>
|
||||
<div class="sndrow"><button type="button"><</button><div class="name">Alarm</div><button type="button">></button></div>
|
||||
<div class="vol">VOL</div><div class="slider"><i></i></div><div class="volpct">40%</div>
|
||||
<div class="audio">Speaker OFF</div>
|
||||
<div class="acts"><button class="start" type="button">START</button><button class="reset" type="button">RESET</button></div>
|
||||
</div></div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
10
firmware/cyd/meta.json
Normal file
10
firmware/cyd/meta.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"boardId": "esp32-2432s028",
|
||||
"name": "CYD Pomodoro",
|
||||
"summary": "Landscape Pomodoro timer with touch, speaker, and TF status",
|
||||
"description": "Presets 1/3/5/10 minutes, custom duration, alarm sound picker, volume slider on GPIO26.",
|
||||
"tags": ["pomodoro", "touch", "speaker", "timer"],
|
||||
"media": {
|
||||
"preview": "media/preview.html"
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,13 @@
|
||||
# Rebuild (optional) and refresh firmware/cyd from PlatformIO build output.
|
||||
# Usage:
|
||||
# .\firmware\export_from_build.ps1
|
||||
# .\firmware\export_from_build.ps1 -SkipBuild
|
||||
# Export firmware bins from PlatformIO build.
|
||||
# Default: 2.8" (cyd -> firmware/cyd)
|
||||
# 3.5": .\firmware\export_from_build.ps1 -Env cyd_35r
|
||||
# (only after: pio run -e cyd_35r)
|
||||
|
||||
param([switch]$SkipBuild)
|
||||
param(
|
||||
[switch]$SkipBuild,
|
||||
[ValidateSet("cyd", "cyd_35r")]
|
||||
[string]$Env = "cyd"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$root = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
|
||||
@@ -12,17 +16,26 @@ Set-Location $root
|
||||
$env:Path = "C:\Users\dsx2016\AppData\Local\Programs\Python\Python312\Scripts;" + $env:Path
|
||||
|
||||
if (-not $SkipBuild) {
|
||||
pio run -e cyd
|
||||
pio run -e $Env
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
}
|
||||
|
||||
$src = Join-Path $root ".pio\build\cyd"
|
||||
$dst = Join-Path $root "firmware\cyd"
|
||||
$src = Join-Path $root (".pio\build\" + $Env)
|
||||
if ($Env -eq "cyd_35r") {
|
||||
$dstName = "cyd_35r"
|
||||
$boardLine = "board: ESP32-3248S035 (ST7796)"
|
||||
$boardIdHint = "esp32-3248s035"
|
||||
} else {
|
||||
$dstName = "cyd"
|
||||
$boardLine = "board: ESP32-2432S028R (ILI9341)"
|
||||
$boardIdHint = "esp32-2432s028"
|
||||
}
|
||||
$dst = Join-Path $root ("firmware\" + $dstName)
|
||||
New-Item -ItemType Directory -Force -Path $dst | Out-Null
|
||||
|
||||
foreach ($f in @("bootloader.bin", "partitions.bin", "firmware.bin")) {
|
||||
$p = Join-Path $src $f
|
||||
if (-not (Test-Path $p)) { Write-Error "Missing build artifact: $p" }
|
||||
if (-not (Test-Path $p)) { Write-Error "Missing build artifact: $p — build with: pio run -e $Env" }
|
||||
Copy-Item $p $dst -Force
|
||||
}
|
||||
|
||||
@@ -32,17 +45,15 @@ if (-not (Test-Path $bootApp)) {
|
||||
}
|
||||
Copy-Item $bootApp (Join-Path $dst "boot_app0.bin") -Force
|
||||
|
||||
# Keep flash scripts if already present
|
||||
$git = ""
|
||||
try { $git = (git -C $root rev-parse --short HEAD).Trim() } catch { $git = "unknown" }
|
||||
$date = Get-Date -Format "yyyy-MM-dd"
|
||||
$hashes = Get-ChildItem (Join-Path $dst "*.bin") | Get-FileHash -Algorithm SHA256
|
||||
$lines = @(
|
||||
"name: CYD TF card + touch speaker (cyd)",
|
||||
"name: CYD tftest ($dstName)",
|
||||
"git: $git",
|
||||
"date: $date",
|
||||
"board: ESP32-2432S028R (ILI9341)",
|
||||
"features: TF/SD info, SoftSPI touch, GPIO26 speaker",
|
||||
$boardLine,
|
||||
"flash_size: 4MB",
|
||||
"offsets:",
|
||||
" 0x1000 bootloader.bin",
|
||||
@@ -55,4 +66,22 @@ foreach ($h in $hashes) {
|
||||
$lines += (" {0,-15} {1}" -f (Split-Path $h.Path -Leaf), $h.Hash)
|
||||
}
|
||||
$lines | Set-Content -Encoding utf8 (Join-Path $dst "VERSION.txt")
|
||||
Write-Host "Exported -> $dst (git $git)"
|
||||
|
||||
# Keep / refresh meta.json boardId for catalog filtering
|
||||
$metaPath = Join-Path $dst "meta.json"
|
||||
if (Test-Path $metaPath) {
|
||||
try {
|
||||
$meta = Get-Content $metaPath -Raw | ConvertFrom-Json
|
||||
$meta.boardId = $boardIdHint
|
||||
if ($Env -eq "cyd_35r" -and $meta.name -notmatch "3\.5") {
|
||||
$meta.name = ($meta.name -replace "\s*$","") + " (3.5\")"
|
||||
}
|
||||
$meta | ConvertTo-Json -Depth 8 | Set-Content -Encoding utf8 $metaPath
|
||||
} catch {
|
||||
Write-Host "meta.json update skipped: $_"
|
||||
}
|
||||
} elseif ($Env -eq "cyd_35r") {
|
||||
Write-Host "Note: add firmware/cyd_35r/meta.json with boardId=$boardIdHint for the web catalog"
|
||||
}
|
||||
|
||||
Write-Host "Exported -> $dst (env=$Env git=$git)"
|
||||
|
||||
@@ -1,24 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
// CYD ESP32-2432S028R
|
||||
// Board pins — 2.8" default; 3.5" when CYD_BOARD_3248S035 is set (env:cyd_35r)
|
||||
|
||||
#if defined(CYD_BOARD_3248S035)
|
||||
#define TFT_BL_PIN 27
|
||||
#define SCREEN_WIDTH 320
|
||||
#define SCREEN_HEIGHT 480
|
||||
#define UI_HOR_RES 480
|
||||
#define UI_VER_RES 320
|
||||
/* 3.5C capacitive GT911 (ESP32-3248S035C). GPIO33 is I2C SDA, not TOUCH_CS.
|
||||
* Backlight is GPIO27; INT is GPIO21 — they are NOT the same pin. */
|
||||
#define CYD_GT911_SDA 33
|
||||
#define CYD_GT911_SCL 32
|
||||
#define CYD_GT911_INT 21
|
||||
#define CYD_GT911_RST 25
|
||||
#else
|
||||
#define TFT_BL_PIN 21
|
||||
|
||||
// TF / microSD (VSPI;触摸用 SoftSPI,不抢总线)
|
||||
#define SD_MISO 19
|
||||
#define SD_MOSI 23
|
||||
#define SD_SCK 18
|
||||
#define SD_CS 5
|
||||
|
||||
#define SPEAKER_PIN 26
|
||||
|
||||
#define SCREEN_WIDTH 240
|
||||
#define SCREEN_HEIGHT 320
|
||||
#define UI_HOR_RES 320
|
||||
#define UI_VER_RES 240
|
||||
/* 2.8" CYD: dedicated SoftSPI touch bus. */
|
||||
#define CYD_TOUCH_IRQ 36
|
||||
#define CYD_TOUCH_MOSI 32
|
||||
#define CYD_TOUCH_MISO 39
|
||||
#define CYD_TOUCH_CLK 25
|
||||
#define CYD_TOUCH_CS 33
|
||||
#endif
|
||||
|
||||
#define SCREEN_WIDTH 240
|
||||
#define SCREEN_HEIGHT 320
|
||||
#define UI_HOR_RES 320
|
||||
#define UI_VER_RES 240
|
||||
/* Map 320x240 design coordinates onto the active UI resolution (identity on 2.8"). */
|
||||
#define UX(x) ((int)(((long)(x) * (UI_HOR_RES)) / 320))
|
||||
#define UY(y) ((int)(((long)(y) * (UI_VER_RES)) / 240))
|
||||
#define UW(w) UX(w)
|
||||
#define UH(h) UY(h)
|
||||
|
||||
/* Partial frame buffer: 3.5 needs smaller ratio to fit DRAM. */
|
||||
#if defined(CYD_BOARD_3248S035)
|
||||
#define DRAW_BUF_DIV 20
|
||||
#else
|
||||
#define DRAW_BUF_DIV 10
|
||||
#endif
|
||||
|
||||
|
||||
#define SD_MISO 19
|
||||
#define SD_MOSI 23
|
||||
#define SD_SCK 18
|
||||
#define SD_CS 5
|
||||
#define SPEAKER_PIN 26
|
||||
|
||||
25
include/pomodoro.h
Normal file
25
include/pomodoro.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
enum PomoState : uint8_t {
|
||||
POMO_IDLE = 0,
|
||||
POMO_RUNNING = 1,
|
||||
POMO_PAUSED = 2,
|
||||
POMO_DONE = 3,
|
||||
};
|
||||
|
||||
void pomo_init();
|
||||
void pomo_tick();
|
||||
|
||||
PomoState pomo_state();
|
||||
uint32_t pomo_remaining_sec();
|
||||
uint16_t pomo_duration_minutes(); // last configured length
|
||||
|
||||
void pomo_set_minutes(uint16_t minutes); // 1..99; idle/paused/done only
|
||||
void pomo_start_pause();
|
||||
void pomo_reset();
|
||||
void pomo_adjust_minutes(int8_t delta);
|
||||
|
||||
const char *pomo_state_name();
|
||||
bool pomo_consume_alarm();
|
||||
@@ -5,9 +5,6 @@
|
||||
|
||||
void ui_create();
|
||||
void ui_update(const SdCardInfo &info);
|
||||
void ui_tick(); // refresh timer + fire alarm sound
|
||||
void ui_refresh_audio_buttons();
|
||||
void ui_refresh_lists();
|
||||
void ui_set_touch_debug(int16_t lx, int16_t ly, bool down);
|
||||
|
||||
// 0=Beep 1=WAV
|
||||
uint8_t ui_active_tab();
|
||||
|
||||
@@ -43,13 +43,38 @@ build_flags =
|
||||
-DLV_FONT_MONTSERRAT_16=1
|
||||
-DLV_FONT_MONTSERRAT_18=1
|
||||
-DLV_FONT_MONTSERRAT_20=1
|
||||
-DLV_FONT_MONTSERRAT_28=1
|
||||
-DLV_USE_TFT_ESPI=1
|
||||
-DLV_USE_FLEX=1
|
||||
-DLV_USE_BUTTON=1
|
||||
-DLV_USE_LABEL=1
|
||||
-DLV_USE_BAR=1
|
||||
-DLV_USE_SLIDER=1
|
||||
-DLV_USE_TABVIEW=1
|
||||
-I${PROJECT_DIR}/include
|
||||
|
||||
extra_scripts = pre:copy_lv_conf.py
|
||||
|
||||
; Optional 3.5" CYD ESP32-3248S035C (ST7796 + GT911 capacitive). Default [env:cyd].
|
||||
; Build: pio run -e cyd_35r
|
||||
; Keep TOUCH_CS=-1 — GPIO33 is GT911 SDA, not resistive touch CS.
|
||||
; TFT_BL=GPIO27 (not 21 — 21 is GT911 INT). See ../CYD_PROJECT_GUIDE.md §1.1
|
||||
[env:cyd_35r]
|
||||
extends = env:cyd
|
||||
build_unflags =
|
||||
-DILI9341_2_DRIVER=1
|
||||
-DTFT_WIDTH=240
|
||||
-DTFT_HEIGHT=320
|
||||
-DTFT_BL=21
|
||||
-DLV_MEM_SIZE=49152
|
||||
build_flags =
|
||||
${env:cyd.build_flags}
|
||||
-DST7796_DRIVER=1
|
||||
-DTFT_WIDTH=320
|
||||
-DTFT_HEIGHT=480
|
||||
-DTFT_BL=27
|
||||
-DCYD_BOARD_3248S035=1
|
||||
-DUSE_HSPI_PORT
|
||||
-DSUPPORT_TRANSACTIONS
|
||||
-DTFT_RGB_ORDER=TFT_BGR
|
||||
-DLV_MEM_SIZE=32768
|
||||
|
||||
|
||||
25
src/main.cpp
25
src/main.cpp
@@ -14,8 +14,9 @@ static constexpr uint32_t DRAW_BUF_SIZE =
|
||||
static uint32_t draw_buf[DRAW_BUF_SIZE / 4];
|
||||
|
||||
static uint32_t last_refresh_ms = 0;
|
||||
static uint32_t last_btn_ui_ms = 0;
|
||||
static constexpr uint32_t REFRESH_MS = 4000;
|
||||
static uint32_t last_ui_ms = 0;
|
||||
static constexpr uint32_t REFRESH_MS = 8000;
|
||||
static constexpr uint32_t UI_TICK_MS = 200;
|
||||
|
||||
static void refresh_sd_ui() {
|
||||
SdCardInfo info = sd_read_info();
|
||||
@@ -28,7 +29,6 @@ static void refresh_sd_ui() {
|
||||
format_bytes(info.card_bytes).c_str());
|
||||
if (!audio_is_playing()) {
|
||||
wav_list_scan();
|
||||
ui_refresh_lists();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ void setup() {
|
||||
Serial.begin(460800);
|
||||
delay(200);
|
||||
Serial.println();
|
||||
Serial.printf("LVGL %d.%d.%d | CYD Beep + WAV\n", lv_version_major(),
|
||||
Serial.printf("LVGL %d.%d.%d | CYD Pomodoro\n", lv_version_major(),
|
||||
lv_version_minor(), lv_version_patch());
|
||||
Serial.println("BOOT_OK");
|
||||
|
||||
@@ -69,7 +69,7 @@ void setup() {
|
||||
|
||||
refresh_sd_ui();
|
||||
last_refresh_ms = millis();
|
||||
last_btn_ui_ms = millis();
|
||||
last_ui_ms = millis();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
@@ -84,20 +84,15 @@ void loop() {
|
||||
audio_tick();
|
||||
|
||||
const uint32_t now = millis();
|
||||
if (now - last_ui_ms >= UI_TICK_MS) {
|
||||
last_ui_ms = now;
|
||||
ui_tick();
|
||||
}
|
||||
|
||||
if (!playing && now - last_refresh_ms >= REFRESH_MS) {
|
||||
last_refresh_ms = now;
|
||||
refresh_sd_ui();
|
||||
}
|
||||
|
||||
if (now - last_btn_ui_ms >= 200) {
|
||||
last_btn_ui_ms = now;
|
||||
static bool was_playing = false;
|
||||
ui_refresh_audio_buttons();
|
||||
if (playing != was_playing) {
|
||||
was_playing = playing;
|
||||
ui_refresh_lists();
|
||||
}
|
||||
}
|
||||
|
||||
delay(playing ? 2 : 5);
|
||||
}
|
||||
|
||||
143
src/pomodoro.cpp
Normal file
143
src/pomodoro.cpp
Normal file
@@ -0,0 +1,143 @@
|
||||
#include "pomodoro.h"
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
static constexpr uint16_t MIN_MINUTES = 1;
|
||||
static constexpr uint16_t MAX_MINUTES = 99;
|
||||
static constexpr uint16_t DEFAULT_MINUTES = 5;
|
||||
|
||||
static PomoState g_state = POMO_IDLE;
|
||||
static uint32_t g_duration_ms = DEFAULT_MINUTES * 60UL * 1000UL;
|
||||
static uint32_t g_remain_ms = DEFAULT_MINUTES * 60UL * 1000UL;
|
||||
static uint32_t g_last_ms = 0;
|
||||
static bool g_alarm_pending = false;
|
||||
|
||||
static uint32_t clamp_minutes_ms(uint16_t minutes) {
|
||||
if (minutes < MIN_MINUTES) {
|
||||
minutes = MIN_MINUTES;
|
||||
}
|
||||
if (minutes > MAX_MINUTES) {
|
||||
minutes = MAX_MINUTES;
|
||||
}
|
||||
return (uint32_t)minutes * 60UL * 1000UL;
|
||||
}
|
||||
|
||||
void pomo_init() {
|
||||
g_state = POMO_IDLE;
|
||||
g_duration_ms = clamp_minutes_ms(DEFAULT_MINUTES);
|
||||
g_remain_ms = g_duration_ms;
|
||||
g_last_ms = 0;
|
||||
g_alarm_pending = false;
|
||||
}
|
||||
|
||||
PomoState pomo_state() {
|
||||
return g_state;
|
||||
}
|
||||
|
||||
uint32_t pomo_remaining_sec() {
|
||||
return (g_remain_ms + 999UL) / 1000UL;
|
||||
}
|
||||
|
||||
uint16_t pomo_duration_minutes() {
|
||||
return (uint16_t)((g_duration_ms + 59999UL) / 60000UL);
|
||||
}
|
||||
|
||||
const char *pomo_state_name() {
|
||||
switch (g_state) {
|
||||
case POMO_RUNNING:
|
||||
return "RUN";
|
||||
case POMO_PAUSED:
|
||||
return "PAUSE";
|
||||
case POMO_DONE:
|
||||
return "DONE";
|
||||
case POMO_IDLE:
|
||||
default:
|
||||
return "READY";
|
||||
}
|
||||
}
|
||||
|
||||
void pomo_set_minutes(uint16_t minutes) {
|
||||
if (g_state == POMO_RUNNING) {
|
||||
return;
|
||||
}
|
||||
g_duration_ms = clamp_minutes_ms(minutes);
|
||||
g_remain_ms = g_duration_ms;
|
||||
g_state = POMO_IDLE;
|
||||
g_alarm_pending = false;
|
||||
}
|
||||
|
||||
void pomo_start_pause() {
|
||||
if (g_state == POMO_DONE) {
|
||||
g_remain_ms = g_duration_ms;
|
||||
g_state = POMO_IDLE;
|
||||
}
|
||||
if (g_state == POMO_RUNNING) {
|
||||
g_state = POMO_PAUSED;
|
||||
return;
|
||||
}
|
||||
if (g_remain_ms == 0) {
|
||||
g_remain_ms = g_duration_ms;
|
||||
}
|
||||
g_state = POMO_RUNNING;
|
||||
g_last_ms = millis();
|
||||
g_alarm_pending = false;
|
||||
}
|
||||
|
||||
void pomo_reset() {
|
||||
g_remain_ms = g_duration_ms;
|
||||
g_state = POMO_IDLE;
|
||||
g_alarm_pending = false;
|
||||
}
|
||||
|
||||
void pomo_adjust_minutes(int8_t delta) {
|
||||
if (g_state == POMO_RUNNING) {
|
||||
return;
|
||||
}
|
||||
int32_t mins = (int32_t)pomo_duration_minutes() + (int32_t)delta;
|
||||
if (mins < (int32_t)MIN_MINUTES) {
|
||||
mins = MIN_MINUTES;
|
||||
}
|
||||
if (mins > (int32_t)MAX_MINUTES) {
|
||||
mins = MAX_MINUTES;
|
||||
}
|
||||
// When paused mid-timer, adjust remaining by same delta minutes from current remain
|
||||
if (g_state == POMO_PAUSED) {
|
||||
int32_t sec = (int32_t)(g_remain_ms / 1000UL) + (int32_t)delta * 60;
|
||||
if (sec < 60) {
|
||||
sec = 60;
|
||||
}
|
||||
if (sec > (int32_t)MAX_MINUTES * 60) {
|
||||
sec = (int32_t)MAX_MINUTES * 60;
|
||||
}
|
||||
g_remain_ms = (uint32_t)sec * 1000UL;
|
||||
g_duration_ms = g_remain_ms;
|
||||
g_alarm_pending = false;
|
||||
return;
|
||||
}
|
||||
pomo_set_minutes((uint16_t)mins);
|
||||
}
|
||||
|
||||
void pomo_tick() {
|
||||
if (g_state != POMO_RUNNING) {
|
||||
return;
|
||||
}
|
||||
const uint32_t now = millis();
|
||||
const uint32_t elapsed = now - g_last_ms;
|
||||
g_last_ms = now;
|
||||
|
||||
if (elapsed >= g_remain_ms) {
|
||||
g_remain_ms = 0;
|
||||
g_state = POMO_DONE;
|
||||
g_alarm_pending = true;
|
||||
return;
|
||||
}
|
||||
g_remain_ms -= elapsed;
|
||||
}
|
||||
|
||||
bool pomo_consume_alarm() {
|
||||
if (!g_alarm_pending) {
|
||||
return false;
|
||||
}
|
||||
g_alarm_pending = false;
|
||||
return true;
|
||||
}
|
||||
191
src/touch.cpp
191
src/touch.cpp
@@ -3,10 +3,13 @@
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#if defined(CYD_BOARD_3248S035)
|
||||
#include <Wire.h>
|
||||
#endif
|
||||
|
||||
/**
|
||||
* SoftSPI XPT2046
|
||||
* BL↔TR 是矩形主对角线镜像(XY 对调),不是 180°(180° 会搞成 TL↔BR)。
|
||||
* 先按 rot2 得到旧 UI,再做对角修正,反变换喂给 LVGL 270。
|
||||
* 2.8": dedicated SoftSPI + BL/TR diagonal fix.
|
||||
* 3.5C (3248S035C): capacitive GT911 on I2C SDA33/SCL32/INT21/RST25.
|
||||
*/
|
||||
|
||||
int16_t g_dbg_phys_x = -1;
|
||||
@@ -15,6 +18,173 @@ int16_t g_dbg_logic_x = -1;
|
||||
int16_t g_dbg_logic_y = -1;
|
||||
bool g_dbg_touch_down = false;
|
||||
|
||||
static int16_t clampi(int32_t v, int32_t lo, int32_t hi) {
|
||||
if (v < lo) {
|
||||
return (int16_t)lo;
|
||||
}
|
||||
if (v > hi) {
|
||||
return (int16_t)hi;
|
||||
}
|
||||
return (int16_t)v;
|
||||
}
|
||||
|
||||
#if defined(CYD_BOARD_3248S035)
|
||||
|
||||
/* Minimal GT911 — no config rewrite (TAMC setResolution can hang Wire). */
|
||||
static uint8_t s_gt_addr = 0x5D;
|
||||
static constexpr uint16_t GT_POINT_INFO = 0x814E;
|
||||
static constexpr uint16_t GT_POINT_1 = 0x814F;
|
||||
|
||||
static bool gt_write8(uint16_t reg, uint8_t val) {
|
||||
Wire.beginTransmission(s_gt_addr);
|
||||
Wire.write((uint8_t)(reg >> 8));
|
||||
Wire.write((uint8_t)(reg & 0xFF));
|
||||
Wire.write(val);
|
||||
return Wire.endTransmission() == 0;
|
||||
}
|
||||
|
||||
static bool gt_read(uint16_t reg, uint8_t *buf, uint8_t len) {
|
||||
Wire.beginTransmission(s_gt_addr);
|
||||
Wire.write((uint8_t)(reg >> 8));
|
||||
Wire.write((uint8_t)(reg & 0xFF));
|
||||
if (Wire.endTransmission(false) != 0) {
|
||||
return false;
|
||||
}
|
||||
const uint8_t n = Wire.requestFrom(s_gt_addr, len);
|
||||
if (n != len) {
|
||||
return false;
|
||||
}
|
||||
for (uint8_t i = 0; i < len; i++) {
|
||||
buf[i] = Wire.read();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool gt_probe(uint8_t addr) {
|
||||
Wire.beginTransmission(addr);
|
||||
return Wire.endTransmission() == 0;
|
||||
}
|
||||
|
||||
/* TAMC-compatible address select: INT low => 0x5D, INT high => 0x14 */
|
||||
static void gt_hw_reset(bool addr_0x14) {
|
||||
pinMode(CYD_GT911_INT, OUTPUT);
|
||||
pinMode(CYD_GT911_RST, OUTPUT);
|
||||
digitalWrite(CYD_GT911_INT, LOW);
|
||||
digitalWrite(CYD_GT911_RST, LOW);
|
||||
delay(10);
|
||||
digitalWrite(CYD_GT911_INT, addr_0x14 ? HIGH : LOW);
|
||||
delay(1);
|
||||
digitalWrite(CYD_GT911_RST, HIGH);
|
||||
delay(5);
|
||||
digitalWrite(CYD_GT911_INT, LOW);
|
||||
delay(50);
|
||||
/* Backlight is GPIO27 — leave INT as input for polling / optional IRQ. */
|
||||
pinMode(CYD_GT911_INT, INPUT);
|
||||
delay(50);
|
||||
}
|
||||
|
||||
static bool gt_find_addr() {
|
||||
for (uint8_t a : {uint8_t(0x5D), uint8_t(0x14)}) {
|
||||
if (gt_probe(a)) {
|
||||
s_gt_addr = a;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void touch_init() {
|
||||
Serial.println("Touch: GT911 I2C capacitive (3.5C) minimal");
|
||||
Wire.begin(CYD_GT911_SDA, CYD_GT911_SCL);
|
||||
Wire.setClock(100000);
|
||||
#if defined(WIRE_HAS_TIMEOUT) || defined(ESP32)
|
||||
Wire.setTimeOut(50);
|
||||
#endif
|
||||
|
||||
if (!gt_find_addr()) {
|
||||
Serial.println("GT911 not found — hw reset 0x5D");
|
||||
gt_hw_reset(false);
|
||||
if (!gt_find_addr()) {
|
||||
Serial.println("GT911 not found — hw reset 0x14");
|
||||
gt_hw_reset(true);
|
||||
gt_find_addr();
|
||||
}
|
||||
}
|
||||
|
||||
Serial.printf("GT911 probe 0x5D=%s 0x14=%s using=0x%02X\n",
|
||||
gt_probe(0x5D) ? "OK" : "no", gt_probe(0x14) ? "OK" : "no",
|
||||
s_gt_addr);
|
||||
Serial.printf("GT911 SDA=%d SCL=%d INT=%d RST=%d\n", CYD_GT911_SDA,
|
||||
CYD_GT911_SCL, CYD_GT911_INT, CYD_GT911_RST);
|
||||
}
|
||||
|
||||
void touch_read_cb(lv_indev_t * /*indev*/, lv_indev_data_t *data) {
|
||||
uint8_t info = 0;
|
||||
static uint32_t last_log = 0;
|
||||
const bool do_log = (millis() - last_log > 200);
|
||||
|
||||
if (!gt_read(GT_POINT_INFO, &info, 1)) {
|
||||
data->state = LV_INDEV_STATE_RELEASED;
|
||||
g_dbg_touch_down = false;
|
||||
if (do_log) {
|
||||
last_log = millis();
|
||||
Serial.println("TouchProbe GT911 I2C err");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const uint8_t touches = info & 0x0F;
|
||||
const bool buffered = (info & 0x80) != 0;
|
||||
|
||||
if (!buffered || touches == 0) {
|
||||
if (buffered) {
|
||||
gt_write8(GT_POINT_INFO, 0);
|
||||
}
|
||||
data->state = LV_INDEV_STATE_RELEASED;
|
||||
g_dbg_touch_down = false;
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t pt[7];
|
||||
if (!gt_read(GT_POINT_1, pt, 7)) {
|
||||
gt_write8(GT_POINT_INFO, 0);
|
||||
data->state = LV_INDEV_STATE_RELEASED;
|
||||
g_dbg_touch_down = false;
|
||||
return;
|
||||
}
|
||||
gt_write8(GT_POINT_INFO, 0);
|
||||
|
||||
const int16_t raw_x = (int16_t)(pt[1] | (pt[2] << 8));
|
||||
const int16_t raw_y = (int16_t)(pt[3] | (pt[4] << 8));
|
||||
|
||||
/* Native 320x480 → LVGL 270. Mirror long axis so L/R matches UI
|
||||
* (else preset 3 hits 10). Resulting UI ≈ (H-1-raw_y, raw_x). */
|
||||
const int16_t nx = clampi(raw_x, 0, SCREEN_WIDTH - 1);
|
||||
const int16_t ny = clampi(raw_y, 0, SCREEN_HEIGHT - 1);
|
||||
const int16_t feed_x = (int16_t)((SCREEN_WIDTH - 1) - nx);
|
||||
const int16_t feed_y = (int16_t)((SCREEN_HEIGHT - 1) - ny);
|
||||
const int16_t ui_x = (int16_t)((SCREEN_HEIGHT - 1) - ny);
|
||||
const int16_t ui_y = nx;
|
||||
|
||||
g_dbg_phys_x = raw_x;
|
||||
g_dbg_phys_y = raw_y;
|
||||
g_dbg_logic_x = ui_x;
|
||||
g_dbg_logic_y = ui_y;
|
||||
g_dbg_touch_down = true;
|
||||
|
||||
if (do_log) {
|
||||
last_log = millis();
|
||||
Serial.printf("Touch HIT GT911 raw(%d,%d) ~ui(%d,%d)\n", raw_x, raw_y,
|
||||
ui_x, ui_y);
|
||||
}
|
||||
|
||||
data->point.x = feed_x;
|
||||
data->point.y = feed_y;
|
||||
data->state = LV_INDEV_STATE_PRESSED;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static void soft_write(uint8_t data) {
|
||||
for (int i = 7; i >= 0; --i) {
|
||||
digitalWrite(CYD_TOUCH_MOSI, (data >> i) & 1);
|
||||
@@ -49,16 +219,6 @@ static uint16_t xpt_read(uint8_t cmd) {
|
||||
return v;
|
||||
}
|
||||
|
||||
static int16_t clampi(int32_t v, int32_t lo, int32_t hi) {
|
||||
if (v < lo) {
|
||||
return (int16_t)lo;
|
||||
}
|
||||
if (v > hi) {
|
||||
return (int16_t)hi;
|
||||
}
|
||||
return (int16_t)v;
|
||||
}
|
||||
|
||||
void touch_init() {
|
||||
pinMode(CYD_TOUCH_CS, OUTPUT);
|
||||
pinMode(CYD_TOUCH_CLK, OUTPUT);
|
||||
@@ -94,8 +254,6 @@ void touch_read_cb(lv_indev_t * /*indev*/, lv_indev_data_t *data) {
|
||||
x = clampi(x, 0, SCREEN_WIDTH - 1);
|
||||
y = clampi(y, 0, SCREEN_HEIGHT - 1);
|
||||
|
||||
// 旧 UI(LVGL270):(y, 239-x) — 该空间里 BL↔TR,做主对角线镜像
|
||||
// (ux,uy) -> (uy*(319/239), ux*(239/319))
|
||||
const int32_t old_ux = y;
|
||||
const int32_t old_uy = (SCREEN_WIDTH - 1) - x;
|
||||
int16_t ui_x = clampi((old_uy * (UI_HOR_RES - 1)) / (UI_VER_RES - 1), 0,
|
||||
@@ -103,7 +261,6 @@ void touch_read_cb(lv_indev_t * /*indev*/, lv_indev_data_t *data) {
|
||||
int16_t ui_y = clampi((old_ux * (UI_VER_RES - 1)) / (UI_HOR_RES - 1), 0,
|
||||
UI_VER_RES - 1);
|
||||
|
||||
// 反变换喂 LVGL,使最终落在 (ui_x, ui_y)
|
||||
const int16_t feed_x = (int16_t)((SCREEN_WIDTH - 1) - ui_y);
|
||||
const int16_t feed_y = ui_x;
|
||||
|
||||
@@ -123,3 +280,5 @@ void touch_read_cb(lv_indev_t * /*indev*/, lv_indev_data_t *data) {
|
||||
data->point.y = feed_y;
|
||||
data->state = LV_INDEV_STATE_PRESSED;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
627
src/ui.cpp
627
src/ui.cpp
@@ -1,10 +1,12 @@
|
||||
#include "ui.h"
|
||||
#include "audio_test.h"
|
||||
#include "pins.h"
|
||||
#include "wav_list.h"
|
||||
#include "pomodoro.h"
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <lvgl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static constexpr uint32_t COL_BG = 0x0A1016;
|
||||
@@ -18,21 +20,35 @@ static constexpr uint32_t COL_BTN_OFF = 0x243140;
|
||||
static constexpr uint32_t COL_BTN_ON = 0x1B5E4A;
|
||||
static constexpr uint32_t COL_BTN_PLAY = 0x1A4A6E;
|
||||
static constexpr uint32_t COL_BTN_STOP = 0x8B2E2E;
|
||||
static constexpr uint32_t COL_ROW = 0x15202C;
|
||||
static constexpr uint32_t COL_ROW_SEL = 0x1E3A4A;
|
||||
static constexpr uint32_t COL_MODE = 0x15202C;
|
||||
static constexpr uint32_t COL_MODE_ON = 0x1E3A4A;
|
||||
static constexpr uint32_t COL_TOMATO = 0xE85D4C;
|
||||
|
||||
static constexpr uint16_t PRESETS[] = {1, 3, 5, 10};
|
||||
static constexpr int PRESET_N = 4;
|
||||
|
||||
static lv_obj_t *lbl_chip;
|
||||
static lv_obj_t *lbl_audio;
|
||||
static lv_obj_t *lbl_timer;
|
||||
static lv_obj_t *lbl_status;
|
||||
static lv_obj_t *lbl_sound;
|
||||
static lv_obj_t *lbl_vol;
|
||||
static lv_obj_t *lbl_audio;
|
||||
static lv_obj_t *slider_vol;
|
||||
static lv_obj_t *tabview;
|
||||
static lv_obj_t *list_beep;
|
||||
static lv_obj_t *list_wav;
|
||||
static lv_obj_t *btn_speaker;
|
||||
static lv_obj_t *btn_play;
|
||||
static lv_obj_t *btn_start;
|
||||
static lv_obj_t *btn_reset;
|
||||
static lv_obj_t *lbl_speaker;
|
||||
static lv_obj_t *lbl_play;
|
||||
static uint8_t g_tab = 0;
|
||||
static lv_obj_t *lbl_start;
|
||||
static lv_obj_t *btn_preset[PRESET_N];
|
||||
static lv_obj_t *btn_custom;
|
||||
static lv_obj_t *panel_custom;
|
||||
static lv_obj_t *lbl_custom_val;
|
||||
|
||||
static char g_timer_text[8] = "05:00";
|
||||
static char g_status_text[40] = "5m - READY";
|
||||
static char g_sound_text[28] = "Alarm";
|
||||
static char g_custom_buf[4] = "";
|
||||
static char g_custom_disp[16] = "min: _";
|
||||
|
||||
static void style_btn(lv_obj_t *btn, uint32_t bg) {
|
||||
lv_obj_set_style_radius(btn, 10, 0);
|
||||
@@ -52,18 +68,94 @@ static void sync_vol_label() {
|
||||
lv_label_set_text(lbl_vol, buf);
|
||||
}
|
||||
|
||||
static void style_row(lv_obj_t *row, bool selected, bool playing) {
|
||||
uint32_t bg = selected ? COL_ROW_SEL : COL_ROW;
|
||||
if (playing) {
|
||||
bg = COL_BTN_PLAY;
|
||||
static void refresh_preset_highlight() {
|
||||
const uint16_t mins = pomo_duration_minutes();
|
||||
bool matched = false;
|
||||
for (int i = 0; i < PRESET_N; i++) {
|
||||
if (!btn_preset[i]) {
|
||||
continue;
|
||||
}
|
||||
const bool match = mins == PRESETS[i];
|
||||
if (match) {
|
||||
matched = true;
|
||||
}
|
||||
style_btn(btn_preset[i], match ? COL_MODE_ON : COL_MODE);
|
||||
}
|
||||
if (btn_custom) {
|
||||
style_btn(btn_custom, matched ? COL_MODE : COL_MODE_ON);
|
||||
}
|
||||
}
|
||||
|
||||
static void refresh_timer_labels() {
|
||||
const uint32_t sec = pomo_remaining_sec();
|
||||
const uint32_t mm = sec / 60U;
|
||||
const uint32_t ss = sec % 60U;
|
||||
snprintf(g_timer_text, sizeof(g_timer_text), "%02u:%02u", (unsigned)mm,
|
||||
(unsigned)ss);
|
||||
if (lbl_timer) {
|
||||
lv_label_set_text(lbl_timer, g_timer_text);
|
||||
const uint32_t color = (pomo_state() == POMO_DONE) ? COL_TOMATO : COL_ACCENT;
|
||||
lv_obj_set_style_text_color(lbl_timer, lv_color_hex(color), 0);
|
||||
}
|
||||
|
||||
snprintf(g_status_text, sizeof(g_status_text), "%um - %s",
|
||||
(unsigned)pomo_duration_minutes(), pomo_state_name());
|
||||
if (lbl_status) {
|
||||
lv_label_set_text(lbl_status, g_status_text);
|
||||
}
|
||||
|
||||
if (lbl_start) {
|
||||
if (pomo_state() == POMO_RUNNING) {
|
||||
lv_label_set_text(lbl_start, "PAUSE");
|
||||
} else if (pomo_state() == POMO_DONE) {
|
||||
lv_label_set_text(lbl_start, "AGAIN");
|
||||
} else {
|
||||
lv_label_set_text(lbl_start, "START");
|
||||
}
|
||||
}
|
||||
if (btn_start) {
|
||||
style_btn(btn_start,
|
||||
pomo_state() == POMO_RUNNING ? COL_BTN_STOP : COL_BTN_PLAY);
|
||||
}
|
||||
refresh_preset_highlight();
|
||||
}
|
||||
|
||||
static void refresh_sound_label() {
|
||||
const int8_t sel = sound_selected();
|
||||
if (sel < 0) {
|
||||
snprintf(g_sound_text, sizeof(g_sound_text), "-");
|
||||
} else {
|
||||
snprintf(g_sound_text, sizeof(g_sound_text), "%s", sound_name((uint8_t)sel));
|
||||
}
|
||||
if (lbl_sound) {
|
||||
lv_label_set_text(lbl_sound, g_sound_text);
|
||||
}
|
||||
}
|
||||
|
||||
static void sync_custom_label() {
|
||||
if (g_custom_buf[0] == '\0') {
|
||||
snprintf(g_custom_disp, sizeof(g_custom_disp), "min: _");
|
||||
} else {
|
||||
snprintf(g_custom_disp, sizeof(g_custom_disp), "min: %s", g_custom_buf);
|
||||
}
|
||||
if (lbl_custom_val) {
|
||||
lv_label_set_text(lbl_custom_val, g_custom_disp);
|
||||
}
|
||||
}
|
||||
|
||||
static void custom_hide() {
|
||||
if (panel_custom) {
|
||||
lv_obj_add_flag(panel_custom, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
}
|
||||
|
||||
static void custom_show() {
|
||||
g_custom_buf[0] = '\0';
|
||||
sync_custom_label();
|
||||
if (panel_custom) {
|
||||
lv_obj_clear_flag(panel_custom, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_move_foreground(panel_custom);
|
||||
}
|
||||
lv_obj_set_style_bg_color(row, lv_color_hex(bg), 0);
|
||||
lv_obj_set_style_bg_opa(row, LV_OPA_COVER, 0);
|
||||
lv_obj_set_style_radius(row, 8, 0);
|
||||
lv_obj_set_style_border_width(row, 0, 0);
|
||||
lv_obj_set_style_shadow_width(row, 0, 0);
|
||||
lv_obj_set_style_pad_left(row, 10, 0);
|
||||
lv_obj_set_style_pad_right(row, 10, 0);
|
||||
}
|
||||
|
||||
static void on_speaker_clicked(lv_event_t *e) {
|
||||
@@ -72,20 +164,130 @@ static void on_speaker_clicked(lv_event_t *e) {
|
||||
ui_refresh_audio_buttons();
|
||||
}
|
||||
|
||||
static void on_play_clicked(lv_event_t *e) {
|
||||
static void on_start_clicked(lv_event_t *e) {
|
||||
LV_UNUSED(e);
|
||||
if (audio_is_playing()) {
|
||||
audio_stop();
|
||||
} else if (g_tab == 1) {
|
||||
const int8_t sel = wav_list_selected();
|
||||
if (sel >= 0) {
|
||||
audio_play_wav_index((uint8_t)sel);
|
||||
}
|
||||
} else {
|
||||
audio_play_selected();
|
||||
}
|
||||
pomo_start_pause();
|
||||
refresh_timer_labels();
|
||||
}
|
||||
|
||||
static void on_reset_clicked(lv_event_t *e) {
|
||||
LV_UNUSED(e);
|
||||
pomo_reset();
|
||||
audio_stop();
|
||||
refresh_timer_labels();
|
||||
ui_refresh_audio_buttons();
|
||||
}
|
||||
|
||||
static void on_preset_clicked(lv_event_t *e) {
|
||||
const int idx = (int)(intptr_t)lv_event_get_user_data(e);
|
||||
if (idx < 0 || idx >= PRESET_N) {
|
||||
return;
|
||||
}
|
||||
pomo_set_minutes(PRESETS[idx]);
|
||||
refresh_timer_labels();
|
||||
}
|
||||
|
||||
static void on_custom_open(lv_event_t *e) {
|
||||
LV_UNUSED(e);
|
||||
if (pomo_state() == POMO_RUNNING) {
|
||||
return;
|
||||
}
|
||||
custom_show();
|
||||
}
|
||||
|
||||
static void on_custom_cancel(lv_event_t *e) {
|
||||
LV_UNUSED(e);
|
||||
custom_hide();
|
||||
}
|
||||
|
||||
static void on_custom_ok(lv_event_t *e) {
|
||||
LV_UNUSED(e);
|
||||
if (g_custom_buf[0] == '\0') {
|
||||
custom_hide();
|
||||
return;
|
||||
}
|
||||
int mins = atoi(g_custom_buf);
|
||||
if (mins < 1) {
|
||||
mins = 1;
|
||||
}
|
||||
if (mins > 99) {
|
||||
mins = 99;
|
||||
}
|
||||
pomo_set_minutes((uint16_t)mins);
|
||||
custom_hide();
|
||||
refresh_timer_labels();
|
||||
}
|
||||
|
||||
static void on_custom_digit(lv_event_t *e) {
|
||||
const int d = (int)(intptr_t)lv_event_get_user_data(e);
|
||||
const size_t len = strlen(g_custom_buf);
|
||||
if (d == -1) { // clear
|
||||
g_custom_buf[0] = '\0';
|
||||
sync_custom_label();
|
||||
return;
|
||||
}
|
||||
if (len >= 2) {
|
||||
return;
|
||||
}
|
||||
if (len == 0 && d == 0) {
|
||||
return; // no leading zero
|
||||
}
|
||||
g_custom_buf[len] = (char)('0' + d);
|
||||
g_custom_buf[len + 1] = '\0';
|
||||
sync_custom_label();
|
||||
}
|
||||
|
||||
static void on_minus_clicked(lv_event_t *e) {
|
||||
LV_UNUSED(e);
|
||||
pomo_adjust_minutes(-1);
|
||||
refresh_timer_labels();
|
||||
}
|
||||
|
||||
static void on_plus_clicked(lv_event_t *e) {
|
||||
LV_UNUSED(e);
|
||||
pomo_adjust_minutes(1);
|
||||
refresh_timer_labels();
|
||||
}
|
||||
|
||||
static void on_sound_prev(lv_event_t *e) {
|
||||
LV_UNUSED(e);
|
||||
const uint8_t n = sound_count();
|
||||
if (n == 0) {
|
||||
return;
|
||||
}
|
||||
int8_t sel = sound_selected();
|
||||
if (sel <= 0) {
|
||||
sel = (int8_t)(n - 1);
|
||||
} else {
|
||||
sel--;
|
||||
}
|
||||
sound_set_selected(sel);
|
||||
refresh_sound_label();
|
||||
}
|
||||
|
||||
static void on_sound_next(lv_event_t *e) {
|
||||
LV_UNUSED(e);
|
||||
const uint8_t n = sound_count();
|
||||
if (n == 0) {
|
||||
return;
|
||||
}
|
||||
int8_t sel = sound_selected();
|
||||
sel++;
|
||||
if (sel >= (int8_t)n) {
|
||||
sel = 0;
|
||||
}
|
||||
sound_set_selected(sel);
|
||||
refresh_sound_label();
|
||||
}
|
||||
|
||||
static void on_sound_preview(lv_event_t *e) {
|
||||
LV_UNUSED(e);
|
||||
const int8_t sel = sound_selected();
|
||||
if (sel < 0) {
|
||||
return;
|
||||
}
|
||||
audio_play_sound((uint8_t)sel);
|
||||
ui_refresh_audio_buttons();
|
||||
ui_refresh_lists();
|
||||
}
|
||||
|
||||
static void on_vol_changed(lv_event_t *e) {
|
||||
@@ -94,107 +296,83 @@ static void on_vol_changed(lv_event_t *e) {
|
||||
sync_vol_label();
|
||||
}
|
||||
|
||||
static void on_beep_clicked(lv_event_t *e) {
|
||||
const int idx = (int)(intptr_t)lv_event_get_user_data(e);
|
||||
if (idx < 0 || idx >= (int)sound_count()) {
|
||||
return;
|
||||
static lv_obj_t *make_small_btn(lv_obj_t *parent, const char *txt, int x, int y,
|
||||
int w, int h, lv_event_cb_t cb, void *ud) {
|
||||
lv_obj_t *btn = lv_button_create(parent);
|
||||
lv_obj_set_pos(btn, x, y);
|
||||
lv_obj_set_size(btn, w, h);
|
||||
lv_obj_add_flag(btn, LV_OBJ_FLAG_CLICKABLE);
|
||||
if (cb) {
|
||||
lv_obj_add_event_cb(btn, cb, LV_EVENT_CLICKED, ud);
|
||||
}
|
||||
audio_play_sound((uint8_t)idx);
|
||||
ui_refresh_audio_buttons();
|
||||
ui_refresh_lists();
|
||||
style_btn(btn, COL_BTN_OFF);
|
||||
|
||||
lv_obj_t *lbl = lv_label_create(btn);
|
||||
lv_label_set_text(lbl, txt);
|
||||
lv_obj_set_style_text_font(lbl, &lv_font_montserrat_14, 0);
|
||||
lv_obj_set_style_text_color(lbl, lv_color_hex(COL_INK), 0);
|
||||
lv_obj_center(lbl);
|
||||
return btn;
|
||||
}
|
||||
|
||||
static void on_wav_clicked(lv_event_t *e) {
|
||||
const int idx = (int)(intptr_t)lv_event_get_user_data(e);
|
||||
if (idx < 0 || idx >= (int)wav_list_count()) {
|
||||
return;
|
||||
}
|
||||
audio_play_wav_index((uint8_t)idx);
|
||||
ui_refresh_audio_buttons();
|
||||
ui_refresh_lists();
|
||||
}
|
||||
static void build_custom_panel(lv_obj_t *scr) {
|
||||
panel_custom = lv_obj_create(scr);
|
||||
lv_obj_set_size(panel_custom, UI_HOR_RES, UI_VER_RES);
|
||||
lv_obj_set_pos(panel_custom, UX(0), UY(0));
|
||||
lv_obj_set_style_bg_color(panel_custom, lv_color_hex(COL_BG), 0);
|
||||
lv_obj_set_style_bg_opa(panel_custom, LV_OPA_COVER, 0);
|
||||
lv_obj_set_style_border_width(panel_custom, 0, 0);
|
||||
lv_obj_set_style_pad_all(panel_custom, 0, 0);
|
||||
lv_obj_set_style_radius(panel_custom, 0, 0);
|
||||
lv_obj_clear_flag(panel_custom, LV_OBJ_FLAG_SCROLLABLE);
|
||||
lv_obj_add_flag(panel_custom, LV_OBJ_FLAG_HIDDEN);
|
||||
|
||||
static void on_tab_changed(lv_event_t *e) {
|
||||
lv_obj_t *tv = (lv_obj_t *)lv_event_get_target(e);
|
||||
g_tab = (uint8_t)lv_tabview_get_tab_active(tv);
|
||||
ui_refresh_lists();
|
||||
}
|
||||
lv_obj_t *title = lv_label_create(panel_custom);
|
||||
lv_label_set_text(title, "Custom minutes");
|
||||
lv_obj_set_style_text_font(title, &lv_font_montserrat_16, 0);
|
||||
lv_obj_set_style_text_color(title, lv_color_hex(COL_ACCENT), 0);
|
||||
lv_obj_align(title, LV_ALIGN_TOP_MID, UX(0), UY(8));
|
||||
|
||||
static void fill_list_beep() {
|
||||
if (!list_beep) {
|
||||
return;
|
||||
}
|
||||
lv_obj_clean(list_beep);
|
||||
const uint8_t n = sound_count();
|
||||
const int8_t sel = sound_selected();
|
||||
const bool playing = audio_is_playing() && g_tab == 0;
|
||||
lbl_custom_val = lv_label_create(panel_custom);
|
||||
lv_label_set_text(lbl_custom_val, "min: _");
|
||||
lv_obj_set_style_text_font(lbl_custom_val, &lv_font_montserrat_28, 0);
|
||||
lv_obj_set_style_text_color(lbl_custom_val, lv_color_hex(COL_INK), 0);
|
||||
lv_obj_align(lbl_custom_val, LV_ALIGN_TOP_MID, UX(0), UY(36));
|
||||
|
||||
for (uint8_t i = 0; i < n; i++) {
|
||||
lv_obj_t *row = lv_button_create(list_beep);
|
||||
lv_obj_set_width(row, lv_pct(100));
|
||||
lv_obj_set_height(row, 32);
|
||||
lv_obj_add_flag(row, LV_OBJ_FLAG_CLICKABLE);
|
||||
lv_obj_add_event_cb(row, on_beep_clicked, LV_EVENT_CLICKED, (void *)(intptr_t)i);
|
||||
style_row(row, sel == (int8_t)i, playing && sel == (int8_t)i);
|
||||
|
||||
lv_obj_t *lbl = lv_label_create(row);
|
||||
char line[36];
|
||||
snprintf(line, sizeof(line), "%u %s", (unsigned)(i + 1), sound_name(i));
|
||||
lv_label_set_text(lbl, line);
|
||||
lv_obj_set_style_text_font(lbl, &lv_font_montserrat_14, 0);
|
||||
lv_obj_set_style_text_color(lbl, lv_color_hex(COL_INK), 0);
|
||||
lv_obj_align(lbl, LV_ALIGN_LEFT_MID, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void fill_list_wav() {
|
||||
if (!list_wav) {
|
||||
return;
|
||||
}
|
||||
lv_obj_clean(list_wav);
|
||||
const uint8_t n = wav_list_count();
|
||||
if (n == 0) {
|
||||
lv_obj_t *empty = lv_label_create(list_wav);
|
||||
lv_label_set_text(empty, "No .wav on TF\nPut in / or /wav");
|
||||
lv_obj_set_style_text_font(empty, &lv_font_montserrat_14, 0);
|
||||
lv_obj_set_style_text_color(empty, lv_color_hex(COL_MUTED), 0);
|
||||
return;
|
||||
}
|
||||
|
||||
const int8_t sel = wav_list_selected();
|
||||
const bool playing = audio_is_playing() && g_tab == 1;
|
||||
|
||||
for (uint8_t i = 0; i < n; i++) {
|
||||
const WavTrack *t = wav_list_get(i);
|
||||
if (!t) {
|
||||
continue;
|
||||
// Numpad 3x4
|
||||
static const char *keys[12] = {"1", "2", "3", "4", "5", "6",
|
||||
"7", "8", "9", "C", "0", "OK"};
|
||||
const int kw = 90;
|
||||
const int kh = 32;
|
||||
const int ox = 16;
|
||||
const int oy = 72;
|
||||
const int gap = 6;
|
||||
for (int i = 0; i < 12; i++) {
|
||||
const int r = i / 3;
|
||||
const int c = i % 3;
|
||||
const int x = ox + c * (kw + gap);
|
||||
const int y = oy + r * (kh + gap);
|
||||
lv_event_cb_t cb = on_custom_digit;
|
||||
void *ud = nullptr;
|
||||
if (i <= 8) {
|
||||
ud = (void *)(intptr_t)(i + 1); // 1..9
|
||||
} else if (i == 9) {
|
||||
ud = (void *)(intptr_t)-1; // clear
|
||||
} else if (i == 10) {
|
||||
ud = (void *)(intptr_t)0; // 0
|
||||
} else {
|
||||
cb = on_custom_ok;
|
||||
}
|
||||
lv_obj_t *b = make_small_btn(panel_custom, keys[i], x, y, kw, kh, cb, ud);
|
||||
if (i == 11) {
|
||||
style_btn(b, COL_BTN_PLAY);
|
||||
} else if (i == 9) {
|
||||
style_btn(b, COL_BTN_STOP);
|
||||
}
|
||||
lv_obj_t *row = lv_button_create(list_wav);
|
||||
lv_obj_set_width(row, lv_pct(100));
|
||||
lv_obj_set_height(row, 32);
|
||||
lv_obj_add_flag(row, LV_OBJ_FLAG_CLICKABLE);
|
||||
lv_obj_add_event_cb(row, on_wav_clicked, LV_EVENT_CLICKED, (void *)(intptr_t)i);
|
||||
style_row(row, sel == (int8_t)i, playing && sel == (int8_t)i);
|
||||
|
||||
lv_obj_t *lbl = lv_label_create(row);
|
||||
char line[40];
|
||||
snprintf(line, sizeof(line), "%u %s", (unsigned)(i + 1), t->name);
|
||||
lv_label_set_text(lbl, line);
|
||||
lv_obj_set_style_text_font(lbl, &lv_font_montserrat_14, 0);
|
||||
lv_obj_set_style_text_color(lbl, lv_color_hex(COL_INK), 0);
|
||||
lv_label_set_long_mode(lbl, LV_LABEL_LONG_SCROLL_CIRCULAR);
|
||||
lv_obj_set_width(lbl, lv_pct(100));
|
||||
lv_obj_align(lbl, LV_ALIGN_LEFT_MID, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void ui_refresh_lists() {
|
||||
fill_list_beep();
|
||||
fill_list_wav();
|
||||
}
|
||||
|
||||
uint8_t ui_active_tab() {
|
||||
return g_tab;
|
||||
make_small_btn(panel_custom, "Cancel", 16, UI_VER_RES - 34, UI_HOR_RES - 32,
|
||||
28, on_custom_cancel, nullptr);
|
||||
}
|
||||
|
||||
void ui_refresh_audio_buttons() {
|
||||
@@ -204,60 +382,101 @@ void ui_refresh_audio_buttons() {
|
||||
if (btn_speaker) {
|
||||
style_btn(btn_speaker, audio_is_enabled() ? COL_BTN_ON : COL_BTN_OFF);
|
||||
}
|
||||
if (lbl_play) {
|
||||
lv_label_set_text(lbl_play, audio_is_playing() ? "STOP" : "PLAY");
|
||||
}
|
||||
if (btn_play) {
|
||||
style_btn(btn_play, audio_is_playing() ? COL_BTN_STOP : COL_BTN_PLAY);
|
||||
}
|
||||
if (lbl_audio) {
|
||||
lv_label_set_text(lbl_audio, audio_last_status());
|
||||
}
|
||||
if (slider_vol && (int32_t)audio_get_volume() != lv_slider_get_value(slider_vol)) {
|
||||
if (slider_vol &&
|
||||
(int32_t)audio_get_volume() != lv_slider_get_value(slider_vol)) {
|
||||
lv_slider_set_value(slider_vol, audio_get_volume(), LV_ANIM_OFF);
|
||||
}
|
||||
sync_vol_label();
|
||||
}
|
||||
|
||||
static void style_scroll_list(lv_obj_t *list) {
|
||||
lv_obj_set_size(list, lv_pct(100), lv_pct(100));
|
||||
lv_obj_set_style_bg_opa(list, LV_OPA_TRANSP, 0);
|
||||
lv_obj_set_style_border_width(list, 0, 0);
|
||||
lv_obj_set_style_pad_all(list, 4, 0);
|
||||
lv_obj_set_style_pad_row(list, 4, 0);
|
||||
lv_obj_set_flex_flow(list, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_scroll_dir(list, LV_DIR_VER);
|
||||
lv_obj_add_flag(list, LV_OBJ_FLAG_SCROLLABLE);
|
||||
lv_obj_clear_flag(list, LV_OBJ_FLAG_CLICKABLE);
|
||||
}
|
||||
|
||||
void ui_create() {
|
||||
pomo_init();
|
||||
sound_set_selected(11); // Alarm
|
||||
|
||||
lv_obj_t *scr = lv_screen_active();
|
||||
lv_obj_set_style_bg_color(scr, lv_color_hex(COL_BG), 0);
|
||||
lv_obj_set_style_bg_opa(scr, LV_OPA_COVER, 0);
|
||||
lv_obj_clear_flag(scr, LV_OBJ_FLAG_SCROLLABLE);
|
||||
|
||||
lv_obj_t *lbl_title = lv_label_create(scr);
|
||||
lv_label_set_text(lbl_title, "CYD");
|
||||
lv_label_set_text(lbl_title, "POMO");
|
||||
lv_obj_set_style_text_font(lbl_title, &lv_font_montserrat_20, 0);
|
||||
lv_obj_set_style_text_color(lbl_title, lv_color_hex(COL_ACCENT), 0);
|
||||
lv_obj_set_pos(lbl_title, 12, 4);
|
||||
lv_obj_set_style_text_color(lbl_title, lv_color_hex(COL_TOMATO), 0);
|
||||
lv_obj_set_pos(lbl_title, UX(12), UY(4));
|
||||
|
||||
lbl_chip = lv_label_create(scr);
|
||||
lv_label_set_text(lbl_chip, "-");
|
||||
lv_obj_set_style_text_font(lbl_chip, &lv_font_montserrat_14, 0);
|
||||
lv_obj_set_style_text_color(lbl_chip, lv_color_hex(COL_MUTED), 0);
|
||||
lv_obj_align(lbl_chip, LV_ALIGN_TOP_RIGHT, -12, 8);
|
||||
lv_obj_align(lbl_chip, LV_ALIGN_TOP_RIGHT, UX(-12), UY(8));
|
||||
|
||||
make_small_btn(scr, "-", 12, 32, 40, 44, on_minus_clicked, nullptr);
|
||||
|
||||
lbl_timer = lv_label_create(scr);
|
||||
lv_label_set_text(lbl_timer, "05:00");
|
||||
lv_obj_set_style_text_font(lbl_timer, &lv_font_montserrat_28, 0);
|
||||
lv_obj_set_style_text_color(lbl_timer, lv_color_hex(COL_ACCENT), 0);
|
||||
lv_obj_align(lbl_timer, LV_ALIGN_TOP_MID, UX(0), UY(34));
|
||||
|
||||
make_small_btn(scr, "+", UI_HOR_RES - 52, 32, 40, 44, on_plus_clicked,
|
||||
nullptr);
|
||||
|
||||
lbl_status = lv_label_create(scr);
|
||||
lv_label_set_text(lbl_status, "5m - READY");
|
||||
lv_obj_set_style_text_font(lbl_status, &lv_font_montserrat_14, 0);
|
||||
lv_obj_set_style_text_color(lbl_status, lv_color_hex(COL_MUTED), 0);
|
||||
lv_obj_align(lbl_status, LV_ALIGN_TOP_MID, UX(0), UY(74));
|
||||
|
||||
// Presets: 1 3 5 10 + Set
|
||||
const int gap = 6;
|
||||
const int pw = (UI_HOR_RES - 24 - gap * 4) / 5;
|
||||
static const char *ptxt[PRESET_N] = {"1", "3", "5", "10"};
|
||||
for (int i = 0; i < PRESET_N; i++) {
|
||||
btn_preset[i] =
|
||||
make_small_btn(scr, ptxt[i], 12 + i * (pw + gap), 94, pw, 30,
|
||||
on_preset_clicked, (void *)(intptr_t)i);
|
||||
}
|
||||
btn_custom = make_small_btn(scr, "Set", 12 + PRESET_N * (pw + gap), 94, pw,
|
||||
30, on_custom_open, nullptr);
|
||||
|
||||
// Sound
|
||||
lv_obj_t *snd_cap = lv_label_create(scr);
|
||||
lv_label_set_text(snd_cap, "SOUND");
|
||||
lv_obj_set_style_text_font(snd_cap, &lv_font_montserrat_14, 0);
|
||||
lv_obj_set_style_text_color(snd_cap, lv_color_hex(COL_MUTED), 0);
|
||||
lv_obj_set_pos(snd_cap, UX(12), UY(134));
|
||||
|
||||
make_small_btn(scr, "<", 68, 128, 32, 28, on_sound_prev, nullptr);
|
||||
|
||||
lv_obj_t *btn_snd = lv_button_create(scr);
|
||||
lv_obj_set_pos(btn_snd, UX(104), UY(128));
|
||||
lv_obj_set_size(btn_snd, UI_HOR_RES - 104 - 48, 28);
|
||||
lv_obj_add_flag(btn_snd, LV_OBJ_FLAG_CLICKABLE);
|
||||
lv_obj_add_event_cb(btn_snd, on_sound_preview, LV_EVENT_CLICKED, nullptr);
|
||||
style_btn(btn_snd, COL_MODE);
|
||||
|
||||
lbl_sound = lv_label_create(btn_snd);
|
||||
lv_label_set_text(lbl_sound, "Alarm");
|
||||
lv_obj_set_style_text_font(lbl_sound, &lv_font_montserrat_14, 0);
|
||||
lv_obj_set_style_text_color(lbl_sound, lv_color_hex(COL_INK), 0);
|
||||
lv_obj_center(lbl_sound);
|
||||
|
||||
make_small_btn(scr, ">", UI_HOR_RES - 44, 128, 32, 28, on_sound_next,
|
||||
nullptr);
|
||||
|
||||
// Volume
|
||||
lv_obj_t *vol_cap = lv_label_create(scr);
|
||||
lv_label_set_text(vol_cap, "VOL");
|
||||
lv_obj_set_style_text_font(vol_cap, &lv_font_montserrat_14, 0);
|
||||
lv_obj_set_style_text_color(vol_cap, lv_color_hex(COL_MUTED), 0);
|
||||
lv_obj_set_pos(vol_cap, 12, 30);
|
||||
lv_obj_set_pos(vol_cap, UX(12), UY(164));
|
||||
|
||||
slider_vol = lv_slider_create(scr);
|
||||
lv_obj_set_size(slider_vol, UI_HOR_RES - 12 - 56 - 44, 14);
|
||||
lv_obj_set_pos(slider_vol, 52, 30);
|
||||
lv_obj_set_pos(slider_vol, UX(52), UY(164));
|
||||
lv_slider_set_range(slider_vol, 0, 100);
|
||||
lv_slider_set_value(slider_vol, audio_get_volume(), LV_ANIM_OFF);
|
||||
lv_obj_set_style_bg_color(slider_vol, lv_color_hex(COL_LINE), LV_PART_MAIN);
|
||||
@@ -267,97 +486,101 @@ void ui_create() {
|
||||
lv_obj_set_style_bg_opa(slider_vol, LV_OPA_COVER, LV_PART_INDICATOR);
|
||||
lv_obj_set_style_bg_color(slider_vol, lv_color_hex(COL_INK), LV_PART_KNOB);
|
||||
lv_obj_set_style_pad_all(slider_vol, 4, LV_PART_KNOB);
|
||||
lv_obj_add_event_cb(slider_vol, on_vol_changed, LV_EVENT_VALUE_CHANGED, nullptr);
|
||||
lv_obj_add_event_cb(slider_vol, on_vol_changed, LV_EVENT_VALUE_CHANGED,
|
||||
nullptr);
|
||||
|
||||
lbl_vol = lv_label_create(scr);
|
||||
lv_label_set_text(lbl_vol, "40%");
|
||||
lv_obj_set_style_text_font(lbl_vol, &lv_font_montserrat_14, 0);
|
||||
lv_obj_set_style_text_color(lbl_vol, lv_color_hex(COL_INK), 0);
|
||||
lv_obj_align(lbl_vol, LV_ALIGN_TOP_RIGHT, -12, 30);
|
||||
lv_obj_align(lbl_vol, LV_ALIGN_TOP_RIGHT, UX(-12), UY(164));
|
||||
|
||||
lbl_audio = lv_label_create(scr);
|
||||
lv_label_set_text(lbl_audio, "Speaker OFF");
|
||||
lv_obj_set_style_text_font(lbl_audio, &lv_font_montserrat_14, 0);
|
||||
lv_obj_set_style_text_color(lbl_audio, lv_color_hex(COL_MUTED), 0);
|
||||
lv_obj_set_pos(lbl_audio, 12, 48);
|
||||
lv_obj_set_pos(lbl_audio, UX(12), UY(182));
|
||||
|
||||
// Tabs: Beep | WAV
|
||||
tabview = lv_tabview_create(scr);
|
||||
lv_obj_set_pos(tabview, 8, 66);
|
||||
lv_obj_set_size(tabview, UI_HOR_RES - 16, 110);
|
||||
lv_tabview_set_tab_bar_size(tabview, 28);
|
||||
lv_obj_set_style_bg_color(tabview, lv_color_hex(COL_BG), 0);
|
||||
lv_obj_add_event_cb(tabview, on_tab_changed, LV_EVENT_VALUE_CHANGED, nullptr);
|
||||
|
||||
lv_obj_t *tab_beep = lv_tabview_add_tab(tabview, "Beep");
|
||||
lv_obj_t *tab_wav = lv_tabview_add_tab(tabview, "WAV");
|
||||
lv_obj_set_style_bg_color(tab_beep, lv_color_hex(COL_BG), 0);
|
||||
lv_obj_set_style_bg_color(tab_wav, lv_color_hex(COL_BG), 0);
|
||||
lv_obj_set_style_pad_all(tab_beep, 0, 0);
|
||||
lv_obj_set_style_pad_all(tab_wav, 0, 0);
|
||||
|
||||
list_beep = lv_obj_create(tab_beep);
|
||||
style_scroll_list(list_beep);
|
||||
|
||||
list_wav = lv_obj_create(tab_wav);
|
||||
style_scroll_list(list_wav);
|
||||
|
||||
const int btn_y = 184;
|
||||
const int btn_h = 48;
|
||||
const int gap = 10;
|
||||
const int btn_w = (UI_HOR_RES - 24 - gap) / 2;
|
||||
const int btn_y = 200;
|
||||
const int btn_h = 36;
|
||||
const int bgap = 8;
|
||||
const int btn_w = (UI_HOR_RES - 24 - bgap * 2) / 3;
|
||||
|
||||
btn_speaker = lv_button_create(scr);
|
||||
lv_obj_set_pos(btn_speaker, 12, btn_y);
|
||||
lv_obj_set_size(btn_speaker, btn_w, btn_h);
|
||||
lv_obj_add_flag(btn_speaker, LV_OBJ_FLAG_CLICKABLE);
|
||||
lv_obj_add_event_cb(btn_speaker, on_speaker_clicked, LV_EVENT_CLICKED, nullptr);
|
||||
lv_obj_add_event_cb(btn_speaker, on_speaker_clicked, LV_EVENT_CLICKED,
|
||||
nullptr);
|
||||
style_btn(btn_speaker, COL_BTN_OFF);
|
||||
|
||||
lv_obj_t *spk_cap = lv_label_create(btn_speaker);
|
||||
lv_label_set_text(spk_cap, "SPEAKER");
|
||||
lv_label_set_text(spk_cap, "SPK");
|
||||
lv_obj_set_style_text_font(spk_cap, &lv_font_montserrat_14, 0);
|
||||
lv_obj_set_style_text_color(spk_cap, lv_color_hex(COL_MUTED), 0);
|
||||
lv_obj_align(spk_cap, LV_ALIGN_TOP_MID, 0, 4);
|
||||
lv_obj_align(spk_cap, LV_ALIGN_LEFT_MID, UX(8), UY(0));
|
||||
|
||||
lbl_speaker = lv_label_create(btn_speaker);
|
||||
lv_label_set_text(lbl_speaker, "OFF");
|
||||
lv_obj_set_style_text_font(lbl_speaker, &lv_font_montserrat_16, 0);
|
||||
lv_obj_set_style_text_font(lbl_speaker, &lv_font_montserrat_14, 0);
|
||||
lv_obj_set_style_text_color(lbl_speaker, lv_color_hex(COL_INK), 0);
|
||||
lv_obj_align(lbl_speaker, LV_ALIGN_BOTTOM_MID, 0, -6);
|
||||
lv_obj_align(lbl_speaker, LV_ALIGN_RIGHT_MID, UX(-8), UY(0));
|
||||
|
||||
btn_play = lv_button_create(scr);
|
||||
lv_obj_set_pos(btn_play, 12 + btn_w + gap, btn_y);
|
||||
lv_obj_set_size(btn_play, btn_w, btn_h);
|
||||
lv_obj_add_flag(btn_play, LV_OBJ_FLAG_CLICKABLE);
|
||||
lv_obj_add_event_cb(btn_play, on_play_clicked, LV_EVENT_CLICKED, nullptr);
|
||||
style_btn(btn_play, COL_BTN_PLAY);
|
||||
btn_start = lv_button_create(scr);
|
||||
lv_obj_set_pos(btn_start, 12 + btn_w + bgap, btn_y);
|
||||
lv_obj_set_size(btn_start, btn_w, btn_h);
|
||||
lv_obj_add_flag(btn_start, LV_OBJ_FLAG_CLICKABLE);
|
||||
lv_obj_add_event_cb(btn_start, on_start_clicked, LV_EVENT_CLICKED, nullptr);
|
||||
style_btn(btn_start, COL_BTN_PLAY);
|
||||
|
||||
lv_obj_t *play_cap = lv_label_create(btn_play);
|
||||
lv_label_set_text(play_cap, "SOUND");
|
||||
lv_obj_set_style_text_font(play_cap, &lv_font_montserrat_14, 0);
|
||||
lv_obj_set_style_text_color(play_cap, lv_color_hex(COL_MUTED), 0);
|
||||
lv_obj_align(play_cap, LV_ALIGN_TOP_MID, 0, 4);
|
||||
lbl_start = lv_label_create(btn_start);
|
||||
lv_label_set_text(lbl_start, "START");
|
||||
lv_obj_set_style_text_font(lbl_start, &lv_font_montserrat_16, 0);
|
||||
lv_obj_set_style_text_color(lbl_start, lv_color_hex(COL_INK), 0);
|
||||
lv_obj_center(lbl_start);
|
||||
|
||||
lbl_play = lv_label_create(btn_play);
|
||||
lv_label_set_text(lbl_play, "PLAY");
|
||||
lv_obj_set_style_text_font(lbl_play, &lv_font_montserrat_16, 0);
|
||||
lv_obj_set_style_text_color(lbl_play, lv_color_hex(COL_INK), 0);
|
||||
lv_obj_align(lbl_play, LV_ALIGN_BOTTOM_MID, 0, -6);
|
||||
btn_reset = lv_button_create(scr);
|
||||
lv_obj_set_pos(btn_reset, 12 + (btn_w + bgap) * 2, btn_y);
|
||||
lv_obj_set_size(btn_reset, btn_w, btn_h);
|
||||
lv_obj_add_flag(btn_reset, LV_OBJ_FLAG_CLICKABLE);
|
||||
lv_obj_add_event_cb(btn_reset, on_reset_clicked, LV_EVENT_CLICKED, nullptr);
|
||||
style_btn(btn_reset, COL_BTN_OFF);
|
||||
|
||||
lv_obj_t *lbl_rst = lv_label_create(btn_reset);
|
||||
lv_label_set_text(lbl_rst, "RESET");
|
||||
lv_obj_set_style_text_font(lbl_rst, &lv_font_montserrat_16, 0);
|
||||
lv_obj_set_style_text_color(lbl_rst, lv_color_hex(COL_INK), 0);
|
||||
lv_obj_center(lbl_rst);
|
||||
|
||||
build_custom_panel(scr);
|
||||
|
||||
refresh_sound_label();
|
||||
refresh_timer_labels();
|
||||
ui_refresh_audio_buttons();
|
||||
ui_refresh_lists();
|
||||
}
|
||||
|
||||
void ui_set_touch_debug(int16_t /*lx*/, int16_t /*ly*/, bool /*down*/) {}
|
||||
|
||||
void ui_tick() {
|
||||
pomo_tick();
|
||||
if (pomo_consume_alarm()) {
|
||||
const int8_t sel = sound_selected();
|
||||
if (sel >= 0) {
|
||||
audio_play_sound((uint8_t)sel);
|
||||
}
|
||||
}
|
||||
refresh_timer_labels();
|
||||
ui_refresh_audio_buttons();
|
||||
}
|
||||
|
||||
void ui_update(const SdCardInfo &info) {
|
||||
if (!info.mounted || !info.present) {
|
||||
lv_label_set_text(lbl_chip, info.error.length() ? info.error.c_str() : "FAIL");
|
||||
lv_label_set_text(lbl_chip,
|
||||
info.error.length() ? info.error.c_str() : "TF?");
|
||||
lv_obj_set_style_text_color(lbl_chip, lv_color_hex(COL_BAD), 0);
|
||||
} else {
|
||||
char chip[40];
|
||||
snprintf(chip, sizeof(chip), "OK %s W%u", info.type, (unsigned)wav_list_count());
|
||||
snprintf(chip, sizeof(chip), "OK %s", info.type);
|
||||
lv_label_set_text(lbl_chip, chip);
|
||||
lv_obj_set_style_text_color(lbl_chip, lv_color_hex(COL_OK), 0);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user