当前位置: 首页 > article >正文

从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的程序设计&#xff08;范例&#xff09; 面对对象C的程序设计&#xff08;应用&#xff09; 进一步谈论我上面给出的代码——继承 实现一个面对对象的文本编辑器 所以&#xff0c;什么是继承 重申我们对菜单的抽象 抽象菜单项目 抽象菜单动画 实现菜单功…...

Java的StackWalker类

Java的StackWalker类怎么使用 Java 中的 StackWalker 类&#xff08;自 Java 9 引入&#xff09;提供了一种高效且灵活的方式来访问堆栈跟踪信息。以下是其使用方法的逐步说明&#xff1a; 1. 基本使用&#xff1a;获取当前堆栈跟踪 import java.lang.StackWalker;public cla…...

农产品价格报告爬虫使用说明

农产品价格报告爬虫使用说明 # ************************************************************************** # * * # * 农产品价格报告爬虫 …...

Pwn 入门核心工具和命令大全

一、调试工具&#xff08;GDB 及其插件&#xff09; GDB 启动调试&#xff1a;gdb ./binary 运行程序&#xff1a;run 或 r 设置断点&#xff1a;break *0x地址 或 b 函数名 查看寄存器&#xff1a;info registers 查看内存&#xff1a;x/10wx 0x地址 &#xff08;查看 10 个 …...

字节iOS面试经验分享:HTTP与网络编程

字节iOS面试经验分享&#xff1a;HTTP与网络编程 &#x1f31f; 嗨&#xff0c;我是LucianaiB&#xff01; &#x1f30d; 总有人间一两风&#xff0c;填我十万八千梦。 &#x1f680; 路漫漫其修远兮&#xff0c;吾将上下而求索。 目录 字节iOS面试经验分享&#xff1a;HTT…...

在汇编语言中,ASSUME 是一个用于告诉汇编器如何将段寄存器与特定段名称关联的指令

在汇编语言中&#xff0c;ASSUME 是一个用于告诉汇编器如何将段寄存器与特定段名称关联的指令。它主要用于定义代码段、数据段和栈段等的段寄存器使用方式&#xff0c;帮助编译器生成正确的代码。 具体到 ASSUME DS:DATA, CS:CODE, SS:STACK&#xff0c;这行代码的作用如下&…...

代码随想录_栈与队列

栈与队列 232.用栈实现队列 232. 用栈实现队列 使用栈实现队列的下列操作&#xff1a; push(x) – 将一个元素放入队列的尾部。 pop() – 从队列首部移除元素。 peek() – 返回队列首部的元素。 empty() – 返回队列是否为空。 思路: 定义两个栈: 入队栈, 出队栈, 控制出入…...

Ubuntu 手动安装 Open WebUI 完整指南

Ubuntu 手动安装 Open WebUI 完整指南 前提条件 在安装 Open WebUI 之前&#xff0c;请确保您的系统满足以下要求&#xff1a; Ubuntu 22.04 LTS 或更高版本Python 3.10Node.js 18Git至少 4GB 内存足够的磁盘空间&#xff08;推荐 20GB 以上&#xff09; 安装步骤 1. 更新…...

【Oracle篇】使用Hint对优化器的执行计划进行干预(含单表、多表、查询块、声明四大类Hint干预)

&#x1f4ab;《博主介绍》&#xff1a;✨又是一天没白过&#xff0c;我是奈斯&#xff0c;从事IT领域✨ &#x1f4ab;《擅长领域》&#xff1a;✌️擅长阿里云AnalyticDB for MySQL(分布式数据仓库)、Oracle、MySQL、Linux、prometheus监控&#xff1b;并对SQLserver、NoSQL(…...

论文阅读(九):通过概率图模型建立连锁不平衡模型和进行关联研究:最新进展访问之旅

1.论文链接&#xff1a;Modeling Linkage Disequilibrium and Performing Association Studies through Probabilistic Graphical Models: a Visiting Tour of Recent Advances 摘要&#xff1a; 本章对概率图模型&#xff08;PGMs&#xff09;的最新进展进行了深入的回顾&…...

【信息系统项目管理师-选择真题】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. 简介 柱状图作为科研论文中常用的实验结果对比图&#xff0c;本文采用了3组实验对比的效果展示图&#xff0c;代码已调试好&#xff0c;只需替换数据即可生成相关柱状图&#xff0c;为科研加分。通过获得Nature配色的柱状图&#xff0c;让你的论文看起来档次更高&#xff0…...

【Elasticsearch】 Intervals Query

Elasticsearch Intervals Query 返回基于匹配术语的顺序和接近度的文档。 intervals 查询使用 匹配规则&#xff0c;这些规则由一小组定义构建而成。这些规则然后应用于指定 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&#xff0c;证明&#xff0c;方程(2x)^(2x)-1y^(z1)没有正整数解。 分析:设x&#xff0c;y&#xff0c;z∈Z满足方程&#xff0c;当x1时&#xff0c;3y^(z1)&#xff0c;无论任意y&#xff0c;z取任意正整数值&#xff0c;3y^(z1)都不成立。方程左端分解因式&#xff0c;…...

后端token校验流程

获取用户信息 前端中只有 await userStore.getInfo() 表示从后端获取数据 在页面中找到info对应的url地址&#xff0c;在IDEA中查找 这里是getInfo函数的声明&#xff0c;我们要找到这个函数的使用&#xff0c;所以点getInfo() Override public JSONObject getInfo() {JSO…...

Ansible自动化运维实战--通过role远程部署nginx并配置(8/8)

文章目录 1、准备工作2、创建角色结构3、编写任务4、准备配置文件&#xff08;金甲模板&#xff09;5、编写变量6、编写处理程序7、编写剧本8、执行剧本Playbook9、验证-游览器访问每台主机的nginx页面 在 Ansible 中&#xff0c;使用角色&#xff08;Role&#xff09;来远程部…...

C语言自定义数据类型详解(二)——结构体类型(下)

书接上回&#xff0c;前面我们已经给大家介绍了如何去声明和创建一个结构体&#xff0c;如何初始化结构体变量等这些关于结构体的基础知识。下面我们将继续给大家介绍和结构体有关的知识&#xff1a; 今天的主题是&#xff1a;结构体大小的计算并简单了解一下位段的相关知识。…...

OpenFeign的工作原理是什么?它第一次加载的时候为什么慢?

OpenFeign的工作原理是什么&#xff1f;它第一次加载的时候为什么慢&#xff1f; OpenFeign的工作原理 接口定义&#xff1a; 开发者定义一个接口&#xff0c;并使用 FeignClient 注解指定该接口所对应的微服务名称。在接口的方法上添加 HTTP 方法相关的注解&#xff08;如 …...

LLM架构与优化:从理论到实践的关键技术

标题&#xff1a;“LLM架构与优化&#xff1a;从理论到实践的关键技术” 文章信息摘要&#xff1a; 文章探讨了大型语言模型&#xff08;LLM&#xff09;开发与应用中的关键技术&#xff0c;包括Transformer架构、注意力机制、采样技术、Tokenization等基础理论&#xff0c;以…...

Maven的单元测试

1. 单元测试的基本概念 单元测试&#xff08;Unit Testing&#xff09; 是一种软件测试方法&#xff0c;专注于测试程序中的最小可测试单元——通常是单个类或方法。通过单元测试&#xff0c;可以确保每个模块按预期工作&#xff0c;从而提高代码的质量和可靠性。 2.安装和配…...

Jetson Xavier NX 安装 CUDA 支持的 PyTorch 指南

本指南将帮助开发者完成在 Jetson Xavier NX 上安装 CUDA 支持的 PyTorch。 安装方法 在 Jetson 上安装 Pytorch 只有两种方法。 一种是直接安装他人已经编译好的 PyTorch 轮子&#xff1b;一种是自己从头开始开始构建 PyTorch 轮子并且安装。 使用轮子安装 可以从我的 Gi…...

AI协助探索AI新构型的自动化创新概念

训练AI自生成输出模块化代码&#xff0c;生成元代码级别的AI功能单元代码&#xff0c;然后再由AI组织为另一个AI&#xff0c;实现AI开发AI的能力&#xff1b;用AI协助探索迭代新构型AI将会出现&#xff0c;并成为一种新的技术路线潮流。 有限结点&#xff0c;无限的连接形式&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

灰狼优化算法&#xff08;Grey Wolf Optimizer&#xff0c;简称 GWO&#xff09;&#xff0c;是一种群智能优化算法&#xff0c;由澳大利亚格里菲斯大学的 Mirjalii 等人于 2014 年提出。该算法的设计灵感源自灰狼群体的捕食行为&#xff0c;核心思想在于模拟灰狼社会的结构与行…...

Unity 粒子特效在UI中使用裁剪效果

1.使用Sprite Mask 首先建立一个粒子特效在UI中显示 新建一个在场景下新建一个空物体&#xff0c;添加Sprite Mask组件&#xff0c;将其的Layer设置为UI相机渲染的UI层&#xff0c; 并将其添加到Canvas子物体中&#xff0c;调整好大小&#xff0c;并选择合适的Sprite&#xff…...

【大厂AI实践】OPPO:大规模知识图谱及其在小布助手中的应用

导读&#xff1a;OPPO知识图谱是OPPO数智工程系统小布助手团队主导、多团队协作建设的自研大规模通用知识图谱&#xff0c;目前已达到数亿实体和数十亿三元组的规模&#xff0c;主要落地在小布助手知识问答、电商搜索等场景。 本文主要分享OPPO知识图谱建设过程中算法相关的技…...

C# 添加、替换、提取、或删除Excel中的图片

在Excel中插入与数据相关的图片&#xff0c;能将关键数据或信息以更直观的方式呈现出来&#xff0c;使文档更加美观。此外&#xff0c;对于已有图片&#xff0c;你有事可能需要更新图片以确保信息的准确性&#xff0c;或者将Excel 中的图片单独保存&#xff0c;用于资料归档、备…...

AI大模型开发原理篇-5:循环神经网络RNN

神经概率语言模型NPLM也存在一些明显的不足之处:模型结构简单&#xff0c;窗口大小固定&#xff0c;缺乏长距离依赖捕捉&#xff0c;训练效率低&#xff0c;词汇表固定等。为了解决这些问题&#xff0c;研究人员提出了一些更先进的神经网络语言模型&#xff0c;如循环神经网络、…...

赛博算卦之周易六十四卦JAVA实现:六幺算尽天下事,梅花化解天下苦。

佬们过年好呀~新年第一篇博客让我们来场赛博算命吧&#xff01; 更多文章&#xff1a;个人主页 系列文章&#xff1a;JAVA专栏 欢迎各位大佬来访哦~互三必回&#xff01;&#xff01;&#xff01; 文章目录 #一、文化背景概述1.文化起源2.起卦步骤 #二、卦象解读#三、just do i…...