最近在准备新国标的移动电源,发现mcu ram 4kb rom 64kb,难以运行像是lvgl绘制方便的图形系统,于是想写一份出来
这个gui系统共有两层:1层是中间层负责和底层驱动打交道 2层是应用层负责图形创建和渲染还有对象的内存管理。
开源地址:
因为gitee账号给封了,得等一会儿
1.中间层
lcd_draw.h
cpp
/******************************************************************************
* @file display.h
* @brief LCD显示驱动头文件 - 提供基本图形绘制、文本显示、图片显示等功能
* @author 古焕发
* @version V1.0
* @date 2026-01-30
* @copyright Copyright (c) 2026
*
* @description 该头文件定义了LCD显示屏的完整驱动接口,包括基本图形绘制、文本显示、
* 图片显示等功能。采用模块化设计,支持多种图形元素和显示效果。
*
* @note 修改日志:
* - 2026-01-30 V1.0 古焕发 创建文件,定义完整的LCD显示接口
******************************************************************************/
#ifndef LCD_DRAW_H
#define LCD_DRAW_H
#include "main.h"
/*---------------------- 颜色常量定义 ----------------------*/
/** 基础颜色定义 (RGB565格式) */
#define WHITE 0xFFFF /**< 白色 */
#define BLACK 0x0000 /**< 黑色 */
#define BLUE 0x001F /**< 蓝色 */
#define BRED 0xF81F /**< 蓝红色 */
#define GRED 0xFFE0 /**< 绿红色 */
#define GBLUE 0x07FF /**< 绿蓝色 */
#define RED 0xF800 /**< 红色 */
/*---------------------- 基本图形绘制函数 ----------------------*/
/**
* @brief 在指定矩形区域填充颜色
* @param[in] xsta, ysta: 区域起始坐标(左上角)
* @param[in] xend, yend: 区域结束坐标(右下角)
* @param[in] color: 填充颜色值
* @retval 无
*/
void LCD_Fill(uint16_t xsta, uint16_t ysta, uint16_t xend, uint16_t yend, uint16_t color);
/**
* @brief 在指定位置绘制单个像素点
* @param[in] x, y: 点的坐标
* @param[in] color: 点的颜色值
* @retval 无
*/
void LCD_DrawPoint(uint16_t x, uint16_t y, uint16_t color);
/**
* @brief 在两点之间绘制直线
* @param[in] x1, y1: 起点坐标
* @param[in] x2, y2: 终点坐标
* @param[in] color: 线条颜色值
* @retval 无
*/
void LCD_DrawLine(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color);
/**
* @brief 绘制水平直线
* @param[in] x0, y0: 起点坐标
* @param[in] len: 直线长度(像素)
* @param[in] color: 线条颜色值
* @retval 无
*/
void LCD_DrawHLine(uint16_t x0, uint16_t y0, uint16_t len, uint16_t color);
/**
* @brief 绘制垂直直线
* @param[in] x0, y0: 起点坐标
* @param[in] len: 直线长度(像素)
* @param[in] color: 线条颜色值
* @retval 无
*/
void LCD_DrawVLine(uint16_t x0, uint16_t y0, uint16_t len, uint16_t color);
/*---------------------- 矩形绘制函数 ----------------------*/
/**
* @brief 绘制矩形边框
* @param[in] x1, y1: 矩形左上角坐标
* @param[in] x2, y2: 矩形右下角坐标
* @param[in] color: 边框颜色值
* @retval 无
*/
void LCD_DrawRectangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color);
/**
* @brief 绘制圆角矩形边框
* @param[in] x1, y1: 矩形左上角坐标
* @param[in] x2, y2: 矩形右下角坐标
* @param[in] r: 圆角半径
* @param[in] color: 边框颜色值
* @retval 无
*/
void LCD_DrawRoundedRect(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2,
uint8_t r, uint16_t color);
/**
* @brief 绘制带粗细的圆角矩形边框
* @param[in] x1, y1: 矩形左上角坐标
* @param[in] x2, y2: 矩形右下角坐标
* @param[in] r: 圆角半径
* @param[in] thickness: 边框粗细
* @param[in] color: 边框颜色值
* @retval 无
*/
void LCD_DrawRoundedRect_Thick(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2,
uint8_t r, uint8_t thickness, uint16_t color);
/**
* @brief 绘制填充式圆角矩形
* @param[in] x, y: 矩形左上角坐标
* @param[in] w: 矩形宽度
* @param[in] h: 矩形高度
* @param[in] r: 圆角半径
* @param[in] color: 填充颜色值
* @retval 无
*/
void LCD_Draw_Filled_Rounded_Rect(uint16_t x, uint16_t y, uint16_t w, uint16_t h,
uint16_t r, uint16_t color);
/*---------------------- 圆形和圆弧绘制函数 ----------------------*/
/**
* @brief 绘制圆形边框
* @param[in] x0, y0: 圆心坐标
* @param[in] r: 圆半径
* @param[in] color: 边框颜色值
* @retval 无
*/
void LCD_Draw_Circle(uint16_t x0, uint16_t y0, uint8_t r, uint16_t color);
/**
* @brief 绘制填充圆形
* @param[in] x0, y0: 圆心坐标
* @param[in] r: 圆半径
* @param[in] color: 填充颜色值
* @retval 无
*/
void LCD_Draw_FillCircle(uint16_t x0, uint16_t y0, uint8_t r, uint16_t color);
/**
* @brief 绘制圆弧(优化版本)
* @param[in] x0, y0: 圆心坐标
* @param[in] r: 圆弧半径
* @param[in] start_angle: 起始角度(0-359度)
* @param[in] end_angle: 结束角度(0-359度)
* @param[in] color: 圆弧颜色值
* @retval 无
*/
void LCD_DrawArc_Optimized(uint16_t x0, uint16_t y0, uint8_t r,
uint16_t start_angle, uint16_t end_angle, uint16_t color);
/**
* @brief 绘制圆弧(极坐标方法)
* @param[in] x0, y0: 圆心坐标
* @param[in] r: 圆弧半径
* @param[in] start_angle: 起始角度(0-359度)
* @param[in] end_angle: 结束角度(0-359度)
* @param[in] thickness: 圆弧粗细
* @param[in] color: 圆弧颜色值
* @retval 无
*/
void LCD_DrawArc_Polar(uint16_t x0, uint16_t y0, uint8_t r,
uint16_t start_angle, uint16_t end_angle,
uint8_t thickness, uint16_t color);
/*---------------------- 文本显示函数 ----------------------*/
/**
* @brief 显示汉字字符串
* @param[in] x, y: 显示起始坐标
* @param[in] s: 要显示的汉字字符串
* @param[in] fc: 文字颜色值
* @param[in] bc: 文字背景颜色值
* @param[in] sizey: 字体大小(12/16/24/32)
* @param[in] mode: 显示模式(0-非叠加, 1-叠加)
* @retval 无
*/
void LCD_ShowChinese(uint16_t x, uint16_t y, uint8_t *s, uint16_t fc,
uint16_t bc, uint8_t sizey, uint8_t mode);
/**
* @brief 显示单个12x12汉字
* @param[in] x, y: 显示坐标
* @param[in] s: 要显示的汉字
* @param[in] fc: 文字颜色值
* @param[in] bc: 文字背景颜色值
* @param[in] sizey: 字体大小
* @param[in] mode: 显示模式(0-非叠加, 1-叠加)
* @retval 无
*/
void LCD_ShowChinese12x12(uint16_t x, uint16_t y, uint8_t *s, uint16_t fc,
uint16_t bc, uint8_t sizey, uint8_t mode);
/**
* @brief 显示单个16x16汉字
* @param[in] 参数同上
* @retval 无
*/
void LCD_ShowChinese16x16(uint16_t x, uint16_t y, uint8_t *s, uint16_t fc,
uint16_t bc, uint8_t sizey, uint8_t mode);
/**
* @brief 显示单个24x24汉字
* @param[in] 参数同上
* @retval 无
*/
void LCD_ShowChinese24x24(uint16_t x, uint16_t y, uint8_t *s, uint16_t fc,
uint16_t bc, uint8_t sizey, uint8_t mode);
/**
* @brief 显示单个32x32汉字
* @param[in] 参数同上
* @retval 无
*/
void LCD_ShowChinese32x32(uint16_t x, uint16_t y, uint8_t *s, uint16_t fc,
uint16_t bc, uint8_t sizey, uint8_t mode);
/**
* @brief 显示单个ASCII字符
* @param[in] x, y: 显示坐标
* @param[in] num: 要显示的字符
* @param[in] fc: 文字颜色值
* @param[in] bc: 文字背景颜色值
* @param[in] sizey: 字体大小
* @param[in] mode: 显示模式(0-非叠加, 1-叠加)
* @retval 无
*/
void LCD_ShowChar(uint16_t x, uint16_t y, uint8_t num, uint16_t fc,
uint16_t bc, uint8_t sizey, uint8_t mode);
/**
* @brief 显示字符串(ASCII)
* @param[in] x, y: 显示起始坐标
* @param[in] p: 要显示的字符串
* @param[in] fc: 文字颜色值
* @param[in] bc: 文字背景颜色值
* @param[in] sizey: 字体大小
* @param[in] mode: 显示模式(0-非叠加, 1-叠加)
* @retval 无
*/
void LCD_ShowString(uint16_t x, uint16_t y, const uint8_t *p, uint16_t fc,
uint16_t bc, uint8_t sizey, uint8_t mode);
/**
* @brief 显示中英文混合文本
* @param[in] x, y: 显示起始坐标
* @param[in] str: 要显示的字符串(支持中英文混合)
* @param[in] fc: 文字颜色值
* @param[in] bc: 文字背景颜色值
* @param[in] sizey: 字体大小
* @param[in] mode: 显示模式(0-非叠加, 1-叠加)
* @retval 无
*/
void LCD_ShowText(uint16_t x, uint16_t y, const uint8_t *str, uint16_t fc,
uint16_t bc, uint8_t sizey, uint8_t mode);
/**
* @brief 自动换行显示文本
* @param[in] x, y: 显示起始坐标
* @param[in] width: 显示区域宽度
* @param[in] str: 要显示的字符串
* @param[in] fc: 文字颜色值
* @param[in] bc: 文字背景颜色值
* @param[in] sizey: 字体大小
* @param[in] mode: 显示模式(0-非叠加, 1-叠加)
* @param[in] line_spacing: 行间距
* @return uint16_t: 实际显示的行数
*/
uint16_t LCD_ShowTextAutoWrap(uint16_t x, uint16_t y, uint16_t width,
const uint8_t *str, uint16_t fc, uint16_t bc,
uint8_t sizey, uint8_t mode, uint8_t line_spacing);
/*---------------------- 数学计算函数 ----------------------*/
/**
* @brief 计算幂运算
* @param[in] m: 底数
* @param[in] n: 指数
* @return uint32_t: 计算结果
*/
uint32_t mypow(uint8_t m, uint8_t n);
/*---------------------- 数字显示函数 ----------------------*/
/**
* @brief 显示整数变量
* @param[in] x, y: 显示坐标
* @param[in] num: 要显示的整数值
* @param[in] len: 显示位数
* @param[in] fc: 文字颜色值
* @param[in] bc: 文字背景颜色值
* @param[in] sizey: 字体大小
* @retval 无
*/
void LCD_ShowIntNum(uint16_t x, uint16_t y, uint16_t num, uint8_t len,
uint16_t fc, uint16_t bc, uint8_t sizey);
/**
* @brief 显示浮点数变量(两位小数)
* @param[in] x, y: 显示坐标
* @param[in] num: 要显示的浮点数值
* @param[in] len: 显示位数
* @param[in] fc: 文字颜色值
* @param[in] bc: 文字背景颜色值
* @param[in] sizey: 字体大小
* @retval 无
*/
void LCD_ShowFloatNum1(uint16_t x, uint16_t y, float num, uint8_t len,
uint16_t fc, uint16_t bc, uint8_t sizey);
/*---------------------- 图片显示函数 ----------------------*/
/**
* @brief 显示图片
* @param[in] x, y: 图片左上角坐标
* @param[in] length: 图片长度(像素)
* @param[in] width: 图片宽度(像素)
* @param[in] pic: 图片数据数组
* @retval 无
*/
void LCD_ShowPicture(uint16_t x, uint16_t y, uint16_t length, uint16_t width,
const uint8_t pic[]);
/**
* @brief 以图片中心为基准显示图片
* @param[in] center_x, center_y: 图片中心坐标
* @param[in] length: 图片宽度(像素)
* @param[in] width: 图片高度(像素)
* @param[in] pic: 图片数据数组
* @retval 无
*/
void LCD_ShowCentrePicture(uint16_t center_x, uint16_t center_y,
uint16_t length, uint16_t width, const uint8_t pic[]);
#endif /* DISPLAY_H */
lcd_draw.c
cpp
/******************************************************************************
* @file lcd_front.c
* @brief LCD图形绘制前端实现 - 提供基本图形、圆角矩形、圆弧等绘制功能
* @author 古焕发
* @version V1.0
* @date 2026-01-30
* @copyright Copyright (c) 2026
*
* @description 该文件实现了LCD显示屏的各种图形绘制功能,包括基本图形、圆角矩形、
* 圆弧等,采用查表法优化三角函数计算,提高绘制效率。
* 所有代码内容保持原样,仅添加注释和格式优化。
*
* @note 修改日志:
* - 2026-01-30 V1.0 古焕发 创建文件,实现完整的图形绘制功能
******************************************************************************/
#include "main.h"
#include "math.h"
#include "string.h"
#include "lcd_draw.h"
#include "st7789.h"
#include "lcd_front.h"
#include "bsp_htim.h"
/*---------------------- 三角函数查表数据 ----------------------*/
/**
* @brief 余弦值查表(0-89度,放大1000倍)
* @note 用于快速计算圆弧绘制,避免浮点运算
*/
static const uint16_t cos_tab[90] = {
2, 15, 32, 49, 67, 84, 102, 119, 136, 153,
171, 188, 205, 222, 239, 256, 273, 289, 306, 323,
339, 355, 372, 388, 404, 420, 436, 451, 467, 482,
497, 512, 527, 542, 556, 571, 585, 599, 613, 627,
640, 653, 667, 679, 692, 705, 717, 729, 741, 752,
764, 775, 786, 796, 807, 817, 827, 837, 846, 855,
864, 873, 881, 889, 897, 905, 912, 919, 926, 932,
938, 944, 950, 955, 960, 965, 969, 973, 977, 981,
984, 987, 989, 992, 994, 995, 997, 998, 999, 999,
};
/**
* @brief 正弦值查表(0-89度,放大1000倍)
* @note 与余弦表配合使用,用于快速三角函数计算
*/
static const uint16_t sin_tab[90] = {
999, 999, 999, 998, 997, 996, 994, 992, 990, 988,
985, 982, 978, 974, 970, 966, 961, 957, 951, 946,
940, 934, 928, 921, 914, 907, 899, 892, 884, 875,
867, 858, 849, 840, 830, 820, 810, 800, 789, 778,
767, 756, 744, 733, 721, 709, 696, 684, 671, 658,
644, 631, 617, 604, 590, 575, 561, 547, 532, 517,
502, 487, 472, 456, 441, 425, 409, 393, 377, 361,
344, 328, 311, 295, 278, 261, 244, 227, 210, 193,
176, 159, 142, 124, 107, 90, 72, 55, 38, 20
};
/*---------------------- 辅助计算函数 ----------------------*/
/**
* @brief 计算角度近似值(0-90度)
* @param[in] x: X坐标分量
* @param[in] y: Y坐标分量
* @return int16_t: 计算的角度值(0-90度)
* @note 使用简化公式 angle ≈ 90 * y / (x + y) 进行快速估算
*/
static int16_t calc_angle(int16_t x, int16_t y)
{
/** 边界条件处理 */
if (x == 0) return 90;
if (y == 0) return 0;
/** 使用简化公式进行角度估算 */
int32_t angle = (90 * y) / (x + y);
return (int16_t)angle;
}
/*---------------------- 基本图形绘制函数 ----------------------*/
/**
* @brief 在指定矩形区域填充颜色
* @param[in] xsta, ysta: 区域起始坐标(左上角)
* @param[in] xend, yend: 区域结束坐标(右下角)
* @param[in] color: 填充颜色值
* @retval 无
* @note 使用双重循环逐个像素填充,适合小区域填充操作
*/
void LCD_Fill(uint16_t xsta, uint16_t ysta, uint16_t xend, uint16_t yend, uint16_t color)
{
uint16_t i, j;
/** 设置显示窗口范围 */
ST7789_Set_Window(xsta, ysta, xend - 1, yend - 1);
/** 双重循环填充每个像素 */
for (i = ysta; i < yend; i++) {
for (j = xsta; j < xend; j++) {
ST7789_Write_Gram(color);
}
}
}
/**
* @brief 在指定位置绘制单个像素点
* @param[in] x, y: 点的坐标位置
* @param[in] color: 点的颜色值
* @retval 无
* @note 直接调用底层ST7789驱动函数进行点绘制
*/
void LCD_DrawPoint(uint16_t x, uint16_t y, uint16_t color)
{
ST7789_Draw_Point(x, y, color);
}
/**
* @brief 使用Bresenham算法绘制直线
* @param[in] x0, y0: 直线起点坐标
* @param[in] x1, y1: 直线终点坐标
* @param[in] color: 直线颜色值
* @retval 无
* @note 采用整数运算的Bresenham算法,避免浮点运算提高效率
*/
void LCD_DrawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t color)
{
int16_t dx, dy, sx, sy, err, e2;
/** 计算坐标差值和方向 */
dx = (x1 > x0) ? (x1 - x0) : (x0 - x1);
dy = (y1 > y0) ? (y1 - y0) : (y0 - y1);
sx = (x0 < x1) ? 1 : -1;
sy = (y0 < y1) ? 1 : -1;
err = dx - dy;
/** Bresenham算法主循环 */
while (1) {
lptim_delay_ms(1);
LCD_DrawPoint(x0, y0, color);
/** 检查是否到达终点 */
if (x0 == x1 && y0 == y1) break;
e2 = 2 * err;
/** 误差项调整和坐标更新 */
if (e2 > -dy) {
err -= dy;
x0 += sx;
}
if (e2 < dx) {
err += dx;
y0 += sy;
}
}
}
/**
* @brief 绘制水平直线
* @param[in] x0, y0: 起点坐标
* @param[in] len: 直线长度(像素)
* @param[in] color: 直线颜色值
* @retval 无
* @note 优化水平线绘制,直接使用水平线函数提高效率
*/
void LCD_DrawHLine(uint16_t x0, uint16_t y0, uint16_t len, uint16_t color)
{
for (uint16_t i = 0; i < len; i++) {
LCD_DrawPoint(x0 + i, y0, color);
}
}
/**
* @brief 绘制垂直直线
* @param[in] x0, y0: 起点坐标
* @param[in] len: 直线长度(像素)
* @param[in] color: 直线颜色值
* @retval 无
* @note 调用通用直线绘制函数实现垂直线
*/
void LCD_DrawVLine(uint16_t x0, uint16_t y0, uint16_t len, uint16_t color)
{
LCD_DrawLine(x0, y0, x0, y0 + len, color);
}
/**
* @brief 绘制矩形边框
* @param[in] x1, y1: 矩形左上角坐标
* @param[in] x2, y2: 矩形右下角坐标
* @param[in] color: 边框颜色值
* @retval 无
* @note 通过绘制四条直线组成矩形边框
*/
void LCD_DrawRectangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color)
{
LCD_DrawLine(x1, y1, x2, y1, color);
LCD_DrawLine(x1, y1, x1, y2, color);
LCD_DrawLine(x1, y2, x2, y2, color);
LCD_DrawLine(x2, y1, x2, y2, color);
}
/*---------------------- 圆角矩形绘制函数 ----------------------*/
/**
* @brief 绘制圆角矩形边框(优化版本)
* @param[in] x1, y1: 矩形左上角坐标
* @param[in] x2, y2: 矩形右下角坐标
* @param[in] r: 圆角半径(像素)
* @param[in] color: 边框颜色值
* @retval 无
* @note 组合圆弧和直线段,实现平滑的圆角矩形效果
*/
void LCD_DrawRoundedRect(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2,
uint8_t r, uint16_t color)
{
/** 参数有效性检查 */
if (x1 > x2 || y1 > y2) {
return;
}
uint16_t width = x2 - x1;
uint16_t height = y2 - y1;
/** 调整圆角半径避免过大 */
if (r > width / 2) r = width / 2;
if (r > height / 2) r = height / 2;
/** 小半径回退到普通矩形绘制 */
if (r <= 1) {
LCD_DrawRectangle(x1, y1, x2, y2, color);
return;
}
/** 绘制四个圆角(使用圆弧函数) */
LCD_DrawArc_Optimized(x1 + r, y1 + r, r, 270, 360, color);
LCD_DrawArc_Optimized(x2 - r, y1 + r, r, 0, 90, color);
LCD_DrawArc_Optimized(x1 + r, y2 - r, r, 180, 270, color);
LCD_DrawArc_Optimized(x2 - r, y2 - r, r, 90, 180, color);
/** 绘制直线边部分 */
if (width > 2 * r) {
LCD_DrawHLine(x1 + r, y1, width - 2 * r, color);
LCD_DrawHLine(x1 + r, y2, width - 2 * r, color);
}
if (height > 2 * r) {
LCD_DrawVLine(x1, y1 + r, height - 2 * r, color);
LCD_DrawVLine(x2, y1 + r, height - 2 * r, color);
}
}
/**
* @brief 绘制带粗细的圆角矩形边框
* @param[in] x1, y1: 矩形左上角坐标
* @param[in] x2, y2: 矩形右下角坐标
* @param[in] r: 圆角半径(像素)
* @param[in] thickness: 边框粗细(像素)
* @param[in] color: 边框颜色值
* @retval 无
* @note 通过绘制多个同心圆角矩形实现边框粗细效果
*/
void LCD_DrawRoundedRect_Thick(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2,
uint8_t r, uint8_t thickness, uint16_t color)
{
/** 参数有效性检查 */
if (x1 > x2 || y1 > y2 || thickness == 0) {
return;
}
uint16_t width = x2 - x1;
uint16_t height = y2 - y1;
/** 调整圆角半径 */
if (r > width / 2) r = width / 2;
if (r > height / 2) r = height / 2;
/** 小半径回退到普通矩形绘制 */
if (r <= 1) {
LCD_DrawRectangle(x1, y1, x2, y2, color);
if (thickness > 1 && width > 2 && height > 2) {
LCD_DrawRectangle(x1 + 1, y1 + 1, x2 - 1, y2 - 1, color);
}
return;
}
/** 绘制多个同心圆角矩形实现边框效果 */
for (uint8_t t = 0; t < thickness; t++) {
uint8_t current_r = r;
uint16_t offset = t;
/** 计算当前矩形坐标和尺寸 */
uint16_t cur_x1 = x1 + offset;
uint16_t cur_y1 = y1 + offset;
uint16_t cur_x2 = x2 - offset;
uint16_t cur_y2 = y2 - offset;
uint16_t cur_width = cur_x2 - cur_x1;
uint16_t cur_height = cur_y2 - cur_y1;
/** 调整当前圆角半径 */
if (current_r > cur_width / 2) current_r = cur_width / 2;
if (current_r > cur_height / 2) current_r = cur_height / 2;
/** 根据半径选择绘制方式 */
if (current_r <= 1) {
LCD_DrawRectangle(cur_x1, cur_y1, cur_x2, cur_y2, color);
} else {
LCD_DrawRoundedRect(cur_x1, cur_y1, cur_x2, cur_y2, current_r, color);
}
}
}
/**
* @brief 绘制填充圆角矩形
* @param[in] x, y: 矩形左上角坐标
* @param[in] w, h: 矩形宽度和高度
* @param[in] r: 圆角半径(像素)
* @param[in] color: 填充颜色值
* @retval 无
* @note 通过组合填充圆形和矩形区域实现圆角填充效果
*/
void LCD_Draw_Filled_Rounded_Rect(uint16_t x, uint16_t y, uint16_t w, uint16_t h,
uint16_t r, uint16_t color)
{
/** 参数检查 */
if (w - (r * 2) < 0 || h - (r * 2) < 0) {
return;
}
x += 1;
/** 绘制四个圆角填充区域 */
LCD_Draw_FillCircle(x + r, y + r, r, color);
LCD_Draw_FillCircle(x + w - r, y + r, r - 2, color);
LCD_Draw_FillCircle(x + r, y + h - r, r - 1, color);
LCD_Draw_FillCircle(x + w - r, y + h - r, r - 2, color);
/** 填充矩形主体区域 */
ST7789_Clear_Window(x + r, y - 1, x + w - r, y + h, color);
ST7789_Clear_Window(x - 1, y + r, x + r, y + h - r, color);
ST7789_Clear_Window(x + w - r, y + r, x + w - 1, y + h - r, color);
}
/*---------------------- 圆弧绘制函数 ----------------------*/
/**
* @brief 使用极坐标方法绘制带粗细的圆弧
* @param[in] x0, y0: 圆心坐标
* @param[in] r: 圆弧半径(像素)
* @param[in] start_angle: 起始角度(0-359度)
* @param[in] end_angle: 结束角度(0-359度)
* @param[in] thickness: 圆弧粗细(像素)
* @param[in] color: 圆弧颜色值
* @retval 无
* @note 使用极坐标公式计算每个点位置,支持任意角度和粗细
*/
void LCD_DrawArc_Polar(uint16_t x0, uint16_t y0, uint8_t r,
uint16_t start_angle, uint16_t end_angle,
uint8_t thickness, uint16_t color)
{
/** 参数有效性检查 */
if (r == 0 || thickness == 0) return;
/** 角度规范化处理 */
start_angle = start_angle % 360;
end_angle = end_angle % 360;
if (start_angle > end_angle) {
end_angle += 360;
}
/** 计算内外半径 */
float inner_r = (float)r;
float outer_r = (float)(r + thickness - 1);
/** 极坐标绘制主循环 */
for (uint16_t angle = start_angle; angle <= end_angle; angle++) {
/** 角度转弧度 */
float rad = (float)angle * 3.14159265f / 180.0f;
/** 计算三角函数值 */
float cos_a = cosf(rad);
float sin_a = sinf(rad);
/** 径向绘制从内到外的点 */
for (float current_r = inner_r; current_r <= outer_r; current_r += 0.5f) {
int16_t x = x0 + (int16_t)(current_r * cos_a);
int16_t y = y0 - (int16_t)(current_r * sin_a);
/** 坐标有效性检查 */
if (x >= 0 && y >= 0) {
LCD_DrawPoint(x, y, color);
}
}
}
}
/******************************************************************************
函数说明:画圆弧(优化版本,使用角度查找表)
入口数据:x0,y0 圆心坐标
r 半径
start_angle 起始角度(度,0-359)
end_angle 结束角度(度,0-359)
color 圆弧的颜色
返回值: 无
******************************************************************************/
void LCD_DrawArc_Optimized(uint16_t x0, uint16_t y0, uint8_t r,
uint16_t start_angle, uint16_t end_angle, uint16_t color)
{
int a, b;
static uint8_t angle_table[361]; // 角度查找表,0-360度
// 参数检查
if (r == 0) return;
// 规范化角度
start_angle = start_angle % 360;
end_angle = end_angle % 360;
// 初始化角度查找表
memset(angle_table, 0, sizeof(angle_table));
// 标记需要绘制的角度
if (start_angle <= end_angle) {
for (uint16_t angle = start_angle; angle <= end_angle; angle++) {
angle_table[angle] = 1;
}
} else {
// 跨越360度的情况
for (uint16_t angle = start_angle; angle < 360; angle++) {
angle_table[angle] = 1;
}
for (uint16_t angle = 0; angle <= end_angle; angle++) {
angle_table[angle] = 1;
}
}
a = 0;
b = r;
// 使用与Draw_Circle相同的算法
while (a <= b) {
// 计算8个点的角度
int16_t angles[8];
// 计算角度(近似计算)
angles[0] = calc_angle(a, b); // 第1象限
angles[1] = calc_angle(b, a); // 第2象限
angles[2] = 180 - angles[1]; // 第3象限
angles[3] = 180 - angles[0]; // 第4象限
angles[4] = 180 + angles[0]; // 第5象限
angles[5] = 180 + angles[1]; // 第6象限
angles[6] = 360 - angles[1]; // 第7象限
angles[7] = 360 - angles[0]; // 第8象限
// 检查并绘制每个点
if (angle_table[angles[0]]) LCD_DrawPoint(x0 + a, y0 - b, color);
if (angle_table[angles[1]]) LCD_DrawPoint(x0 + b, y0 - a, color);
if (angle_table[angles[2]]) LCD_DrawPoint(x0 + b, y0 + a, color);
if (angle_table[angles[3]]) LCD_DrawPoint(x0 + a, y0 + b, color);
if (angle_table[angles[4]]) LCD_DrawPoint(x0 - a, y0 + b, color);
if (angle_table[angles[5]]) LCD_DrawPoint(x0 - b, y0 + a, color);
if (angle_table[angles[6]]) LCD_DrawPoint(x0 - b, y0 - a, color);
if (angle_table[angles[7]]) LCD_DrawPoint(x0 - a, y0 - b, color);
a++;
if ((a * a + b * b) > (r * r)) {
b--;
}
}
}
/******************************************************************************
函数说明:画圆
入口数据:x0,y0 圆心坐标
r 半径
color 圆的颜色
返回值: 无
******************************************************************************/
void LCD_Draw_Circle(uint16_t x,uint16_t y,uint8_t r,uint16_t color)
{
uint32_t rx = 0;
uint32_t ry = 0;
uint32_t last_rx = 0;
uint32_t last_ry = 0;
LCD_DrawPoint(x, y, color); /* 圆心 */
for (uint16_t angle = 0; angle < 90; angle++)
{
rx = r * cos_tab[angle] / 1000 + x;
ry = r * sin_tab[angle] / 1000 + y;
if (last_rx != rx || last_ry != ry)
{
LCD_DrawLine(rx, y - (ry - y), rx, ry, color);
LCD_DrawLine(x - (rx - x), y - (ry - y), x - (rx - x), ry, color);
last_rx = rx;
last_ry = ry;
}
}
}
/******************************************************************************
函数说明:画圆角填充圆形
入口数据:x0,y0 圆心坐标
r 半径
color 矩形的颜色
返回值: 无
******************************************************************************/
void LCD_Draw_FillCircle(uint16_t x0, uint16_t y0, uint8_t r, uint16_t color)
{
int16_t x = 0;
int16_t y = r;
int16_t d = 3 - 2 * r;
while (x <= y) {
// 关键:内部循环确保每个垂直段都被填充
for (int16_t i = x; i <= y; i++) {
// 绘制8个对称位置
// 使用水平线提高效率
LCD_DrawPoint(x0 + x, y0 + i, color);
LCD_DrawPoint(x0 - x, y0 + i, color);
if (i != 0) {
LCD_DrawPoint(x0 - i, y0 + x, color);
LCD_DrawPoint(x0 - i, y0 - x, color);
LCD_DrawPoint(x0 - x, y0 - i, color);
LCD_DrawPoint(x0 + x, y0 - i, color);
LCD_DrawPoint(x0 + i, y0 - x, color);
LCD_DrawPoint(x0 + i, y0 + x, color);
}
}
if (d < 0) {
d = d + 4 * x + 6;
} else {
d = d + 4 * (x - y) + 10;
y--;
}
x++;
}
}
/******************************************************************************
函数说明:显示汉字串
入口数据:x,y显示坐标
*s 要显示的汉字串
fc 字的颜色
bc 字的背景色
sizey 字号 可选 16 24 32
mode: 0非叠加模式 1叠加模式
返回值: 无
******************************************************************************/
void LCD_ShowChinese(uint16_t x,uint16_t y,uint8_t *s,uint16_t fc,uint16_t bc,uint8_t sizey,uint8_t mode)
{
while(*s!=0)
{
if(sizey==12) LCD_ShowChinese12x12(x,y,s,fc,bc,sizey,mode);
else if(sizey==16) LCD_ShowChinese16x16(x,y,s,fc,bc,sizey,mode);
else if(sizey==24) LCD_ShowChinese24x24(x,y,s,fc,bc,sizey,mode);
else if(sizey==32) LCD_ShowChinese32x32(x,y,s,fc,bc,sizey,mode);
else return;
s+=2;
x+=sizey;
}
}
/******************************************************************************
函数说明:显示单个12x12汉字
入口数据:x,y显示坐标
*s 要显示的汉字
fc 字的颜色
bc 字的背景色
sizey 字号
mode: 0非叠加模式 1叠加模式
返回值: 无
******************************************************************************/
void LCD_ShowChinese12x12(uint16_t x,uint16_t y,uint8_t *s,uint16_t fc,uint16_t bc,uint8_t sizey,uint8_t mode)
{
uint8_t i,j,m=0;
uint16_t k;
uint16_t HZnum;//汉字数目
uint16_t TypefaceNum;//一个字符所占字节大小
uint16_t x0=x;
TypefaceNum=(sizey/8+((sizey%8)?1:0))*sizey;
HZnum=sizeof(tfont12)/sizeof(typFNT_GB12); //统计汉字数目
for(k=0;k<HZnum;k++)
{
if((tfont12[k].Index[0]==*(s))&&(tfont12[k].Index[1]==*(s+1)))
{
ST7789_Set_Window(x,y,x+sizey-1,y+sizey-1);
for(i=0;i<TypefaceNum;i++)
{
for(j=0;j<8;j++)
{
if(!mode)//非叠加方式
{
if(tfont12[k].Msk[i]&(0x01<<j))ST7789_Write_Gram(fc);
else ST7789_Write_Gram(bc);
m++;
if(m%sizey==0)
{
m=0;
break;
}
}
else//叠加方式
{
if(tfont12[k].Msk[i]&(0x01<<j)) LCD_DrawPoint(x,y,fc);//画一个点
x++;
if((x-x0)==sizey)
{
x=x0;
y++;
break;
}
}
}
}
}
continue; //查找到对应点阵字库立即退出,防止多个汉字重复取模带来影响
}
}
/******************************************************************************
函数说明:显示单个16x16汉字
入口数据:x,y显示坐标
*s 要显示的汉字
fc 字的颜色
bc 字的背景色
sizey 字号
mode: 0非叠加模式 1叠加模式
返回值: 无
******************************************************************************/
void LCD_ShowChinese16x16(uint16_t x,uint16_t y,uint8_t *s,uint16_t fc,uint16_t bc,uint8_t sizey,uint8_t mode)
{
uint8_t i,j,m=0;
uint16_t k;
uint16_t HZnum;//汉字数目
uint16_t TypefaceNum;//一个字符所占字节大小
uint16_t x0=x;
TypefaceNum=(sizey/8+((sizey%8)?1:0))*sizey;
HZnum=sizeof(tfont16)/sizeof(typFNT_GB16); //统计汉字数目
for(k=0;k<HZnum;k++)
{
if ((tfont16[k].Index[0]==*(s))&&(tfont16[k].Index[1]==*(s+1)))
{
ST7789_Set_Window(x,y,x+sizey-1,y+sizey-1);
for(i=0;i<TypefaceNum;i++)
{
for(j=0;j<8;j++)
{
if(!mode)//非叠加方式
{
if(tfont16[k].Msk[i]&(0x01<<j))ST7789_Write_Gram(fc);
else ST7789_Write_Gram(bc);
m++;
if(m%sizey==0)
{
m=0;
break;
}
}
else//叠加方式
{
if(tfont16[k].Msk[i]&(0x01<<j)) LCD_DrawPoint(x,y,fc);//画一个点
x++;
if((x-x0)==sizey)
{
x=x0;
y++;
break;
}
}
}
}
}
continue; //查找到对应点阵字库立即退出,防止多个汉字重复取模带来影响
}
}
/******************************************************************************
函数说明:显示单个24x24汉字
入口数据:x,y显示坐标
*s 要显示的汉字
fc 字的颜色
bc 字的背景色
sizey 字号
mode: 0非叠加模式 1叠加模式
返回值: 无
******************************************************************************/
void LCD_ShowChinese24x24(uint16_t x,uint16_t y,uint8_t *s,uint16_t fc,uint16_t bc,uint8_t sizey,uint8_t mode)
{
uint8_t i,j,m=0;
uint16_t k;
uint16_t HZnum;//汉字数目
uint16_t TypefaceNum;//一个字符所占字节大小
uint16_t x0=x;
TypefaceNum=(sizey/8+((sizey%8)?1:0))*sizey;
HZnum=sizeof(tfont24)/sizeof(typFNT_GB24); //统计汉字数目
for(k=0;k<HZnum;k++)
{
if ((tfont24[k].Index[0]==*(s))&&(tfont24[k].Index[1]==*(s+1)))
{
ST7789_Set_Window(x,y,x+sizey-1,y+sizey-1);
for(i=0; i <TypefaceNum; i++)
{
for(j=0;j<8;j++)
{
if(!mode)//非叠加方式
{
if(tfont24[k].Msk[i]&(0x01<<j))ST7789_Write_Gram(fc);
else ST7789_Write_Gram(bc);
m++;
if(m%sizey==0)
{
m=0;
break;
}
}
else//叠加方式
{
if(tfont24[k].Msk[i]&(0x01<<j)) LCD_DrawPoint(x,y,fc);//画一个点
x++;
if((x-x0)==sizey)
{
x=x0;
y++;
break;
}
}
}
}
}
continue; //查找到对应点阵字库立即退出,防止多个汉字重复取模带来影响
}
}
/******************************************************************************
函数说明:显示单个32x32汉字
入口数据:x,y显示坐标
*s 要显示的汉字
fc 字的颜色
bc 字的背景色
sizey 字号
mode: 0非叠加模式 1叠加模式
返回值: 无
******************************************************************************/
void LCD_ShowChinese32x32(uint16_t x,uint16_t y,uint8_t *s,uint16_t fc,uint16_t bc,uint8_t sizey,uint8_t mode)
{
uint8_t i,j,m=0;
uint16_t k;
uint16_t HZnum;//汉字数目
uint16_t TypefaceNum;//一个字符所占字节大小
uint16_t x0=x;
TypefaceNum=(sizey/8+((sizey%8)?1:0))*sizey;
HZnum=sizeof(tfont32)/sizeof(typFNT_GB32); //统计汉字数目
for(k=0;k<HZnum;k++)
{
if ((tfont32[k].Index[0]==*(s))&&(tfont32[k].Index[1]==*(s+1)))
{
ST7789_Set_Window(x,y,x+sizey-1,y+sizey-1);
for(i=0;i<TypefaceNum;i++)
{
for(j=0;j<8;j++)
{
if(!mode)//非叠加方式
{
if(tfont32[k].Msk[i]&(0x01<<j))ST7789_Write_Gram(fc);
else ST7789_Write_Gram(bc);
m++;
if(m%sizey==0)
{
m=0;
break;
}
}
else//叠加方式
{
if(tfont32[k].Msk[i]&(0x01<<j)) LCD_DrawPoint(x,y,fc);//画一个点
x++;
if((x-x0)==sizey)
{
x=x0;
y++;
break;
}
}
}
}
}
continue; //查找到对应点阵字库立即退出,防止多个汉字重复取模带来影响
}
}
/******************************************************************************
函数说明:显示单个字符
入口数据:x,y显示坐标
num 要显示的字符
fc 字的颜色
bc 字的背景色
sizey 字号
mode: 0非叠加模式 1叠加模式
返回值: 无
******************************************************************************/
void LCD_ShowChar(uint16_t x,uint16_t y,uint8_t num,uint16_t fc,uint16_t bc,uint8_t sizey,uint8_t mode)
{
uint8_t temp,sizex,t,m=0;
uint16_t i,TypefaceNum;//一个字符所占字节大小
uint16_t x0=x;
sizex=sizey/2;
TypefaceNum=(sizex/8+((sizex%8)?1:0))*sizey;
num=num-' '; //得到偏移后的值
ST7789_Set_Window(x,y,x+sizex-1,y+sizey-1); //设置光标位置
for(i=0;i<TypefaceNum;i++)
{
if(sizey==12)temp=ascii_1206[num][i]; //调用6x12字体
else if(sizey==16)temp=ascii_1608[num][i]; //调用8x16字体
else if(sizey==24)temp=ascii_2412[num][i]; //调用12x24字体
else if(sizey==32)temp=ascii_3216[num][i]; //调用16x32字体
else return;
for(t=0;t<8;t++)
{
if(!mode)//非叠加模式
{
if(temp&(0x01<<t))ST7789_Write_Gram(fc);
else ST7789_Write_Gram(bc);
m++;
if(m%sizex==0)
{
m=0;
break;
}
}
else//叠加模式
{
if(temp&(0x01<<t))LCD_DrawPoint(x,y,fc);//画一个点
x++;
if((x-x0)==sizex)
{
x=x0;
y++;
break;
}
}
}
}
}
/******************************************************************************
函数说明:显示字符串
入口数据:x,y显示坐标
*p 要显示的字符串
fc 字的颜色
bc 字的背景色
sizey 字号
mode: 0非叠加模式 1叠加模式
返回值: 无
******************************************************************************/
void LCD_ShowString(uint16_t x,uint16_t y,const uint8_t *p,uint16_t fc,uint16_t bc,uint8_t sizey,uint8_t mode)
{
while(*p!='\0')
{
LCD_ShowChar(x,y,*p,fc,bc,sizey,mode);
x+=sizey/2;
p++;
}
}
/******************************************************************************
函数说明:显示数字
入口数据:m底数,n指数
返回值: 无
******************************************************************************/
uint32_t mypow(uint8_t m,uint8_t n)
{
uint32_t result=1;
while(n--)result*=m;
return result;
}
/******************************************************************************
函数说明:显示整数变量
入口数据:x,y显示坐标
num 要显示整数变量
len 要显示的位数
fc 字的颜色
bc 字的背景色
sizey 字号
返回值: 无
******************************************************************************/
void LCD_ShowIntNum(uint16_t x,uint16_t y,uint16_t num,uint8_t len,uint16_t fc,uint16_t bc,uint8_t sizey)
{
uint8_t t,temp;
uint8_t enshow=0;
uint8_t sizex=sizey/2;
for(t=0;t<len;t++)
{
temp=(num/mypow(10,len-t-1))%10;
if(enshow==0&&t<(len-1))
{
if(temp==0)
{
LCD_ShowChar(x+t*sizex,y,' ',fc,bc,sizey,0);
continue;
}else enshow=1;
}
LCD_ShowChar(x+t*sizex,y,temp+48,fc,bc,sizey,0);
}
}
/******************************************************************************
函数说明:显示两位小数变量
入口数据:x,y显示坐标
num 要显示小数变量
len 要显示的位数
fc 字的颜色
bc 字的背景色
sizey 字号
返回值: 无
******************************************************************************/
void LCD_ShowFloatNum1(uint16_t x,uint16_t y,float num,uint8_t len,uint16_t fc,uint16_t bc,uint8_t sizey)
{
uint8_t t,temp,sizex;
uint16_t num1;
sizex=sizey/2;
num1=num*100;
for(t=0;t<len;t++)
{
temp=(num1/mypow(10,len-t-1))%10;
if(t==(len-2))
{
LCD_ShowChar(x+(len-2)*sizex,y,'.',fc,bc,sizey,0);
t++;
len+=1;
}
LCD_ShowChar(x+t*sizex,y,temp+48,fc,bc,sizey,0);
}
}
/******************************************************************************
函数说明:显示图片
入口数据:x,y起点坐标
length 图片长度
width 图片宽度
pic[] 图片数组
返回值: 无
******************************************************************************/
void LCD_ShowPicture(uint16_t x,uint16_t y,uint16_t length,uint16_t width,const uint8_t pic[])
{
uint16_t i,j;
uint32_t k=0;
ST7789_Set_Window(x,y,x+length-1,y+width-1);
for(i=0;i<length;i++)
{
for(j=0;j<width;j++)
{
uint16_t data = (pic[k*2+1] << 8) | pic[k*2];
ST7789_Write_Gram(data);
k++;
}
}
}
/**
* @brief 以图片中心为基准绘制图片
* @param center_x: 图片中心X坐标
* @param center_y: 图片中心Y坐标
* @param length: 图片宽度
* @param width: 图片高度
* @param pic: 图片数据数组
*
* 注意:函数将图片中心放在指定的(center_x, center_y)位置
*/
void LCD_ShowCentrePicture(uint16_t center_x, uint16_t center_y,
uint16_t length, uint16_t width,
const uint8_t pic[])
{
// 1. 计算左上角起点坐标
uint16_t start_x = center_x - (length / 2);
uint16_t start_y = center_y - (width / 2);
// 2. 设置显示窗口
ST7789_Set_Window(start_x, start_y,
start_x + length - 1,
start_y + width - 1);
// 3. 写入图片数据
uint16_t i, j;
uint32_t k = 0;
for(i = 0; i < length; i++) {
for(j = 0; j < width; j++) {
uint16_t data = (pic[k*2+1] << 8) | pic[k*2];
ST7789_Write_Gram(data);
k++;
}
}
}
/******************************************************************************
函数说明:显示中英文字符串(通用函数)
入口数据:x,y显示坐标
*str 要显示的字符串(支持中英文混合)
fc 字的颜色
bc 字的背景色
sizey 字号
mode: 0非叠加模式 1叠加模式
返回值: 无
******************************************************************************/
void LCD_ShowText(uint16_t x, uint16_t y, const uint8_t *str, uint16_t fc, uint16_t bc, uint8_t sizey, uint8_t mode)
{
uint16_t cursor_x = x;
uint16_t cursor_y = y;
while (*str != '\0')
{
// 判断是否为中文字符(GB2312编码,首字节0xA1~0xF7,次字节0xA1~0xFE)
if ((*str >= 0xA1) && (*str <= 0xF7))
{
// 检查是否有下一个字符
if (*(str + 1) != '\0' && (*(str + 1) >= 0xA1) && (*(str + 1) <= 0xFE))
{
// 显示单个汉字
if (sizey == 12)
LCD_ShowChinese12x12(cursor_x, cursor_y, (uint8_t *)str, fc, bc, sizey, mode);
else if (sizey == 16)
LCD_ShowChinese16x16(cursor_x, cursor_y, (uint8_t *)str, fc, bc, sizey, mode);
else if (sizey == 24)
LCD_ShowChinese24x24(cursor_x, cursor_y, (uint8_t *)str, fc, bc, sizey, mode);
else if (sizey == 32)
LCD_ShowChinese32x32(cursor_x, cursor_y, (uint8_t *)str, fc, bc, sizey, mode);
cursor_x += sizey; // 中文字符宽度
str += 2; // 跳过两个字节
continue;
}
}
// 判断是否为ASCII字符
else if (*str >= 0x20 && *str <= 0x7E)
{
// 显示单个ASCII字符
LCD_ShowChar(cursor_x, cursor_y, *str, fc, bc, sizey, mode);
cursor_x += sizey / 2; // ASCII字符宽度是中文的一半
}
// 处理换行符
else if (*str == '\n')
{
cursor_x = x; // 回到行首
cursor_y += sizey; // 换行
}
// 处理回车符
else if (*str == '\r')
{
cursor_x = x; // 回到行首
}
// 处理制表符(4个空格)
else if (*str == '\t')
{
cursor_x += (sizey / 2) * 4; // 4个空格宽度
}
// 其他字符(如控制字符)跳过
else
{
// 不显示的字符,跳过
}
str++; // 移动到下一个字符
}
}
/******************************************************************************
函数说明:显示中英文字符串(带换行自动处理)
入口数据:x,y显示坐标
width 显示区域宽度
*str 要显示的字符串
fc 字的颜色
bc 字的背景色
sizey 字号
mode: 0非叠加模式 1叠加模式
line_spacing 行间距
返回值: 实际显示的行数
******************************************************************************/
uint16_t LCD_ShowTextAutoWrap(uint16_t x, uint16_t y, uint16_t width,
const uint8_t *str, uint16_t fc, uint16_t bc,
uint8_t sizey, uint8_t mode, uint8_t line_spacing)
{
uint16_t cursor_x = x;
uint16_t cursor_y = y;
uint16_t line_count = 1;
uint16_t char_width = 0;
const uint8_t *line_start = str;
const uint8_t *word_start = str;
uint8_t is_chinese = 0;
while (*str != '\0')
{
// 判断字符类型
if ((*str >= 0xA1) && (*str <= 0xF7))
{
// 中文字符
if (*(str + 1) != '\0' && (*(str + 1) >= 0xA1) && (*(str + 1) <= 0xFE))
{
char_width = sizey; // 中文字符宽度
is_chinese = 1;
}
}
else if (*str >= 0x20 && *str <= 0x7E)
{
// ASCII字符
char_width = sizey / 2;
is_chinese = 0;
}
else if (*str == '\n')
{
// 强制换行
if (cursor_x != x)
{
// 显示当前行
uint8_t line_len = str - line_start;
uint8_t temp_str[256];
if (line_len < 255)
{
memcpy(temp_str, line_start, line_len);
temp_str[line_len] = '\0';
LCD_ShowText(x, cursor_y, temp_str, fc, bc, sizey, mode);
}
}
cursor_x = x;
cursor_y += sizey + line_spacing;
line_start = str + 1;
line_count++;
char_width = 0;
}
else if (*str == ' ')
{
char_width = sizey / 2; // 空格宽度
}
else
{
char_width = 0;
}
// 检查是否需要自动换行
if (cursor_x - x + char_width > width && cursor_x > x)
{
// 找到行内最后一个空格或标点符号进行换行
const uint8_t *wrap_pos = str;
uint8_t found_break = 0;
// 向前查找合适的断点
while (wrap_pos > word_start)
{
if (*wrap_pos == ' ' || *wrap_pos == ',' || *wrap_pos == '.' ||
*wrap_pos == ';' || *wrap_pos == '!' || *wrap_pos == '?' ||
*wrap_pos == ':')
{
found_break = 1;
break;
}
wrap_pos--;
}
if (!found_break)
{
wrap_pos = str; // 没有找到合适的断点,在当前字符前换行
}
// 显示当前行
uint8_t line_len = wrap_pos - line_start;
if (line_len > 0)
{
uint8_t temp_str[256];
if (line_len < 255)
{
memcpy(temp_str, line_start, line_len);
temp_str[line_len] = '\0';
LCD_ShowText(x, cursor_y, temp_str, fc, bc, sizey, mode);
}
}
// 准备下一行
cursor_x = x;
cursor_y += sizey + line_spacing;
line_start = wrap_pos;
if (found_break) line_start++; // 跳过断点字符
// 重置当前位置
str = line_start - 1; // 减1,因为循环末尾会自增
word_start = line_start;
line_count++;
}
else
{
cursor_x += char_width;
// 记录单词开始位置(用于英文断行)
if (*str == ' ' || is_chinese)
{
word_start = str;
if (*str == ' ') word_start++;
}
}
str++;
}
// 显示最后一行
if (line_start < str)
{
uint8_t last_line_len = str - line_start;
if (last_line_len > 0)
{
uint8_t temp_str[256];
if (last_line_len < 255)
{
memcpy(temp_str, line_start, last_line_len);
temp_str[last_line_len] = '\0';
LCD_ShowText(x, cursor_y, temp_str, fc, bc, sizey, mode);
}
}
}
return line_count;
}
2.GVGL层
内存管理
gui_mem.h
cpp
/******************************************************************************
* @file gui_mem.h
* @brief GUI内存管理头文件 - 栈式内存池实现,1KB固定大小内存管理
* @author 古焕发
* @version V1.0
* @date 2026-01-30
* @copyright Copyright (c) 2026
*
* @description 该头文件定义了嵌入式GUI系统的内存管理接口,采用栈式内存池设计,
* 提供固定1KB内存空间的分配、释放、压缩和完整性检查功能。
* 使用句柄代替指针,避免内存碎片,适合资源受限的嵌入式环境。
*
* @note 修改日志:
* - 2026-01-30 V1.0 古焕发 创建文件,实现栈式内存池管理
******************************************************************************/
#ifndef GUI_MEM_H
#define GUI_MEM_H
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
/*---------------------- 内存池配置常量 ----------------------*/
/** 内存池总大小配置(1KB固定空间) */
#define TOTAL_POOL_SIZE 1024 /**< 内存池总容量:1024字节(1KB) */
/** 句柄和块管理配置 */
#define MAX_HANDLES 16 /**< 最大句柄数量:支持同时管理16个内存块 */
/** 内存对齐配置(4字节对齐优化访问效率) */
#define ALIGNMENT 4 /**< 内存对齐字节数 */
#define ALIGN_MASK (ALIGNMENT - 1) /**< 对齐掩码 */
/**
* @brief 内存大小对齐宏
* @param[in] x: 需要对齐的内存大小
* @return 对齐后的内存大小
* @note 确保分配的内存块按4字节边界对齐,提高访问效率
*/
#define ALIGN_UP(x) (((x) + ALIGN_MASK) & ~ALIGN_MASK)
/*---------------------- 枚举类型定义 ----------------------*/
/**
* @brief 内存块状态枚举
* @note 定义内存块的生命周期状态,支持延迟释放和碎片整理机制
*/
typedef enum {
BLOCK_FREE = 0, /**< 空闲状态:块未被使用,可分配 */
BLOCK_USED, /**< 已用状态:块已被分配,正在使用 */
BLOCK_DANGLING, /**< 悬挂状态:已释放但尚未压缩回收 */
} block_state_t;
/*---------------------- 数据结构定义 ----------------------*/
/**
* @brief 内存块描述符结构
* @note 记录每个内存块的元数据信息,用于管理和追踪内存使用情况
*/
typedef struct {
uint16_t offset; /**< 在内存池中的偏移地址(相对于pool[0]) */
uint16_t size; /**< 块实际大小(包括对齐填充) */
uint8_t state; /**< 块当前状态(FREE/USED/DANGLING) */
uint8_t type; /**< 对象类型标识(由调用者定义用途) */
} block_desc_t;
/**
* @brief 栈式内存池主结构
* @note 完整的内存池管理系统,包含存储空间、管理数据和状态信息
*/
typedef struct {
uint8_t pool[TOTAL_POOL_SIZE]; /**< 1KB实际内存存储空间 */
block_desc_t blocks[MAX_HANDLES]; /**< 块描述符表(管理元数据) */
uint8_t handle_stack[MAX_HANDLES]; /**< 空闲句柄栈(LIFO管理) */
int16_t stack_top; /**< 栈顶指针(-1表示栈空) */
uint16_t alloc_ptr; /**< 当前分配指针(下一个空闲位置) */
uint16_t watermark; /**< 高水位标记(历史最大使用量) */
uint16_t compactions; /**< 内存压缩次数统计 */
} stack_mempool_t;
/*---------------------- 类型定义和常量 ----------------------*/
/** 内存句柄类型(代替直接指针使用) */
typedef uint8_t mem_handle_t;
/** 无效句柄常量 */
#define INVALID_HANDLE 0xFF /**< 无效句柄标识(分配失败返回值) */
/*---------------------- 全局变量声明 ----------------------*/
/** 全局内存池实例 */
extern stack_mempool_t mempool;
/*---------------------- 函数声明 ----------------------*/
/**
* @defgroup mem_init 内存池初始化函数
* @{
*/
/**
* @brief 初始化栈式内存池系统
* @return bool: 成功返回true,失败返回false
* @note 清零内存池,初始化空闲句柄栈,设置初始状态。
* 必须在其他内存操作函数之前调用。
*/
bool stack_mempool_init(void);
/** @} */
/**
* @defgroup mem_alloc 内存分配和释放函数
* @{
*/
/**
* @brief 从内存池分配指定大小的内存块
* @param[in] size: 请求的内存大小(字节)
* @param[in] type: 内存块类型标识(用户自定义)
* @return mem_handle_t: 成功返回有效句柄,失败返回INVALID_HANDLE
* @note 自动处理内存对齐,空间不足时尝试压缩,支持延迟释放机制。
* 返回句柄而非指针,避免内存碎片问题。
*/
mem_handle_t stack_mempool_alloc(uint16_t size, uint8_t type);
/**
* @brief 释放指定句柄对应的内存块
* @param[in] handle: 要释放的内存块句柄
* @return bool: 成功返回true,失败返回false
* @note 采用延迟释放策略,标记为悬挂状态,等待压缩时真正回收内存。
* 立即回收句柄到空闲栈供后续分配使用。
*/
bool stack_mempool_free(mem_handle_t handle);
/**
* @brief 执行内存压缩,解决内存碎片问题
* @retval 无
* @note 移动所有在用内存块到低地址,合并悬挂块,回收碎片空间。
* 压缩操作会更新所有块的偏移量,确保数据完整性。
*/
void stack_mempool_compact(void);
/** @} */
/**
* @defgroup mem_query 内存状态查询函数
* @{
*/
/**
* @brief 获取当前可用内存大小
* @return uint16_t: 可用内存字节数
* @note 基于当前分配指针计算剩余连续空间。
* 不包括悬挂块占用的空间(压缩后可回收)。
*/
uint16_t stack_mempool_available(void);
/**
* @brief 根据句柄获取对应的内存数据指针
* @param[in] handle: 内存块句柄
* @return void*: 成功返回数据指针,失败返回NULL
* @note 必须确保句柄有效且对应的内存块处于已用状态。
* 返回的指针在内存压缩后可能失效,使用后应及时更新。
*/
void* stack_mempool_get_ptr(mem_handle_t handle);
/**
* @brief 执行内存池完整性检查
* @return bool: 检查通过返回true,发现问题返回false
* @note 验证分配指针、栈指针、块描述符等关键数据的合理性。
* 用于调试和系统可靠性保障。
*/
bool stack_mempool_integrity_check(void);
/** @} */
/**
* @defgroup mem_debug 调试和诊断函数
* @{
*/
/**
* @brief 内存池状态转储(调试用)
* @retval 无
* @note 详细显示内存池使用情况、块状态和内存布局信息。
* 仅在调试版本中启用,生产环境建议关闭。
*/
void stack_mempool_dump(void);
/** @} */
#endif /* GUI_MEM_H */
gui_mem.c
cpp
#include "gui_mem.h"
#include <string.h>
#include <stdio.h>
/******************************************************************************
* @file gui_mem.c
* @brief 栈式内存池实现 - 1KB固定大小内存池,支持句柄管理和内存压缩
* @author 古焕发
* @version V1.0
* @date 2026-01-30
* @copyright Copyright (c) 2026
*
* @description 该文件实现了一个高效的栈式内存池管理系统,采用固定1KB内存空间,
* 支持句柄管理、内存压缩和完整性检查,适用于嵌入式GUI系统。
* 所有代码内容保持原样,仅添加注释和格式优化。
*
* @note 修改日志:
* - 2026-01-30 V1.0 古焕发 创建文件,实现栈式内存池管理
******************************************************************************/
#include "gui_mem.h"
#include <string.h>
#include <stdio.h>
/** 全局内存池实例 */
stack_mempool_t mempool = {0};
/**
* @brief 初始化内存池系统
* @return bool: 初始化成功返回true
* @note 清零内存池,初始化空闲句柄栈和块描述符,设置初始状态
*/
bool stack_mempool_init(void) {
/** 清零整个内存池结构 */
memset(&mempool, 0, sizeof(stack_mempool_t));
/** 初始化空闲句柄栈(从高到低填充) */
mempool.stack_top = MAX_HANDLES - 1;
for (int i = 0; i < MAX_HANDLES; i++) {
mempool.handle_stack[i] = i;
}
/** 初始化所有块描述符为空闲状态 */
for (int i = 0; i < MAX_HANDLES; i++) {
mempool.blocks[i].state = BLOCK_FREE;
mempool.blocks[i].offset = 0;
mempool.blocks[i].size = 0;
}
/** 初始化内存分配指针和统计信息 */
mempool.alloc_ptr = 0;
mempool.watermark = 0;
mempool.compactions = 0;
return true;
}
/**
* @brief 从内存池分配指定大小的内存块
* @param[in] size: 请求的内存大小(字节)
* @param[in] type: 内存块类型标识
* @return mem_handle_t: 成功返回有效句柄,失败返回INVALID_HANDLE
* @note 自动处理内存对齐,空间不足时尝试压缩,更新高水位标记
*/
mem_handle_t stack_mempool_alloc(uint16_t size, uint8_t type) {
/** 参数检查:大小不能为0,且必须有空闲句柄 */
if (size == 0 || mempool.stack_top < 0) {
return INVALID_HANDLE;
}
/** 计算对齐后的实际需要大小 */
uint16_t aligned_size = ALIGN_UP(size);
if (aligned_size == 0) {
aligned_size = ALIGNMENT;
}
/** 检查当前连续空间是否足够 */
uint16_t needed_space = mempool.alloc_ptr + aligned_size;
/** 空间不足时尝试内存压缩 */
if (needed_space > TOTAL_POOL_SIZE) {
stack_mempool_compact();
needed_space = mempool.alloc_ptr + aligned_size;
/** 压缩后仍然空间不足,返回错误 */
if (needed_space > TOTAL_POOL_SIZE) {
return INVALID_HANDLE;
}
}
/** 从栈顶弹出空闲句柄 */
mem_handle_t handle = mempool.handle_stack[mempool.stack_top--];
/** 设置块描述符信息 */
mempool.blocks[handle].offset = mempool.alloc_ptr;
mempool.blocks[handle].size = aligned_size;
mempool.blocks[handle].state = BLOCK_USED;
mempool.blocks[handle].type = type;
/** 更新内存分配指针 */
mempool.alloc_ptr += aligned_size;
/** 更新高水位标记(记录历史最大使用量) */
if (mempool.alloc_ptr > mempool.watermark) {
mempool.watermark = mempool.alloc_ptr;
}
return handle;
}
/**
* @brief 释放指定句柄对应的内存块
* @param[in] handle: 要释放的内存块句柄
* @return bool: 成功返回true,失败返回false
* @note 实际采用延迟释放策略,标记为悬挂状态,等待压缩时真正回收
*/
bool stack_mempool_free(mem_handle_t handle) {
/** 句柄有效性检查 */
if (handle >= MAX_HANDLES) {
return false;
}
/** 获取块描述符并检查状态 */
block_desc_t* block = &mempool.blocks[handle];
if (block->state != BLOCK_USED) {
return false; /**< 不是已使用的块 */
}
/** 标记为悬挂状态(延迟释放) */
block->state = BLOCK_DANGLING;
/** 将句柄压回空闲栈,供后续分配使用 */
if (mempool.stack_top < MAX_HANDLES - 1) {
mempool.handle_stack[++mempool.stack_top] = handle;
}
return true;
}
/**
* @brief 执行内存压缩,解决内存碎片问题
* @retval 无
* @note 移动所有在用内存块到低地址,回收悬挂块,更新分配指针
*/
void stack_mempool_compact(void) {
uint16_t new_offset = 0;
/** 创建临时数组记录需要移动的块信息 */
struct {
mem_handle_t handle; /**< 块句柄 */
uint16_t old_offset; /**< 原始偏移量 */
} move_list[MAX_HANDLES];
int move_count = 0;
/** 第一遍扫描:收集所有需要保留的已用块 */
for (int i = 0; i < MAX_HANDLES; i++) {
if (mempool.blocks[i].state == BLOCK_USED) {
move_list[move_count].handle = i;
move_list[move_count].old_offset = mempool.blocks[i].offset;
move_count++;
} else {
/** 释放悬挂块,标记为空闲状态 */
mempool.blocks[i].state = BLOCK_FREE;
}
}
/** 如果没有需要移动的块,直接重置分配指针 */
if (move_count == 0) {
mempool.alloc_ptr = 0;
mempool.compactions++;
return;
}
/** 第二遍扫描:按顺序移动内存块到新位置 */
for (int i = 0; i < move_count; i++) {
block_desc_t* block = &mempool.blocks[move_list[i].handle];
/** 检查是否需要实际移动(偏移量是否改变) */
if (new_offset != move_list[i].old_offset) {
/** 计算源地址和目标地址 */
uint8_t* src = &mempool.pool[move_list[i].old_offset];
uint8_t* dst = &mempool.pool[new_offset];
/** 执行内存移动操作 */
memmove(dst, src, block->size);
/** 更新块的偏移量 */
block->offset = new_offset;
}
/** 更新新偏移量指针 */
new_offset += block->size;
}
/** 更新全局分配指针和压缩计数 */
mempool.alloc_ptr = new_offset;
mempool.compactions++;
/** 可选:清零剩余内存区域(提高安全性) */
if (mempool.alloc_ptr < TOTAL_POOL_SIZE) {
memset(&mempool.pool[mempool.alloc_ptr], 0,
TOTAL_POOL_SIZE - mempool.alloc_ptr);
}
}
/**
* @brief 获取当前可用内存大小
* @return uint16_t: 可用内存字节数
* @note 基于当前分配指针计算剩余空间
*/
uint16_t stack_mempool_available(void) {
return TOTAL_POOL_SIZE - mempool.alloc_ptr;
}
/**
* @brief 根据句柄获取对应的内存数据指针
* @param[in] handle: 内存块句柄
* @return void*: 成功返回数据指针,失败返回NULL
* @note 必须确保句柄有效且对应的内存块处于已用状态
*/
void* stack_mempool_get_ptr(mem_handle_t handle) {
/** 句柄有效性检查和状态验证 */
if (handle >= MAX_HANDLES || mempool.blocks[handle].state != BLOCK_USED) {
return NULL;
}
/** 返回内存池中对应偏移量的数据指针 */
return &mempool.pool[mempool.blocks[handle].offset];
}
/**
* @brief 执行内存池完整性检查
* @return bool: 检查通过返回true,发现问题返回false
* @note 验证分配指针、栈指针、块描述符等关键数据的合理性
*/
bool stack_mempool_integrity_check(void) {
/** 检查分配指针是否超出总容量 */
if (mempool.alloc_ptr > TOTAL_POOL_SIZE) {
return false;
}
/** 检查栈指针范围是否合理 */
if (mempool.stack_top < -1 || mempool.stack_top >= MAX_HANDLES) {
return false;
}
/** 检查高水位标记是否合理 */
if (mempool.watermark > TOTAL_POOL_SIZE) {
return false;
}
/** 检查所有块描述符的完整性 */
uint16_t total_used = 0;
uint16_t used_count = 0;
for (int i = 0; i < MAX_HANDLES; i++) {
block_desc_t* block = &mempool.blocks[i];
if (block->state == BLOCK_USED) {
/** 检查块偏移量是否有效 */
if (block->offset >= TOTAL_POOL_SIZE) {
return false;
}
/** 检查块大小是否会导致越界 */
if (block->offset + block->size > TOTAL_POOL_SIZE) {
return false;
}
total_used += block->size;
used_count++;
}
}
/** 验证分配指针是否与已用内存总量匹配 */
uint16_t current_used = 0;
for (int i = 0; i < MAX_HANDLES; i++) {
if (mempool.blocks[i].state == BLOCK_USED) {
current_used += mempool.blocks[i].size;
}
}
if (current_used != mempool.alloc_ptr) {
return false;
}
return true;
}
/**
* @brief 内存池状态转储函数(已注释,需要时启用)
* @retval 无
* @note 详细显示内存池使用情况、块状态和内存布局信息
*/
/*
void stack_mempool_dump(void) {
printf("\n=== 1KB栈式内存池状态 ===\n");
printf("分配指针: %u bytes\n", mempool.alloc_ptr);
printf("可用内存: %u bytes\n", stack_mempool_available());
printf("高水位: %u bytes\n", mempool.watermark);
printf("压缩次数: %u\n", mempool.compactions);
printf("空闲句柄: %d\n", mempool.stack_top + 1);
printf("\n块状态:\n");
printf("ID | 状态 | 偏移 | 大小 | 类型\n");
printf("----|-------|-------|-------|-----\n");
for (int i = 0; i < MAX_HANDLES; i++) {
block_desc_t* block = &mempool.blocks[i];
if (block->state != BLOCK_FREE) {
const char* state_str = "";
switch (block->state) {
case BLOCK_USED: state_str = "已用"; break;
case BLOCK_DANGLING: state_str = "悬挂"; break;
case BLOCK_FREE: state_str = "空闲"; break;
}
printf("%2d | %-6s| %-5u | %-5u | %-3u\n",
i, state_str, block->offset, block->size, block->type);
}
}
// 显示内存布局图形化表示
printf("\n内存布局:\n");
char layout[TOTAL_POOL_SIZE / 16 + 1] = {0};
for (int i = 0; i < TOTAL_POOL_SIZE / 16; i++) {
layout[i] = '.';
}
for (int i = 0; i < MAX_HANDLES; i++) {
if (mempool.blocks[i].state == BLOCK_USED) {
uint16_t start = mempool.blocks[i].offset / 16;
uint16_t end = (mempool.blocks[i].offset + mempool.blocks[i].size - 1) / 16;
for (uint16_t j = start; j <= end && j < sizeof(layout); j++) {
layout[j] = 'X';
}
}
}
printf("内存使用: |%.*s|\n", TOTAL_POOL_SIZE / 16, layout);
printf(" 0");
for (int i = 1; i < TOTAL_POOL_SIZE / 16; i++) {
if (i % 8 == 0) printf("%d", i/8);
else printf(" ");
}
printf("%d bytes\n", TOTAL_POOL_SIZE);
}
*/
对象管理器+渲染
gui_obj_mgr.h
cpp
/******************************************************************************
* @file gui_obj_mgr.h
* @brief GUI对象管理系统头文件 - 提供图形对象创建、管理、渲染和事件处理功能
* @author 古焕发
* @version V1.0
* @date 2026-01-30
* @copyright Copyright (c) 2026
*
* @description 该头文件定义了完整的GUI对象管理系统,采用分层对象模型设计,
* 支持多种图形元素、相对定位、事件回调等高级特性。
* 所有代码内容保持原样,仅添加注释和格式优化。
*
* @note 修改日志:
* - 2026-01-30 V1.0 [您的姓名] 创建文件,定义完整的GUI对象管理系统
******************************************************************************/
#ifndef GUI_OBJ_MGR_H
#define GUI_OBJ_MGR_H
#include <stdint.h>
#include <stdbool.h>
#include "gui_mem.h"
/*---------------------- 颜色定义常量 ----------------------*/
/** 颜色类型定义 (RGB565格式) */
typedef uint16_t gui_color_t;
/** 特殊颜色标记 - 使用父对象背景色 */
#define USE_PARENT_BG 0xFEFE /**< 使用父对象背景色的特殊标记 */
/** 基础颜色定义 (RGB565格式) */
#define GUI_COLOR_BLACK 0x0000 /**< 黑色 */
#define GUI_COLOR_WHITE 0xFFFF /**< 白色 */
#define GUI_COLOR_RED 0xF800 /**< 红色 */
#define GUI_COLOR_GREEN 0x07E0 /**< 绿色 */
#define GUI_COLOR_BLUE 0x001F /**< 蓝色 */
#define GUI_COLOR_YELLOW 0xFFE0 /**< 黄色 */
#define GUI_COLOR_CYAN 0x07FF /**< 青色 */
#define GUI_COLOR_MAGENTA 0xF81F /**< 洋红色 */
#define GUI_COLOR_GRAY 0x8410 /**< 灰色 */
#define GUI_COLOR_LIGHTGRAY 0xC618 /**< 浅灰色 */
#define GUI_COLOR_DARKGRAY 0x4208 /**< 深灰色 */
/** 暖色系扩展 */
#define GUI_COLOR_ORANGE 0xFD20 /**< 橙色 */
#define GUI_COLOR_BROWN 0xBC40 /**< 棕色 */
#define GUI_COLOR_BRRED 0xFC07 /**< 棕红色 */
#define GUI_COLOR_PINK 0xFE19 /**< 粉色 */
#define GUI_COLOR_CORAL 0xFBEA /**< 珊瑚色 */
#define GUI_COLOR_GOLD 0xFEA0 /**< 金色 */
#define GUI_COLOR_MAROON 0x7800 /**< 栗色 */
#define GUI_COLOR_PURPLE 0x8010 /**< 紫色 */
#define GUI_COLOR_OLIVE 0x5340 /**< 橄榄绿 */
/** 冷色系扩展 */
#define GUI_COLOR_DARKBLUE 0x01CF /**< 深蓝色 */
#define GUI_COLOR_LIGHTBLUE 0x7D7C /**< 浅蓝色 */
#define GUI_COLOR_GRAYBLUE 0x5458 /**< 灰蓝色 */
#define GUI_COLOR_NAVY 0x000F /**< 海军蓝 */
#define GUI_COLOR_TEAL 0x0410 /**< 青蓝色 */
#define GUI_COLOR_TURQUOISE 0x06FF /**< 绿松石色 */
#define GUI_COLOR_DARKGREEN 0x0320 /**< 深绿色 */
#define GUI_COLOR_LIGHTGREEN 0x841F /**< 浅绿色 */
#define GUI_COLOR_PALEGREEN 0x9FD3 /**< 淡绿色 */
#define GUI_COLOR_SEAGREEN 0x2C4A /**< 海绿色 */
#define GUI_COLOR_AQUAMARINE 0x7FFA /**< 海蓝宝石色 */
#define GUI_COLOR_EMERALDGREEN 0x07D0 /**< 宝石绿色 */
#define GUI_COLOR_SPRINGGREEN 0x07EF /**< 春绿色 */
#define GUI_COLOR_MEDIUMSPRINGGREEN 0x06D3 /**< 中春绿色 */
/** 中性色与特殊色扩展 */
#define GUI_COLOR_SILVER 0xC618 /**< 银色 */
#define GUI_COLOR_VIOLET 0xEC1D /**< 紫罗兰色 */
#define GUI_COLOR_INDIGO 0x4810 /**< 靛蓝色 */
#define GUI_COLOR_MINTCREAM 0xF7FE /**< 薄荷奶油色 */
#define GUI_COLOR_MINTPASTEL 0x8FF0 /**< 柔和薄荷色 */
#define GUI_COLOR_PALETURQUOISE 0xAF7D /**< 苍绿松石色 */
#define GUI_COLOR_DARKTURQUOISE 0x067A /**< 深绿松石色 */
#define GUI_COLOR_MEDIUMTURQUOISE 0x4E0B /**< 中绿松石色 */
/** 专业UI色系扩展 */
#define GUI_COLOR_SLATEBLUE 0x6AD9 /**< 石板蓝色 */
#define GUI_COLOR_CADETBLUE 0x5CF4 /**< 军服蓝色 */
#define GUI_COLOR_POWDERBLUE 0xAEFC /**< 粉末蓝色 */
#define GUI_COLOR_LGRAYBLUE 0xA651 /**< 浅灰蓝色(中间层颜色) */
#define GUI_COLOR_LBBLUE 0x2B12 /**< 浅棕蓝色(选择条反色) */
/*---------------------- 基本类型定义 ----------------------*/
/** 图形对象句柄类型 (基于内存管理器) */
typedef mem_handle_t gui_handle_t;
/** 无效句柄常量 */
#define GUI_INVALID_HANDLE INVALID_HANDLE
/*---------------------- 枚举类型定义 ----------------------*/
/**
* @brief 字体大小枚举
* @note 支持12、16、24、32四种标准字号
*/
typedef enum {
GUI_FONT_12 = 12, /**< 12像素字体 */
GUI_FONT_16 = 16, /**< 16像素字体 */
GUI_FONT_24 = 24, /**< 24像素字体 */
GUI_FONT_32 = 32 /**< 32像素字体 */
} gui_font_size_t;
/**
* @brief 图形对象类型枚举
* @note 定义系统中支持的各类图形对象类型
*/
typedef enum {
GUI_OBJ_TYPE_WIDGET = 1, /**< 基本控件类型 */
GUI_OBJ_TYPE_LABEL, /**< 标签对象 */
GUI_OBJ_TYPE_LABEL_WRAP, /**< 自动换行标签 */
GUI_OBJ_TYPE_BUTTON, /**< 按钮对象 */
GUI_OBJ_TYPE_PANEL, /**< 面板容器 */
GUI_OBJ_TYPE_IMAGE, /**< 图片对象 */
GUI_OBJ_TYPE_WINDOW /**< 窗口对象 */
} gui_obj_type_t;
/**
* @brief 事件类型枚举
* @note 定义GUI系统支持的用户交互事件类型
*/
typedef enum {
GUI_EVENT_CLICK = 0, /**< 点击事件 */
GUI_EVENT_PRESS, /**< 按下事件 */
GUI_EVENT_RELEASE, /**< 释放事件 */
GUI_EVENT_LONG_PRESS, /**< 长按事件 */
GUI_EVENT_UPDATE /**< 更新事件 */
} gui_event_t;
/**
* @brief 对齐方式枚举
* @note 定义对象在容器内的水平和对齐方式
*/
typedef enum {
GUI_ALIGN_LEFT = 0, /**< 左对齐 */
GUI_ALIGN_CENTER, /**< 居中对齐 */
GUI_ALIGN_RIGHT, /**< 右对齐 */
GUI_ALIGN_TOP = 0, /**< 顶部对齐 */
GUI_ALIGN_MIDDLE, /**< 垂直居中 */
GUI_ALIGN_BOTTOM /**< 底部对齐 */
} gui_align_t;
/**
* @brief 定位模式枚举
* @note 定义对象相对于父容器或屏幕的定位方式
*/
typedef enum {
GUI_POS_MODE_ABSOLUTE = 0, /**< 绝对定位(相对于屏幕) */
GUI_POS_MODE_RELATIVE, /**< 相对定位(相对于父对象) */
GUI_POS_MODE_ANCHOR /**< 锚点定位 */
} gui_pos_mode_t;
/**
* @brief 锚点类型枚举
* @note 定义对象在父容器内的锚点位置,用于精确定位
*/
typedef enum {
GUI_ANCHOR_NONE = 0, /**< 无锚点 */
GUI_ANCHOR_TOP_LEFT, /**< 左上角锚点 */
GUI_ANCHOR_TOP_CENTER, /**< 顶部居中锚点 */
GUI_ANCHOR_TOP_RIGHT, /**< 右上角锚点 */
GUI_ANCHOR_CENTER_LEFT, /**< 左侧居中锚点 */
GUI_ANCHOR_CENTER, /**< 中心锚点 */
GUI_ANCHOR_CENTER_RIGHT, /**< 右侧居中锚点 */
GUI_ANCHOR_BOTTOM_LEFT, /**< 左下角锚点 */
GUI_ANCHOR_BOTTOM_CENTER, /**< 底部居中锚点 */
GUI_ANCHOR_BOTTOM_RIGHT /**< 右下角锚点 */
} gui_anchor_t;
/**
* @brief 面板形状类型枚举
* @note 定义面板对象支持的各种形状样式
*/
typedef enum {
GUI_PANEL_SHAPE_RECT = 0, /**< 矩形面板 */
GUI_PANEL_SHAPE_ROUNDED, /**< 圆角矩形面板 */
GUI_PANEL_SHAPE_CIRCLE, /**< 圆形面板 */
GUI_PANEL_SHAPE_ARC /**< 圆弧形状面板 */
} gui_panel_shape_t;
/*---------------------- 数据结构定义 ----------------------*/
/**
* @brief 数据块尾部结构(用于存储句柄信息)
* @note 附加在数据块末尾,用于管理数据块的生命周期和内存管理
*/
typedef struct {
mem_handle_t data_handle; /**< 数据块句柄 */
} gui_data_footer_t;
/**
* @brief 坐标点结构
* @note 定义二维坐标系中的点坐标
*/
typedef struct {
int16_t x; /**< X坐标 */
int16_t y; /**< Y坐标 */
} gui_point_t;
/**
* @brief 自动换行标签数据结构
* @note 用于支持文本自动换行功能的标签对象
*/
typedef struct {
gui_font_size_t font_size; /**< 字体大小 */
int16_t max_width; /**< 最大宽度(0表示不限制) */
int16_t max_lines; /**< 最大行数 */
int16_t char_width; /**< 字符宽度 */
int16_t char_height; /**< 字符高度 */
char* text; /**< 文本内容指针 */
} gui_label_wrap_data_t;
/**
* @brief 面板扩展数据结构(紧密打包)
* @note 使用#pragma pack确保结构体紧密排列,节省内存空间
*/
#pragma pack(push, 1) // 确保紧密打包
typedef struct {
gui_panel_shape_t shape; /**< 面板形状类型 */
uint8_t radius; /**< 圆角半径/圆弧半径 */
uint8_t thickness; /**< 边框粗细 */
uint16_t start_angle; /**< 圆弧起始角度(0-359度) */
uint16_t end_angle; /**< 圆弧结束角度(0-359度) */
} gui_panel_data_t;
#pragma pack(pop)
/**
* @brief 矩形区域结构
* @note 定义矩形区域的坐标和尺寸信息
*/
typedef struct {
int16_t x; /**< 左上角X坐标 */
int16_t y; /**< 左上角Y坐标 */
int16_t width; /**< 矩形宽度 */
int16_t height; /**< 矩形高度 */
} gui_rect_t;
/**
* @brief 相对定位信息结构
* @note 存储对象相对于父对象的定位信息和锚点设置
*/
typedef struct {
gui_handle_t parent; /**< 父对象句柄 */
int16_t rel_x; /**< 相对于父对象的X坐标偏移 */
int16_t rel_y; /**< 相对于父对象的Y坐标偏移 */
gui_pos_mode_t pos_mode; /**< 定位模式 */
gui_anchor_t anchor; /**< 锚点类型 */
} gui_pos_info_t;
/*---------------------- 对象特定数据结构 ----------------------*/
/** 基本图形对象结构前置声明 */
typedef struct gui_obj_t gui_obj_t;
/**
* @brief 事件回调函数类型定义
* @param[in] handle: 触发事件的对象句柄
* @param[in] event: 事件类型
* @note 用于处理用户交互事件的回调函数接口
*/
typedef void (*gui_event_callback_t)(gui_handle_t handle, uint8_t event);
/**
* @brief 标签对象数据结构
* @note 存储标签对象的文本和字体信息
*/
typedef struct {
char* text; /**< 文本内容指针 */
gui_font_size_t font_size; /**< 字体大小 */
} gui_label_data_t;
/**
* @brief 按钮对象数据结构
* @note 存储按钮对象的文本、字体和状态信息
*/
typedef struct {
char* text; /**< 按钮文本 */
gui_font_size_t font_size; /**< 字体大小 */
bool pressed; /**< 按钮按下状态 */
} gui_button_data_t;
/**
* @brief 图片对象数据结构
* @note 存储图片对象的数据指针和相关信息
*/
typedef struct {
const uint8_t* image_data; /**< 图片数据指针 */
} gui_image_data_t;
/*---------------------- 核心对象结构定义 ----------------------*/
/**
* @brief 基本图形对象结构
* @note 所有GUI对象的基类,包含对象的通用属性和方法
* 采用4字节对齐优化内存访问效率
*/
struct gui_obj_t {
/* 基本属性 */
gui_obj_type_t type; /**< 对象类型 */
bool visible; /**< 可见性标志 */
bool updated; /**< 更新标志 */
/* 几何属性 */
int16_t x; /**< X坐标(绝对或相对) */
int16_t y; /**< Y坐标(绝对或相对) */
int16_t width; /**< 对象宽度 */
int16_t height; /**< 对象高度 */
/* 外观属性 */
gui_color_t color; /**< 前景颜色 */
gui_color_t bg_color; /**< 背景颜色 */
/* 事件回调 */
gui_event_callback_t callback; /**< 事件回调函数指针 */
void* data; /**< 对象特定数据指针 */
/* 相对定位信息 */
gui_handle_t parent; /**< 父对象句柄 */
int16_t rel_x; /**< 相对于父对象的X坐标 */
int16_t rel_y; /**< 相对于父对象的Y坐标 */
gui_pos_mode_t pos_mode; /**< 定位模式 */
gui_anchor_t anchor; /**< 锚点类型 */
} __attribute__((aligned(4))); /**< 4字节对齐,优化内存访问 */
/*---------------------- 系统初始化和诊断函数 ----------------------*/
/**
* @brief 执行内存对齐诊断
* @retval 无
* @note 用于调试阶段检查内存对齐情况,确保数据结构正确对齐
*/
void diagnose_memory_alignment(void);
/**
* @brief 执行完整内存测试
* @retval 无
* @note 测试所有内存相关功能,包括分配、释放、边界检查等完整性测试
*/
void test_memory_all(void);
/**
* @brief 初始化GUI图形系统
* @return bool: 初始化成功返回true,失败返回false
* @note 必须在调用其他GUI函数前执行,负责初始化内存管理和基本硬件
*/
bool gui_init(void);
/*---------------------- 基础绘制函数 ----------------------*/
/**
* @brief 自动换行文本绘制函数
* @param[in] x, y: 文本起始坐标
* @param[in] width: 文本区域宽度
* @param[in] text: 要显示的文本字符串
* @param[in] color: 文本颜色
* @param[in] bg_color: 背景颜色
* @param[in] font_size: 字体大小
* @param[in] line_spacing: 行间距
* @retval 无
* @note 支持自动换行功能,适合显示长文本内容
*/
void gui_draw_text_auto(int16_t x, int16_t y, int16_t width, const char* text,
gui_color_t color, gui_color_t bg_color,
gui_font_size_t font_size, uint8_t line_spacing);
/*---------------------- 对象管理核心函数 ----------------------*/
/**
* @brief 创建基本图形对象
* @param[in] type: 对象类型
* @param[in] x, y: 对象位置坐标
* @param[in] width, height: 对象尺寸
* @return gui_handle_t: 成功返回对象句柄,失败返回GUI_INVALID_HANDLE
* @note 这是创建所有图形对象的基础函数,分配内存并初始化基本属性
*/
gui_handle_t gui_create_obj(gui_obj_type_t type, int16_t x, int16_t y,
int16_t width, int16_t height);
/**
* @brief 销毁图形对象并释放资源
* @param[in] handle: 要销毁的对象句柄
* @return bool: 成功返回true,失败返回false
* @note 会递归销毁所有子对象,并释放相关内存资源
*/
bool gui_destroy_obj(gui_handle_t handle);
/**
* @brief 设置对象位置
* @param[in] handle: 目标对象句柄
* @param[in] x, y: 新的位置坐标
* @return bool: 成功返回true,失败返回false
* @note 设置对象的绝对或相对位置,触发界面更新
*/
bool gui_set_obj_pos(gui_handle_t handle, int16_t x, int16_t y);
/**
* @brief 设置对象尺寸
* @param[in] handle: 目标对象句柄
* @param[in] width, height: 新的尺寸
* @return bool: 成功返回true,失败返回false
* @note 调整对象宽度和高度,可能影响子对象布局
*/
bool gui_set_obj_size(gui_handle_t handle, int16_t width, int16_t height);
/**
* @brief 设置对象可见性
* @param[in] handle: 目标对象句柄
* @param[in] visible: 可见性标志
* @return bool: 成功返回true,失败返回false
* @note 控制对象显示或隐藏,隐藏对象不参与渲染但仍保留在内存中
*/
bool gui_set_obj_visible(gui_handle_t handle, bool visible);
/**
* @brief 设置对象事件回调函数
* @param[in] handle: 目标对象句柄
* @param[in] callback: 回调函数指针
* @return bool: 成功返回true,失败返回false
* @note 注册用户交互事件的处理函数,如点击、触摸等事件
*/
bool gui_set_obj_callback(gui_handle_t handle, gui_event_callback_t callback);
/**
* @brief 根据句柄获取对象指针
* @param[in] handle: 对象句柄
* @return gui_obj_t*: 成功返回对象指针,失败返回NULL
* @note 返回的指针在对象销毁后无效,使用时需确保对象生命周期
*/
gui_obj_t* gui_get_obj(gui_handle_t handle);
/*---------------------- 特定对象创建函数 ----------------------*/
// 基本对象创建函数
gui_handle_t gui_create_label(int16_t x, int16_t y, const char* text,
gui_font_size_t font_size, gui_color_t color,
gui_color_t bg_color);
gui_handle_t gui_create_button(int16_t x, int16_t y, int16_t width, int16_t height,
const char* text, gui_font_size_t font_size);
gui_handle_t gui_create_panel(int16_t x, int16_t y, int16_t width, int16_t height,
gui_color_t color, gui_color_t border_color);
gui_handle_t gui_create_image(int16_t x, int16_t y, int16_t width, int16_t height,
const uint8_t* image_data);
// 扩展面板创建函数
gui_handle_t gui_create_panel_ex(int16_t x, int16_t y, int16_t width, int16_t height,
gui_color_t color, gui_color_t border_color,
gui_panel_shape_t shape, uint8_t radius, uint8_t thickness,
uint16_t start_angle, uint16_t end_angle);
gui_handle_t gui_create_rounded_panel(int16_t x, int16_t y, int16_t width, int16_t height,
gui_color_t color, gui_color_t border_color,
uint8_t radius, uint8_t thickness);
gui_handle_t gui_create_circle_panel(int16_t x, int16_t y, int16_t diameter,
gui_color_t color, gui_color_t border_color,
uint8_t thickness);
gui_handle_t gui_create_arc_panel(int16_t x, int16_t y, uint8_t radius,
gui_color_t color, gui_color_t border_color,
uint8_t thickness, uint16_t start_angle, uint16_t end_angle);
/*---------------------- 相对定位对象创建函数 ----------------------*/
gui_handle_t gui_create_panel_relative(gui_handle_t parent, int16_t rel_x, int16_t rel_y,
int16_t width, int16_t height,
gui_color_t color, gui_color_t border_color);
gui_handle_t gui_create_panel_anchor(gui_handle_t parent, gui_anchor_t anchor,
int16_t offset_x, int16_t offset_y,
int16_t width, int16_t height,
gui_color_t color, gui_color_t border_color);
gui_handle_t gui_create_label_relative(gui_handle_t parent, int16_t rel_x, int16_t rel_y,
const char* text, gui_font_size_t font_size,
gui_color_t color, gui_color_t bg_color);
gui_handle_t gui_create_label_anchor(gui_handle_t parent, gui_anchor_t anchor,
int16_t offset_x, int16_t offset_y,
const char* text, gui_font_size_t font_size,
gui_color_t color, gui_color_t bg_color);
gui_handle_t gui_create_button_relative(gui_handle_t parent, int16_t rel_x, int16_t rel_y,
int16_t width, int16_t height,
const char* text, gui_font_size_t font_size);
/*---------------------- 对象属性设置函数 ----------------------*/
bool gui_set_text(gui_handle_t handle, const char* text);
bool gui_set_color(gui_handle_t handle, gui_color_t color);
bool gui_set_bg_color(gui_handle_t handle, gui_color_t bg_color);
bool gui_set_font_size(gui_handle_t handle, gui_font_size_t font_size);
bool gui_set_image(gui_handle_t handle, const uint8_t* image_data);
bool gui_set_panel_shape(gui_handle_t handle, gui_panel_shape_t shape,
uint8_t radius, uint8_t thickness);
bool gui_set_panel_arc_angle(gui_handle_t handle, uint16_t start_angle,
uint16_t end_angle);
bool gui_set_panel_radius(gui_handle_t handle, uint8_t radius);
/*---------------------- 相对定位相关函数 ----------------------*/
bool gui_set_parent(gui_handle_t child, gui_handle_t parent);
bool gui_set_relative_position(gui_handle_t child, int16_t rel_x, int16_t rel_y);
bool gui_set_anchor(gui_handle_t child, gui_anchor_t anchor,
int16_t offset_x, int16_t offset_y);
bool gui_update_relative_position(gui_handle_t parent);
/*---------------------- 渲染和事件处理函数 ----------------------*/
void gui_render_obj(gui_handle_t handle);
void gui_render_all(void);
void gui_handle_touch(int16_t x, int16_t y, uint8_t event);
void gui_update(void);
/*---------------------- 内存管理函数 ----------------------*/
uint16_t gui_get_memory_used(void);
uint16_t gui_get_memory_available(void);
bool gui_memory_check(void);
#endif /* GUI_OBJ_MGR_H */
gui_obj_mgr.c
cpp
#include "gui_obj_mgr.h"
#include "lcd_draw.h"
#include "bsp_all.h"
#include <string.h>
#include <stdio.h>
/******************************************************************************
* @file gui_obj_mgr.c
* @brief GUI对象管理系统实现文件 - 提供图形对象创建、管理、渲染和事件处理功能
* @author 古焕发
* @version V1.0
* @date 2026-01-30
* @copyright Copyright (c) 2026
*
* @description 该文件实现了GUI对象管理系统的核心功能,包括图形对象的创建、销毁、
* 管理、渲染和事件处理等。采用分层对象模型设计,支持多种图形元素、
* 相对定位、事件回调等高级特性。
*
* @note 修改日志:
* - 2026-01-30 V1.0 古焕发 创建文件,实现完整的GUI对象管理系统
******************************************************************************/
/*---------------------- 内部辅助函数 ----------------------*/
/******************************************************************************
函数说明:获取数据块的句柄
入口数据:type 对象类型
data_ptr 数据指针
返回值: 数据句柄
******************************************************************************/
static mem_handle_t get_data_handle_from_footer(gui_obj_type_t type, void* data_ptr) {
if (!data_ptr) return INVALID_HANDLE;
gui_data_footer_t* footer = NULL;
switch (type) {
case GUI_OBJ_TYPE_LABEL: {
gui_label_data_t* label_data = (gui_label_data_t*)data_ptr;
if (!label_data->text) return INVALID_HANDLE;
int text_len = strlen(label_data->text) + 1;
footer = (gui_data_footer_t*)((uint8_t*)label_data +
sizeof(gui_label_data_t) + text_len);
break;
}
case GUI_OBJ_TYPE_BUTTON: {
gui_button_data_t* button_data = (gui_button_data_t*)data_ptr;
if (!button_data->text) return INVALID_HANDLE;
int text_len = strlen(button_data->text) + 1;
footer = (gui_data_footer_t*)((uint8_t*)button_data +
sizeof(gui_button_data_t) + text_len);
break;
}
case GUI_OBJ_TYPE_IMAGE: {
footer = (gui_data_footer_t*)((uint8_t*)data_ptr + sizeof(gui_image_data_t));
break;
}
default:
return INVALID_HANDLE;
}
return footer ? footer->data_handle : INVALID_HANDLE;
}
/******************************************************************************
函数说明:渲染圆角矩形面板(修复版本)
入口数据:obj 对象指针
panel_data 面板数据
返回值: 无
******************************************************************************/
static void render_rounded_panel_fixed(gui_obj_t* obj, gui_panel_data_t* panel_data) {
if (!panel_data) return;
uint8_t radius = panel_data->radius;
uint8_t border_thickness = panel_data->thickness;
uint16_t width = obj->width;
uint16_t height = obj->height;
// 限制半径大小
if (radius > width / 2) radius = width / 2;
if (radius > height / 2) radius = height / 2;
if (radius <= 1) {
// 半径太小,绘制普通矩形
if (obj->bg_color != obj->color) {
LCD_DrawRectangle(obj->x, obj->y,
obj->x + width - 1,
obj->y + height - 1,
obj->bg_color);
LCD_Fill(obj->x + 1, obj->y + 1,
obj->x + width - 2,
obj->y + height - 2,
obj->color);
} else {
LCD_Fill(obj->x, obj->y,
obj->x + width - 1,
obj->y + height - 1,
obj->color);
}
} else {
// 绘制圆角矩形
// 1. 先绘制填充的圆角矩形(主体)
LCD_Draw_Filled_Rounded_Rect(obj->x, obj->y,
width, height, radius, obj->color);
// 2. 绘制边框
if (border_thickness > 0) {
if (border_thickness == 1) {
// 薄边框
LCD_DrawRoundedRect(obj->x, obj->y,
obj->x + width - 1,
obj->y + height - 1,
radius, obj->bg_color);
} else {
// 厚边框,使用LCD_DrawRoundedRect_Thick函数
LCD_DrawRoundedRect_Thick(obj->x, obj->y,
obj->x + width - 1,
obj->y + height - 1,
radius, 4, obj->bg_color);
}
}
}
}
/*---------------------- 系统初始化和诊断函数 ----------------------*/
/******************************************************************************
函数说明:初始化图形系统
入口数据:无
返回值: true-成功 false-失败
******************************************************************************/
bool gui_init(void) {
// 初始化内存池
if (!stack_mempool_init()) {
return false;
}
return true;
}
/******************************************************************************
函数说明:检查面板数据内存
入口数据:panel_data 面板数据指针
返回值: 无
******************************************************************************/
static void check_panel_memory(gui_panel_data_t* panel_data) {
if (!panel_data) {
TFTP_DEBUG("MEM: NULL");
return;
}
// 直接查看内存字节
uint8_t* bytes = (uint8_t*)panel_data;
// 打印前8个字节
TFTP_DEBUG("MEM: [0]=%d [1]=%d [2]=%d [3]=%d [4]=%d [5]=%d [6]=%d [7]=%d",
bytes[0], bytes[1], bytes[2], bytes[3],
bytes[4], bytes[5], bytes[6], bytes[7]);
// 解释这些字节
TFTP_DEBUG("DECODE: shape=%d, radius=%d, thickness=%d",
bytes[0], bytes[1], bytes[2]);
}
void diagnose_memory_alignment(void) {
TFTP_DEBUG("=== Memory Alignment Diagnosis ===");
// 1. 检查结构体大小和对齐
TFTP_DEBUG("1. Structure Info:");
TFTP_DEBUG(" sizeof(gui_panel_data_t) = %d", sizeof(gui_panel_data_t));
gui_panel_data_t dummy;
TFTP_DEBUG(" address of dummy: %p", &dummy);
TFTP_DEBUG(" alignment of dummy: %d", (uintptr_t)&dummy % 4);
// 2. 直接从内存池分配和读取
TFTP_DEBUG("\n2. Direct Memory Pool Test:");
uint16_t size = sizeof(gui_panel_data_t);
mem_handle_t handle = stack_mempool_alloc(size, 0);
if (handle != INVALID_HANDLE) {
void* ptr = stack_mempool_get_ptr(handle);
TFTP_DEBUG(" Allocated handle: %d", handle);
TFTP_DEBUG(" Pointer: %p", ptr);
TFTP_DEBUG(" Pointer alignment: %d", (uintptr_t)ptr % 4);
// 写入数据
gui_panel_data_t* data = (gui_panel_data_t*)ptr;
data->shape = GUI_PANEL_SHAPE_ROUNDED;
data->radius = 10;
data->thickness = 5;
// 读取数据
TFTP_DEBUG(" Written: shape=%d, radius=%d, thickness=%d",
data->shape, data->radius, data->thickness);
// 通过字节读取
uint8_t* bytes = (uint8_t*)ptr;
TFTP_DEBUG(" Bytes: [0]=%d, [1]=%d, [2]=%d",
bytes[0], bytes[1], bytes[2]);
}
TFTP_DEBUG("\n3. Creating panel through GUI...");
// 3. 通过GUI系统创建
gui_handle_t panel = gui_create_rounded_panel(50, 50, 100, 60,
MAGENTA, GRED, 10, 5);
if (panel != GUI_INVALID_HANDLE) {
gui_obj_t* obj = gui_get_obj(panel);
if (obj && obj->data) {
TFTP_DEBUG(" Object data pointer: %p", obj->data);
TFTP_DEBUG(" Pointer alignment: %d", (uintptr_t)obj->data % 4);
// 直接读取内存
uint8_t* bytes = (uint8_t*)obj->data;
TFTP_DEBUG(" Memory bytes: [0]=%d, [1]=%d, [2]=%d",
bytes[0], bytes[1], bytes[2]);
}
}
TFTP_DEBUG("=== Diagnosis Complete ===");
}
void test_memory_all(void){
// 遍历内存池中的所有块
for (int i = 0; i < MAX_HANDLES; i++) {
if (mempool.blocks[i].state == BLOCK_USED) {
uint8_t type = mempool.blocks[i].type;
if (type >= GUI_OBJ_TYPE_WIDGET && type <= GUI_OBJ_TYPE_WINDOW) {
gui_obj_t* obj = (gui_obj_t*)stack_mempool_get_ptr(i);
TFTP_DEBUG("Address of panel_data: %p", obj->data);
check_panel_memory(obj->data);
}
}
}
}
/*---------------------- 对象管理核心函数 ----------------------*/
/******************************************************************************
函数说明:创建基本对象
入口数据:type 对象类型
x,y 坐标
width,height 宽高
返回值: 对象句柄
******************************************************************************/
gui_handle_t gui_create_obj(gui_obj_type_t type, int16_t x, int16_t y,
int16_t width, int16_t height) {
// 分配对象内存
gui_handle_t handle = stack_mempool_alloc(sizeof(gui_obj_t), (uint8_t)type);
if (handle == GUI_INVALID_HANDLE) {
return GUI_INVALID_HANDLE;
}
// 获取对象指针
gui_obj_t* obj = (gui_obj_t*)stack_mempool_get_ptr(handle);
if (!obj) {
return GUI_INVALID_HANDLE;
}
// 初始化对象
obj->type = type;
obj->visible = true;
obj->updated = true;
obj->x = x;
obj->y = y;
obj->width = width;
obj->height = height;
obj->color = GUI_COLOR_BLACK;
obj->bg_color = GUI_COLOR_WHITE;
obj->callback = NULL;
obj->data = NULL;
// 初始化相对定位字段
obj->parent = GUI_INVALID_HANDLE;
obj->rel_x = 0;
obj->rel_y = 0;
obj->pos_mode = GUI_POS_MODE_ABSOLUTE;
obj->anchor = GUI_ANCHOR_NONE;
return handle;
}
/******************************************************************************
函数说明:销毁对象
入口数据:handle 对象句柄
返回值: true-成功 false-失败
******************************************************************************/
bool gui_destroy_obj(gui_handle_t handle) {
if (handle == GUI_INVALID_HANDLE) {
return false;
}
// 获取对象
gui_obj_t* obj = gui_get_obj(handle);
if (!obj) {
return false;
}
// 释放对象特定数据
if (obj->data) {
// 获取数据块的句柄
mem_handle_t data_handle = get_data_handle_from_footer(obj->type, obj->data);
if (data_handle != INVALID_HANDLE) {
stack_mempool_free(data_handle);
}
obj->data = NULL;
}
// 释放对象本身
return stack_mempool_free(handle);
}
/******************************************************************************
函数说明:创建相对定位的矩形面板
入口数据:parent 父对象句柄
rel_x,rel_y 相对坐标
width,height 宽高
color 填充颜色
border_color 边框颜色
返回值: 对象句柄
******************************************************************************/
gui_handle_t gui_create_panel_relative(gui_handle_t parent, int16_t rel_x, int16_t rel_y,
int16_t width, int16_t height,
gui_color_t color, gui_color_t border_color) {
// 获取父对象
gui_obj_t* parent_obj = gui_get_obj(parent);
if (!parent_obj) {
return GUI_INVALID_HANDLE;
}
// 计算绝对坐标
int16_t abs_x = parent_obj->x + rel_x;
int16_t abs_y = parent_obj->y + rel_y;
// 边界检查,确保面板不超出父对象范围
if (abs_x < parent_obj->x) abs_x = parent_obj->x;
if (abs_y < parent_obj->y) abs_y = parent_obj->y;
if (abs_x + width > parent_obj->x + parent_obj->width) {
width = parent_obj->x + parent_obj->width - abs_x;
}
if (abs_y + height > parent_obj->y + parent_obj->height) {
height = parent_obj->y + parent_obj->height - abs_y;
}
// 确保宽高不为负
if (width <= 0) width = 1;
if (height <= 0) height = 1;
// 创建面板
gui_handle_t handle = gui_create_panel(abs_x, abs_y, width, height, color, border_color);
if (handle == GUI_INVALID_HANDLE) {
return GUI_INVALID_HANDLE;
}
// 设置相对定位信息
gui_obj_t* obj = gui_get_obj(handle);
if (obj) {
obj->parent = parent;
obj->rel_x = rel_x;
obj->rel_y = rel_y;
obj->pos_mode = GUI_POS_MODE_RELATIVE;
}
return handle;
}
/******************************************************************************
函数说明:创建锚点定位的矩形面板
入口数据:parent 父对象句柄
anchor 锚点类型
offset_x,offset_y 偏移量
width,height 宽高
color 填充颜色
border_color 边框颜色
返回值: 对象句柄
******************************************************************************/
gui_handle_t gui_create_panel_anchor(gui_handle_t parent, gui_anchor_t anchor,
int16_t offset_x, int16_t offset_y,
int16_t width, int16_t height,
gui_color_t color, gui_color_t border_color) {
// 获取父对象
gui_obj_t* parent_obj = gui_get_obj(parent);
if (!parent_obj) {
return GUI_INVALID_HANDLE;
}
// 根据锚点计算位置
int16_t abs_x = 0, abs_y = 0;
switch(anchor) {
case GUI_ANCHOR_TOP_LEFT:
abs_x = parent_obj->x + offset_x;
abs_y = parent_obj->y + offset_y;
break;
case GUI_ANCHOR_TOP_CENTER:
abs_x = parent_obj->x + (parent_obj->width - width) / 2 + offset_x;
abs_y = parent_obj->y + offset_y;
break;
case GUI_ANCHOR_TOP_RIGHT:
abs_x = parent_obj->x + parent_obj->width - width - offset_x;
abs_y = parent_obj->y + offset_y;
break;
case GUI_ANCHOR_CENTER_LEFT:
abs_x = parent_obj->x + offset_x;
abs_y = parent_obj->y + (parent_obj->height - height) / 2 + offset_y;
break;
case GUI_ANCHOR_CENTER:
abs_x = parent_obj->x + (parent_obj->width - width) / 2 + offset_x;
abs_y = parent_obj->y + (parent_obj->height - height) / 2 + offset_y;
break;
case GUI_ANCHOR_CENTER_RIGHT:
abs_x = parent_obj->x + parent_obj->width - width - offset_x;
abs_y = parent_obj->y + (parent_obj->height - height) / 2 + offset_y;
break;
case GUI_ANCHOR_BOTTOM_LEFT:
abs_x = parent_obj->x + offset_x;
abs_y = parent_obj->y + parent_obj->height - height - offset_y;
break;
case GUI_ANCHOR_BOTTOM_CENTER:
abs_x = parent_obj->x + (parent_obj->width - width) / 2 + offset_x;
abs_y = parent_obj->y + parent_obj->height - height - offset_y;
break;
case GUI_ANCHOR_BOTTOM_RIGHT:
abs_x = parent_obj->x + parent_obj->width - width - offset_x;
abs_y = parent_obj->y + parent_obj->height - height - offset_y;
break;
default:
abs_x = parent_obj->x + offset_x;
abs_y = parent_obj->y + offset_y;
}
// 边界检查
if (abs_x < parent_obj->x) abs_x = parent_obj->x;
if (abs_y < parent_obj->y) abs_y = parent_obj->y;
if (abs_x + width > parent_obj->x + parent_obj->width) {
width = parent_obj->x + parent_obj->width - abs_x;
}
if (abs_y + height > parent_obj->y + parent_obj->height) {
height = parent_obj->y + parent_obj->height - abs_y;
}
// 确保宽高不为负
if (width <= 0) width = 1;
if (height <= 0) height = 1;
// 创建面板
gui_handle_t handle = gui_create_panel(abs_x, abs_y, width, height, color, border_color);
if (handle == GUI_INVALID_HANDLE) {
return GUI_INVALID_HANDLE;
}
// 设置锚点定位信息
gui_obj_t* obj = gui_get_obj(handle);
if (obj) {
obj->parent = parent;
obj->anchor = anchor;
obj->rel_x = offset_x;
obj->rel_y = offset_y;
obj->pos_mode = GUI_POS_MODE_ANCHOR;
}
return handle;
}
/******************************************************************************
* 函数说明:创建一个支持自动换行的文本标签控件
* 入口数据:x, y 标签的起始坐标
* max_width 单行最大宽度(像素),超过此宽度将自动换行
* max_lines 最大显示行数
* text 要显示的文本字符串
* font_size 字体大小
* color 文本颜色
* bg_color 背景颜色(若为透明,可指定为GUI_TRANSPARENT)
* 返回值: 成功创建返回标签对象的句柄,失败返回GUI_NULL
******************************************************************************/
gui_handle_t gui_create_label_wrap(int16_t x, int16_t y, int16_t max_width, int16_t max_lines,
const char* text, gui_font_size_t font_size,
gui_color_t color, gui_color_t bg_color) {
// 计算字符宽度
int16_t char_width, char_height;
switch(font_size) {
case GUI_FONT_12: char_width = 6; char_height = 12; break;
case GUI_FONT_16: char_width = 8; char_height = 16; break;
case GUI_FONT_24: char_width = 12; char_height = 24; break;
case GUI_FONT_32: char_width = 16; char_height = 32; break;
default: char_width = 8; char_height = 16;
}
int text_len = strlen(text);
int16_t text_width, text_height;
if (max_width > 0) {
// 需要换行
int chars_per_line = max_width / char_width;
int total_lines = (text_len + chars_per_line - 1) / chars_per_line;
// 如果指定了最大行数,则限制行数
int actual_lines = (max_lines > 0 && total_lines > max_lines) ? max_lines : total_lines;
text_width = max_width;
text_height = actual_lines * char_height;
} else {
// 不换行
text_width = char_width * text_len;
text_height = char_height;
max_lines = 0; // 表示无限制
}
gui_handle_t handle = gui_create_obj(GUI_OBJ_TYPE_LABEL_WRAP, x, y, text_width, text_height);
if (handle == GUI_INVALID_HANDLE) {
return GUI_INVALID_HANDLE;
}
// 获取对象
gui_obj_t* obj = gui_get_obj(handle);
if (!obj) {
gui_destroy_obj(handle);
return GUI_INVALID_HANDLE;
}
// 设置颜色
obj->color = color;
obj->bg_color = bg_color;
// 分配标签数据
uint16_t data_size = sizeof(gui_label_wrap_data_t) + text_len + 1 + sizeof(gui_data_footer_t);
mem_handle_t data_handle = stack_mempool_alloc(data_size, (uint8_t)GUI_OBJ_TYPE_LABEL_WRAP);
if (data_handle == GUI_INVALID_HANDLE) {
gui_destroy_obj(handle);
return GUI_INVALID_HANDLE;
}
// 设置标签数据
gui_label_wrap_data_t* label_data = (gui_label_wrap_data_t*)stack_mempool_get_ptr(data_handle);
if (!label_data) {
stack_mempool_free(data_handle);
gui_destroy_obj(handle);
return GUI_INVALID_HANDLE;
}
label_data->font_size = font_size;
label_data->max_width = max_width;
label_data->max_lines = max_lines; // 0表示无限制
label_data->char_width = char_width;
label_data->char_height = char_height;
label_data->text = (char*)(label_data + 1);
strcpy(label_data->text, text);
// 存储数据句柄在数据块末尾
gui_data_footer_t* footer = (gui_data_footer_t*)((uint8_t*)label_data + sizeof(gui_label_wrap_data_t) + text_len + 1);
footer->data_handle = data_handle;
// 设置对象数据
obj->data = label_data;
return handle;
}
/******************************************************************************
函数说明:创建相对定位的标签
入口数据:parent 父对象句柄
rel_x,rel_y 相对坐标
text 文本内容
font_size 字体大小
color 字体颜色
bg_color 背景颜色
返回值: 对象句柄
******************************************************************************/
gui_handle_t gui_create_label_relative(gui_handle_t parent, int16_t rel_x, int16_t rel_y,
const char* text, gui_font_size_t font_size,
gui_color_t color, gui_color_t bg_color) {
// 获取父对象
gui_obj_t* parent_obj = gui_get_obj(parent);
if (!parent_obj) {
return GUI_INVALID_HANDLE;
}
// 计算文本尺寸
int16_t char_width, char_height;
switch(font_size) {
case GUI_FONT_12: char_width = 6; char_height = 12; break;
case GUI_FONT_16: char_width = 8; char_height = 16; break;
case GUI_FONT_24: char_width = 12; char_height = 24; break;
case GUI_FONT_32: char_width = 16; char_height = 32; break;
default: char_width = 8; char_height = 16;
}
int text_len = strlen(text);
int16_t text_width = char_width * text_len;
int16_t text_height = char_height;
// 计算绝对坐标
int16_t abs_x = parent_obj->x + rel_x;
int16_t abs_y = parent_obj->y + rel_y;
// 创建标签
gui_handle_t handle = gui_create_label(abs_x, abs_y, text, font_size, color, bg_color);
if (handle == GUI_INVALID_HANDLE) {
return GUI_INVALID_HANDLE;
}
// 设置相对定位信息
gui_obj_t* obj = gui_get_obj(handle);
if (obj) {
obj->parent = parent;
obj->rel_x = rel_x;
obj->rel_y = rel_y;
obj->pos_mode = GUI_POS_MODE_RELATIVE;
}
return handle;
}
/******************************************************************************
函数说明:创建锚点定位的标签(增强版,可选择使用父对象背景色)
入口数据:parent 父对象句柄
anchor 锚点类型
offset_x,offset_y 偏移量
text 文本内容
font_size 字体大小
color 字体颜色
bg_color 背景颜色(如果为USE_PARENT_BG则使用父对象背景色)
返回值: 对象句柄
******************************************************************************/
gui_handle_t gui_create_label_anchor(gui_handle_t parent, gui_anchor_t anchor,
int16_t offset_x, int16_t offset_y,
const char* text, gui_font_size_t font_size,
gui_color_t color, gui_color_t bg_color) {
// 获取父对象
gui_obj_t* parent_obj = gui_get_obj(parent);
if (!parent_obj) {
return GUI_INVALID_HANDLE;
}
// 如果bg_color为特殊值USE_PARENT_BG,则使用父对象的背景色
gui_color_t final_bg_color = bg_color;
if (bg_color == USE_PARENT_BG) {
final_bg_color = parent_obj->bg_color;
}
// 计算字符尺寸
int16_t char_width, char_height;
switch(font_size) {
case GUI_FONT_12: char_width = 6; char_height = 12; break;
case GUI_FONT_16: char_width = 8; char_height = 16; break;
case GUI_FONT_24: char_width = 12; char_height = 24; break;
case GUI_FONT_32: char_width = 16; char_height = 32; break;
default: char_width = 8; char_height = 16;
}
int text_len = strlen(text);
int16_t max_width = parent_obj->width;
bool need_wrap = false;
int16_t actual_width = 0;
int16_t text_height = char_height;
int16_t max_lines = 0;
// 分析文本内容,处理换行符
int line_count = 1;
int current_line_chars = 0;
int max_line_chars = 0;
// 第一遍扫描:统计实际行数和最大行宽
for (int i = 0; i < text_len; i++) {
if (text[i] == '\n') {
line_count++;
if (current_line_chars > max_line_chars) {
max_line_chars = current_line_chars;
}
current_line_chars = 0;
} else {
current_line_chars++;
}
}
// 处理最后一行
if (current_line_chars > max_line_chars) {
max_line_chars = current_line_chars;
}
// 计算单行文本宽度
int16_t single_line_width = char_width * max_line_chars;
// 检查是否需要自动换行
if (single_line_width > max_width) {
need_wrap = true;
actual_width = max_width;
int chars_per_line = max_width / char_width;
// 第二遍扫描:考虑自动换行后的实际行数
line_count = 0;
current_line_chars = 0;
for (int i = 0; i < text_len; i++) {
if (text[i] == '\n') {
line_count++;
current_line_chars = 0;
} else {
current_line_chars++;
if (current_line_chars >= chars_per_line) {
line_count++;
current_line_chars = 0;
}
}
}
if (current_line_chars > 0) line_count++;
// 限制最大行数
int max_possible_lines = parent_obj->height / char_height;
if (max_possible_lines <= 0) max_possible_lines = 999;
max_lines = (line_count > max_possible_lines) ? max_possible_lines : line_count;
text_height = max_lines * char_height;
} else {
// 不需要自动换行
actual_width = single_line_width;
text_height = line_count * char_height;
// 检查高度是否超出父对象
if (text_height > parent_obj->height && parent_obj->height > 0) {
int max_possible_lines = parent_obj->height / char_height;
if (max_possible_lines <= 0) max_possible_lines = 1;
max_lines = (line_count > max_possible_lines) ? max_possible_lines : line_count;
text_height = max_lines * char_height;
}
}
// 根据锚点计算位置
int16_t abs_x = 0, abs_y = 0;
switch(anchor) {
case GUI_ANCHOR_TOP_LEFT:
abs_x = parent_obj->x + offset_x;
abs_y = parent_obj->y + offset_y;
break;
case GUI_ANCHOR_TOP_CENTER:
abs_x = parent_obj->x + (parent_obj->width - actual_width) / 2 + offset_x;
abs_y = parent_obj->y + offset_y;
break;
case GUI_ANCHOR_TOP_RIGHT:
abs_x = parent_obj->x + parent_obj->width - actual_width - offset_x;
abs_y = parent_obj->y + offset_y;
break;
case GUI_ANCHOR_CENTER_LEFT:
abs_x = parent_obj->x + offset_x;
abs_y = parent_obj->y + (parent_obj->height - text_height) / 2 + offset_y;
break;
case GUI_ANCHOR_CENTER:
abs_x = parent_obj->x + (parent_obj->width - actual_width) / 2 + offset_x;
abs_y = parent_obj->y + (parent_obj->height - text_height) / 2 + offset_y;
break;
case GUI_ANCHOR_CENTER_RIGHT:
abs_x = parent_obj->x + parent_obj->width - actual_width - offset_x;
abs_y = parent_obj->y + (parent_obj->height - text_height) / 2 + offset_y;
break;
case GUI_ANCHOR_BOTTOM_LEFT:
abs_x = parent_obj->x + offset_x;
abs_y = parent_obj->y + parent_obj->height - text_height - offset_y;
break;
case GUI_ANCHOR_BOTTOM_CENTER:
abs_x = parent_obj->x + (parent_obj->width - actual_width) / 2 + offset_x;
abs_y = parent_obj->y + parent_obj->height - text_height - offset_y;
break;
case GUI_ANCHOR_BOTTOM_RIGHT:
abs_x = parent_obj->x + parent_obj->width - actual_width - offset_x;
abs_y = parent_obj->y + parent_obj->height - text_height - offset_y;
break;
default:
abs_x = parent_obj->x + offset_x;
abs_y = parent_obj->y + offset_y;
}
// 边界检查
if (abs_y < parent_obj->y) abs_y = parent_obj->y;
if (abs_y + text_height > parent_obj->y + parent_obj->height) {
text_height = parent_obj->y + parent_obj->height - abs_y;
if (text_height < 0) text_height = 0;
}
if (abs_x < parent_obj->x) abs_x = parent_obj->x;
if (abs_x + actual_width > parent_obj->x + parent_obj->width) {
actual_width = parent_obj->x + parent_obj->width - abs_x;
if (actual_width < 0) actual_width = 0;
}
// 创建标签
gui_handle_t handle;
if (need_wrap && max_lines > 0) {
handle = gui_create_label_wrap(abs_x, abs_y, max_width, max_lines,
text, font_size, color, final_bg_color);
} else {
handle = gui_create_label_wrap(abs_x, abs_y,
need_wrap ? max_width : 0,
0, text, font_size, color, final_bg_color);
}
if (handle == GUI_INVALID_HANDLE) {
return GUI_INVALID_HANDLE;
}
// 设置标签属性
gui_obj_t* obj = gui_get_obj(handle);
if (obj) {
obj->width = actual_width;
obj->height = text_height;
obj->parent = parent;
obj->anchor = anchor;
obj->rel_x = offset_x;
obj->rel_y = offset_y;
obj->pos_mode = GUI_POS_MODE_ANCHOR;
}
return handle;
}
/******************************************************************************
函数说明:创建相对定位的按钮
入口数据:parent 父对象句柄
rel_x,rel_y 相对坐标
width,height 宽高
text 文本内容
font_size 字体大小
返回值: 对象句柄
******************************************************************************/
gui_handle_t gui_create_button_relative(gui_handle_t parent, int16_t rel_x, int16_t rel_y,
int16_t width, int16_t height,
const char* text, gui_font_size_t font_size) {
// 获取父对象
gui_obj_t* parent_obj = gui_get_obj(parent);
if (!parent_obj) {
return GUI_INVALID_HANDLE;
}
// 计算绝对坐标
int16_t abs_x = parent_obj->x + rel_x;
int16_t abs_y = parent_obj->y + rel_y;
// 创建按钮
gui_handle_t handle = gui_create_button(abs_x, abs_y, width, height, text, font_size);
if (handle == GUI_INVALID_HANDLE) {
return GUI_INVALID_HANDLE;
}
// 设置相对定位信息
gui_obj_t* obj = gui_get_obj(handle);
if (obj) {
obj->parent = parent;
obj->rel_x = rel_x;
obj->rel_y = rel_y;
obj->pos_mode = GUI_POS_MODE_RELATIVE;
}
return handle;
}
/******************************************************************************
函数说明:获取对象
入口数据:handle 对象句柄
返回值: 对象指针
******************************************************************************/
gui_obj_t* gui_get_obj(gui_handle_t handle) {
if (handle == GUI_INVALID_HANDLE) {
return NULL;
}
return (gui_obj_t*)stack_mempool_get_ptr(handle);
}
/******************************************************************************
函数说明:设置父对象
入口数据:child 子对象句柄
parent 父对象句柄
返回值: true-成功 false-失败
******************************************************************************/
bool gui_set_parent(gui_handle_t child, gui_handle_t parent) {
if (child == GUI_INVALID_HANDLE) {
return false;
}
gui_obj_t* obj = gui_get_obj(child);
if (!obj) {
return false;
}
if (parent != GUI_INVALID_HANDLE) {
gui_obj_t* parent_obj = gui_get_obj(parent);
if (!parent_obj) {
return false;
}
// 计算相对坐标
obj->rel_x = obj->x - parent_obj->x;
obj->rel_y = obj->y - parent_obj->y;
} else {
obj->rel_x = 0;
obj->rel_y = 0;
}
obj->parent = parent;
return true;
}
/******************************************************************************
函数说明:设置相对位置
入口数据:child 子对象句柄
rel_x,rel_y 相对坐标
返回值: true-成功 false-失败
******************************************************************************/
bool gui_set_relative_position(gui_handle_t child, int16_t rel_x, int16_t rel_y) {
gui_obj_t* obj = gui_get_obj(child);
if (!obj || obj->parent == GUI_INVALID_HANDLE) {
return false;
}
gui_obj_t* parent = gui_get_obj(obj->parent);
if (!parent) {
return false;
}
obj->rel_x = rel_x;
obj->rel_y = rel_y;
obj->pos_mode = GUI_POS_MODE_RELATIVE;
// 更新绝对位置
int16_t new_x = parent->x + rel_x;
int16_t new_y = parent->y + rel_y;
return gui_set_obj_pos(child, new_x, new_y);
}
/******************************************************************************
函数说明:设置锚点
入口数据:child 子对象句柄
anchor 锚点类型
offset_x,offset_y 偏移量
返回值: true-成功 false-失败
******************************************************************************/
bool gui_set_anchor(gui_handle_t child, gui_anchor_t anchor, int16_t offset_x, int16_t offset_y) {
gui_obj_t* obj = gui_get_obj(child);
if (!obj || obj->parent == GUI_INVALID_HANDLE) {
return false;
}
gui_obj_t* parent = gui_get_obj(obj->parent);
if (!parent) {
return false;
}
obj->anchor = anchor;
obj->rel_x = offset_x;
obj->rel_y = offset_y;
obj->pos_mode = GUI_POS_MODE_ANCHOR;
// 根据锚点计算新位置
int16_t new_x = 0, new_y = 0;
switch(anchor) {
case GUI_ANCHOR_TOP_LEFT:
new_x = parent->x + offset_x;
new_y = parent->y + offset_y;
break;
case GUI_ANCHOR_TOP_CENTER:
new_x = parent->x + (parent->width - obj->width) / 2 + offset_x;
new_y = parent->y + offset_y;
break;
case GUI_ANCHOR_TOP_RIGHT:
new_x = parent->x + parent->width - obj->width - offset_x;
new_y = parent->y + offset_y;
break;
case GUI_ANCHOR_CENTER_LEFT:
new_x = parent->x + offset_x;
new_y = parent->y + (parent->height - obj->height) / 2 + offset_y;
break;
case GUI_ANCHOR_CENTER:
new_x = parent->x + (parent->width - obj->width) / 2 + offset_x;
new_y = parent->y + (parent->height - obj->height) / 2 + offset_y;
break;
case GUI_ANCHOR_CENTER_RIGHT:
new_x = parent->x + parent->width - obj->width - offset_x;
new_y = parent->y + (parent->height - obj->height) / 2 + offset_y;
break;
case GUI_ANCHOR_BOTTOM_LEFT:
new_x = parent->x + offset_x;
new_y = parent->y + parent->height - obj->height - offset_y;
break;
case GUI_ANCHOR_BOTTOM_CENTER:
new_x = parent->x + (parent->width - obj->width) / 2 + offset_x;
new_y = parent->y + parent->height - obj->height - offset_y;
break;
case GUI_ANCHOR_BOTTOM_RIGHT:
new_x = parent->x + parent->width - obj->width - offset_x;
new_y = parent->y + parent->height - obj->height - offset_y;
break;
default:
return false;
}
return gui_set_obj_pos(child, new_x, new_y);
}
/******************************************************************************
函数说明:更新相对位置(当父对象移动时调用)
入口数据:parent_handle 父对象句柄
返回值: 无
******************************************************************************/
static void update_relative_position(gui_handle_t parent_handle) {
gui_obj_t* parent_obj = gui_get_obj(parent_handle);
if (!parent_obj) return;
// 遍历所有对象,找到以此对象为父对象的子对象
for (mem_handle_t handle = 0; handle < MAX_HANDLES; handle++) {
if (mempool.blocks[handle].state == BLOCK_USED) {
gui_obj_t* child_obj = (gui_obj_t*)stack_mempool_get_ptr(handle);
if (child_obj && child_obj->parent == parent_handle) {
// 根据定位模式重新计算子对象位置
if (child_obj->pos_mode == GUI_POS_MODE_RELATIVE) {
int16_t new_x = parent_obj->x + child_obj->rel_x;
int16_t new_y = parent_obj->y + child_obj->rel_y;
if (child_obj->x != new_x || child_obj->y != new_y) {
// 清除原位置
if (child_obj->visible) {
LCD_Fill(child_obj->x, child_obj->y,
child_obj->x + child_obj->width,
child_obj->y + child_obj->height,
child_obj->bg_color);
}
child_obj->x = new_x;
child_obj->y = new_y;
child_obj->updated = true;
}
}
else if (child_obj->pos_mode == GUI_POS_MODE_ANCHOR) {
// 重新计算锚点位置
int16_t new_x = 0, new_y = 0;
switch(child_obj->anchor) {
case GUI_ANCHOR_TOP_LEFT:
new_x = parent_obj->x + child_obj->rel_x;
new_y = parent_obj->y + child_obj->rel_y;
break;
case GUI_ANCHOR_TOP_CENTER:
new_x = parent_obj->x + (parent_obj->width - child_obj->width) / 2 + child_obj->rel_x;
new_y = parent_obj->y + child_obj->rel_y;
break;
case GUI_ANCHOR_TOP_RIGHT:
new_x = parent_obj->x + parent_obj->width - child_obj->width - child_obj->rel_x;
new_y = parent_obj->y + child_obj->rel_y;
break;
case GUI_ANCHOR_CENTER_LEFT:
new_x = parent_obj->x + child_obj->rel_x;
new_y = parent_obj->y + (parent_obj->height - child_obj->height) / 2 + child_obj->rel_y;
break;
case GUI_ANCHOR_CENTER:
new_x = parent_obj->x + (parent_obj->width - child_obj->width) / 2 + child_obj->rel_x;
new_y = parent_obj->y + (parent_obj->height - child_obj->height) / 2 + child_obj->rel_y;
break;
case GUI_ANCHOR_CENTER_RIGHT:
new_x = parent_obj->x + parent_obj->width - child_obj->width - child_obj->rel_x;
new_y = parent_obj->y + (parent_obj->height - child_obj->height) / 2 + child_obj->rel_y;
break;
case GUI_ANCHOR_BOTTOM_LEFT:
new_x = parent_obj->x + child_obj->rel_x;
new_y = parent_obj->y + parent_obj->height - child_obj->height - child_obj->rel_y;
break;
case GUI_ANCHOR_BOTTOM_CENTER:
new_x = parent_obj->x + (parent_obj->width - child_obj->width) / 2 + child_obj->rel_x;
new_y = parent_obj->y + parent_obj->height - child_obj->height - child_obj->rel_y;
break;
case GUI_ANCHOR_BOTTOM_RIGHT:
new_x = parent_obj->x + parent_obj->width - child_obj->width - child_obj->rel_x;
new_y = parent_obj->y + parent_obj->height - child_obj->height - child_obj->rel_y;
break;
default:
continue;
}
if (child_obj->x != new_x || child_obj->y != new_y) {
// 清除原位置
if (child_obj->visible) {
LCD_Fill(child_obj->x, child_obj->y,
child_obj->x + child_obj->width,
child_obj->y + child_obj->height,
child_obj->bg_color);
}
child_obj->x = new_x;
child_obj->y = new_y;
child_obj->updated = true;
}
}
}
}
}
}
/******************************************************************************
函数说明:更新父对象的所有子对象的相对位置
入口数据:parent 父对象句柄
返回值: true-成功 false-失败
******************************************************************************/
bool gui_update_relative_position(gui_handle_t parent) {
update_relative_position(parent);
return true;
}
/******************************************************************************
函数说明:设置对象位置
入口数据:handle 对象句柄
x,y 新坐标
返回值: true-成功 false-失败
******************************************************************************/
bool gui_set_obj_pos(gui_handle_t handle, int16_t x, int16_t y) {
gui_obj_t* obj = gui_get_obj(handle);
if (!obj) {
return false;
}
if (obj->x != x || obj->y != y) {
// 清除原位置
if (obj->visible) {
LCD_Fill(obj->x, obj->y, obj->x + obj->width, obj->y + obj->height, obj->bg_color);
}
obj->x = x;
obj->y = y;
obj->updated = true;
// 更新所有子对象的相对位置
update_relative_position(handle);
}
return true;
}
/******************************************************************************
函数说明:设置对象大小
入口数据:handle 对象句柄
width,height 新宽高
返回值: true-成功 false-失败
******************************************************************************/
bool gui_set_obj_size(gui_handle_t handle, int16_t width, int16_t height) {
gui_obj_t* obj = gui_get_obj(handle);
if (!obj) {
return false;
}
if (obj->width != width || obj->height != height) {
// 清除原区域
if (obj->visible) {
LCD_Fill(obj->x, obj->y, obj->x + obj->width, obj->y + obj->height, obj->bg_color);
}
obj->width = width;
obj->height = height;
obj->updated = true;
}
return true;
}
/******************************************************************************
函数说明:设置对象可见性
入口数据:handle 对象句柄
visible 是否可见
返回值: true-成功 false-失败
******************************************************************************/
bool gui_set_obj_visible(gui_handle_t handle, bool visible) {
gui_obj_t* obj = gui_get_obj(handle);
if (!obj) {
return false;
}
if (obj->visible != visible) {
obj->visible = visible;
obj->updated = true;
}
return true;
}
/******************************************************************************
函数说明:设置对象回调
入口数据:handle 对象句柄
callback 回调函数
返回值: true-成功 false-失败
******************************************************************************/
bool gui_set_obj_callback(gui_handle_t handle, gui_event_callback_t callback) {
gui_obj_t* obj = gui_get_obj(handle);
if (!obj) {
return false;
}
obj->callback = callback;
return true;
}
/******************************************************************************
函数说明:创建扩展面板
入口数据:x,y 坐标
width,height 宽高
color 填充颜色
border_color 边框颜色
shape 面板形状
radius 半径
thickness 边框粗细
start_angle 起始角度
end_angle 结束角度
返回值: 对象句柄
******************************************************************************/
/*---------------------- 特定对象创建函数 ----------------------*/
gui_handle_t gui_create_panel_ex(int16_t x, int16_t y, int16_t width, int16_t height,
gui_color_t color, gui_color_t border_color,
gui_panel_shape_t shape, uint8_t radius,
uint8_t thickness, uint16_t start_angle,
uint16_t end_angle) {
// 创建基本对象
gui_handle_t handle = gui_create_obj(GUI_OBJ_TYPE_PANEL, x, y, width, height);
if (handle == GUI_INVALID_HANDLE) {
return GUI_INVALID_HANDLE;
}
// 获取对象
gui_obj_t* obj = gui_get_obj(handle);
if (!obj) {
gui_destroy_obj(handle);
return GUI_INVALID_HANDLE;
}
// 设置颜色
obj->color = color;
obj->bg_color = border_color;
// 分配面板数据
uint16_t data_size = sizeof(gui_panel_data_t) + sizeof(gui_data_footer_t);
mem_handle_t data_handle = stack_mempool_alloc(data_size, (uint8_t)GUI_OBJ_TYPE_PANEL);
if (data_handle == GUI_INVALID_HANDLE) {
gui_destroy_obj(handle);
return GUI_INVALID_HANDLE;
}
// 设置面板数据
gui_panel_data_t* panel_data = (gui_panel_data_t*)stack_mempool_get_ptr(data_handle);
if (!panel_data) {
stack_mempool_free(data_handle);
gui_destroy_obj(handle);
return GUI_INVALID_HANDLE;
}
// 初始化面板数据
panel_data->shape = shape;
panel_data->radius = radius;
panel_data->thickness = thickness;
panel_data->start_angle = start_angle;
panel_data->end_angle = end_angle;
// 存储数据句柄
gui_data_footer_t* footer = (gui_data_footer_t*)((uint8_t*)panel_data + sizeof(gui_panel_data_t));
footer->data_handle = data_handle;
// 设置对象数据
obj->data = panel_data;
TFTP_DEBUG("Address of panel_data: %p", obj->data);
check_panel_memory(obj->data);
return handle;
}
/******************************************************************************
函数说明:创建矩形面板
入口数据:x,y 坐标
width,height 宽高
color 填充颜色
border_color 边框颜色
返回值: 对象句柄
******************************************************************************/
gui_handle_t gui_create_panel(int16_t x, int16_t y, int16_t width, int16_t height,
gui_color_t color, gui_color_t border_color) {
return gui_create_panel_ex(x, y, width, height, color, border_color,
GUI_PANEL_SHAPE_RECT, 0, 1, 0, 0);
}
/******************************************************************************
函数说明:创建圆角矩形面板
入口数据:x,y 坐标
width,height 宽高
color 填充颜色
border_color 边框颜色
radius 圆角半径
thickness 边框粗细
返回值: 对象句柄
******************************************************************************/
gui_handle_t gui_create_rounded_panel(int16_t x, int16_t y, int16_t width, int16_t height,
gui_color_t color, gui_color_t border_color,
uint8_t radius, uint8_t thickness) {
return gui_create_panel_ex(x, y, width, height, color, border_color,
GUI_PANEL_SHAPE_ROUNDED, radius, thickness, 0, 0);
}
/******************************************************************************
函数说明:创建圆形面板
入口数据:x,y 坐标
diameter 直径
color 填充颜色
border_color 边框颜色
thickness 边框粗细
返回值: 对象句柄
******************************************************************************/
gui_handle_t gui_create_circle_panel(int16_t x, int16_t y, int16_t diameter,
gui_color_t color, gui_color_t border_color,
uint8_t thickness) {
return gui_create_panel_ex(x, y, diameter, diameter, color, border_color,
GUI_PANEL_SHAPE_CIRCLE, diameter/2, thickness, 0, 0);
}
/******************************************************************************
函数说明:创建圆弧面板
入口数据:x,y 坐标
radius 半径
color 填充颜色
border_color 边框颜色
thickness 边框粗细
start_angle 起始角度
end_angle 结束角度
返回值: 对象句柄
******************************************************************************/
gui_handle_t gui_create_arc_panel(int16_t x, int16_t y, uint8_t radius,
gui_color_t color, gui_color_t border_color,
uint8_t thickness, uint16_t start_angle,
uint16_t end_angle) {
int16_t size = radius * 2 + thickness * 2;
return gui_create_panel_ex(x - radius - thickness, y - radius - thickness,
size, size, color, border_color,
GUI_PANEL_SHAPE_ARC, radius, thickness,
start_angle, end_angle);
}
/******************************************************************************
函数说明:创建标签
入口数据:x,y 坐标
text 文本内容
font_size 字体大小
color 字体颜色
bg_color 背景颜色
返回值: 对象句柄
******************************************************************************/
gui_handle_t gui_create_label(int16_t x, int16_t y, const char* text,
gui_font_size_t font_size, gui_color_t color,
gui_color_t bg_color) {
// 计算文本长度
int text_len = strlen(text) + 1; // 包括结束符
// 根据字体大小计算字符宽度
int16_t char_width, char_height, text_width;
switch(font_size) {
case GUI_FONT_12:
char_width = 6; // 12x6字体
char_height = 12;
break;
case GUI_FONT_16:
char_width = 8; // 16x8字体
char_height = 16;
break;
case GUI_FONT_24:
char_width = 12; // 24x12字体
char_height = 24;
break;
case GUI_FONT_32:
char_width = 16; // 32x16字体
char_height = 32;
break;
default:
char_width = 8;
char_height = 16;
}
// 文本宽度 = 字符数 * 字符宽度
text_width = char_width * (text_len - 1); // 减1因为text_len包含结束符
gui_handle_t handle = gui_create_obj(GUI_OBJ_TYPE_LABEL, x, y, text_width, char_height);
if (handle == GUI_INVALID_HANDLE) {
return GUI_INVALID_HANDLE;
}
// 获取对象
gui_obj_t* obj = gui_get_obj(handle);
if (!obj) {
gui_destroy_obj(handle);
return GUI_INVALID_HANDLE;
}
// 设置颜色
obj->color = color;
obj->bg_color = bg_color;
// 分配标签数据
uint16_t data_size = sizeof(gui_label_data_t) + text_len + sizeof(gui_data_footer_t);
mem_handle_t data_handle = stack_mempool_alloc(data_size, (uint8_t)GUI_OBJ_TYPE_LABEL);
if (data_handle == GUI_INVALID_HANDLE) {
gui_destroy_obj(handle);
return GUI_INVALID_HANDLE;
}
// 设置标签数据
gui_label_data_t* label_data = (gui_label_data_t*)stack_mempool_get_ptr(data_handle);
if (!label_data) {
stack_mempool_free(data_handle);
gui_destroy_obj(handle);
return GUI_INVALID_HANDLE;
}
label_data->font_size = font_size;
label_data->text = (char*)(label_data + 1);
strcpy(label_data->text, text);
// 存储数据句柄在数据块末尾
gui_data_footer_t* footer = (gui_data_footer_t*)((uint8_t*)label_data + sizeof(gui_label_data_t) + text_len);
footer->data_handle = data_handle;
// 设置对象数据
obj->data = label_data;
return handle;
}
/******************************************************************************
函数说明:创建按钮
入口数据:x,y 坐标
width,height 宽高
text 文本内容
font_size 字体大小
返回值: 对象句柄
******************************************************************************/
gui_handle_t gui_create_button(int16_t x, int16_t y, int16_t width, int16_t height,
const char* text, gui_font_size_t font_size) {
// 创建基本对象
gui_handle_t handle = gui_create_obj(GUI_OBJ_TYPE_BUTTON, x, y, width, height);
if (handle == GUI_INVALID_HANDLE) {
return GUI_INVALID_HANDLE;
}
// 获取对象
gui_obj_t* obj = gui_get_obj(handle);
if (!obj) {
gui_destroy_obj(handle);
return GUI_INVALID_HANDLE;
}
// 设置按钮默认颜色
obj->color = GUI_COLOR_WHITE;
obj->bg_color = GUI_COLOR_BLUE;
// 分配按钮数据
int text_len = strlen(text) + 1;
uint16_t data_size = sizeof(gui_button_data_t) + text_len + sizeof(gui_data_footer_t);
mem_handle_t data_handle = stack_mempool_alloc(data_size, (uint8_t)GUI_OBJ_TYPE_BUTTON);
if (data_handle == GUI_INVALID_HANDLE) {
gui_destroy_obj(handle);
return GUI_INVALID_HANDLE;
}
// 设置按钮数据
gui_button_data_t* button_data = (gui_button_data_t*)stack_mempool_get_ptr(data_handle);
if (!button_data) {
stack_mempool_free(data_handle);
gui_destroy_obj(handle);
return GUI_INVALID_HANDLE;
}
button_data->font_size = font_size;
button_data->pressed = false;
button_data->text = (char*)(button_data + 1);
strcpy(button_data->text, text);
// 存储数据句柄
gui_data_footer_t* footer = (gui_data_footer_t*)((uint8_t*)button_data + sizeof(gui_button_data_t) + text_len);
footer->data_handle = data_handle;
obj->data = button_data;
return handle;
}
/******************************************************************************
函数说明:创建图片
入口数据:x,y 坐标
width,height 宽高
image_data 图片数据
返回值: 对象句柄
******************************************************************************/
/*---------------------- 对象属性设置函数 ----------------------*/
gui_handle_t gui_create_image(int16_t x, int16_t y, int16_t width, int16_t height,
const uint8_t* image_data) {
// 创建基本对象
gui_handle_t handle = gui_create_obj(GUI_OBJ_TYPE_IMAGE, x, y, width, height);
if (handle == GUI_INVALID_HANDLE) {
return GUI_INVALID_HANDLE;
}
// 获取对象
gui_obj_t* obj = gui_get_obj(handle);
if (!obj) {
gui_destroy_obj(handle);
return GUI_INVALID_HANDLE;
}
// 分配图片数据
uint16_t data_size = sizeof(gui_image_data_t) + sizeof(gui_data_footer_t);
mem_handle_t data_handle = stack_mempool_alloc(data_size, (uint8_t)GUI_OBJ_TYPE_IMAGE);
if (data_handle == GUI_INVALID_HANDLE) {
gui_destroy_obj(handle);
return GUI_INVALID_HANDLE;
}
// 设置图片数据
gui_image_data_t* image_data_struct = (gui_image_data_t*)stack_mempool_get_ptr(data_handle);
if (!image_data_struct) {
stack_mempool_free(data_handle);
gui_destroy_obj(handle);
return GUI_INVALID_HANDLE;
}
image_data_struct->image_data = image_data;
// 存储数据句柄
gui_data_footer_t* footer = (gui_data_footer_t*)((uint8_t*)image_data_struct + sizeof(gui_image_data_t));
footer->data_handle = data_handle;
obj->data = image_data_struct;
return handle;
}
/******************************************************************************
函数说明:设置文本
入口数据:handle 对象句柄
text 文本内容
返回值: true-成功 false-失败
******************************************************************************/
bool gui_set_text(gui_handle_t handle, const char* text) {
gui_obj_t* obj = gui_get_obj(handle);
if (!obj || !obj->data) {
return false;
}
switch (obj->type) {
case GUI_OBJ_TYPE_LABEL: {
gui_label_data_t* label_data = (gui_label_data_t*)obj->data;
int text_len = strlen(text) + 1;
// 获取旧数据句柄
mem_handle_t old_data_handle = get_data_handle_from_footer(obj->type, obj->data);
if (old_data_handle == INVALID_HANDLE) {
return false;
}
// 分配新数据
uint16_t new_data_size = sizeof(gui_label_data_t) + text_len + sizeof(gui_data_footer_t);
mem_handle_t new_data_handle = stack_mempool_alloc(new_data_size, (uint8_t)GUI_OBJ_TYPE_LABEL);
if (new_data_handle == INVALID_HANDLE) {
return false;
}
// 复制数据
gui_label_data_t* new_label_data = (gui_label_data_t*)stack_mempool_get_ptr(new_data_handle);
if (!new_label_data) {
stack_mempool_free(new_data_handle);
return false;
}
new_label_data->font_size = label_data->font_size;
new_label_data->text = (char*)(new_label_data + 1);
strcpy(new_label_data->text, text);
// 存储新句柄
gui_data_footer_t* footer = (gui_data_footer_t*)((uint8_t*)new_label_data +
sizeof(gui_label_data_t) + text_len);
footer->data_handle = new_data_handle;
// 更新对象
obj->data = new_label_data;
// 释放旧数据
stack_mempool_free(old_data_handle);
// 更新对象大小
int16_t char_width = label_data->font_size / 2;
int16_t char_height = label_data->font_size;
obj->width = char_width * (text_len - 1);
obj->height = char_height;
obj->updated = true;
break;
}
case GUI_OBJ_TYPE_BUTTON: {
gui_button_data_t* button_data = (gui_button_data_t*)obj->data;
int text_len = strlen(text) + 1;
// 获取旧数据句柄
mem_handle_t old_data_handle = get_data_handle_from_footer(obj->type, obj->data);
if (old_data_handle == INVALID_HANDLE) {
return false;
}
// 分配新数据
uint16_t new_data_size = sizeof(gui_button_data_t) + text_len + sizeof(gui_data_footer_t);
mem_handle_t new_data_handle = stack_mempool_alloc(new_data_size, (uint8_t)GUI_OBJ_TYPE_BUTTON);
if (new_data_handle == INVALID_HANDLE) {
return false;
}
// 复制数据
gui_button_data_t* new_button_data = (gui_button_data_t*)stack_mempool_get_ptr(new_data_handle);
if (!new_button_data) {
stack_mempool_free(new_data_handle);
return false;
}
new_button_data->font_size = button_data->font_size;
new_button_data->pressed = button_data->pressed;
new_button_data->text = (char*)(new_button_data + 1);
strcpy(new_button_data->text, text);
// 存储新句柄
gui_data_footer_t* footer = (gui_data_footer_t*)((uint8_t*)new_button_data +
sizeof(gui_button_data_t) + text_len);
footer->data_handle = new_data_handle;
// 更新对象
obj->data = new_button_data;
// 释放旧数据
stack_mempool_free(old_data_handle);
obj->updated = true;
break;
}
default:
return false;
}
return true;
}
/******************************************************************************
函数说明:设置颜色
入口数据:handle 对象句柄
color 颜色
返回值: true-成功 false-失败
******************************************************************************/
bool gui_set_color(gui_handle_t handle, gui_color_t color) {
gui_obj_t* obj = gui_get_obj(handle);
if (!obj) {
return false;
}
if (obj->color != color) {
obj->color = color;
obj->updated = true;
}
return true;
}
/******************************************************************************
函数说明:设置背景色
入口数据:handle 对象句柄
bg_color 背景颜色
返回值: true-成功 false-失败
******************************************************************************/
bool gui_set_bg_color(gui_handle_t handle, gui_color_t bg_color) {
gui_obj_t* obj = gui_get_obj(handle);
if (!obj) {
return false;
}
if (obj->bg_color != bg_color) {
obj->bg_color = bg_color;
obj->updated = true;
}
return true;
}
/******************************************************************************
函数说明:设置字体大小
入口数据:handle 对象句柄
font_size 字体大小
返回值: true-成功 false-失败
******************************************************************************/
bool gui_set_font_size(gui_handle_t handle, gui_font_size_t font_size) {
gui_obj_t* obj = gui_get_obj(handle);
if (!obj || !obj->data) {
return false;
}
switch (obj->type) {
case GUI_OBJ_TYPE_LABEL: {
gui_label_data_t* label_data = (gui_label_data_t*)obj->data;
if (label_data->font_size != font_size) {
label_data->font_size = font_size;
// 更新对象大小
int16_t char_width = font_size / 2;
int16_t char_height = font_size;
int text_len = label_data->text ? strlen(label_data->text) : 0;
obj->width = char_width * text_len;
obj->height = char_height;
obj->updated = true;
}
break;
}
case GUI_OBJ_TYPE_BUTTON: {
gui_button_data_t* button_data = (gui_button_data_t*)obj->data;
if (button_data->font_size != font_size) {
button_data->font_size = font_size;
obj->updated = true;
}
break;
}
default:
return false;
}
return true;
}
/******************************************************************************
函数说明:设置图片
入口数据:handle 对象句柄
image_data 图片数据
返回值: true-成功 false-失败
******************************************************************************/
bool gui_set_image(gui_handle_t handle, const uint8_t* image_data) {
gui_obj_t* obj = gui_get_obj(handle);
if (!obj || obj->type != GUI_OBJ_TYPE_IMAGE || !obj->data) {
return false;
}
gui_image_data_t* image_data_struct = (gui_image_data_t*)obj->data;
image_data_struct->image_data = image_data;
obj->updated = true;
return true;
}
/*---------------------- 渲染和事件处理函数 ----------------------*/
/******************************************************************************
函数说明:渲染单个对象
入口数据:handle 对象句柄
返回值: 无
******************************************************************************/
void gui_render_obj(gui_handle_t handle) {
gui_obj_t* obj = gui_get_obj(handle);
if (!obj || !obj->visible) {
return;
}
// 如果不需要更新,跳过
if (!obj->updated) {
return;
}
// 根据对象类型进行渲染
switch (obj->type) {
case GUI_OBJ_TYPE_PANEL: {
if (!obj->data) {
// 没有数据,默认绘制矩形
LCD_DrawRectangle(obj->x, obj->y,
obj->x + obj->width - 1,
obj->y + obj->height - 1,
obj->bg_color);
LCD_Fill(obj->x + 1, obj->y + 1,
obj->x + obj->width - 2,
obj->y + obj->height - 2,
obj->color);
break;
}
gui_panel_data_t* panel_data = (gui_panel_data_t*)obj->data;
switch (panel_data->shape) {
case GUI_PANEL_SHAPE_RECT: {
// 绘制矩形面板
if (obj->bg_color != obj->color) {
// 画边框
LCD_DrawRectangle(obj->x, obj->y,
obj->x + obj->width - 1,
obj->y + obj->height - 1,
obj->bg_color);
// 填充内部
LCD_Fill(obj->x + 1, obj->y + 1,
obj->x + obj->width - 2,
obj->y + obj->height - 2,
obj->color);
} else {
// 无边框
LCD_Fill(obj->x, obj->y,
obj->x + obj->width - 1,
obj->y + obj->height - 1,
obj->color);
}
break;
}
case GUI_PANEL_SHAPE_ROUNDED: {
check_panel_memory(panel_data);
// 打印结构体值
TFTP_DEBUG("Address of panel_data: %p", obj->data);
TFTP_DEBUG("STRUCT: shape=%d, radius=%d, thickness=%d",
panel_data->shape, panel_data->radius, panel_data->thickness);
render_rounded_panel_fixed(obj, panel_data);
break;
}
case GUI_PANEL_SHAPE_CIRCLE: {
uint8_t r = panel_data->radius;
uint8_t thickness = panel_data->thickness;
int16_t center_x = obj->x + obj->width / 2;
int16_t center_y = obj->y + obj->height / 2;
if (r == 0) {
r = (obj->width < obj->height) ?
obj->width / 2 : obj->height / 2;
}
if (thickness <= 1) {
// 薄边框或无边框
if (obj->bg_color != obj->color) {
// 绘制边框圆
LCD_Draw_Circle(center_x, center_y, r, obj->bg_color);
// 填充内部圆
if (r > 1) {
LCD_Draw_FillCircle(center_x, center_y, r - 1, obj->color);
}
} else {
// 无边框
LCD_Draw_FillCircle(center_x, center_y, r, obj->color);
}
} else {
// 绘制带粗细的圆
for (uint8_t t = 0; t < thickness; t++) {
LCD_Draw_Circle(center_x, center_y, r + t, obj->bg_color);
}
}
break;
}
case GUI_PANEL_SHAPE_ARC: {
uint8_t r = panel_data->radius;
uint8_t thickness = panel_data->thickness;
uint16_t start_angle = panel_data->start_angle;
uint16_t end_angle = panel_data->end_angle;
int16_t center_x = obj->x + obj->width / 2;
int16_t center_y = obj->y + obj->height / 2;
if (thickness <= 1) {
// 薄边框圆弧
LCD_DrawArc_Optimized(center_x, center_y, r,
start_angle, end_angle, obj->color);
} else {
// 厚边框圆弧
LCD_DrawArc_Polar(center_x, center_y, r,
start_angle, end_angle, thickness, obj->color);
}
break;
}
default:
// 默认绘制矩形
LCD_DrawRectangle(obj->x, obj->y,
obj->x + obj->width - 1,
obj->y + obj->height - 1,
obj->bg_color);
LCD_Fill(obj->x + 1, obj->y + 1,
obj->x + obj->width - 2,
obj->y + obj->height - 2,
obj->color);
}
break;
}
case GUI_OBJ_TYPE_LABEL: {
if (obj->data) {
gui_label_data_t* label_data = (gui_label_data_t*)obj->data;
// 填充背景
LCD_Fill(obj->x, obj->y, obj->x + obj->width, obj->y + obj->height, obj->bg_color);
// 绘制文本
LCD_ShowText(obj->x, obj->y, (uint8_t*)label_data->text, obj->color, obj->bg_color, label_data->font_size, 0);
}
break;
}
case GUI_OBJ_TYPE_LABEL_WRAP: {
if (obj->data) {
gui_label_wrap_data_t* label_data = (gui_label_wrap_data_t*)obj->data;
// 填充背景
LCD_Fill(obj->x, obj->y, obj->x + obj->width, obj->y + obj->height, obj->bg_color);
const char* text = label_data->text;
int16_t x = obj->x;
int16_t y = obj->y;
int16_t char_width = label_data->char_width;
int16_t char_height = label_data->char_height;
int16_t max_width = label_data->max_width;
if (max_width <= 0) {
// 不换行的情况
LCD_ShowText(x, y, (uint8_t*)text, obj->color, obj->bg_color, label_data->font_size, 0);
} else {
// 换行处理
int chars_per_line = max_width / char_width;
int text_len = strlen(text);
int current_pos = 0;
while (current_pos < text_len && y < obj->y + obj->height) {
// 计算当前行的字符数
int line_chars = text_len - current_pos;
if (line_chars > chars_per_line) {
line_chars = chars_per_line;
}
// 复制当前行
char line[128]; // 假设最大行长度
strncpy(line, text + current_pos, line_chars);
line[line_chars] = '\0';
// 绘制当前行
LCD_ShowText(x, y, (uint8_t*)line, obj->color, obj->bg_color, label_data->font_size, 0);
// 更新位置
current_pos += line_chars;
y += char_height;
}
}
}
break;
}
case GUI_OBJ_TYPE_BUTTON: {
if (obj->data) {
gui_button_data_t* button_data = (gui_button_data_t*)obj->data;
// 绘制按钮
if (button_data->pressed) {
// 按下状态
LCD_Fill(obj->x, obj->y, obj->x + obj->width, obj->y + obj->height, GUI_COLOR_DARKGRAY);
LCD_DrawRectangle(obj->x, obj->y, obj->x + obj->width - 1, obj->y + obj->height - 1, GUI_COLOR_BLACK);
} else {
// 正常状态
LCD_Fill(obj->x, obj->y, obj->x + obj->width, obj->y + obj->height, obj->bg_color);
LCD_DrawRectangle(obj->x, obj->y, obj->x + obj->width - 1, obj->y + obj->height - 1, GUI_COLOR_BLACK);
// 绘制阴影效果
LCD_DrawLine(obj->x, obj->y + obj->height - 1,
obj->x + obj->width - 1, obj->y + obj->height - 1,
GUI_COLOR_DARKGRAY);
LCD_DrawLine(obj->x + obj->width - 1, obj->y,
obj->x + obj->width - 1, obj->y + obj->height - 1,
GUI_COLOR_DARKGRAY);
}
// 绘制按钮文本
int16_t text_x = obj->x + (obj->width - (button_data->font_size / 2) * strlen(button_data->text)) / 2;
int16_t text_y = obj->y + (obj->height - button_data->font_size) / 2;
LCD_ShowText(text_x, text_y, (uint8_t*)button_data->text, obj->color, obj->bg_color, button_data->font_size, 0);
}
break;
}
case GUI_OBJ_TYPE_IMAGE: {
if (obj->data) {
gui_image_data_t* image_data = (gui_image_data_t*)obj->data;
if (image_data->image_data) {
LCD_ShowCentrePicture(obj->x, obj->y, obj->width, obj->height, image_data->image_data);
}
}
break;
}
default:
break;
}
obj->updated = false;
}
/**
* @brief 渲染所有GUI对象(按层次顺序)
*
* 渲染顺序:
* 1. 先渲染所有根对象(无父对象的对象)
* 2. 按层次顺序渲染子对象
* 3. 渲染剩余未处理的对象(容错机制)
*/
void gui_render_all(void) {
static gui_handle_t rendered_handles[MAX_HANDLES];
static int rendered_count = 0;
rendered_count = 0;
// 第一阶段:渲染所有根对象(无父对象的对象)
for (int i = 0; i < MAX_HANDLES; i++) {
if (mempool.blocks[i].state == BLOCK_USED) {
uint8_t type = mempool.blocks[i].type;
if (type >= GUI_OBJ_TYPE_WIDGET && type <= GUI_OBJ_TYPE_WINDOW) {
gui_obj_t* obj = (gui_obj_t*)stack_mempool_get_ptr(i);
if (obj && obj->parent == GUI_INVALID_HANDLE) {
// 渲染根对象
gui_render_obj(i);
rendered_handles[rendered_count++] = i;
}
}
}
}
// 第二阶段:按层次顺序渲染子对象
bool found_child = true;
while (found_child) {
found_child = false;
for (int i = 0; i < MAX_HANDLES; i++) {
if (mempool.blocks[i].state == BLOCK_USED) {
uint8_t type = mempool.blocks[i].type;
if (type >= GUI_OBJ_TYPE_WIDGET && type <= GUI_OBJ_TYPE_WINDOW) {
// 检查是否已渲染
bool already_rendered = false;
for (int j = 0; j < rendered_count; j++) {
if (rendered_handles[j] == i) {
already_rendered = true;
break;
}
}
if (!already_rendered) {
gui_obj_t* obj = (gui_obj_t*)stack_mempool_get_ptr(i);
if (obj) {
// 检查父对象是否已渲染
bool parent_rendered = false;
for (int j = 0; j < rendered_count; j++) {
if (rendered_handles[j] == obj->parent) {
parent_rendered = true;
break;
}
}
if (parent_rendered) {
gui_render_obj(i);
rendered_handles[rendered_count++] = i;
found_child = true;
}
}
}
}
}
}
}
// 第三阶段:渲染剩余未处理的对象(容错机制)
for (int i = 0; i < MAX_HANDLES; i++) {
if (mempool.blocks[i].state == BLOCK_USED) {
uint8_t type = mempool.blocks[i].type;
if (type >= GUI_OBJ_TYPE_WIDGET && type <= GUI_OBJ_TYPE_WINDOW) {
// 检查是否已渲染
bool already_rendered = false;
for (int j = 0; j < rendered_count; j++) {
if (rendered_handles[j] == i) {
already_rendered = true;
break;
}
}
if (!already_rendered) {
gui_render_obj(i);
rendered_handles[rendered_count++] = i;
}
}
}
}
// 记录渲染完成信息
TFTP_DEBUG("Rendering completed: %d objects rendered", rendered_count);
// 边界检查:验证子对象是否在父对象范围内
for (int i = 0; i < rendered_count; i++) {
gui_obj_t* obj = gui_get_obj(rendered_handles[i]);
if (obj && obj->parent != GUI_INVALID_HANDLE) {
gui_obj_t* parent = gui_get_obj(obj->parent);
if (parent) {
// 检查子对象是否超出父对象边界
if (obj->x < parent->x || obj->x + obj->width > parent->x + parent->width ||
obj->y < parent->y || obj->y + obj->height > parent->y + parent->height) {
TFTP_DEBUG("WARNING: Child %d exceeds parent %d bounds",
rendered_handles[i], obj->parent);
}
}
}
}
}
/**
* @brief 处理触摸屏输入事件
*
* 此函数遍历所有按钮对象,检测触摸点是否落在按钮区域内,并根据触摸事件类型
* (按下/释放)更新按钮状态。当触摸释放事件发生在按钮区域内时,会触发按钮的
* 点击回调函数。
*
* @param x 触摸点的X坐标(屏幕坐标系)
* @param y 触摸点的Y坐标(屏幕坐标系)
* @param event 触摸事件类型,应为以下值之一:
* - GUI_EVENT_PRESS: 触摸按下事件
* - GUI_EVENT_RELEASE: 触摸释放事件
*
* @note 该函数会遍历所有已使用的按钮类型对象,检查触摸点是否在对象区域内。
* 对于触摸按下事件,设置按钮的按下状态并标记需要更新显示。
* 对于触摸释放事件,清除按钮的按下状态,并如果触摸点在按钮区域内释放,
* 则触发GUI_EVENT_CLICK回调函数。
*
* @see GUI_EVENT_PRESS, GUI_EVENT_RELEASE, GUI_EVENT_CLICK
*/
void gui_handle_touch(int16_t x, int16_t y, uint8_t event) {
// 遍历所有按钮对象
for (mem_handle_t handle = 0; handle < MAX_HANDLES; handle++) {
if (mempool.blocks[handle].state == BLOCK_USED &&
mempool.blocks[handle].type == GUI_OBJ_TYPE_BUTTON) {
gui_obj_t* obj = gui_get_obj(handle);
if (obj && obj->visible) {
// 检查触摸点是否在按钮内
if (x >= obj->x && x < obj->x + obj->width &&
y >= obj->y && y < obj->y + obj->height) {
if (obj->data) {
gui_button_data_t* button_data = (gui_button_data_t*)obj->data;
if (event == GUI_EVENT_PRESS) {
button_data->pressed = true;
obj->updated = true;
}
else if (event == GUI_EVENT_RELEASE) {
button_data->pressed = false;
obj->updated = true;
// 触发点击事件
if (obj->callback) {
obj->callback(handle, GUI_EVENT_CLICK);
}
}
}
}
}
}
}
}
/**
* @brief 执行图形系统更新(渲染所有脏区)
* @note 此函数会触发整个GUI系统的重绘流程,通常在主循环中调用
*/
void gui_update(void) {
// 渲染所有需要更新的对象
gui_render_all();
}
/**
* @brief 获取GUI系统已使用的内存大小
* @return uint16_t 已使用内存大小(字节)
* @note 用于监控内存使用情况,防止内存泄漏
*/
uint16_t gui_get_memory_used(void) {
return mempool.alloc_ptr;
}
/**
* @brief 获取GUI系统当前可用内存大小
* @return uint16_t 可用内存大小(字节)
* @note 在创建新对象前可调用此函数检查内存是否充足
*/
uint16_t gui_get_memory_available(void) {
return stack_mempool_available();
}
/**
* @brief 执行内存池完整性检查
* @return bool 检查通过返回true,发现错误返回false
* @note 用于调试和验证内存管理系统的稳定性
*/
bool gui_memory_check(void) {
return stack_mempool_integrity_check();
}