带有Rust的QtQuick中的高效自定义形状

2021-01-20 21:26:28

构建Qt应用程序时,QWidgets的优点之一是能够使用QPainterAPI以简单的方式构建自定义窗口小部件。这使Qt开发人员几乎完全可以为其小部件实现复杂的几何形状。

另一方面,QML默认仅包含矩形。这些矩形可以更改半径以创建圆形和圆形的矩形,但是形状越复杂则越复杂。

幸运的是,Qt API提供了多种实现自定义形状的方法,根据需要可能就足够了。

Canvas API使用与Web上的canvas API相同的API,但使用的是QML。它易于使用,但速度很慢,我不建议您这样做。

从QML方面而不是Canvas API,而是QtQuick Shapes模块。该模块允许使用简单的声明性API直接从QML创建更复杂的形状。在许多情况下,这对于应用程序开发人员来说已经足够了,但是此模块不提供公共的C ++ API。

如果需要更多控件,则需要使用C ++来实现自定义QQuickItem。不幸的是,使用QQuickItem在GPU上绘图比QPainter API更复杂。您不仅可以使用诸如drawRect之类的命令,还需要首先将所有形状转换为三角形。这涉及很多数学运算,例如可以从官方文档或KDAB教程(Qt Quick中的高效自定义形状)的示例中看到。

QQuick方法也可用于QQuickPaintedItem,但它很慢,因为它会在“场景图”中的带纹理的矩形中渲染形状。

如果我们可以将任意形状转换为三角形怎么办?我们将获得高级API,但仍然可以获得出色的性能。此过程称为镶嵌细分,并且有一些库可以实现该细分。例如,在C ++中,我们拥有Skia和CGAL。不幸的是,两者都不容易使用,所以我决定研究Rust库生态系统,尤其是在里昂,它的设计目标是性能和符合SVG标准,因为目标是将来在Servo中使用它。

里昂(Lyon)没有任何C ++绑定,但是我受到Jonahand最近博客文章的启发,我需要说写绑定的经验是轻而易举的。

第一步是围绕Lyon原语创建包装器结构。 LyonPoint,LyonGeometry和LyonBuilder将在C ++方面直接可用。

#[cxx :: bridge] mod ffi {pub struct LyonPoint {x:f32,y:f32,} pub struct LyonVector {x:f32,y:f32,} pub struct LyonGeometry {顶点:Vec<里昂点> ,索引:Vec< u16> ,} extern" Rust" {类型LyonBuilder; fn new_builder()->框< LyonBuilder> ; fn move_to(self:& mut LyonBuilder,point:& LyonPoint); fn line_to(self:& mut LyonBuilder,point:& LyonPoint); fn relative_move_to(self:& mut LyonBuilder,to:LyonVector); fn close(self:& mut LyonBuilder); fn quadratic_bezier_to(self:& mut LyonBuilder,ctrl:& LyonPoint,to:& LyonPoint); fncubic_bezier_to(self:& mut LyonBuilder,ctrl1:& LyonPoint,ctrl2:& LyonPoint,to:& LyonPoint); fn build_fill(构建器:Box< LyonBuilder>)->里昂几何; fn build_stroke(构建器:Box< LyonBuilder>)->里昂几何; }}

然后,我们需要定义上面声明的方法。这些都是简单的实现,因为它们只是包装了Lyon API。

使用ffi:{LyonPoint,LyonVector,LyonGeometry}; //创建一个环绕里昂svg路径的包装器。从C ++端来看,此结构是不透明的,因此我们将无法访问内部对象,但是//我们仍然可以在其上调用方法。 pub struct LyonBuilder {builder:WithSvg<建造者> ,} //实现包装方法impl LyonBuilder {fn close(& mut self){self。建设者。关 (); } fn move_to(& mut self,至:& LyonPoint){self。建设者。 move_to(指向(指向。x,指向。y)); } fn line_to(& mut self,至:& LyonPoint){self。建设者。 line_to(点(指向。x,指向。y)); } fn quadratic_bezier_to(& mut self,ctrl:& LyonPoint,to:& LyonPoint){self。建设者。 quadratic_bezier_to(点(ctrl。x,ctrl。y),点(到。x,到。y)); } ...} // Lyon Builder构造函数pub fn new_builder()->框< LyonBuilder> {return Box :: new(LyonBuilder {builder:Path :: builder()。with_svg()})}

下一步是添加build_fill,它将SVG路径指令转换为一组顶点和索引。这些顶点和索引可以从C ++端直接获得。这非常方便,因为它可以直接输入QSGGeometry绘制方法中。

pub fn build_fill(builder:Box< LyonBuilder>)-> LyonGeometry {让mut缓冲区:VertexBuffers<点,u16> = VertexBuffers :: new(); {let mut vertex_builder = simple_builder(& mut buffers); //创建镶嵌器。 let mut tessellator = FillTessellator :: new();让path = builder。建设者。 build(); //计算镶嵌。让结果=镶嵌。 tessellate_path(& path,& FillOptions ::公差(0.01),& mut vertex_builder);断言! (结果。is_ok()); } LyonGeometry {// convert_points将lyon :: point转换为我们的LyonPoint包装器顶点:convert_points(buffers。vertices),索引:buffers。索引,}}

我们几乎已经完成了Rust方面的工作,我们仍然需要创建货物和腐蚀配置,但是在本文中我将不做详细介绍。您可以了解一下在这个宠物项目中是如何完成的。

为了简化存储和操作路径,我为各种SVG路径指令创建了一个简单的抽象。

#include< QList> #include< variant> #include< tessellation.rs.h> ///移动到该点而不画一条线。 struct MoveTo {///目标。里昂点到; }; ///画一条线到特定点。 struct LineTo {///目标。里昂点到; }; ///在该点处绘制三次贝塞尔曲线。 struct CubicBezierTo {///第一个控制点。 LyonPoint ctrl1; ///第二个控制点。 LyonPoint ctrl2; ///目的地。里昂点到; }; ///关闭路径。 struct Close {}; /// SVG使用PathSection = std :: variant<遵循路径命令MoveTo,LineTo,CubicBezierTo,Close> ;模板<类... Ts>结构重载:Ts ... {使用Ts :: operator()...; };模板<类... Ts>超载(Ts ...)->超载< Ts ...> ; /// SVG路径数据。它包含指令列表(移至,行至...)。使用PathData = QList<路径部分> ;

现在,让我们最终使用Lyon生成几何图元。每次更新命令列表时都需要调用此方法。它使用的是我之前构建的命令抽象,但是可以直接调用LyonBuilder方法。

const自动命令<< MoveTo {LyonPoint {0.0,0.0}}<< LineTo {LyonPoint {0.0,40.0}}<< LineTo {LyonPoint {40.0,40.0}}}<< CubicBezierTo {LyonPoint {70.0,40.0},LyonPoint {70.0,0.0},LyonPoint {50.0,20.0}}<< LineTo {LyonPoint {40.0,0.0}}}<<关 {};自动lyonBuilder = new_builder(); for(const auto& command:命令){std :: visit(重载{[& lyonBuilder](MoveTo moveTo){lyonBuilder-> move_to(moveTo。to);},[& lyonBuilder](LineTo lineTo) {lyonBuilder-> line_to(lineTo。to);},[&lyonBuilder](CubicBezierTo cubeBezierTo){lyonBuilder-> cubic_bezier_to(cubicBezierTo。ctrl1,cubicBezierTo。ctrl2,cubicBezierly,[to]至) (Close){lyonBuilder-> close();},},命令); } auto m_ge​​ometry = build_fill(std :: move(lyonBuilder));

最后是我们的updatePaintNode方法。它使用GL_TRIANGLES绘图模式,并且顶点和索引是直接从geometryLyon给我们的几何图形中复制的。

QSGNode * PathItem :: updatePaintNode(QSGNode * oldNode,UpdatePaintNodeData *){QSGGeometryNode * node = nullptr; QSGGeometry *几何= nullptr;如果(!oldNode){node = new QSGGeometryNode; geometry = new QSGGeometry(QSGGeometry :: defaultAttributes_Point2D(),m_geometry。顶点。size(),m_geometry。index。size());几何-> setIndexDataPattern(QSGGeometry :: StaticPattern);几何-> setDrawingMode(GL_TRIANGLES);节点-> setGeometry(geometry);节点-> setFlag(QSGNode :: OwnsGeometry); QSGFlatColorMaterial *材质=新的QSGFlatColorMaterial;材料-> setColor(QColor(255,0,0));节点-> setMaterial(material);节点-> setFlag(QSGNode :: OwnsMaterial); } else {node = static_cast< QSGGeometryNode *> (oldNode);几何=节点->几何();几何->分配(m_geometry。顶点。size(),m_geometry。索引。size()); } QSGGeometry :: Point2D *点=几何-> vertexDataAsPoint2D(); std :: size_t i = 0; for(const auto& vertice:m_geometry。vertices){点[i]。设置(顶点。x,顶点。y);我++; } quint16 *索引=几何-> indexDataAsUShort();我= 0; for(常量自动索引:m_geometry.indexs){索引[i] =索引;我++; }节点-> markDirty(QSGNode :: DirtyGeometry);返回节点; }

它仅使用Lyon SVG路径渲染,但是Lyon提供了更多的API,例如,有一个抽象允许绘制圆形,椭圆形,圆角矩形和其他基本几何形式。

也可以为纹理坐标或颜色坐标添加自定义属性。根据您的需要,可以包装API的更多部分,我可以创建一个包装大多数API的小型库。

我在制作的新玩具中使用了这种技术。我不确定要去哪里,但是我现在有这个: