从0开始使用面对对象C语言搭建一个基于OLED的图形显示框架(动态菜单组件实现)
目录
面对对象C的程序设计(范例)
面对对象C的程序设计(应用)
进一步谈论我上面给出的代码——继承
实现一个面对对象的文本编辑器
所以,什么是继承
重申我们对菜单的抽象
抽象菜单项目
抽象菜单动画
实现菜单功能
初始化我们的菜单
关于我们的图标设置,显示和隐藏
菜单本体功能
关于切换focus的菜单和进入父子菜单的函数
完整的测试文件
终于,我们来到了这个令人激动的部分了,现在,我们终于把所有的基础工作做好了,就差我们的动态组件了。
面对对象C的程序设计(范例)
我想,你可能使用过C++这门语言,他派生于C,但是最终的惯用编程范式又远远不同于C。尽管如此,C仍然可以按照一个相对变扭的方式完成面对对象的程序设计。这是因为在C语言本质上是过程化语言,没有直接的类和对象概念,因此面向对象设计需要通过结构体、函数指针等手段模拟实现。
面对对象,首先讲究的就是把所有的目标看成对象。举个例子,现在我们来看看动态多级菜单这个东西,按照面对对象的设计思路。我们说面对对象它通过抽象和封装将数据与功能结合,形成具有特定属性和行为的对象。
typedef struct {int x;int y;void (*move)(struct Point*, int, int);
} Point;
void movePoint(Point* p, int dx, int dy) {p->x += dx;p->y += dy;
}
int main() {Point p = {0, 0, movePoint};p.move(&p, 5, 3);return 0;
}
这个就是一个将点看作对象的例子。
我们设计对象的时候,要思考对象能干什么,进一步的,才需要知道他需要有什么。这种方式可以辅助一个习惯于面对过程的朋友设计一个对象。
面对对象C的程序设计(应用)
我们现在把上面谈到的用一下。
-
他能显示多级的文字菜单
-
他能将目前选中的文本区域进行反色
-
他能再切换选中文本的时候演示一个阻塞的动画(提示,笔者的框架没有做异步,这需要牵扯到中断,笔者不打算现在做)
-
如果一个子项存在子菜单,他能显示出来这个子菜单,然后还能返回去(怎么样触发进入和返回不是我们关心的,他能!)
-
他可以显示和隐藏我们的icon,为此,我们还需要注册接口。
为了做到上面的事情,我们要想他要拥有什么。
-
一个简略的文本编辑器,他能展示文字,我们菜单的文本绘制基本上依赖于这个文本编辑器
-
一个负责动画演示的结构体(对象),他能完成我们对“他能再切换选中文本的时候演示一个阻塞的动画”这个任务
-
一个菜单子项结构体数组,他维护了当前这个菜单子项的文本显示,是否有子菜单,甚至,还需要有callback行为的结构体数组(这个是额外任务,笔者没有做callback)
typedef void* CCgraphicWidgetBase;
/* update requist functions */
typedef void(*Update)(CCgraphicWidgetBase);
typedef void(*Hide)(CCgraphicWidgetBase);
typedef void(*Show)(CCgraphicWidgetBase);
typedef struct{Update update;Hide hide;Show show;
}CCGraphicWidgetCommonOperation;
typedef struct
{CCGraphicWidgetCommonOperation common;void (*switchToIndex)(CCGraphic_Menu*, uint8_t index);void (*enabled_showAnimations)(CCGraphic_Menu*, uint8_t enabled);void (*setIcon)(CCGraphic_Menu*, CCGraphic_Image* image, uint8_t size);void (*showIcon)(CCGraphic_Menu*);void (*hideIcon)(CCGraphic_Menu*);CCGraphic_Menu* (*enterSub)(CCGraphic_Menu*);CCGraphic_Menu* (*backParent)(CCGraphic_Menu*);
}CCGraphic_MenuOperations;
typedef struct __CCGraphic_Menu{// 菜单项数组CCGraphic_MenuItem* menuItemArrays;// 菜单项数组个数uint8_t menuArraySize;// 内部主控件CCGraphicTextEdit* internelTextEdit;// 动画负责的结构体CCGraphic_MenuAnimations* animation_holder;// 操作CCGraphic_MenuOperations operations;// 当前维护的其他信息uint8_t current_offset;uint8_t enabled_animations;CCGraphic_Image* icons_sources;uint8_t icon_size;uint8_t icon_state;
}CCGraphic_Menu;
任务:你可以改进这个抽象!你可以看到零碎一地的变量成员不太美观!
进一步谈论我上面给出的代码——继承
让我们进一步讨论更多的概念,上面的代码出现了一个很有意思的片段
typedef void* CCgraphicWidgetBase;
/* update requist functions */
typedef void(*Update)(CCgraphicWidgetBase);
typedef void(*Hide)(CCgraphicWidgetBase);
typedef void(*Show)(CCgraphicWidgetBase);
typedef struct{Update update;Hide hide;Show show;
}CCGraphicWidgetCommonOperation;
typedef struct
{CCGraphicWidgetCommonOperation common;void (*switchToIndex)(CCGraphic_Menu*, uint8_t index);void (*enabled_showAnimations)(CCGraphic_Menu*, uint8_t enabled);void (*setIcon)(CCGraphic_Menu*, CCGraphic_Image* image, uint8_t size);void (*showIcon)(CCGraphic_Menu*);void (*hideIcon)(CCGraphic_Menu*);CCGraphic_Menu* (*enterSub)(CCGraphic_Menu*);CCGraphic_Menu* (*backParent)(CCGraphic_Menu*);
}CCGraphic_MenuOperations;
仔细研究一下,你会发现,我们似乎复用了一个结构体:CCGraphicWidgetCommonOperation,也就是组件Widget的通用操作。为了理解这个特征,我们先不着急,实现一个完全面对对象的,一个简单的文本编辑器
实现一个面对对象的文本编辑器
#ifndef CCGraphic_TextEdit_H
#define CCGraphic_TextEdit_H
#include "Graphic/widgets/common/CCGraphic_WidgetBase.h"
#include "Graphic/base/CCGraphic_Point/CCGraphic_Point.h"
#include "Graphic/widgets/common/CCGraphic_Size/CCGraphic_Size.h"
#include "Graphic/widgets/common/CCGraphic_WidgetBase.h"
#include "Graphic/widgets/base/CCGraphic_TextItem/CCGraphic_TextItem.h"
#include "Graphic/widgets/base/CCGraphic_TextItem/CCGraphic_TextConfig.h"
typedef struct __CCGraphicTextEdit CCGraphicTextEdit;
// 前向声明:定义一个名为 `CCGraphicTextEdit` 的结构体类型。
typedef struct { CCGraphicWidgetCommonOperation operation; // 控件通用操作,提供基本控件功能。
void (*appendText)(CCGraphicTextEdit*, char* appendee); // 函数指针:向文本控件追加文本。
void (*setText)(CCGraphicTextEdit*, char* text); // 函数指针:设置控件内的完整文本内容。
void (*newLineText)(CCGraphicTextEdit*, char* text); // 函数指针:在控件中新起一行并写入文本。
void (*clear)(CCGraphicTextEdit*); // 函数指针:清空控件中的文本。
void (*relocate)(CCGraphicTextEdit*, CCGraphic_Point p, CCGraphic_Size size); // 函数指针:重新定位控件位置并调整控件尺寸。
} CCGraphicTextEdit_SupportiveOperations;
// 结构体 `CCGraphicTextEdit_SupportiveOperations`:定义文本控件支持的操作集合。
typedef struct __CCGraphicTextEdit { uint8_t acquired_stepped_update; // 标记是否启用分步更新机制的标志变量。
CCDeviceHandler* borrowed_device; // 设备处理器指针,用于管理外部设备资源。
CCGraphicTextEdit_SupportiveOperations operations; // 文本控件支持操作的集合。
CCGraphic_AsciiTextItem* handle; // 控件中的具体文本项句柄,用于操作字符显示内容。
} CCGraphicTextEdit;
// 结构体 `CCGraphicTextEdit`:定义文本控件的属性与操作。
void CCGraphic_init_CCGraphicTextEdit( CCGraphicTextEdit* text_self, CCDeviceHandler* handler, CCGraphic_AsciiTextItem* inited
);
// 函数声明:初始化 `CCGraphicTextEdit` 控件,传入控件对象、设备处理器和已初始化的文本项。
#endif
你可能会问,怎么看起来这么奇怪,我们应该如何调用功能呢?你看,这就是思维没有转变过来,笔者想要说的是,现在功能被集成进入了结构体,意味着,我们想要调用的不叫函数了,是一个结构体的方法。
static void __helper_on_set_text(CCGraphicTextEdit* edit, char* sources, uint32_t shown_time)
{edit->operations.setText(edit, sources);HAL_Delay(shown_time * 1000);edit->operations.clear(edit);
}
看到了吗?当我们想要设置文本的时候,不是
CCGraphicTextEdit_setText(edit, sources);
而是
edit->operations.setText(edit, sources);
看起来好像没什么区别,我想说的是,你现在不知道,也没法去引用一个函数,叫“给一个是CCGraphicTextEdit的结构体设置文本”的函数,你找不到,我藏起来了(笑),而是,一个属于CCGraphicTextEdit这个类的对象可以被设置文本,文本是sources,这就是面对对象的设计思考范式。换而言之,一个CCGraphicTextEdit的对象可以设置文本,他能设置文本而且优先的投射到绘图设备上,而你完全不知道底下发生了什么,只知道这样做一定没有问题!
在源文件中,我们才将如何实现暴露出来
#include "Graphic/widgets/components/CCGraphic_TextEdit/CCGraphic_TextEdit.h"
#include "Graphic/widgets/base/CCGraphic_TextItem/CCGraphic_TextItem.h"
#include "Graphic/CCGraphic_device_adapter.h"
static void __pvt_update_text(CCGraphicTextEdit* text_self)
// 静态函数:更新控件所依赖的设备内容。
{text_self->borrowed_device->operations.update_device_function(text_self->borrowed_device // 调用设备的更新函数,使文本控件的内容刷新显示。 );
}
static void __pvt_show(CCGraphicTextEdit* text_self)
// 静态函数:绘制并显示文本控件内容。
{CCGraphicWidget_drawAsciiTextItem(text_self->borrowed_device, text_self->handle // 绘制文本控件的内容。 );if(text_self->acquired_stepped_update) // 如果启用了分步更新,则执行设备更新。 __pvt_update_text(text_self);
}
static void __pvt_hide(CCGraphicTextEdit* text_self)
// 静态函数:隐藏控件,即清除其显示区域。
{text_self->borrowed_device->operations.clearArea_function(text_self->borrowed_device, text_self->handle->tl_point.x, text_self->handle->tl_point.y, text_self->handle->TexthandleSize.width, text_self->handle->TexthandleSize.height // 清除控件所在区域的内容。 );__pvt_update_text(text_self);
}
static void __pvt_clear_text(CCGraphicTextEdit* text_self)
// 静态函数:清除控件中的文本内容。
{CCGraphic_Point tl = text_self->handle->tl_point; CCGraphic_Size size = text_self->handle->TexthandleSize; // 获取控件左上角坐标和尺寸,用于清除操作。
text_self->borrowed_device->operations.clearArea_function(text_self->borrowed_device, tl.x, tl.y, size.width, size.height // 执行清除操作。 );__pvt_update_text(text_self);
}
static void __pvt_append_text(CCGraphicTextEdit* text_self, char* text)
// 静态函数:向控件追加文本。
{CCGraphicWidget_AsciiTextItem_setAsciiText(text_self->handle, text); // 设置追加的文本内容。 __pvt_show(text_self); // 显示控件内容。
}
static void __pvt_newLine_text(CCGraphicTextEdit* text_self, char* text)
// 静态函数:在控件中新建一行并写入文本。
{CCGraphic_Point new_begin = CCGraphicWidget_AsciiTextItem_on_newLine_point(text_self->handle); // 获取新行起始点坐标。
CCGraphicWidget_AsciiTextItem_setAsciiText(text_self->handle, text); // 设置文本内容。
CCGraphicWidget_AsciiTextItem_setIndexedPoint(text_self->handle, &new_begin); // 更新文本项的绘制位置。
__pvt_show(text_self); // 显示控件内容。
}
static void __pvt_setText(CCGraphicTextEdit* text_self, char* text)
// 静态函数:设置控件的完整文本内容。
{__pvt_clear_text(text_self); // 清除原有内容。
CCGraphicWidget_AsciiTextItem_setIndexedPoint(text_self->handle, &(text_self->handle->tl_point) // 重置文本绘制位置为控件起点。 );
CCGraphicWidget_AsciiTextItem_setAsciiText(text_self->handle, text); // 设置新的文本内容。
__pvt_show(text_self); // 显示控件内容。
}
static void __pvt_relocate(CCGraphicTextEdit* edit, CCGraphic_Point p, CCGraphic_Size size)
// 静态函数:重新定位控件位置并调整尺寸。
{__pvt_hide(edit); // 隐藏控件内容。
CCGraphicWidget_AsciiTextItem_relocate(edit->handle, p, size); // 重新设置控件位置和尺寸。
}
void CCGraphic_init_CCGraphicTextEdit( CCGraphicTextEdit* text_self, CCDeviceHandler* handler, CCGraphic_AsciiTextItem* inited
)
// 初始化函数:设置文本编辑控件的初始状态。
{text_self->acquired_stepped_update = 0; // 初始化为未启用分步更新。
text_self->borrowed_device = handler; // 绑定设备处理器。
text_self->handle = inited; // 设置文本项句柄。
text_self->operations.appendText = __pvt_append_text; text_self->operations.clear = __pvt_clear_text; text_self->operations.newLineText = __pvt_newLine_text; text_self->operations.setText = __pvt_setText; text_self->operations.relocate = __pvt_relocate; // 绑定控件的各类操作函数。
text_self->operations.operation.hide = (Hide)__pvt_hide; text_self->operations.operation.show = (Show)__pvt_show; text_self->operations.operation.update = (Update)__pvt_update_text; // 绑定通用控件操作。
}
可以看到,代码被分成了一层一层的,关心哪一个方法,只需要进入对应的方法查看即可。
所以,什么是继承
继承允许一个类从另一个类中获取属性和方法,从而实现代码复用和逻辑扩展。也就是说,我们认为继承表达了“一个对象是另一个对象”的陈述。比如说。
typedef struct { CCGraphicWidgetCommonOperation operation; // 控件通用操作,提供基本控件功能。
void (*appendText)(CCGraphicTextEdit*, char* appendee); // 函数指针:向文本控件追加文本。
void (*setText)(CCGraphicTextEdit*, char* text); // 函数指针:设置控件内的完整文本内容。
void (*newLineText)(CCGraphicTextEdit*, char* text); // 函数指针:在控件中新起一行并写入文本。
void (*clear)(CCGraphicTextEdit*); // 函数指针:清空控件中的文本。
void (*relocate)(CCGraphicTextEdit*, CCGraphic_Point p, CCGraphic_Size size); // 函数指针:重新定位控件位置并调整控件尺寸。
} CCGraphicTextEdit_SupportiveOperations;
表达了CCGraphicTextEdit的功能集合是CCGraphicWidget的一个超集,他不光拥有一个Widget该有的操作,而且,还有自己跟Widget独有的操作,这就是继承的力量——复用接口,甚至可以是实现!
重申我们对菜单的抽象
根据最之前的描述,菜单本身应该是一个树状的结构,你可以看到:
抽象菜单项目
#ifndef CCGraphic_MenuItem_H
#define CCGraphic_MenuItem_H
#include "Graphic/CCGraphic_common.h"
/* This version we use simple menu Item */
/* announced the menu type for the further usage */
// 预声明 `CCGraphic_Menu` 类型,用于菜单关联。
typedef struct __CCGraphic_Menu CCGraphic_Menu;
// 结构体 `CCGraphic_Menu` 的前向声明,以便在结构中使用指针引用该类型。
#define NO_SUB_MENU (NULL)
// 定义宏 `NO_SUB_MENU` 表示没有子菜单,为空指针。
typedef struct __CCGraphic_MenuItem { char* text; // 菜单项显示的文本内容。
CCGraphic_Menu* subMenu; // 指向子菜单的指针,若无子菜单则为 `NO_SUB_MENU`。
CCGraphic_Menu* parentMenu; // 指向父菜单的指针,用于返回或层级控制。
} CCGraphic_MenuItem;
// 定义菜单项结构体 `CCGraphic_MenuItem`,包含菜单文字、子菜单及父菜单指针。
void CCGraphic_MenuItem_register_menuItem( CCGraphic_MenuItem* item, // 菜单项指针,用于注册菜单项。
CCGraphic_Menu* parentMenu, // 父菜单指针,将菜单项附加到此菜单下。
char* text, // 菜单项文本内容。
CCGraphic_Menu* subMenu // 子菜单指针,可为 `NO_SUB_MENU`。
);
// 函数声明:将菜单项注册到指定父菜单下,同时设置菜单项文本和子菜单。
#endif
提示:需要做callback?(用户明确选择了这个菜单项目)试一下在CCGraphic_MenuItem中添加抽象!完成你的代码!
抽象菜单动画
typedef struct __CCGraphic_MenuAnimations CCGraphic_MenuAnimations;
// 前向声明 `CCGraphic_MenuAnimations` 结构体,表示菜单动画的管理结构。
typedef void (*DoByStep)(CCGraphic_MenuAnimations*);
// 定义一个函数指针类型 `DoByStep`,指向以 `CCGraphic_MenuAnimations*` 为参数的函数,
// 该函数用于执行逐步动画操作。
typedef struct { DoByStep doByStep; // 操作结构体,包含逐步执行动画的函数指针。
} CCGraphic_MenuAnimationsOperations;
// 定义 `CCGraphic_MenuAnimationsOperations` 结构体,封装了逐步动画执行的操作。
/*this struct shouldn't be registered by programmersit shoule be registered by program, so no interface ispubliced!
*/
// 该结构体不应由程序员手动注册,而是由程序自动注册,因此没有提供公开接口。
typedef struct __CCGraphic_MenuAnimations { /* animating rectangle */ // 定义菜单动画的结构体。
CCDeviceHandler* handler; // 设备处理器,用于控制设备的操作。
CCGraphic_Point tl_point; // 动画的起始点(左上角坐标)。
CCGraphic_Size animationOffsetSize; // 动画的偏移尺寸,用于表示动画区域的大小。
int16_t x_step; // x轴每步移动的像素值,用于控制动画的水平位移。
int16_t y_step; // y轴每步移动的像素值,用于控制动画的垂直位移。
CCGraphic_MenuAnimationsOperations op; // 操作对象,包含执行逐步动画的函数指针。
uint8_t is_doing; // 标志位,表示动画是否正在进行中。
} CCGraphic_MenuAnimations;
// 定义菜单动画结构体,封装了动画的状态、操作及设备控制。
初始化一个动画的办法是:
static void __pvt_init_animations( CCGraphic_Menu* menu, CCGraphic_MenuAnimations* animations
) { /* no animations are registered */ // 如果没有提供动画对象,直接返回。 if (animations == NULL) { return; }
// 获取菜单中的文本编辑项句柄,进行后续动画初始化。 CCGraphic_AsciiTextItem* internelTextEdit = menu->internelTextEdit->handle;
/* calculate the animations holding size */ // 计算动画的大小,首先设置动画起始点为文本编辑项的起始点。 animations->tl_point = internelTextEdit->tl_point;
// 设置动画的高度为字体的大小(通过 `__fetch_font_size` 获取字体的高度)。 animations->animationOffsetSize.height = __fetch_font_size(internelTextEdit->font_size).height;
// 设置动画的宽度为文本处理器的宽度。 animations->animationOffsetSize.width = internelTextEdit->TexthandleSize.width;
// 设置设备处理器,使用菜单中的文本编辑项借用的设备。 animations->handler = menu->internelTextEdit->borrowed_device;
// 设置每步的水平和垂直步长(默认值)。 animations->x_step = _DEFAULT_X_STEP; animations->y_step = _DEFAULT_Y_STEP;
// 设置执行逐步动画操作的函数指针,指向 `__pvt_doByStep` 函数。 animations->op.doByStep = __pvt_doByStep;
/* set state */ // 设置动画状态为未开始。 animations->is_doing = 0;
}
对于逐步开始动画的办法是
/* do by steps */
static void __pvt_doByStep(CCGraphic_MenuAnimations* animations)
{ // 如果动画尚未开始(is_doing 为 0),则直接返回,避免不必要的操作。 if (!animations->is_doing) return;
// 使用设备的操作对象反转(擦除)动画区域,传入当前动画的起始位置(tl_point)和尺寸。 animations->handler->operations.reverseArea_function( animations->handler, animations->tl_point.x, animations->tl_point.y, animations->animationOffsetSize.width, animations->animationOffsetSize.height );
// 更新动画的起始点(左上角坐标),按水平步长(x_step)和垂直步长(y_step)更新。 animations->tl_point.x += animations->x_step; animations->tl_point.y += animations->y_step;
// 再次调用反转(擦除)区域,传入更新后的动画位置和尺寸。 animations->handler->operations.reverseArea_function( animations->handler, animations->tl_point.x, animations->tl_point.y, animations->animationOffsetSize.width, animations->animationOffsetSize.height );
// 调用更新设备函数,刷新屏幕以显示动画效果。 animations->handler->operations.update_device_function( animations->handler );
}
看到了吗,非必要不调用刷新设备的操作就在这里体现了。当然,当我们配置不需要动画的时候
static void __pvt_do_as_immediate(CCGraphic_MenuAnimations* animations, CCGraphic_Point* end_tpl)
{if(!animations->is_doing) return;animations->handler->operations.reverseArea_function(animations->handler, animations->tl_point.x, animations->tl_point.y,animations->animationOffsetSize.width, animations->animationOffsetSize.height);animations->tl_point = *end_tpl;animations->handler->operations.reverseArea_function(animations->handler, animations->tl_point.x, animations->tl_point.y,animations->animationOffsetSize.width, animations->animationOffsetSize.height);animations->handler->operations.update_device_function(animations->handler);
}
直接拉到最后就好了。
实现菜单功能
到了真正实现的时候,一切反而水到渠成。
初始化我们的菜单
void CCGraphic_init_Menu(CCGraphic_Menu* blank_menu,CCGraphic_MenuItem* menuItemArrays,uint8_t menuArraySize,CCGraphicTextEdit* configured_menu,CCGraphic_MenuAnimations* blank_animations,uint8_t enabled_animations
)
{blank_menu->internelTextEdit = configured_menu;blank_menu->menuItemArrays = menuItemArrays;blank_menu->menuArraySize = menuArraySize;blank_menu->animation_holder = blank_animations;blank_menu->current_offset = 0;blank_menu->icon_state = 0;blank_menu->enabled_animations = enabled_animations;
// map the functionsblank_menu->operations.common.hide = (Hide)__pvt_hide_CCGraphic_Menu;blank_menu->operations.common.show = (Show)__pvt_show_CCGraphic_Menu;blank_menu->operations.common.update = (Update)__pvt_update;blank_menu->operations.switchToIndex = __pvt_switchIndex;blank_menu->operations.enabled_showAnimations = __pvt_setAnimationShowState_wrapper;// iconsblank_menu->operations.setIcon = __pvt_setIcon;blank_menu->operations.hideIcon = __pvt_hideIcon;blank_menu->operations.showIcon = __pvt_showIcon;blank_menu->operations.enterSub = __pvt_enterSub;blank_menu->operations.backParent = __pvt_backParent;__pvt_init_animations(blank_menu, blank_animations);
}
关于我们的图标设置,显示和隐藏
static void __pvt_setIcon(CCGraphic_Menu* menu, CCGraphic_Image* sources, uint8_t size)
{ // 设置菜单的图标源和图标数量 menu->icons_sources = sources; menu->icon_size = size;
// 初始化每个图标的尺寸和位置 for (uint8_t i = 0; i < menu->icon_size; i++) { // 设置图标的高度和宽度 sources[i].image_size.height = ICON_HEIGHT; sources[i].image_size.width = ICON_WIDTH;
// 设置每个图标的位置,`y` 方向依次排列 sources[i].point.x = 0; sources[i].point.y = i * ICON_HEIGHT; }
// 显示图标 __pvt_showIcon(menu);
}
static void __pvt_showIcon(CCGraphic_Menu* menu)
{ // 如果没有图标源,则不执行任何操作 if (!menu->icons_sources) return; // 设置图标的状态为显示(1) menu->icon_state = 1; CCGraphic_Point tlp; CCGraphic_Size _size; // 获取显示图标的位置和大小 __pvt_providePoint(menu, &tlp, 1); __pvt_provideSize(menu, &_size, 1); // 设置动画的显示状态为 0(关闭动画) __pvt_setAnimationShowState(menu->animation_holder, 0); // 将菜单项的文本编辑区域重新定位到指定的位置和大小 menu->internelTextEdit->operations.relocate(menu->internelTextEdit, tlp, _size); // 遍历图标源,逐一绘制每个图标 for (uint8_t i = 0; i < menu->icon_size; i++) { CCGraphicWidget_draw_image( menu->internelTextEdit->borrowed_device, &menu->icons_sources[i] ); } // 设置动画的显示状态为 1(启用动画) __pvt_setAnimationShowState(menu->animation_holder, 1); // 仅显示文本编辑器 __pvt_show_textEditOnly(menu);
}
static void __pvt_hideIcon(CCGraphic_Menu* menu)
{ // 如果没有图标源,则不执行任何操作 if (!menu->icons_sources) return; CCGraphic_Point tlp; CCGraphic_Size _size; // 设置图标的状态为隐藏(0) menu->icon_state = 0; // 获取隐藏图标的位置和大小 __pvt_providePoint(menu, &tlp, 0); __pvt_provideSize(menu, &_size, 0); // 设置动画的显示状态为 0(关闭动画) __pvt_setAnimationShowState(menu->animation_holder, 0); // 将菜单项的文本编辑区域重新定位到指定的位置和大小 menu->internelTextEdit->operations.relocate(menu->internelTextEdit, tlp, _size); // 清除图标区域 menu->internelTextEdit->borrowed_device->operations.clearArea_function( menu->internelTextEdit->borrowed_device, 0, 0, ICON_WIDTH, ICON_HEIGHT * menu->icon_size ); // 仅显示文本编辑器 __pvt_show_textEditOnly(menu);
}
图标的绘制就是让位子绘制,清除掉重新绘制这个思路。
菜单本体功能
static void __pvt_update(CCGraphic_Menu* menu)
{// 调用文本编辑器的更新操作,刷新菜单显示menu->internelTextEdit->operations.operation.update(menu->internelTextEdit);
}
// 更新动画状态
static void __pvt_setAnimationShowState(CCGraphic_MenuAnimations* animations, uint8_t is_doing)
{// 如果动画状态没有变化,直接返回if(is_doing == animations->is_doing){return;}// 如果动画正在进行,先逆向绘制区域,清除之前的显示animations->handler->operations.reverseArea_function(animations->handler, animations->tl_point.x, animations->tl_point.y,animations->animationOffsetSize.width, animations->animationOffsetSize.height);// 更新动画状态animations->is_doing = is_doing;// 更新设备,刷新显示animations->handler->operations.update_device_function(animations->handler);
}
/*以下是显示/隐藏图标时,提供布局计算的函数
*/
static void __pvt_providePoint(CCGraphic_Menu* menu, CCGraphic_Point* p, uint8_t icons_enabled)
{// 根据是否启用图标,设置图标显示的起始位置p->x = icons_enabled ? ICON_WIDTH : 0;p->y = 0;
}
static void __pvt_provideSize(CCGraphic_Menu* menu, CCGraphic_Size* size, uint8_t icons_enabled
){// 根据是否启用图标,调整文本区域的宽度和高度size->width = menu->internelTextEdit->handle->TexthandleSize.width - (icons_enabled ? ICON_HEIGHT : 0);size->height = menu->internelTextEdit->handle->TexthandleSize.height;
}
// 获取当前菜单项是否有子菜单
static inline CCGraphic_Menu* __pvt_current_owns_subMenu(CCGraphic_Menu* menu)
{return menu->menuItemArrays[menu->current_offset].subMenu;
}
// 获取当前菜单项的父菜单
static inline CCGraphic_Menu* __pvt_owns_parent_current(CCGraphic_Menu* menu)
{return menu->menuItemArrays[menu->current_offset].parentMenu;
}
// 仅显示文本编辑器的内容,更新菜单显示
void __pvt_show_textEditOnly(CCGraphic_Menu* menu)
{// 如果菜单没有项,则直接返回if(menu->menuArraySize == 0){return;}// 设置动画状态为不显示__pvt_setAnimationShowState(menu->animation_holder, 0);// 设置文本编辑器的内容,显示第一项菜单CCGraphicTextEdit* edit = menu->internelTextEdit;edit->operations.setText(edit, menu->menuItemArrays[0].text);// 显示后续菜单项for(uint8_t i = 1; i < menu->menuArraySize; i++){edit->operations.newLineText(edit, menu->menuItemArrays[i].text);}// 设置动画状态为显示__pvt_setAnimationShowState(menu->animation_holder, 1);
}
// 隐藏菜单和图标
void __pvt_hide_CCGraphic_Menu(CCGraphic_Menu* menu)
{// 隐藏图标__pvt_hideIcon(menu);// 隐藏文本编辑器menu->internelTextEdit->operations.operation.hide(menu->internelTextEdit);// 获取动画控制器CCGraphic_MenuAnimations* animation = menu->animation_holder;// 如果没有动画控制器,则返回if(!animation) return;// 如果动画正在进行,则停止动画if(animation->is_doing){__pvt_setAnimationShowState(animation, 0);}
}
/* 绘制菜单显示 */
void __pvt_show_CCGraphic_Menu(CCGraphic_Menu* menu)
{// 仅显示文本编辑器内容__pvt_show_textEditOnly(menu);
}
// 执行动画,逐步更新菜单位置直到目标位置
void __pvt_do_stepped_animate(CCGraphic_MenuAnimations* animations, CCGraphic_Point* end_tl_p
)
{// 如果动画步长为负,表示需要向下移动if(animations->y_step < 0){// 逐步向下执行动画,直到达到目标位置while(animations->tl_point.y > end_tl_p->y){__pvt_doByStep(animations); // 执行单步动画
#ifdef REQ_ANIMATION_DELAY// 延时,模拟动画效果__device_delay(ANIMATION_DELAY_MS);
#endif}}// 如果动画步长为正,表示需要向上移动else{// 逐步向上执行动画,直到达到目标位置while(animations->tl_point.y < end_tl_p->y){__pvt_doByStep(animations); // 执行单步动画
#ifdef REQ_ANIMATION_DELAY// 延时,模拟动画效果__device_delay(ANIMATION_DELAY_MS);
#endif} }}
关于切换focus的菜单和进入父子菜单的函数
// 切换菜单项索引并执行动画
static void __pvt_switchIndex(CCGraphic_Menu* menu, uint8_t index)
{// 如果索引没有变化,不做任何操作if(index == menu->current_offset) return;
// 如果新索引大于当前索引,表示需要向下移动if(index > menu->current_offset){// 如果当前动画步长为负,改为正值if(menu->animation_holder->y_step < 0){menu->animation_holder->y_step = -menu->animation_holder->y_step;}}// 如果新索引小于当前索引,表示需要向上移动else{// 如果当前动画步长为正,改为负值if(menu->animation_holder->y_step > 0){menu->animation_holder->y_step = -menu->animation_holder->y_step;}}// 更新当前菜单项的索引menu->current_offset = index;// 计算目标位置CCGraphic_Point end_tlp;end_tlp = menu->animation_holder->tl_point; end_tlp.y = index * menu->animation_holder->animationOffsetSize.height;// 如果启用了动画,执行逐步动画if(menu->enabled_animations)__pvt_do_stepped_animate(menu->animation_holder, &end_tlp);else// 否则,立即执行动画__pvt_do_as_immediate(menu->animation_holder, &end_tlp);
}
// 进入子菜单并显示子菜单的内容
static CCGraphic_Menu* __pvt_enterSub(CCGraphic_Menu* parentMenu)
{// 缓存父菜单的图标状态uint8_t cached_icon_state = parentMenu->icon_state;// 获取父菜单的子菜单CCGraphic_Menu* subone = __pvt_current_owns_subMenu(parentMenu);// 如果没有子菜单,返回NULLif(!subone) return NULL;// 隐藏当前菜单parentMenu->operations.common.hide(parentMenu);// 恢复父菜单的图标状态parentMenu->icon_state = cached_icon_state;// 如果子菜单有图标,显示图标,否则显示子菜单if(subone->icon_state){subone->operations.showIcon(subone);}else{subone->operations.common.show(subone);}// 返回子菜单return subone;
}
// 返回父菜单并显示父菜单的内容
static CCGraphic_Menu* __pvt_backParent(CCGraphic_Menu* subMenu)
{// 缓存子菜单的图标状态uint8_t cached_icon_state = subMenu->icon_state;// 获取子菜单的父菜单CCGraphic_Menu* parentMenu = __pvt_owns_parent_current(subMenu);// 如果没有父菜单,返回NULLif(!parentMenu) return NULL;// 隐藏当前子菜单subMenu->operations.common.hide(subMenu);// 恢复子菜单的图标状态subMenu->icon_state = cached_icon_state;// 如果父菜单有图标,显示图标,否则显示父菜单if(parentMenu->icon_state){parentMenu->operations.showIcon(parentMenu);}else{parentMenu->operations.common.show(parentMenu);}// 返回父菜单return parentMenu;
}
完整的测试文件
现在来看看完整的测试文件!
#include "Test/OLED_TEST/oled_test.h"
#include "Test/GraphicTest/graphic_test.h"
#include "Graphic/widgets/components/CCGraphic_TextEdit/CCGraphic_TextEdit.h"
void test_oled_iic_functionalities()
{OLED_Handle handle;user_init_hard_iic_oled_handle(&handle);test_set_pixel_line(&handle, 1, 2);HAL_Delay(1000);test_clear(&handle);test_set_pixel_line(&handle, 2, 1);HAL_Delay(1000);test_clear(&handle);
}
void test_oled_spi_functionalities()
{OLED_Handle handle;user_init_hard_spi_oled_handle(&handle);test_set_pixel_line(&handle, 1, 2);HAL_Delay(1000);test_clear(&handle);test_set_pixel_line(&handle, 2, 1);HAL_Delay(1000);test_clear(&handle);
}
static void __helper_on_set_text(CCGraphicTextEdit* edit, char* sources, uint32_t shown_time)
{edit->operations.setText(edit, sources);HAL_Delay(shown_time * 1000);edit->operations.clear(edit);
}
#define SET_TEXT_CONV(SRC, SECS) do{ sources = SRC;\__helper_on_set_text(&edit, sources, SECS);}while(0)
static void __test_common(CCDeviceHandler* handler)
{CCGraphicTextEdit edit;CCGraphic_AsciiTextItem item;CCGraphic_Point p;p.x = 0;p.y = 0;CCGraphic_Size acceptablesize = CCGraphicWidget_MaxAcceptable_Size(handler);CCGraphicWidget_init_AsciiTextItem(&item, p, acceptablesize, ASCII_6x8);CCGraphic_init_CCGraphicTextEdit(&edit, handler, &item);edit.acquired_stepped_update = 1;char* sources;SET_TEXT_CONV("Hello! Welcome CCGraphic SimpleTest!", 5);SET_TEXT_CONV("If you see this sentences, ""it means that you have passed the GraphicTest""and congratulations!", 7);
SET_TEXT_CONV("Graphic Test On Base begin", 4);SET_TEXT_CONV("Test Points", 4);on_test_draw_points(handler);HAL_Delay(1000);SET_TEXT_CONV("Test Lines", 4);on_test_draw_line(handler);HAL_Delay(1000);SET_TEXT_CONV("Test Circles", 4);on_test_draw_circle(handler);HAL_Delay(1000);SET_TEXT_CONV("Test Rectangle", 4);on_test_draw_rectangle(handler);HAL_Delay(1000);SET_TEXT_CONV("Test Triangle", 4);on_test_draw_triangle(handler);HAL_Delay(1000);SET_TEXT_CONV("Test Ellipse", 4);on_test_draw_ellipse(handler);HAL_Delay(1000);SET_TEXT_CONV("Test Arc", 4);on_test_draw_arc(handler);HAL_Delay(1000);SET_TEXT_CONV("Graphic Test On Base end", 4);SET_TEXT_CONV("Graphic Test On widget begin", 4);SET_TEXT_CONV("Test Image Drawing", 4);
/* widget test */on_test_draw_image(handler);HAL_Delay(1000);SET_TEXT_CONV("Test Ascii Draw", 4);on_test_draw_ascii(handler);HAL_Delay(1000);SET_TEXT_CONV("Graphic Test On widget end", 4);SET_TEXT_CONV("Graphic Test On component begin", 4);SET_TEXT_CONV("Test TextEdit", 4);/* components test */on_test_component_textEdit_test(handler);HAL_Delay(1000);SET_TEXT_CONV("Test Frame", 4);on_test_component_frame_test(handler);HAL_Delay(1000);SET_TEXT_CONV("Test Menu", 4);on_test_component_menu(handler);HAL_Delay(1000);SET_TEXT_CONV("Graphic Test On component end", 4);SET_TEXT_CONV("Finish Testing, enjoy!", 4);
}
void test_graphic_hardiic_functionalities()
{CCDeviceHandler handler;on_test_init_hardiic_oled(&handler);
__test_common(&handler);
}
void test_graphic_soft_spi_functionalities()
{CCDeviceHandler handler;on_test_init_softspi_oled(&handler);
__test_common(&handler);
}
void test_graphic_hard_spi_functionalities()
{CCDeviceHandler handler;on_test_init_hardspi_oled(&handler);
__test_common(&handler);
}
效果就是这个完整的测试视频:
完整测试视频
目录导览
总览
协议层封装
OLED设备封装
绘图设备抽象
基础图形库封装
基础组件实现
动态菜单组件实现
相关文章:
从0开始使用面对对象C语言搭建一个基于OLED的图形显示框架(动态菜单组件实现)
目录 面对对象C的程序设计(范例) 面对对象C的程序设计(应用) 进一步谈论我上面给出的代码——继承 实现一个面对对象的文本编辑器 所以,什么是继承 重申我们对菜单的抽象 抽象菜单项目 抽象菜单动画 实现菜单功…...
Java的StackWalker类
Java的StackWalker类怎么使用 Java 中的 StackWalker 类(自 Java 9 引入)提供了一种高效且灵活的方式来访问堆栈跟踪信息。以下是其使用方法的逐步说明: 1. 基本使用:获取当前堆栈跟踪 import java.lang.StackWalker;public cla…...
农产品价格报告爬虫使用说明
农产品价格报告爬虫使用说明 # ************************************************************************** # * * # * 农产品价格报告爬虫 …...
Pwn 入门核心工具和命令大全
一、调试工具(GDB 及其插件) GDB 启动调试:gdb ./binary 运行程序:run 或 r 设置断点:break *0x地址 或 b 函数名 查看寄存器:info registers 查看内存:x/10wx 0x地址 (查看 10 个 …...
字节iOS面试经验分享:HTTP与网络编程
字节iOS面试经验分享:HTTP与网络编程 🌟 嗨,我是LucianaiB! 🌍 总有人间一两风,填我十万八千梦。 🚀 路漫漫其修远兮,吾将上下而求索。 目录 字节iOS面试经验分享:HTT…...
在汇编语言中,ASSUME 是一个用于告诉汇编器如何将段寄存器与特定段名称关联的指令
在汇编语言中,ASSUME 是一个用于告诉汇编器如何将段寄存器与特定段名称关联的指令。它主要用于定义代码段、数据段和栈段等的段寄存器使用方式,帮助编译器生成正确的代码。 具体到 ASSUME DS:DATA, CS:CODE, SS:STACK,这行代码的作用如下&…...
代码随想录_栈与队列
栈与队列 232.用栈实现队列 232. 用栈实现队列 使用栈实现队列的下列操作: push(x) – 将一个元素放入队列的尾部。 pop() – 从队列首部移除元素。 peek() – 返回队列首部的元素。 empty() – 返回队列是否为空。 思路: 定义两个栈: 入队栈, 出队栈, 控制出入…...
Ubuntu 手动安装 Open WebUI 完整指南
Ubuntu 手动安装 Open WebUI 完整指南 前提条件 在安装 Open WebUI 之前,请确保您的系统满足以下要求: Ubuntu 22.04 LTS 或更高版本Python 3.10Node.js 18Git至少 4GB 内存足够的磁盘空间(推荐 20GB 以上) 安装步骤 1. 更新…...
【Oracle篇】使用Hint对优化器的执行计划进行干预(含单表、多表、查询块、声明四大类Hint干预)
💫《博主介绍》:✨又是一天没白过,我是奈斯,从事IT领域✨ 💫《擅长领域》:✌️擅长阿里云AnalyticDB for MySQL(分布式数据仓库)、Oracle、MySQL、Linux、prometheus监控;并对SQLserver、NoSQL(…...
论文阅读(九):通过概率图模型建立连锁不平衡模型和进行关联研究:最新进展访问之旅
1.论文链接:Modeling Linkage Disequilibrium and Performing Association Studies through Probabilistic Graphical Models: a Visiting Tour of Recent Advances 摘要: 本章对概率图模型(PGMs)的最新进展进行了深入的回顾&…...
【信息系统项目管理师-选择真题】2005上半年综合知识答案和详解
更多内容请见: 备考信息系统项目管理师-专栏介绍和目录 文章目录 【第1题】【第2~3题】【第4~6题】【第7题】【第8题】【第9题】【第10~11题】【第12题】【第13题】【第14题】【第15题】【第16题】【第17题】【第18~19题】【第20题】【第21~22题】【第23题】【第24~25题】【第…...
【Matlab高端绘图SCI绘图模板】第006期 对比绘柱状图 (只需替换数据)
1. 简介 柱状图作为科研论文中常用的实验结果对比图,本文采用了3组实验对比的效果展示图,代码已调试好,只需替换数据即可生成相关柱状图,为科研加分。通过获得Nature配色的柱状图,让你的论文看起来档次更高࿰…...
【Elasticsearch】 Intervals Query
Elasticsearch Intervals Query 返回基于匹配术语的顺序和接近度的文档。 intervals 查询使用 匹配规则,这些规则由一小组定义构建而成。这些规则然后应用于指定 field 中的术语。 这些定义生成覆盖文本中术语的最小间隔序列。这些间隔可以进一步由父源组合和过滤…...
YOLOv8源码修改(4)- 实现YOLOv8模型剪枝(任意YOLO模型的简单剪枝)
目录 前言 1. 需修改的源码文件 1.1添加C2f_v2模块 1.2 修改模型读取方式 1.3 增加 L1 正则约束化训练 1.4 在tensorboard上增加BN层权重和偏置参数分布的可视化 1.5 增加剪枝处理文件 2. 工程目录结构 3. 源码文件修改 3.1 添加C2f_v2模块和模型读取 3.2 添加L1正则…...
数论问题80
命题1,证明,方程(2x)^(2x)-1y^(z1)没有正整数解。 分析:设x,y,z∈Z满足方程,当x1时,3y^(z1),无论任意y,z取任意正整数值,3y^(z1)都不成立。方程左端分解因式,…...
后端token校验流程
获取用户信息 前端中只有 await userStore.getInfo() 表示从后端获取数据 在页面中找到info对应的url地址,在IDEA中查找 这里是getInfo函数的声明,我们要找到这个函数的使用,所以点getInfo() Override public JSONObject getInfo() {JSO…...
Ansible自动化运维实战--通过role远程部署nginx并配置(8/8)
文章目录 1、准备工作2、创建角色结构3、编写任务4、准备配置文件(金甲模板)5、编写变量6、编写处理程序7、编写剧本8、执行剧本Playbook9、验证-游览器访问每台主机的nginx页面 在 Ansible 中,使用角色(Role)来远程部…...
C语言自定义数据类型详解(二)——结构体类型(下)
书接上回,前面我们已经给大家介绍了如何去声明和创建一个结构体,如何初始化结构体变量等这些关于结构体的基础知识。下面我们将继续给大家介绍和结构体有关的知识: 今天的主题是:结构体大小的计算并简单了解一下位段的相关知识。…...
OpenFeign的工作原理是什么?它第一次加载的时候为什么慢?
OpenFeign的工作原理是什么?它第一次加载的时候为什么慢? OpenFeign的工作原理 接口定义: 开发者定义一个接口,并使用 FeignClient 注解指定该接口所对应的微服务名称。在接口的方法上添加 HTTP 方法相关的注解(如 …...
LLM架构与优化:从理论到实践的关键技术
标题:“LLM架构与优化:从理论到实践的关键技术” 文章信息摘要: 文章探讨了大型语言模型(LLM)开发与应用中的关键技术,包括Transformer架构、注意力机制、采样技术、Tokenization等基础理论,以…...
Maven的单元测试
1. 单元测试的基本概念 单元测试(Unit Testing) 是一种软件测试方法,专注于测试程序中的最小可测试单元——通常是单个类或方法。通过单元测试,可以确保每个模块按预期工作,从而提高代码的质量和可靠性。 2.安装和配…...
Jetson Xavier NX 安装 CUDA 支持的 PyTorch 指南
本指南将帮助开发者完成在 Jetson Xavier NX 上安装 CUDA 支持的 PyTorch。 安装方法 在 Jetson 上安装 Pytorch 只有两种方法。 一种是直接安装他人已经编译好的 PyTorch 轮子;一种是自己从头开始开始构建 PyTorch 轮子并且安装。 使用轮子安装 可以从我的 Gi…...
AI协助探索AI新构型的自动化创新概念
训练AI自生成输出模块化代码,生成元代码级别的AI功能单元代码,然后再由AI组织为另一个AI,实现AI开发AI的能力;用AI协助探索迭代新构型AI将会出现,并成为一种新的技术路线潮流。 有限结点,无限的连接形式&a…...
Kafka 压缩算法详细介绍
文章目录 一 、Kafka 压缩算法概述二、Kafka 压缩的作用2.1 降低网络带宽消耗2.2 提高 Kafka 生产者和消费者吞吐量2.3 减少 Kafka 磁盘存储占用2.4 减少 Kafka Broker 负载2.5 降低跨数据中心同步成本 三、Kafka 压缩的原理3.1 Kafka 压缩的基本原理3.2. Kafka 压缩的工作流程…...
GWO优化GRNN回归预测matlab
灰狼优化算法(Grey Wolf Optimizer,简称 GWO),是一种群智能优化算法,由澳大利亚格里菲斯大学的 Mirjalii 等人于 2014 年提出。该算法的设计灵感源自灰狼群体的捕食行为,核心思想在于模拟灰狼社会的结构与行…...
Unity 粒子特效在UI中使用裁剪效果
1.使用Sprite Mask 首先建立一个粒子特效在UI中显示 新建一个在场景下新建一个空物体,添加Sprite Mask组件,将其的Layer设置为UI相机渲染的UI层, 并将其添加到Canvas子物体中,调整好大小,并选择合适的Spriteÿ…...
【大厂AI实践】OPPO:大规模知识图谱及其在小布助手中的应用
导读:OPPO知识图谱是OPPO数智工程系统小布助手团队主导、多团队协作建设的自研大规模通用知识图谱,目前已达到数亿实体和数十亿三元组的规模,主要落地在小布助手知识问答、电商搜索等场景。 本文主要分享OPPO知识图谱建设过程中算法相关的技…...
C# 添加、替换、提取、或删除Excel中的图片
在Excel中插入与数据相关的图片,能将关键数据或信息以更直观的方式呈现出来,使文档更加美观。此外,对于已有图片,你有事可能需要更新图片以确保信息的准确性,或者将Excel 中的图片单独保存,用于资料归档、备…...
AI大模型开发原理篇-5:循环神经网络RNN
神经概率语言模型NPLM也存在一些明显的不足之处:模型结构简单,窗口大小固定,缺乏长距离依赖捕捉,训练效率低,词汇表固定等。为了解决这些问题,研究人员提出了一些更先进的神经网络语言模型,如循环神经网络、…...
赛博算卦之周易六十四卦JAVA实现:六幺算尽天下事,梅花化解天下苦。
佬们过年好呀~新年第一篇博客让我们来场赛博算命吧! 更多文章:个人主页 系列文章:JAVA专栏 欢迎各位大佬来访哦~互三必回!!! 文章目录 #一、文化背景概述1.文化起源2.起卦步骤 #二、卦象解读#三、just do i…...
