HiFi player project, first commit.
This commit is contained in:
commit
4126ea13a1
49 files changed
+6333
No files matched your search
@@ -0,0 +1,349 @@
|
||||
/**
|
||||
****************************************************************************************************
|
||||
* @file ui_home.c
|
||||
* @author HiFi Player Project
|
||||
* @version V1.3
|
||||
* @date 2026-01-01
|
||||
* @brief 主界面 —— iPhone 风格图标网格(应用启动器)
|
||||
*
|
||||
* ┌──────────────────────────────────┐
|
||||
* │ APP LAUNCHER 1/4 │ 状态栏
|
||||
* ├──────────────────────────────────┤
|
||||
* │ ╭────╮ ╭────╮ ╭────╮ ╭────╮│
|
||||
* │ │ ♪ │ │ 📁 │ │ ⚙ │ │ ☰ ││ 图标(圆角渐变 + 字形)
|
||||
* │ ╰────╯ ╰────╯ ╰────╯ ╰────╯│
|
||||
* │ MUSIC FILES SETTINGS ABOUT │ 名称
|
||||
* │ WAV files in SD /MUSIC │ 选中项说明
|
||||
* │ ● ○ ○ │ 页码圆点
|
||||
* ├──────────────────────────────────┤
|
||||
* │ [◀K1] [▶K2] [✔K3] [→K0] │ 按键提示(Dock)
|
||||
* └──────────────────────────────────┘
|
||||
*
|
||||
* 按键:
|
||||
* K1 上一个图标 K2 下一个图标 K3 打开 K0 切换应用(全局)
|
||||
*
|
||||
* 图标不使用图片文件,而是「圆角渐变底 + LVGL 内置字形」,因此无需额外资源,
|
||||
* 新增应用时只要在 app.c 的表项里填 icon / color_from / color_to 即可。
|
||||
****************************************************************************************************
|
||||
*/
|
||||
|
||||
#include "ui_home.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "ui_common.h"
|
||||
#include "app.h"
|
||||
|
||||
/* ---------------- 网格布局参数 ---------------- */
|
||||
#define HOME_COLS 4 /* 每页图标列数(iPhone 风格一排) */
|
||||
#define HOME_MAX_PAGES 4 /* 最多支持多少页 */
|
||||
|
||||
#define CELL_W 76
|
||||
#define CELL_H 88
|
||||
#define TILE_SIZE 68 /* 图标方块尺寸 */
|
||||
#define TILE_RADIUS 16 /* 图标圆角(iOS 超级圆角观感) */
|
||||
#define TILE_Y 0
|
||||
#define NAME_DY 72 /* 名称相对单元格顶部的偏移 */
|
||||
#define GRID_Y 36 /* 网格顶部 */
|
||||
#define GRID_X 8 /* 网格左侧起始 x */
|
||||
|
||||
#define DOTS_Y 150 /* 页码圆点 y */
|
||||
#define INFO_Y 132 /* 选中项说明 y */
|
||||
|
||||
/* ---------------- 控件 ---------------- */
|
||||
static lv_obj_t *s_scr = NULL;
|
||||
static lv_obj_t *s_lbl_index = NULL;
|
||||
static lv_obj_t *s_lbl_info = NULL;
|
||||
static lv_obj_t *s_dot[HOME_MAX_PAGES];
|
||||
|
||||
/* 每页的图标控件 */
|
||||
static lv_obj_t *s_tile[HOME_COLS];
|
||||
static lv_obj_t *s_glyph[HOME_COLS];
|
||||
static lv_obj_t *s_name[HOME_COLS];
|
||||
|
||||
static ui_key_bar_t s_bar;
|
||||
static ui_flash_t s_flash;
|
||||
|
||||
/* ---------------- 缓存(避免无谓重绘) ---------------- */
|
||||
static int s_page = -1;
|
||||
static int s_sel = -2;
|
||||
|
||||
/* ---------------- 按键提示(Dock) ---------------- */
|
||||
static const char *const s_symbols[APP_KEY_NUM] = {
|
||||
LV_SYMBOL_RIGHT, /* K0:切换应用 */
|
||||
LV_SYMBOL_LEFT, /* K1:上一个图标 */
|
||||
LV_SYMBOL_RIGHT, /* K2:下一个图标 */
|
||||
LV_SYMBOL_OK, /* K3:打开 */
|
||||
};
|
||||
|
||||
static const char *const s_captions[APP_KEY_NUM] = {
|
||||
"K0 SWITCH",
|
||||
"K1 PREV",
|
||||
"K2 NEXT",
|
||||
"K3 OPEN",
|
||||
};
|
||||
|
||||
static const bool s_enabled[APP_KEY_NUM] = { true, true, true, true };
|
||||
|
||||
/* ---------------- 内部实现 ---------------- */
|
||||
|
||||
/**
|
||||
* @brief 设置图标方块的外观(渐变 / 选中环 / 未实现时变暗)
|
||||
*/
|
||||
static void style_tile(int slot, const app_entry_t *app, bool selected)
|
||||
{
|
||||
lv_obj_t *tile = s_tile[slot];
|
||||
|
||||
lv_obj_set_style_bg_color(tile, lv_color_hex(app->color_from), LV_PART_MAIN);
|
||||
lv_obj_set_style_bg_grad_color(tile, lv_color_hex(app->color_to), LV_PART_MAIN);
|
||||
lv_obj_set_style_bg_grad_dir(tile, LV_GRAD_DIR_VER, LV_PART_MAIN);
|
||||
lv_obj_set_style_bg_opa(tile, app->enabled ? LV_OPA_COVER : LV_OPA_50, LV_PART_MAIN);
|
||||
|
||||
/* 选中:白色描边环 + 更强的投影(iOS 图标的“抬起”观感) */
|
||||
lv_obj_set_style_border_width(tile, selected ? 3 : 0, LV_PART_MAIN);
|
||||
lv_obj_set_style_border_color(tile, lv_color_hex(UI_COL_TEXT), LV_PART_MAIN);
|
||||
lv_obj_set_style_border_opa(tile, LV_OPA_COVER, LV_PART_MAIN);
|
||||
lv_obj_set_style_shadow_width(tile, selected ? 22 : 10, LV_PART_MAIN);
|
||||
lv_obj_set_style_shadow_color(tile, lv_color_hex(app->color_to), LV_PART_MAIN);
|
||||
lv_obj_set_style_shadow_opa(tile, selected ? LV_OPA_50 : LV_OPA_30, LV_PART_MAIN);
|
||||
lv_obj_set_style_shadow_ofs_y(tile, selected ? 6 : 3, LV_PART_MAIN);
|
||||
|
||||
/* 字形 */
|
||||
lv_obj_set_style_text_color(s_glyph[slot],
|
||||
lv_color_hex(app->enabled ? 0xFFFFFF : UI_COL_DIM), LV_PART_MAIN);
|
||||
|
||||
/* 名称 */
|
||||
lv_obj_set_style_text_color(s_name[slot],
|
||||
lv_color_hex(app->enabled ? (selected ? UI_COL_TEXT : UI_COL_SUB)
|
||||
: UI_COL_DIM),
|
||||
LV_PART_MAIN);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 刷新当前页的图标、页码与说明文字
|
||||
*/
|
||||
static void refresh(bool force)
|
||||
{
|
||||
int count = 0;
|
||||
const app_entry_t *apps = app_get_entries(&count);
|
||||
int sel = app_get_selection();
|
||||
|
||||
if (count <= 0)
|
||||
{
|
||||
sel = -1;
|
||||
}
|
||||
else if (sel >= count)
|
||||
{
|
||||
sel = count - 1;
|
||||
}
|
||||
|
||||
int page = (sel < 0) ? 0 : (sel / HOME_COLS);
|
||||
|
||||
if (!force && (page == s_page) && (sel == s_sel))
|
||||
{
|
||||
return; /* 无变化 */
|
||||
}
|
||||
|
||||
s_page = page;
|
||||
s_sel = sel;
|
||||
|
||||
/* ---- 图标 ---- */
|
||||
for (int i = 0; i < HOME_COLS; i++)
|
||||
{
|
||||
int idx = page * HOME_COLS + i;
|
||||
|
||||
if (idx < count)
|
||||
{
|
||||
const app_entry_t *app = &apps[idx];
|
||||
|
||||
ui_label_set_if_changed(s_glyph[i], app->icon);
|
||||
ui_label_set_if_changed(s_name[i], app->title);
|
||||
style_tile(i, app, (idx == sel));
|
||||
|
||||
lv_obj_clear_flag(s_tile[i], LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_clear_flag(s_name[i], LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
else
|
||||
{
|
||||
lv_obj_add_flag(s_tile[i], LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_add_flag(s_name[i], LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- 状态栏计数 ---- */
|
||||
char buffer[24];
|
||||
if ((count > 0) && (sel >= 0))
|
||||
{
|
||||
snprintf(buffer, sizeof(buffer), "%d/%d", sel + 1, count);
|
||||
}
|
||||
else
|
||||
{
|
||||
snprintf(buffer, sizeof(buffer), "--/--");
|
||||
}
|
||||
ui_label_set_if_changed(s_lbl_index, buffer);
|
||||
|
||||
/* ---- 选中项说明 ---- */
|
||||
const char *info = "";
|
||||
if ((sel >= 0) && (apps[sel].subtitle != NULL))
|
||||
{
|
||||
info = apps[sel].subtitle;
|
||||
}
|
||||
ui_label_set_if_changed(s_lbl_info, info);
|
||||
|
||||
/* ---- 页码圆点 ---- */
|
||||
int pages = (count + HOME_COLS - 1) / HOME_COLS;
|
||||
|
||||
if (pages > HOME_MAX_PAGES)
|
||||
{
|
||||
pages = HOME_MAX_PAGES;
|
||||
}
|
||||
|
||||
for (int i = 0; i < HOME_MAX_PAGES; i++)
|
||||
{
|
||||
if (i < pages)
|
||||
{
|
||||
lv_obj_clear_flag(s_dot[i], LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_set_style_bg_opa(s_dot[i], (i == page) ? LV_OPA_COVER : LV_OPA_30, LV_PART_MAIN);
|
||||
}
|
||||
else
|
||||
{
|
||||
lv_obj_add_flag(s_dot[i], LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 打开当前选中的应用
|
||||
*/
|
||||
static void open_selected(void)
|
||||
{
|
||||
int count = 0;
|
||||
const app_entry_t *apps = app_get_entries(&count);
|
||||
int sel = app_get_selection();
|
||||
|
||||
if ((sel < 0) || (sel >= count))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!apps[sel].enabled || (apps[sel].mode >= APP_MODE_NUM))
|
||||
{
|
||||
return; /* 尚未实现的应用,忽略 */
|
||||
}
|
||||
|
||||
app_set_mode(apps[sel].mode);
|
||||
}
|
||||
|
||||
/* ---------------- 对外接口 ---------------- */
|
||||
|
||||
void ui_home_create(void)
|
||||
{
|
||||
s_scr = lv_obj_create(NULL);
|
||||
ui_style_screen(s_scr);
|
||||
|
||||
/* ---------------- 状态栏 ---------------- */
|
||||
lv_obj_t *header = ui_card(s_scr, 0, 0, UI_HOR_RES, 28, UI_COL_PANEL, 0);
|
||||
|
||||
lv_obj_t *title = ui_label(header, LV_SYMBOL_LIST " APP LAUNCHER",
|
||||
&lv_font_montserrat_14, UI_COL_SUB);
|
||||
lv_obj_align(title, LV_ALIGN_LEFT_MID, 10, 0);
|
||||
|
||||
s_lbl_index = ui_label(header, "--/--", &lv_font_montserrat_12, UI_COL_SUB);
|
||||
lv_obj_align(s_lbl_index, LV_ALIGN_RIGHT_MID, -10, 0);
|
||||
|
||||
/* ---------------- 图标网格(一排,iOS 风格) ---------------- */
|
||||
for (int i = 0; i < HOME_COLS; i++)
|
||||
{
|
||||
lv_coord_t cell_x = (lv_coord_t)(GRID_X + i * CELL_W);
|
||||
|
||||
/* 图标方块(直接挂在屏幕上,避免投影被容器裁掉) */
|
||||
s_tile[i] = ui_card(s_scr,
|
||||
(lv_coord_t)(cell_x + (CELL_W - TILE_SIZE) / 2), GRID_Y,
|
||||
TILE_SIZE, TILE_SIZE, UI_COL_CARD, TILE_RADIUS);
|
||||
|
||||
/* 图标字形 */
|
||||
s_glyph[i] = ui_label(s_tile[i], "", &lv_font_montserrat_24, 0xFFFFFF);
|
||||
lv_obj_center(s_glyph[i]);
|
||||
|
||||
/* 名称 */
|
||||
s_name[i] = ui_label(s_scr, "", &lv_font_montserrat_12, UI_COL_SUB);
|
||||
lv_obj_set_pos(s_name[i], cell_x, (lv_coord_t)(GRID_Y + NAME_DY));
|
||||
lv_obj_set_size(s_name[i], CELL_W, 14);
|
||||
lv_obj_set_style_text_align(s_name[i], LV_TEXT_ALIGN_CENTER, LV_PART_MAIN);
|
||||
lv_label_set_long_mode(s_name[i], LV_LABEL_LONG_DOT);
|
||||
}
|
||||
|
||||
/* ---------------- 选中项说明 ---------------- */
|
||||
s_lbl_info = ui_label(s_scr, "", &lv_font_montserrat_12, UI_COL_DIM);
|
||||
lv_obj_set_pos(s_lbl_info, 0, INFO_Y);
|
||||
lv_obj_set_size(s_lbl_info, UI_HOR_RES, 14);
|
||||
lv_obj_set_style_text_align(s_lbl_info, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN);
|
||||
lv_label_set_long_mode(s_lbl_info, LV_LABEL_LONG_DOT);
|
||||
|
||||
/* ---------------- 页码圆点 ---------------- */
|
||||
for (int i = 0; i < HOME_MAX_PAGES; i++)
|
||||
{
|
||||
lv_obj_t *dot = lv_obj_create(s_scr);
|
||||
lv_obj_set_size(dot, 6, 6);
|
||||
lv_obj_clear_flag(dot, LV_OBJ_FLAG_SCROLLABLE | LV_OBJ_FLAG_CLICKABLE);
|
||||
lv_obj_set_style_radius(dot, LV_RADIUS_CIRCLE, LV_PART_MAIN);
|
||||
lv_obj_set_style_bg_color(dot, lv_color_hex(UI_COL_TEXT), LV_PART_MAIN);
|
||||
lv_obj_set_style_bg_opa(dot, LV_OPA_30, LV_PART_MAIN);
|
||||
lv_obj_set_style_border_width(dot, 0, LV_PART_MAIN);
|
||||
|
||||
int total_w = HOME_MAX_PAGES * 6 + (HOME_MAX_PAGES - 1) * 8;
|
||||
lv_obj_set_pos(dot, (lv_coord_t)((UI_HOR_RES - total_w) / 2 + i * 14), DOTS_Y);
|
||||
lv_obj_add_flag(dot, LV_OBJ_FLAG_HIDDEN);
|
||||
|
||||
s_dot[i] = dot;
|
||||
}
|
||||
|
||||
/* ---------------- 底部按键提示(Dock) ---------------- */
|
||||
ui_key_bar_create(s_scr, &s_bar, s_symbols, s_captions, s_enabled);
|
||||
|
||||
refresh(true);
|
||||
}
|
||||
|
||||
void ui_home_show(void)
|
||||
{
|
||||
s_sel = -2; /* 强制刷新一次 */
|
||||
s_page = -1;
|
||||
|
||||
refresh(true);
|
||||
ui_key_bar_set_highlight(&s_bar, -1);
|
||||
lv_scr_load(s_scr);
|
||||
}
|
||||
|
||||
void ui_home_tick(void)
|
||||
{
|
||||
ui_key_bar_set_highlight(&s_bar, ui_flash_active(&s_flash));
|
||||
refresh(false);
|
||||
}
|
||||
|
||||
void ui_home_flash(int key)
|
||||
{
|
||||
ui_flash_trigger(&s_flash, key);
|
||||
}
|
||||
|
||||
void ui_home_handle_key(uint8_t key)
|
||||
{
|
||||
ui_flash_trigger(&s_flash, key);
|
||||
|
||||
switch (key)
|
||||
{
|
||||
case APP_KEY_1: /* K1:上一个图标 */
|
||||
app_select_move(-1);
|
||||
break;
|
||||
|
||||
case APP_KEY_2: /* K2:下一个图标 */
|
||||
app_select_move(+1);
|
||||
break;
|
||||
|
||||
case APP_KEY_3: /* K3:打开选中的应用 */
|
||||
open_selected();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
****************************************************************************************************
|
||||
* @file ui_home.h
|
||||
* @author HiFi Player Project
|
||||
* @version V1.2
|
||||
* @date 2026-01-01
|
||||
* @brief 主界面(iPhone 风格图标网格)界面接口
|
||||
*
|
||||
* 按键:K1 上一个图标 / K2 下一个图标 / K3 打开 / K0 切换应用(全局)
|
||||
****************************************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef __UI_HOME_H
|
||||
#define __UI_HOME_H
|
||||
|
||||
#include "lvgl.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** 创建主界面(只创建,不显示) */
|
||||
void ui_home_create(void);
|
||||
|
||||
/** 显示主界面并复位内部状态 */
|
||||
void ui_home_show(void);
|
||||
|
||||
/** 周期刷新显示 */
|
||||
void ui_home_tick(void);
|
||||
|
||||
/** 处理本界面的功能键(K1/K2/K3);K0 由 ui.c 统一处理 */
|
||||
void ui_home_handle_key(uint8_t key);
|
||||
|
||||
/** 仅触发按键高亮(不做动作),供 K0 使用 */
|
||||
void ui_home_flash(int key);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __UI_HOME_H */
|
||||
Reference in new issue
Block a user