ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

【LibreCAD 2D架构】从鼠标点击到图形创建:RS_ActionDrawLine交互流程与状态机解析

【LibreCAD 2D架构】从鼠标点击到图形创建:RS_ActionDrawLine交互流程与状态机解析 励志不掉头发的内向程序员个人主页✨️ 个人专栏: 《C语言》《Linux学习》偶尔悲伤偶尔被幸福所完善️博主简介:文章目录前言一、RS_ActionDrawLine 如何处理第一次点击1.1、RS_ActionDrawLine 等待什么1.2、第一次点击从哪里进入 RS_ActionDrawLine1.3、屏幕坐标不是 CAD 坐标1.4、Action 获取鼠标对应的 CAD 坐标1.5、第一次点击记录起点1.6、第一次点击之后Action 的状态发生了变化1.7、为什么第一次点击之后不会立即创建 Entity1.8、鼠标移动为什么会出现预览线1.9、把第一次点击完整串起来1.10、从源码阅读的角度我们发现了什么二、鼠标移动时发生什么2.1、鼠标移动首先产生 MouseMove 事件2.2、第一次点击之后Action 已经进入了新的阶段2.3、鼠标坐标还需要进行坐标转换2.4、为什么每移动一次都要重新计算2.5、预览线并不等于真正的 RS_Line2.6、为什么预览线看起来像“实时绘制”2.7、从源码角度应该关注什么2.8、鼠标移动的完整流程2.9、到这里画线过程已经越来越清晰2.10、下一步第二次点击三、RS_ActionDrawLine 的状态机3.1、什么是状态机3.2、RS_ActionDrawLine 的核心状态3.3、第一次点击状态发生第一次转换3.4、鼠标移动状态不变但数据不断变化3.5、第二次点击状态机完成一次循环3.6、用状态转换图理解整个过程3.7、同一个事件在不同状态下意义不同3.8、RS_ActionDrawLine 为什么要使用状态机3.9、从 RS_ActionDrawLine 看 LibreCAD 的 Action 设计总结前言上一篇文章中我们追踪了 LibreCAD 的鼠标事件传递过程了解了用户操作如何经过 Qt 的 QMouseEvent、绘图区、RS_EventHandler最终进入当前工作的 RS_ActionDrawLine。但是事件进入 RS_ActionDrawLine 之后并不意味着一条直线就已经被创建了。对于用户来说画线似乎只是简单地点击两下鼠标而从源码角度来看这两次点击之间还包含着坐标获取、状态保存、预览更新以及图形创建等多个步骤。例如第一次点击时程序需要确定直线的起点鼠标继续移动时程序需要不断获取当前鼠标位置并显示跟随鼠标变化的临时预览线第二次点击之后程序才会真正确定终点并创建最终的 RS_Line。因此本篇文章将继续围绕 RS_ActionDrawLine 展开分析重点研究三个问题1、第一次鼠标点击后RS_ActionDrawLine 如何获取并保存起点2、鼠标移动时为什么能够实时更新预览线3、同样的鼠标点击为什么在不同阶段会执行不同的操作通过这几个问题可以进一步理解 RS_ActionDrawLine 背后的状态机设计等待第一点 → 确定起点 → 等待第二点 → 更新预览 → 确定终点 → 创建 RS_Line。也就是说RS_ActionDrawLine 并不只是一个“画线函数”而是一个负责管理完整用户交互过程的状态机。理解这一点也就能更好地理解 LibreCAD 中其他 Action 的工作方式。一、RS_ActionDrawLine 如何处理第一次点击前面我们已经追踪了鼠标事件的传递过程用户点击鼠标 - QMouseEvent - GraphicView - RS_EventHandler - 当前 Action - RS_ActionDrawLine。现在鼠标事件终于进入了真正负责“画线”的对象RS_ActionDrawLine那么问题来了当用户第一次点击绘图区时RS_ActionDrawLine 到底做了什么如果从用户的角度来看这一步非常简单点击一下确定直线的起点。但从源码角度来看这次点击实际上完成了几个关键动作鼠标点击 - 获取鼠标位置 - 转换为 CAD 世界坐标 - 记录起点 - 进入下一阶段。1.1、RS_ActionDrawLine 等待什么在真正开始画线之前RS_ActionDrawLine 并不知道用户想从哪里画。所以 Action 初始化之后首先处于一种等待用户指定第一个点的状态。可以把它理解成 RS_ActionDrawLine 等待第一点。此时用户移动鼠标并不会立即创建一条线。只有当用户第一次点击鼠标之后Action 才获得了第一个确定的坐标。因此第一次点击 确定起点第二次点击 确定终点这实际上就是一个非常典型的状态驱动交互过程。1.2、第一次点击从哪里进入 RS_ActionDrawLine上一节已经知道鼠标事件最终会被 EventHandler 分发给当前 Action。因此当用户点击鼠标左键时最终会进入 RS_ActionDrawLine 对应的鼠标事件处理逻辑。这里可以重点关注 RS_ActionDrawLine 对鼠标事件的处理函数。voidRS_ActionDrawLine::mouseMoveEvent(QMouseEvent*e){RS_Vector mousesnapPoint(e);if(getStatus()SetEndpointpPoints-data.startpoint.valid){// Snapping to angle(15*) if shift key is pressedif(e-modifiers()Qt::ShiftModifier){mousesnapToAngle(mouse,pPoints-data.startpoint,15.);}deletePreview();RS_Line*linenewRS_Line(pPoints-data.startpoint,mouse);preview-addEntity(line);line-setLayerToActive();line-setPenToActive();drawPreview();}}voidRS_ActionDrawLine::mouseReleaseEvent(QMouseEvent*e){if(e-button()Qt::LeftButton){RS_Vector snappedsnapPoint(e);// Snapping to angle(15*) if shift key is pressedif((e-modifiers()Qt::ShiftModifier)getStatus()SetEndpoint){snappedsnapToAngle(snapped,pPoints-data.startpoint,15.);}RS_CoordinateEventce(snapped);coordinateEvent(ce);}elseif(e-button()Qt::RightButton){deletePreview();switch(getStatus()){default:caseSetStartpoint:init(getStatus()-1);break;caseSetEndpoint:next();break;}}}它拿到的并不是一个简单的x、y。而是 Qt / LibreCAD 事件系统传递过来的鼠标事件信息。也就是说QMouseEvent - 鼠标位置 - Action。但这里还有一个非常重要的问题鼠标位置究竟是屏幕坐标还是 CAD 坐标1.3、屏幕坐标不是 CAD 坐标假设用户在屏幕上点击(500, 300)这个 (500, 300) 首先表示的是屏幕上的像素位置。但 CAD 中的直线并不是使用屏幕像素保存的。例如 CAD 图形可能使用(125.35, 86.72)这样的世界坐标。所以第一次点击时LibreCAD 不能简单地把鼠标位置 500,300 直接当成直线起点。中间还需要经过一个非常重要的步骤屏幕坐标 - CAD 世界坐标。RS_VectorRS_Snapper::snapPoint(QMouseEvent*e){// ....// 这一步就是将传过来的鼠标在屏幕上的坐标转换成世界坐标的函数RS_Vector mouseCoordgraphicView-toGraph(e-x(),e-y());// ....}也就是我们后面要重点分析的坐标转换。1.4、Action 获取鼠标对应的 CAD 坐标当鼠标事件进入 Action 后LibreCAD 会根据当前鼠标位置获取对应的 CAD 坐标。可以抽象成鼠标点击 - 屏幕位置 - GraphicView 坐标转换 - CAD 世界坐标 - RS_Vector。最终Action 得到的是一个可以用于 CAD 几何计算的坐标。例如RS_Vector point;可以把它理解成point(x,y);例如point (125.35, 86.72)这才是后面创建 RS_Line 时真正使用的坐标。RS_VectorRS_GraphicView::toGraph(intx,inty)const{returnRS_Vector(toGraphX(x),toGraphY(y));}// 鼠标在屏幕的位置 - 原点的位置 x 的离原点的长度// x 离原点的长度 / 缩放比例 在图纸上的实际长度doubleRS_GraphicView::toGraphX(intx)const{return(x-offsetX)/factor.x;}// 这里屏幕和图纸的 y 轴是相反的所以这里加了一个负号doubleRS_GraphicView::toGraphY(inty)const{return-(y-getHeight()offsetY)/factor.y;}1.5、第一次点击记录起点拿到第一个 CAD 坐标之后RS_ActionDrawLine 就可以把它保存下来。逻辑上可以理解成第一次点击 - 获取 CAD 坐标 - startPoint 当前坐标。也就是说startPoint point此时并没有创建一条完整的线。因为现在只有起点。而一条直线至少需要起点和终点。所以第一次点击之后LibreCAD 只是完成了 “我知道这条线从哪里开始了。”1.6、第一次点击之后Action 的状态发生了变化第一次点击最重要的事情其实不只是保存坐标。它还意味着Action 从“等待第一点”进入了“等待第二点”的阶段。enumStatus{SetStartpoint,/// Setting the startpointSetEndpoint/// Setting the endpoint};整个过程可以理解为这就是 RS_ActionDrawLine 中非常重要的状态变化。1.7、为什么第一次点击之后不会立即创建 Entity这一点非常容易产生误解。有人可能会认为第一次点击后就创建 RS_Line。实际上这时候还不能创建最终的直线。因为第一次点击只有起点还缺少终点。所以第一次点击完成后程序更像是在保存一个“中间状态”startPoint 已确定endPoint 未确定此时 Action 继续等待用户下一次操作。1.8、鼠标移动为什么会出现预览线这时候就会出现一个非常有意思的现象。用户第一次点击之后并没有马上点击第二次。但是当用户移动鼠标时LibreCAD 的绘图区却可以看到一条跟随鼠标移动的预览线。例如鼠标继续移动这说明第一次点击之后Action 已经进入了一个新的状态起点已经确定终点暂时使用鼠标当前位置所以这里其实可以得到一个非常重要的结论第一次点击并没有结束画线操作而是把 RS_ActionDrawLine 从“等待起点”切换到了“等待终点”的交互阶段。接下来鼠标移动事件就可以不断更新临时终点。1.9、把第一次点击完整串起来现在我们可以把第一次点击的完整流程总结成用户第一次点击 - Qt 产生 QMouseEvent - GraphicView 接收事件 - RS_EventHandler 分发事件 - RS_ActionDrawLine - 获取鼠标位置 - 屏幕坐标 - CAD 世界坐标 - 保存起点 - 状态发生变化 - 等待第二点。用一句话概括就是第一次点击的本质是让 RS_ActionDrawLine 获取并保存用户指定的第一个 CAD 坐标同时将 Action 从“等待起点”切换到“等待终点”的状态。1.10、从源码阅读的角度我们发现了什么到这里我们实际上已经开始看到 LibreCAD 的一个重要设计特点 RS_ActionDrawLine 并不是点击一次后直接画线而是维护了一个交互过程Action - 等待输入 - 获取第一点 - 保存状态 - 等待下一次输入 - 获取第二点 - 创建 Entity。所以LibreCAD 中的 Action 更像是一个小型状态机。这也是为什么前面我们一直强调EventHandler - 负责把事件交给 ActionAction - 负责根据当前状态解释事件同样一个 MouseButtonPress在不同 Action、不同状态下产生的结果完全不同。对于 RS_ActionDrawLine第一次点击 → 设置起点第二次点击 → 设置终点并创建直线而对于其他 ActionRS_ActionDrawCircle第一次点击 → 设置圆心第二次操作 → 确定半径因此EventHandler 解决的是“事件交给谁”而 Action 解决的是“这个事件在当前状态下意味着什么”。接下来我们还剩下一个非常关键的问题第一次点击之后用户移动鼠标时RS_ActionDrawLine 是如何不断更新终点并显示预览线的这就是下一节要分析的内容。二、鼠标移动时发生什么上一节我们分析了RS_ActionDrawLine如何处理第一次鼠标点击。第一次点击之后LibreCAD 已经获得了直线的起点但是此时终点还没有确定。如果用户继续移动鼠标会发现一个非常有意思的现象鼠标移动到另一个位置线段会跟着鼠标一起移动。那么问题来了鼠标只是移动了一下LibreCAD 为什么就能实时更新这条线答案就是鼠标移动会不断产生MouseMove事件而RS_ActionDrawLine会利用这些事件不断更新当前的鼠标坐标并重新绘制预览图形。2.1、鼠标移动首先产生MouseMove事件和鼠标点击一样鼠标移动也属于 Qt 的事件。当用户移动鼠标时Qt 会产生MouseMove - QMouseEvent然后按照前面分析的事件传递流程进入 LibreCAD用户移动鼠标 - Qt - QMouseEvent - GraphicView - EventHandler - 当前 Action - RS_ActionDrawLine所以如果用户快速移动鼠标实际上会产生大量连续的鼠标移动事件。可以理解为**鼠标移动 - MouseMove - 鼠标移动 - MouseMove - 鼠标移动 - MouseMove - ……**LibreCAD 正是通过不断处理这些事件才能让绘图过程看起来是实时的。voidRS_ActionDrawLine::mouseMoveEvent(QMouseEvent*e){RS_Vector mousesnapPoint(e);if(getStatus()SetEndpointpPoints-data.startpoint.valid){// Snapping to angle(15*) if shift key is pressedif(e-modifiers()Qt::ShiftModifier){mousesnapToAngle(mouse,pPoints-data.startpoint,15.);}deletePreview();RS_Line*linenewRS_Line(pPoints-data.startpoint,mouse);preview-addEntity(line);line-setLayerToActive();line-setPenToActive();drawPreview();}}2.2、第一次点击之后Action 已经进入了新的阶段这里是理解整个过程的关键。第一次点击之前 RS_ActionDrawLine 等待起点。第一次点击之后RS_ActionDrawLine 起点 已确定 终点 未确定因此当MouseMove事件到来时Action 已经知道起点在哪里。但还不知道终点在哪里。所以当前鼠标位置就可以暂时充当“临时终点”。例如鼠标移动之后因此可以简单理解为startPoint 已确定的起点 currentPoint 当前鼠标位置预览线就是startPoint → currentPoint2.3、鼠标坐标还需要进行坐标转换这里又会遇到前面提到的一个问题。Qt 的QMouseEvent给出的鼠标位置本质上是窗口/绘图区中的屏幕坐标。例如鼠标位置 (600, 350)但是 CAD 图形使用的是自己的世界坐标。所以 LibreCAD 不能简单地endPointmouseX,mouseY;而需要将鼠标所在的屏幕位置转换成 CAD 坐标。整体过程可以表示成QMouseEvent - 鼠标屏幕坐标 - GraphicView 坐标转换 - CAD 世界坐标 - currentPoint例如屏幕坐标 坐标转换 CAD 坐标 (600, 350) ———————— (125.4, 86.7)这样RS_ActionDrawLine才能使用这个坐标进行 CAD 几何计算。2.4、为什么每移动一次都要重新计算假设第一次点击确定startPoint (100, 100)此时鼠标位于currentPoint (200, 150)那么预览线就是如果鼠标继续移动currentPoint (300,200)那么预览线就应该变成所以每一次MouseMove都需要重新获取当前鼠标位置然后更新currentPoint再根据startPoint → currentPoint重新计算预览结果。这就是为什么鼠标移动时预览线能够实时跟随鼠标。2.5、预览线并不等于真正的RS_Line这里有一个非常重要的区别。第一次点击之后屏幕上虽然已经能够看到一条线但这并不意味着 LibreCAD 已经创建了一条最终的RS_Line。因为现在起点 确定终点 未确定所以这条线实际上只是绘图过程中的临时预览。可以把它分成两个概念预览阶段 startPoint ───── currentPoint鼠标位置和最终确定最终结果 startPoint ───── endPoint只有用户第二次点击之后终点才真正确定LibreCAD 才会进入创建实体的流程。因此鼠标移动阶段主要是在更新“临时状态”而不是不断创建新的 CAD 实体。这一点非常重要。2.6、为什么预览线看起来像“实时绘制”实际上LibreCAD 并不是鼠标移动 - 创建一条新的 RS_Line - 鼠标再移动 - 删除上一条 RS_Line - 创建下一条 RS_Line如果这样做效率会非常低而且会污染真正的 CAD 文档。更合理的方式是鼠标移动 - 更新当前 Action 的临时数据 - 重新绘制预览也就是说最终用户第二次点击之后临时状态才会转化成真正的图形实体。2.7、从源码角度应该关注什么如果继续往RS_ActionDrawLine源码里追踪这一阶段最值得关注的不是“画线函数叫什么”而是三个问题第一鼠标移动事件进入了哪个函数也就是mouseMoveEvent(...)或者当前 LibreCAD 版本中对应的鼠标移动处理函数。第二鼠标当前位置保存到了哪里重点寻找类似mousePoint currentPoint coord snapPoint这样的数据。第三鼠标移动之后为什么会重新显示也就是继续追踪MouseMove - 更新坐标 - 更新 Action 状态 - 请求重新绘制 - GraphicView 重绘这一条链实际上已经开始涉及 LibreCAD 的预览绘制机制。2.8、鼠标移动的完整流程现在可以把整个过程总结起来第一次点击 - 确定 startPoint - 进入“等待终点”状态 - 用户移动鼠标 - Qt 产生 MouseMove - QMouseEvent - GraphicView - EventHandler - RS_ActionDrawLine - 获取当前鼠标位置 - 屏幕坐标 - CAD 世界坐标 - 更新 currentPoint - 根据 startPoint currentPoint计算临时预览线 - 重新绘制所以用户看到的“鼠标一动线就跟着动。”背后实际上是一条不断重复的事件处理流程MouseMove - 获取坐标 - 更新状态 - 重新绘制 - MouseMove - 获取坐标 - 更新状态 - 重新绘制 - ……2.9、到这里画线过程已经越来越清晰现在把前三个阶段放在一起点击“画线”QAction - ActionHandler - RS_ActionDrawLine第一次点击QMouseEvent - EventHandler - RS_ActionDrawLine - 确定起点鼠标移动MouseMove - EventHandler - RS_ActionDrawLine - 获取当前坐标 - 更新临时终点 - 绘制预览此时屏幕上的线虽然已经出现但它还不是最终的 CAD 图形。真正决定这条线的是下一次鼠标点击。2.10、下一步第二次点击当用户再次按下鼠标左键第二次点击 - 确定终点 - startPoint endPoint - 创建 RS_Line - 加入 Graphic / Document - 最终显示在绘图区至此“画线”这个交互过程才真正完成。因此整个RS_ActionDrawLine的核心流程可以概括成等待第一点 - 第一次点击 - 确定起点 - 鼠标移动 - 实时更新临时终点 - 显示预览 - 第二次点击 - 确定终点 - 创建 RS_Line下一节我们就可以继续追踪最后一步第二次点击之后RS_ActionDrawLine如何把两个点变成真正的RS_Line三、RS_ActionDrawLine 的状态机前面我们分别分析了用户第一次点击如何确定起点鼠标移动时如何更新预览第二次点击如何确定终点。如果把这些过程拆开来看会感觉只是几个独立的鼠标事件。但实际上RS_ActionDrawLine并不是简单地收到一次鼠标事件就执行一次固定的操作。同样是一次鼠标点击在不同的阶段RS_ActionDrawLine的处理方式完全不同。例如第一次点击 → 确定起点第二次点击 → 确定终点为什么同样是鼠标左键点击却会产生不同的结果答案就在于RS_ActionDrawLine会记录自己当前所处的状态。这实际上就是一个典型的有限状态机Finite State MachineFSM。3.1、什么是状态机可以先不从复杂的理论定义开始理解。所谓状态机可以简单理解为同一个输入在不同状态下会产生不同的行为并可能进入新的状态。例如对于画线操作当前状态等待起点鼠标点击 - 保存起点 - 切换状态此时再次点击当前状态等待终点:鼠标点击 - 保存终点 - 创建直线虽然两次输入都是鼠标左键点击但是由于当前状态不同最终执行的逻辑也不同。因此Action 不能只知道“用户点击了鼠标。”还必须知道“用户现在进行到画线流程的哪一步了”3.2、RS_ActionDrawLine 的核心状态从画线流程来看可以将RS_ActionDrawLine的状态抽象成两个主要阶段需要注意的是实际源码中的状态名称需要以RS_ActionDrawLine的真实定义为准。但从整个交互逻辑来看它的核心过程就是等待起点 - 获得起点 - 等待终点 - 获得终点 - 创建直线 - 继续等待下一次输入3.3、第一次点击状态发生第一次转换刚进入画线 Action 时可以抽象为State 等待第一点此时用户点击鼠标MousePressAction 会根据当前状态判断“我现在正在等待第一点。”因此这次点击的坐标会被解释为startPoint整个过程可以表示成因此第一次点击之后startPoint 已确定 endPoint 未确定Action 的状态也随之发生变化。3.4、鼠标移动状态不变但数据不断变化第一次点击之后Action 已经进入等待第二点此时用户移动鼠标。注意鼠标移动并不会让状态发生切换。Action 仍然处于等待第二点但是当前鼠标位置会不断变化。因此可以理解为状态 MouseMove **等待第二点 ——————— 更新当前坐标 - 更新预览 - 仍然等待第二点**也就是说State 等待第二点MouseMove - 更新 currentPoint - 绘制预览 - State 不变这也是状态机中非常常见的一种情况输入发生了但状态本身并没有改变只更新当前状态相关的数据。3.5、第二次点击状态机完成一次循环当用户第二次点击鼠标时当前状态已经不是等待第一点而是等待第二点因此Action 会将当前坐标解释为endPoint然后startPoint endPoint - 创建 RS_Line整个过程可以表示成为什么最后又回到了“等待第一点”因为画线工具通常不会在画完一条线之后自动退出。例如用户可以连续绘制第一条线 ●────────● 第二条线 ●──────● 第三条线 ●────●因此完成一条线之后Action 会重新进入等待第一点准备下一次绘制。3.6、用状态转换图理解整个过程现在把整个RS_ActionDrawLine抽象成一个完整的状态机从这个图可以清楚地看到状态只有两个核心阶段1、等待第一点2、等待第二点而用户的输入会驱动状态发生变化。3.7、同一个事件在不同状态下意义不同这是理解RS_ActionDrawLine最关键的一点。假设收到同样的事件MouseButtonPress如果当前状态是等待第一点那么MouseButtonPress - 设置 startPoint - 切换到等待第二点如果当前状态是等待第二点那么MouseButtonPress - 设置 endPoint - 创建 RS_Line - 回到等待第一点因此可以总结为当前状态输入事件执行操作下一个状态等待第一点鼠标点击保存起点等待第二点等待第二点鼠标移动更新预览终点等待第二点等待第二点鼠标点击保存终点、创建直线等待第一点这张表实际上已经完整描述了整个画线 Action 的核心逻辑。3.8、RS_ActionDrawLine 为什么要使用状态机如果不使用状态画线逻辑会很难区分用户点击到底意味着设置起点 还是设置终点而通过状态变量status或者类似的状态定义Action 就可以明确知道当前正在等待什么输入。因此整个逻辑可以抽象成switch(status){caseSetStartPoint:// 第一次点击startPointpoint;statusSetEndPoint;break;caseSetEndPoint:// 第二次点击endPointpoint;// 创建直线statusSetStartPoint;break;}上面的代码只是对状态机逻辑的抽象用来帮助理解并不代表 LibreCAD 源码中的实际实现。真正的源码可能还会涉及捕捉Snap正交模式坐标限制取消操作连续绘制Undo/RedoPreview因此在阅读源码时不能只盯着ifswitch而应该关注当前 Action 保存了什么状态什么事件会改变状态状态改变之后又会触发什么操作3.9、从RS_ActionDrawLine看 LibreCAD 的 Action 设计到这里其实可以发现一个更重要的结论。RS_ActionDrawLine并不只是“画一条线的函数集合。”它实际上保存了整个用户交互过程。可以理解成因此LibreCAD 中的 Action 本质上更接近一个负责管理用户交互流程的状态机。这也是为什么同一个 EventHandler可以服务于画线 画圆 移动 复制 旋转 裁剪因为 EventHandler 只负责把事件交出去。而不同的 Action 则通过自己的状态机解释这些事件。例如鼠标点击RS_ActionDrawLine - 设置起点 / 终点RS_ActionDrawCircle - 设置圆心 / 半径RS_ActionMove - 选择对象 / 确定目标位置事件本身没有意义真正赋予事件含义的是当前 Action 以及它所处的状态。总结通过RS_ActionDrawLine我们可以看到一个完整的状态驱动过程启动 DrawLine Action - 等待第一点 - 第一次点击 - 记录起点 - 等待第二点 - 鼠标移动 - 更新临时终点 显示预览 - 第二次点击 - 确定终点 - 创建 RS_Line - 重新等待第一点因此RS_ActionDrawLine的核心并不是简单地接收两个坐标然后画一条线。而是通过状态保存整个交互过程让同样的鼠标事件在不同阶段产生不同的行为。这也让我们真正理解了 LibreCAD 中 Action 的作用Action 不只是“执行一个功能”更是“管理一次完整用户交互过程的状态机”。到这里从用户点击“画线”工具到第一次点击、鼠标移动、第二次点击整个交互流程已经基本完整。下一步就可以继续追踪两个坐标确定之后RS_ActionDrawLine是如何创建RS_Line并将它加入 LibreCAD 图形数据中的坚持到这里已经很厉害啦辛苦啦ʕ • ᴥ • ʔづ♡ど
返回列表