基于OpenGL编写一个简易的2D渲染框架-07 鼠标事件和键盘事件

这次为程序添加鼠标事件和键盘事件

 

  当检测到鼠标事件和键盘事件的信息时,捕获其信息并将信息传送到需要信息的对象处理。为此,需要一个可以分派信息的对象,这个对象能够正确的把信息交到正确的对象。

 

实现思路:

  要实现以上的功能,需要几个对象:

    事件分派器:EventDispatcher,负责将 BaseEvent 分派给 EventListener 对象

    事件监听器:EventListener,这只是一个接口类,接受 BaseEvent 的对象,真正的处理在它的子类中实现

    事件:BaseEvent,储存用户数据,事件信息载体

  假设我要分派一个 BaseEvent, 那么我应该将 BaseEvent 分派给哪个监听器 EventListener ?可以在 BaseEvent 上添加一个 ID,通过这个 ID 将 BaseEvent 分派到对应 ID 的监听器。

  有这样一个场景,有 A、B、C、D 四个监听器,需要把 ID 为 5 的 BaseEvent 分派给 A、B 监听器,而 C、D 监听器不需要接受这个 BaseEvent。

这时可以创建一个映射表,存储有 ID 和 监听器之间的联系信息

typedef std::map<int, std::list<EventListener*>> ListenerGroup;

A、B 需要监听 ID 为 5 的 BaseEvent,就把 A、B 注册到这个表中,表中就有了 5-A、B 这样的信息。事件分派器就能根据这个表将 ID 为 5 的 BaseEvent 分派到需要监听这个 BaseEvent 的监听器 A 和 B。对于 C、D 监听器,只能监听到对应 ID 的 BaseEvent,实现思路就这样。

  

代码实现:

  BaseEvent 结构如下

    struct BaseEvent{int nEventID;                    /* 事件 ID */int nParams[MAX_EVENT_PARAM];    /* 自定义参数 */void* pUserData;                 /* 用户数据 */};

nParams 用来储存几个自定义参数,对于其他数据就用 void 指针储存,需要时转换一下就可以了。

 

事件分派器有两个属性,分别是 事件池 和 ID-监听器表,事件池主要是用来储存所有要分派的事件

        std::list<BaseEvent> vEventPool;ListenerGroup listenerGroup;

接下来是监听器的实现

    class DLL_export EventListener{friend class EventDispatcher;public:EventListener();virtual ~EventListener() {}protected:void appendListener(int eventID, EventListener* listener);void removeListener(int eventID, EventListener* listener);virtual void handleEvent(const BaseEvent& event) = 0;private:static unsigned int nIDCounter;unsigned int nID;};

主要有三个函数,用于将监听器注册到 ID-监听器表和从 ID-监听器表中移除监听器,最后一个是处理 BaseEvent 的函数,这是一个抽象函数,表示在子类中实现处理函数。

将监听器注册到表中,需要一个监听器要监听的 BaseEvent ID 以及监听器本身

    void EventListener::appendListener(int eventID, EventListener* new_listener){auto listenerList = pDispatcher->listenerGroup.find(eventID);/* 事件 ID 没有监听列表?为 ID 创建监听列表,添加 eListener */if ( listenerList == pDispatcher->listenerGroup.end() ) {std::list<EventListener*> newListenerList;newListenerList.push_back(new_listener);pDispatcher->listenerGroup.insert(std::make_pair(eventID, newListenerList));}else {/* 如果监听列表中没有监听器,添加监听器到列表中 */std::list<EventListener*>::iterator listener_it;for ( listener_it = listenerList->second.begin(); listener_it != listenerList->second.end(); ++listener_it ) {if ( (*listener_it)->nID == new_listener->nID ) return;}if ( listener_it == listenerList->second.end() ) {listenerList->second.push_back(new_listener);}}}

先判断该 ID 的 BaseEvent 是否有一张表了,如果没有就新建表,然后将监听器添加到表中。

将监听器中表中移除

    void EventListener::removeListener(int eventID, EventListener* listener){auto listenerList = pDispatcher->listenerGroup.find(eventID);if ( listenerList == pDispatcher->listenerGroup.end() ) return;/* 从监听列表中移除监听器 */for ( auto it = listenerList->second.begin(); it != listenerList->second.end(); ++it ) {if ( (*it)->nID == listener->nID ) {listenerList->second.erase(it);break;}}/* 移除空监听列表 */if ( listenerList->second.empty() ) {pDispatcher->listenerGroup.erase(listenerList);}}

 

如果要分派一个 BaseEvent,先将其添加到分派器中

    void EventDispatcher::dispatchEvent(const BaseEvent& event){/* 只是暂时添加事件到事件池中,并没有立即分派事件,避免递归分派错误 */vEventPool.push_back(event);}

这里没有立即将 BaseEvent 交给对应的监听器处理,是因为如果处理函数中有将 BaseEvent 添加到事件分派器中的操作,会发生递归错误。所以就将 BaseEvent 添加到一个事件池中,稍后在函数 flushEvent 中统一分派

    void EventDispatcher::flushEvent(){if ( vEventPool.empty() ) return;/* 分派事件池中的所有事件 */for ( auto& event : vEventPool ) {this->realDispatchEvent(event);}vEventPool.clear();}

分派每一个 BaseEvent,需要找到其对应的监听表,再交给表中的监听器处理

    void EventDispatcher::realDispatchEvent(const BaseEvent& event){auto listenerList_it = listenerGroup.find(event.nEventID);if ( listenerList_it != listenerGroup.end() ) {std::list<EventListener*>& listenerList = listenerList_it->second;for ( auto listener_it : listenerList ) {listener_it->handleEvent(event);}}}

以上就实现了一个事件分派模块,费如此大的一番功夫,是为了让它不仅仅分派鼠标和键盘事件,还可以分派其他需要的事件。

 

鼠标事件和键盘事件处理

  为鼠标事件和键盘事件分别定义事件 ID

    enum EventType { ET_UNKNOWN,            /* 未知事件 */ET_MOUSE,              /* 鼠标事件 */ET_KEY                 /* 按键事件 */};

  

  先实现鼠标事件的处理,定义一个鼠标监听器类,继承于事件监听器

    class DLL_export MouseEventListener : public EventListener{public:MouseEventListener();virtual ~MouseEventListener();virtual void mouseMove(const MouseEvent& event) {}virtual void mousePress(const MouseEvent& event) {}virtual void mouseRelease(const MouseEvent& event) {}virtual void mouseDoubleClick(const MouseEvent& event) {}virtual void mouseWheel(const MouseEvent& event) {}void handleEvent(const BaseEvent& event);};

在构造函数和析构函数中,主要是注册监听器到事件分派器和从事件分派器中移除监听器

    MouseEventListener::MouseEventListener(){this->appendListener(EventType::ET_MOUSE, this);}MouseEventListener::~MouseEventListener(){this->removeListener(EventType::ET_MOUSE, this);}

 

鼠标事件分别有按键按下、释放、双击、鼠标移动和滚轮滑动等动作

    enum EventAction{ACT_MOVE,             /* 移动 */ACT_PRESS,            /* 按压 */ACT_RELAESE,          /* 释放 */ACT_DUBBLE_CLICK,     /* 双击 */ACT_SCROLL            /* 滚动 */};

 

以及按钮类型,左键、右键和中键

    enum ButtonType { LEFT_BUTTON,         /* 鼠标左键 */RIGHT_BUTTON,        /* 鼠标右键 */MIDDLE_BUTTON        /* 鼠标中键 */};

 

对于一个鼠标事件,需要的数据信息如下

    /* 鼠标事件  */struct MouseEvent{EventAction eventAction;ButtonType buttonType;int nDelta;int nX, nY;};

动作类型、按钮类型、滚轮滚动数据和坐标数据。

 

为了捕捉窗口程序的鼠标信息,定义一个窗口信息处理类

    //------------------------------------------------------------------// WinMsgHandle// 窗口信息处理//------------------------------------------------------------------class WinMsgHandle{public:WinMsgHandle();void handleMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);private:BaseEvent baseEvent;KeyEvent keyEvent;MouseEvent mouseEvent;};

函数 handleMessage 主要捕捉窗口信息

    void WinMsgHandle::handleMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam){baseEvent.nEventID = ET_UNKNOWN;/* 鼠标事件信息  */if ( msg >= WM_MOUSEMOVE && msg <= WM_MBUTTONDBLCLK || msg == WM_MOUSEWHEEL ) {switch ( msg ) {case WM_LBUTTONDOWN:mouseEvent.buttonType = ButtonType::LEFT_BUTTON;mouseEvent.eventAction = EventAction::ACT_PRESS;break;case WM_LBUTTONUP:mouseEvent.buttonType = ButtonType::LEFT_BUTTON;mouseEvent.eventAction = EventAction::ACT_RELAESE;break;case WM_LBUTTONDBLCLK:mouseEvent.buttonType = ButtonType::LEFT_BUTTON;mouseEvent.eventAction = EventAction::ACT_DUBBLE_CLICK;break;case WM_MBUTTONDOWN:mouseEvent.buttonType = ButtonType::MIDDLE_BUTTON;mouseEvent.eventAction = EventAction::ACT_PRESS;break;case WM_MBUTTONUP:mouseEvent.buttonType = ButtonType::MIDDLE_BUTTON;mouseEvent.eventAction = EventAction::ACT_RELAESE;break;case WM_MBUTTONDBLCLK:mouseEvent.buttonType = ButtonType::MIDDLE_BUTTON;mouseEvent.eventAction = EventAction::ACT_DUBBLE_CLICK;break;case WM_RBUTTONDOWN:mouseEvent.buttonType = ButtonType::RIGHT_BUTTON;mouseEvent.eventAction = EventAction::ACT_PRESS;break;case WM_RBUTTONUP:mouseEvent.buttonType = ButtonType::RIGHT_BUTTON;mouseEvent.eventAction = EventAction::ACT_RELAESE;break;case WM_RBUTTONDBLCLK:mouseEvent.buttonType = ButtonType::RIGHT_BUTTON;mouseEvent.eventAction = EventAction::ACT_DUBBLE_CLICK;break;case WM_MOUSEMOVE:mouseEvent.eventAction = EventAction::ACT_MOVE;break;case WM_MOUSEWHEEL:mouseEvent.eventAction = EventAction::ACT_SCROLL;mouseEvent.nDelta = ( short ) HIWORD(wParam);break;}mouseEvent.nX = ( short ) LOWORD(lParam);mouseEvent.nY = ( short ) HIWORD(lParam);baseEvent.nEventID = ET_MOUSE;baseEvent.pUserData = &mouseEvent;EventDispatcher::getInstance()->dispatchEvent(baseEvent);}}

主要是获取鼠标事件数据 MouseEvent,然后将数据附加到 BaseEvent 上,设置其 ID 为 鼠标事件ID——ET_MOUSE,最后由事件分派器分派 BaseEvent。

当鼠标事件监听器处理 BaseEvent 时,需要获取 MouseEvent 数据,然后根据按钮类型和动作类型调用相应函数

    void MouseEventListener::handleEvent(const BaseEvent& event){if ( event.nEventID != EventType::ET_MOUSE && event.pUserData ) return;MouseEvent* mouseEvent = static_cast<MouseEvent*>(event.pUserData);switch ( mouseEvent->eventAction ) {case Simple2D::ACT_MOVE:         this->mouseMove(*mouseEvent);        break;case Simple2D::ACT_PRESS:        this->mousePress(*mouseEvent);       break;case Simple2D::ACT_RELAESE:      this->mouseRelease(*mouseEvent);     break;case Simple2D::ACT_SCROLL:       this->mouseWheel(*mouseEvent);       break;case Simple2D::ACT_DUBBLE_CLICK: this->mouseDoubleClick(*mouseEvent); break;}}

当然这些函数都没有具体的实现,具体的实现由子类完成。

 

对于键盘事件,只有两个按键动作按压和释放,及事件的数据结构体

    /* 按键事件 */struct KeyEvent{EventAction eventAction;bool keys[256];KeyType keyType;};

bool 类型的按键数组 keys 储存哪一个按键被按下的信息,当同时有多个按键按压时也可以检测。而 KeyType 就记录了当前按压的按键类型,这里并不包括键盘上的所有按键,只包含字母键、数字键和其它常用按键。

    /** VK_0 - VK_9 are the same as ASCII '0' - '9' (0x30 - 0x39)* 0x40 : unassigned* VK_A - VK_Z are the same as ASCII 'A' - 'Z' (0x41 - 0x5A)*/enum KeyType{Key_Unknown,Key_Space = 0x20,Key_Prior,Key_Next,Key_End,Key_Home,Key_Left,Key_Up,Key_Right,Key_Down,Key_Select,Key_Print,Key_Execute,Key_Snapshot,Key_Insert,Key_Delete,Key_Help,/* 主键盘上的数字键 */Key_0 = 0x30,Key_1,Key_2,Key_3,Key_4,Key_5,Key_6,Key_7,Key_8,Key_9,Key_A = 0x41,Key_B,Key_C,Key_D,Key_E,Key_F,Key_G,Key_H,Key_I,Key_J,Key_K,Key_L,Key_M,Key_N,Key_O,Key_P,Key_Q,Key_R,Key_S,Key_T,Key_U,Key_V,Key_W,Key_X,Key_Y,Key_Z,/* 小键盘上的数字 */Key_NumPad_0 = 0x60,Key_NumPad_1,Key_NumPad_2,Key_NumPad_3,Key_NumPad_4,Key_NumPad_5,Key_NumPad_6,Key_NumPad_7,Key_NumPad_8,Key_NumPad_9,Key_F1 = 0x70,Key_F2,Key_F3,Key_F4,Key_F5,Key_F6,Key_F7,Key_F8,Key_F9,Key_F10,Key_F11,Key_F12,Key_F13,Key_F14,Key_F15,Key_F16,Key_F17,Key_F18,Key_F19,Key_F20,Key_F21,Key_F22,Key_F23,Key_F24,};

 

键盘事件监听器定义

    class DLL_export KeyEventListener : public EventListener{public:KeyEventListener();virtual ~KeyEventListener();virtual void keyPress(const KeyEvent& event) {}virtual void keyRelease(const KeyEvent& event) {}void handleEvent(const BaseEvent& event);};

 

对于按键信息的捕捉,和鼠标事件一样在 handleMessage 函数中,这里只截取了键盘事件

    void WinMsgHandle::handleMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam){baseEvent.nEventID = ET_UNKNOWN;/* 键盘按键事件信息 */if ( msg == WM_KEYDOWN || msg == WM_KEYUP ) {keyEvent.eventAction = (msg == WM_KEYDOWN) ? EventAction::ACT_PRESS : EventAction::ACT_RELAESE;keyEvent.keyType = keyMap(( UINT ) wParam);keyEvent.keys[( UINT ) wParam] = (msg == WM_KEYDOWN) ? true : false;baseEvent.nEventID = ET_KEY;baseEvent.pUserData = &keyEvent;EventDispatcher::getInstance()->dispatchEvent(baseEvent);}}

和鼠标事件一样,获取按键数据 KeyEvent,然后附加到 BaseEvent 中,设置其 ID 为 ET_KEY,最后由分派器分派事件。按键事件监听器处理 BaseEvent 时,根据动作类型调用相应函数,其函数有子类实现。

    void KeyEventListener::handleEvent(const BaseEvent& event){if ( event.nEventID != EventType::ET_KEY && event.pUserData ) return;KeyEvent* keyEvent = static_cast<KeyEvent*>(event.pUserData);switch ( keyEvent->eventAction ) {case Simple2D::ACT_PRESS:      this->keyPress(*keyEvent);        break;case Simple2D::ACT_RELAESE:    this->keyRelease(*keyEvent);      break;}}

 

最后在窗口的 proc 函数中

        /* 处理鼠标和按键事件  */if ( self ) {self->winMsgHandle.handleMessage(wnd, msg, wParam, lParam);}

 

主循环中分派所有事件

        if ( PeekMessage(&msg, 0, 0, 0, PM_REMOVE) ) {TranslateMessage(&msg);DispatchMessage(&msg);EventDispatcher::getInstance()->flushEvent();}

 

新建一个测试类,继承与鼠标事件监听器和按键事件监听器,实现监听器中的函数,输出到输出窗口

class EventTest : public MouseEventListener, public KeyEventListener
{
public://void mouseMove(const MouseEvent& event)//{//    log("mouse move");//    log("x:%d - y:%d", event.nX, event.nY);//}void mousePress(const MouseEvent& event){if ( event.buttonType == ButtonType::LEFT_BUTTON ) {log("left button press");}else if ( event.buttonType == ButtonType::MIDDLE_BUTTON ) {log("middle button press");}else if ( event.buttonType == ButtonType::RIGHT_BUTTON ) {log("right button press");}log("x:%d - y:%d", event.nX, event.nY);}void mouseRelease(const MouseEvent& event){log("mouse release");log("x:%d - y:%d", event.nX, event.nY);}void mouseDoubleClick(const MouseEvent& event){log("mouse double click");log("x:%d - y:%d", event.nX, event.nY);}void mouseWheel(const MouseEvent& event){log("mouse wheel");log("delta: %d", event.nDelta);}void keyPress(const KeyEvent& event){if ( event.keys[KeyType::Key_A] && event.keys[KeyType::Key_S] ) {log("同时按下 AS");}}void keyRelease(const KeyEvent& event){if ( event.keyType == KeyType::Key_NumPad_1 ) {log("释放键 1");}}
};

 

运行程序的结果

 

源码下载:http://pan.baidu.com/s/1skOmP21

转载于:https://www.cnblogs.com/ForEmail5/p/6882998.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/488297.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

重新定义Wi-Fi功能,Wi-Fi 6为什么要分两步?

来源&#xff1a;传感器技术Wi-Fi 6重新定义Wi-Fi&#xff0c;先是双频并发&#xff0c;然后才是6GHz下的160MHz。Wi-Fi是什么&#xff1f;它是一个基于IEEE 802.11标准的无线局域网技术。如今&#xff0c;Wi-Fi已经覆盖了绝大部分的室内场景&#xff0c;你的手机、电脑、智能音…

《科学》:中德解析新冠主要蛋白酶晶体结构,有助抑制剂研发

来源&#xff1a;澎湃新闻自新型冠状病毒引起的疫情暴发以来&#xff0c;科学家们一直在努力寻找有效的病毒抑制剂。当地时间3月20日&#xff0c;顶级学术期刊《科学》在线发表了一篇题为“Crystal structure of SARS-CoV-2 main protease provides a basis for design of impr…

render_notebook()结果没有图_来自百度的良心产品!百度iOS截长图App

我们知道百度是国内最大的搜索引擎&#xff0c;但你知道它也会出品一些和搜索无关的精品工具&#xff1f;例如在最近&#xff0c;百度就推出了一款iOS平台上的滚动截长图App&#xff0c;要知道iOS系统一直以来就不支持系统级别的截长图&#xff0c;百度这工具可谓是非常实用了&…

2020年度国家科学技术奖提名项目公示丨附全名单

来源&#xff1a;科学技术部3月24日&#xff0c;科学技术部发布了《国家科学技术奖励工作办公室公告第95号》文件。该文件称&#xff0c;2020年度国家科学技术奖提名工作已结束&#xff0c;并对2020年度国家自然科学奖、国家技术发明奖通用项目、国家科学技术进步奖通用项目受理…

linux实验试题 cp,cp命令实验,cp命令

cp命令实验&#xff0c;cp命令创建条件[rootlocalhost ~]#mkdir /source[rootlocalhost~]#mkdir /target[rootlocalhost~]#cp /etc/l*.conf /source[rootlocalhost~]#ll /sourcetotal20-rw-r--r--. 1 root root 28 Aug 10 09:24 ld.so.conf-rw-r-----. 1 root root 191 Aug 10 …

机载雷达发展简史:从蝙蝠到机载相控阵

来源&#xff1a;军事高科技在线 从地基起步蝙蝠&#xff0c;虽然像人一样拥有双眼&#xff0c;但它看起东西来&#xff0c;用到的却不是眼睛。蝙蝠从鼻子里发出的超声波在传输过程中遇到物体后会立刻反弹&#xff0c;根据声波发射和回波接收之间的…

苹果手机时区改不了怎么办_天价手机摔了怎么办?苹果:你尽管摔,坏了算我输。...

智能手机比起过去的功能机&#xff0c;好处多到数不过来&#xff0c;但有一点智能手机怎么都比不上功能机&#xff0c;那就是不抗摔。当年的很多功能机那都是可以用来砸核桃的&#xff0c;抗摔性一流&#xff0c;基本不存在摔坏的说法&#xff0c;但智能手机就不一样了&#xf…

2020 最新自动驾驶技术报告出炉:Waymo、特斯拉、沃尔沃技术方案大起底

来源&#xff1a;《2020 自动驾驶技术报告》进入 2020 年&#xff0c;自动驾驶技术的跨越式路线与渐进式路线之间的阵营划分已经十分明显。但最终自动驾驶要完全实现无人化&#xff0c;其技术还需要进行不断的迭代和发展。对于自动驾驶的技术进展&#xff0c;WEVOLVER 发布的《…

PC_excel完毕一列英文小写变大写

原创作品&#xff0c;出自 “深蓝的blog” 博客。欢迎转载&#xff0c;转载时请务必注明出处。否则追究版权法律责任。深蓝的blog&#xff1a;http://blog.csdn.net/huangyanlong/article/details/44493869 使用excel中的UPPER函数就可以实现这个目标。介绍例如以下&#xff1a…

c语言if的作用,c语言中if 语句的作用范围示例代码

c语言中if 语句的作用范围示例代码复制代码 代码如下:# include int main(void){if (1 > 2)printf("第一条表达式");printf("第二条表达式");}/*输出结果第二条表达式*/说明if语句的作用范围只有紧跟if的第一条表达式时间&#xff1a; 2013-09-06在高中…

几何画板画椭圆_几何画板条件下椭圆的26种绘制方法(二)——代数法

“几何画板是一种常用的数学软件&#xff0c;具有强大的绘图功能&#xff0c;也为我们探究椭圆的不同绘制方法提供了平台.在探究过程中&#xff0c;我们遇到了一些麻烦和困难&#xff0c;但也获得了很多意想不到的惊喜和收获&#xff01;我们将椭圆的绘制方法分为七类&#xff…

中国抗疫十大黑科技盘点!

来源&#xff1a;工业机器人新冠肺炎来势汹汹&#xff0c;为了阻止疫情的扩散&#xff0c;不少人没来及过春节就投入到了抗疫第一线。不过如今已是2020年&#xff0c;除了人力以外&#xff0c;设计与科技也在积极参与这场抗疫战。你知道吗&#xff1f;不论是“火神山”“雷神山…

JavaScript(js)/上

JavaScript(js) ECMA-----定义的基础语法 DOM------document object model BOM------Browser object model Javasript 基于对象的&#xff0c;也是面向对象 ECMAScript描述了以下内容&#xff1a; 语法 类型 语句 关键字 保留字 运算符 对象&#xff08;封装 继承 多态&…

ap6212 Linux 蓝牙,替代正基AP6212、AP6330的SDIO WiFi+UART蓝牙组合

原标题&#xff1a;替代正基AP6212、AP6330的SDIO WiFiUART蓝牙组合SKYLAB推出的SDIO wifi串口蓝牙二合一模块WG221&#xff0c;可完全替代正基的WiFi蓝牙二合一模块AP6212和AP6330两个型号。WG221是WiFi和蓝牙二合一的组合解决方案模块&#xff0c;支持1 x 1 802.11 a/b/g/n W…

2020十大最具创新性的AR/VR开发与应用公司

来源&#xff1a; 资本实验室 在经历过去几年的热情高涨与资本蜂拥之后&#xff0c;AR与VR技术正在进入波澜不惊的发展时期&#xff0c;多家曾经风光一时的AR与VR初创企业都已成为昨日黄花。例如&#xff0c;昔日的独角兽&#xff0c;最受瞩目的AR公司Magic Leap一直受裁员、高…

关于直播学习笔记-005-nginx-rtmp-win32在Win10上使用

在Win10上使用nginx-rtmp-win32会提示文件路径问题。 可以将nginx-rtmp-win32拷贝到用户目录文件夹之中。 在命令行中执行nginx.exe程序 转载于:https://www.cnblogs.com/defineconst/p/6899720.html

连筋字体在线生成_四个超实用的字体网站,PPT和海报就靠它,收藏来一波

今天分享四个一键生成艺术字体的网站给你们&#xff0c;非常适合用到各种活动海报设计和PPT设计中。1、手写在线字体生成器这是一个专门生成手写字体的网站&#xff0c;比如毛笔字体、硬笔等手写字体。字体生成后也可以编辑文字的大小、颜色和背景。2、篆书在线生成器这是一个专…

用物理学突破深度学习理论瓶颈? Google-斯坦福发布《深度学习统计力学》综述论文,30页pdf阐述深度学习成功机制...

来源&#xff1a;专知【导读】深度学习革新了很多应用&#xff0c;但是背后的理论作用机制一直没有得到统一的解释。最近来自谷歌大脑和斯坦福的学者共同在Annual Review of Condensed Matter Physics 发布了深度学习统计力学的综述论文《Statistical Mechanics of Deep Learni…

软件工程——团队作业2

组长&#xff1a; 马海花 1500802002 组员&#xff1a;王莉娟 1500802028 安梨雅 1500802039 马晓燕 1500802046 马菊瑞 1500802066 马继娴 1500802081 NABCD模型&#xff1a; &#xff08;1&#xff09;N&#xff08;Need 需求&#xff09; 1.学生可以通过网络&#xff0c;随…

【智能驾驶】数字钥匙打开汽车安全潘多拉磨盒,2019年智能网联汽车发生“十大安全事件”...

来源&#xff1a;AutoR智驾【导读】3月24日&#xff0c;360举办了一场线上发布会&#xff0c;正式发布了《2019智能网联汽车信息安全年度报告》&#xff0c;从智能网联汽车网络安全发展趋势、新兴攻击手段、汽车安全攻击事件、汽车安全风险总结和安全建设建议等方面对2019年智能…