C++拾趣——绘制Console中圆形进度
大纲
- 实心圆进度
- 代码
- 空心圆进度
- 代码
在《C++拾趣——绘制Console中单个进度条》一文中,我们看到了如何绘制单向前进的进度条。
本文我们将介绍两种圆形进度的绘制方法。
实心圆进度
像上图这样需要重绘场景,都需要调整光标位置。
我们可以采用《C++拾趣——绘制Console中多个进度条》中的\033[【N】F
的方案,即调整到N行的开始位置。但是有更加简单的方法——用于清屏并将光标移动到左上角的\033[2J\033[H
。
下面的代码会绘制一个Y轴半径为10个元素,X轴半径为20个元素的近似圆。半径不同的原因是字符的长度和宽度是不等的,所以我们需要使用不同元素个数来构成一个近似的圆。
然后我们使用x * x / (aspectRatio * aspectRatio) + y * y <= radius * radius
来判断点是否在圆内,从而保证输出的是一个近似圆。
最后我们用四象限反正切
来判断点是否处于进度之内。四象限反正切
函数atan2的返回值在[-π, π] 之间。我们通过给这个结果加上π,让其归于[0, 2π] 。这样就可以控制进度了。
#include <iostream>
#include <thread>
#include <chrono>
#include <cmath>
using namespace std;
void printCircularProgress(int duration) {
const int radius = 10;
const int steps = 100;
const double aspectRatio = 2.0; // 调整宽高比
const string red = "\033[31m"; // 红色
const string green = "\033[32m"; // 绿色
const string reset = "\033[0m"; // 重置颜色
for (int step = 0; step <= steps; ++step) { // 逐步绘制进度指示器
cout << "\033[2J\033[H"; // 清屏并将光标移动到左上角
for (int y = -radius; y <= radius; ++y) { // 循环绘制圆形的每一行
for (int x = -radius * aspectRatio; x <= radius * aspectRatio; ++x) { // 循环绘制圆形的每一列
if (x * x / (aspectRatio * aspectRatio) + y * y <= radius * radius) { // 判断当前点是否在圆形内
double angle = atan2(y, x / aspectRatio) + M_PI; // 计算当前点的角度
double progressAngle = 2 * M_PI * step / steps; // 计算当前进度的角度
if (angle <= progressAngle) { // 判断当前点是否在进度范围内
cout << green << "@" << reset;
} else {
cout << red << "@" << reset;
}
} else {
cout << " ";
}
}
cout << endl;
}
cout << "Progress: " << int((step / (float)steps) * 100.0) << "%" << endl;
this_thread::sleep_for(chrono::milliseconds(duration / steps));
}
}
int main() {
int duration = 5000; // 进度指示器持续时间(毫秒)
// 创建并启动进度指示器线程
thread progressThread(printCircularProgress, duration);
// 等待进度指示器线程完成
progressThread.join();
return 0;
}
代码
https://github.com/f304646673/cpulsplus/tree/master/console_ui/progress/circular
空心圆进度
空心圆的做法就是在实心圆的基础上,增加一个点和圆心距离的判断。
sqrt((x / aspectRatio) * (x / aspectRatio) + y * y)
可以计算出点到圆心的距离。
在distance >= radius - 0.5 && distance <= radius + 0.5
范围内的点,我们才考虑是否绘制。
#include <iostream>
#include <thread>
#include <chrono>
#include <cmath>
using namespace std;
void printCircularProgress(int duration) {
const int radius = 10;
const int steps = 100;
const double aspectRatio = 2.0; // 调整宽高比
const string red = "\033[31m"; // 红色
const string green = "\033[32m"; // 绿色
const string reset = "\033[0m"; // 重置颜色
for (int step = 0; step <= steps; ++step) {
cout << "\033[2J\033[H"; // 清屏并将光标移动到左上角
for (int y = -radius; y <= radius; ++y) { // 循环绘制圆形的每一行
for (int x = -radius * aspectRatio; x <= radius * aspectRatio; ++x) { // 循环绘制圆形的每一列
double distance = sqrt((x / aspectRatio) * (x / aspectRatio) + y * y); // 计算当前点到圆心的距离
if (distance >= radius - 0.5 && distance <= radius + 0.5) { // 判断当前点是否在圆形边界上
double angle = atan2(y, x / aspectRatio) + M_PI; // 计算当前点的角度
double progressAngle = 2 * M_PI * step / steps; // 计算当前进度的角度
if (angle <= progressAngle) {
cout << green << "@" << reset;
} else {
cout << red << "@" << reset;
}
} else {
cout << " ";
}
}
cout << endl;
}
cout << "Progress: " << int((step / (float)steps) * 100.0) << "%" << endl;
this_thread::sleep_for(chrono::milliseconds(duration / steps));
}
}
int main() {
int duration = 5000; // 进度指示器持续时间(毫秒)
// 创建并启动进度指示器线程
thread progressThread(printCircularProgress, duration);
// 等待进度指示器线程完成
progressThread.join();
return 0;
}
代码
https://github.com/f304646673/cpulsplus/tree/master/console_ui/progress/hollow_circle