1966年,Seymour Papert 和 Wally Feurzig 发明了一种专门给儿童学习编程的语言——LOGO 语言,它的特色就是通过编程指挥一个小海龟(turtle)在屏幕上绘图。海龟绘图(Turtle Graphics)后来被移植到各种高级语言中,Python内置了 turtle 库,基本上完整复制了原始 Turtle Graphics 的所有功能。
海龟绘图的特点就是以动画的形式绘制出图案,这比直接展示最终图案更有趣。
要在 Qt 中实现这种动画效果,我的思路就是通过定时器刷新当前进度,然后根据进度来决定绘制哪一部分。趁着假期无聊就写了个简易版本的,可以画直线和圆弧,以及填充路径,后面有空再完善下。
代码链接:https://github.com/gongjianbo/QtTurtle
效果展示:
调用代码:
drawPainter.begin(QPointF(300,200));
drawPainter.beginFill(QColor(0,100,0));
drawPainter.setPenWidth(2);
drawPainter.setPenColor(QColor(0,150,0));
drawPainter.arc(QPointF(200,200),100,0,180);
drawPainter.arc(QPointF(400,100),318,-161,53);
drawPainter.arc(QPointF(200,100),318,-72,53);
drawPainter.arc(QPointF(400,200),100,0,180);
drawPainter.endFill();
drawPainter.end();
核心部分就是每一步操作就 new 一个对象,绘制时遍历这些对象,遍历过程中超出当前进度就 break,等定时器 timeout 时进度会增加并再次遍历绘制。
部分代码:
#include "MyPainter.h"
#include <QtMath>
#include <QDebug>
MyPainter::MyPainter(QObject *parent) : QObject(parent)
{
timer=new QTimer(this);
connect(timer,&QTimer::timeout,this,[=]{
drawLength+=10;
update();
//时长超过表示线条绘制完了
if(drawEnd)
timer->stop();
});
}
MyPainter::~MyPainter()
{
qDeleteAll(dataList);
dataList.clear();
}
void MyPainter::draw(QPaintDevice *device)
{
if(drawLength<=0)
return;
QPainter painter(device);
//目前没有设置路径填充规则 TODO
qint64 progress_temp=0;
int i=0;
for(i=0;i<dataList.count()&&progress_temp<drawLength;i++)
{
double len=drawLength-progress_temp;
AbstractElement* item=dataList[i];
//判断begin和end fill,执行到endfill才开始填充begin中的路径
switch (item->type()) {
case 3:
{
const int ele_index=static_cast<BeginFillElement*>(item)->index();
if(drawEndIndex>=ele_index){
item->draw(&painter,0);
}
}
break;
case 4:
drawEndIndex=static_cast<EndFillElement*>(item)->index();
break;
default:
item->draw(&painter,len>item->length()?1:len/item->length());
break;
}
//len不够就是还没画完,后面的就不画了
if(len<item->length())
break;
progress_temp+=item->length();
}
drawEnd=bool(i>=dataList.count()&&progress_temp>=dataLength);
}
void MyPainter::start()
{
timer->start(timerInterval);
drawLength=0;
drawEndIndex=-1;
drawEnd=false;
startTime=QTime::currentTime();
}
void MyPainter::begin(const QPointF &pos)
{
//begin为路径规划开始,状态复位
currentPos=pos;
dataLength=0;
dataList.clear();
}
void MyPainter::end()
{
//end暂无操作
}
void MyPainter::setPenColor(const QColor &color)
{
PenColorElement *ele=new PenColorElement(color);
dataList.push_back(ele);
}
void MyPainter::setPenWidth(int width)
{
PenWidthElement *ele=new PenWidthElement(width);
dataList.push_back(ele);
}
void MyPainter::beginFill(const QColor &color)
{
//开始填充后,beginElement!=null,path在别的函数判断中就能追加路径
fillBeginIndex++;
fillBeginPath=QPainterPath();
fillBeginPath.moveTo(currentPos);
fillBeginElement=new BeginFillElement(fillBeginIndex,color);
dataList.push_back(fillBeginElement);
}
void MyPainter::endFill()
{
if(fillBeginElement){
//end后路径就完成了
fillBeginElement->setFillPath(fillBeginPath);
EndFillElement *ele=new EndFillElement(fillBeginIndex);
dataList.push_back(ele);
fillBeginElement=nullptr;
}
}
void MyPainter::moveTo(const QPointF &pos)
{
if(fillBeginElement){
fillBeginPath.moveTo(pos);
}
currentPos=pos;
}
void MyPainter::lineTo(const QPointF &pos)
{
if(fillBeginElement){
fillBeginPath.lineTo(pos);
}
LineElement *ele=new LineElement(QLineF(currentPos,pos));
dataList.push_back(ele);
dataLength+=ele->length();
currentPos=pos;
}
void MyPainter::arc(const QPointF ¢er, int radius, int startAngle, int spanAngle)
{
QRectF ele_rect=QRectF(center.x()-radius,center.y()-radius,
radius*2,radius*2);
if(fillBeginElement){
//const int len=std::ceil(std::abs(spanAngle)*M_PI*radius/180.0);
fillBeginPath.arcTo(ele_rect,startAngle,spanAngle);
}
ArcElement *ele=new ArcElement(ele_rect,startAngle,spanAngle);
dataList.push_back(ele);
dataLength+=ele->length();
//currentPos=stop;
}