DPS_Manage/DelegateUI/Controls/DelProgress.qml

313 lines
13 KiB
QML

import QtQuick 2.15
import DelegateUI 1.0
Item {
id: control
width: parent.width
height: 16
enum Type {
Type_Line = 0,
Type_Circle = 1,
Type_Dashboard = 2
}
enum Status {
Status_Normal = 0,
Status_Success = 1,
Status_Exception = 2,
Status_Active = 3
}
property bool animationEnabled: DelTheme.animationEnabled
property int type: DelProgress.Type_Line
property int status: DelProgress.Status_Normal
property real percent: 0
property real barThickness: 8
property string strokeLineCap: 'round'
property int steps: 0
property int currentStep: 0
property real gap: 4
property real gapDegree: 60
property bool useGradient: false
property var gradientStops: ({
'0%': control.colorBar,
'100%': control.colorBar
})
property bool showInfo: true
property int precision: 0
property var formatter: function() {
switch (control.status) {
case DelProgress.Status_Success:
return control.type === DelProgress.Type_Line ? DelIcon.CheckCircleFilled : DelIcon.CheckOutlined;
case DelProgress.Status_Exception:
return control.type === DelProgress.Type_Line ? DelIcon.CloseCircleFilled : DelIcon.CloseOutlined;
default: return `${control.percent.toFixed(control.precision)}%`;
}
}
property color colorBar: {
switch (control.status) {
case DelProgress.Status_Success: return DelTheme.DelProgress.colorBarSuccess;
case DelProgress.Status_Exception: return DelTheme.DelProgress.colorBarException;
case DelProgress.Status_Normal: return DelTheme.DelProgress.colorBarNormal;
case DelProgress.Status_Active : return DelTheme.DelProgress.colorBarNormal;
default: return DelTheme.DelProgress.colorBarNormal;
}
}
property color colorTrack: DelTheme.DelProgress.colorTrack
property color colorInfo: {
switch (control.status) {
case DelProgress.Status_Success: return DelTheme.DelProgress.colorInfoSuccess;
case DelProgress.Status_Exception: return DelTheme.DelProgress.colorInfoException;
default: return DelTheme.DelProgress.colorInfoNormal;
}
}
property Component infoDelegate: DelIconText {
color: control.colorInfo
font.family: DelTheme.DelProgress.fontFamily
font.pixelSize: type === DelProgress.Type_Line ? DelTheme.DelProgress.fontSize + (!isIcon ? 0 : 2) :
DelTheme.DelProgress.fontSize + (!isIcon ? 8 : 16)
text: isIcon ? String.fromCharCode(formatText) : formatText
property var formatText: control.formatter()
property bool isIcon: typeof formatText == 'number'
}
onPercentChanged: __canvas.requestPaint();
onStepsChanged: __canvas.requestPaint();
onCurrentStepChanged: __canvas.requestPaint();
onBarThicknessChanged: __canvas.requestPaint();
onStrokeLineCapChanged: __canvas.requestPaint();
onGapChanged: __canvas.requestPaint();
onGapDegreeChanged: __canvas.requestPaint();
onUseGradientChanged: __canvas.requestPaint();
onGradientStopsChanged: __canvas.requestPaint();
onColorBarChanged: __canvas.requestPaint();
onColorTrackChanged: __canvas.requestPaint();
onColorInfoChanged: __canvas.requestPaint();
Behavior on percent { enabled: control.animationEnabled; NumberAnimation { duration: DelTheme.Primary.durationMid } }
Behavior on colorBar { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationMid } }
Behavior on colorTrack { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationMid } }
Behavior on colorInfo { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationMid } }
Canvas {
id: __canvas
height: parent.height
anchors.left: parent.left
anchors.right: control.type === DelProgress.Type_Line ? __infoLoader.left : parent.right
anchors.rightMargin: control.type === DelProgress.Type_Line ? 5 : 0
antialiasing: true
onWidthChanged: requestPaint();
onHeightChanged: requestPaint();
onActiveWidthChanged: requestPaint();
property color activeColor: DelThemeFunctions.alpha(DelTheme.Primary.colorBgBase, 0.15)
property real activeWidth: 0
property real progressWidth: control.percent * 0.01 * width
NumberAnimation on activeWidth {
running: control.type == DelProgress.Type_Line && control.status == DelProgress.Status_Active
from: 0
to: __canvas.progressWidth
loops: Animation.Infinite
duration: 2000
easing.type: Easing.OutQuint
}
function createGradient(ctx) {
let gradient = ctx.createLinearGradient(0, 0, width, height);
Object.keys(control.gradientStops).forEach(
stop => {
const percentage = parseFloat(stop) / 100;
gradient.addColorStop(percentage, control.gradientStops[stop]);
});
return gradient;
}
function getCurrentColor(ctx) {
return control.useGradient ? createGradient(ctx) : control.colorBar;
}
function drawStrokeWithRadius(ctx, x, y, radius, startAngle, endAngle, color) {
ctx.beginPath();
ctx.arc(x, y, radius, startAngle, endAngle);
ctx.lineWidth = control.barThickness;
ctx.strokeStyle = color;
ctx.stroke();
}
function drawRoundLine(ctx, x, y, width, height, radius, color) {
ctx.beginPath();
if (control.strokeLineCap === 'butt') {
ctx.moveTo(x, y + height * 0.5);
ctx.lineTo(x + width, y + height * 0.5);
} else {
ctx.moveTo(x + radius, y + height * 0.5);
ctx.lineTo(x + width - radius * 2, y + radius);
}
ctx.lineWidth = control.barThickness;
ctx.lineCap = control.strokeLineCap;
ctx.strokeStyle = color;
ctx.stroke();
}
function drawLine(ctx) {
const color = getCurrentColor(ctx);
if (control.steps > 0) {
const stepWidth = (width - ((control.steps - 1) * control.gap)) / control.steps;
const stepHeight = control.barThickness;
const stepY = (__canvas.height - stepHeight) * 0.5;
for (let i = 0; i < control.steps; i++) {
const stepX = i * control.gap + i * stepWidth;
ctx.fillStyle = control.colorTrack;
ctx.fillRect(stepX, stepY, stepWidth, stepHeight);
}
for (let ii = 0; ii < control.currentStep; ii++) {
const stepX = ii * control.gap + ii * stepWidth;
ctx.fillStyle = color;
ctx.fillRect(stepX, stepY, stepWidth, stepHeight);
}
} else {
const x = 0;
const y = (height - control.barThickness) * 0.5;
const progressWidth = control.percent * 0.01 * width;
const radius = control.strokeLineCap === 'round' ? control.barThickness * 0.5 : 0;
drawRoundLine(ctx, x, y, width, control.barThickness, radius, control.colorTrack);
if (progressWidth > 0) {
drawRoundLine(ctx, x, y, progressWidth, control.barThickness, radius, color);
/*! 绘制激活状态动画 */
if (control.status == DelProgress.Status_Active) {
drawRoundLine(ctx, x, y, __canvas.activeWidth, control.barThickness, radius, __canvas.activeColor);
}
}
}
}
function drawCircle(ctx, centerX, centerY, radius) {
/*! 确保绘制不会超出边界 */
radius = Math.min(radius, Math.min(width, height) * 0.5 - control.barThickness);
const color = getCurrentColor(ctx);
if (control.steps > 0) {
/*! 计算每个步骤的弧长,考虑圆角影响 */
const gap = control.gap;
const circumference = Math.PI * 2 * radius;
const totalGapLength = gap * control.steps;
const availableLength = circumference - totalGapLength;
const stepLength = availableLength / control.steps;
/*! 绘制背景圆环段 */
for (let i = 0; i < control.steps; i++) {
const gapDistance = (gap * i) / radius;
const stepAngle = stepLength / radius;
const startAngle = (i * stepAngle) + gapDistance - Math.PI / 2;
const endAngle = startAngle + stepLength / radius;
drawStrokeWithRadius(ctx, centerX, centerY, radius, startAngle, endAngle, control.colorTrack);
}
/*! 绘制已完成的步骤 */
for (let ii = 0; ii < control.currentStep; ii++) {
const gapDistance = (gap * ii) / radius;
const stepAngle = stepLength / radius;
const startAngle = (ii * stepAngle) + gapDistance - Math.PI / 2;
const endAngle = startAngle + stepLength / radius;
drawStrokeWithRadius(ctx, centerX, centerY, radius, startAngle, endAngle, color);
}
} else {
/*! 非步骤条需要使用线帽 */
ctx.lineCap = control.strokeLineCap;
/*! 绘制轨道 */
drawStrokeWithRadius(ctx, centerX, centerY, radius, 0, Math.PI * 2, control.colorTrack);
/*! 绘制进度 */
const progress = control.percent * 0.01 * Math.PI * 2;
drawStrokeWithRadius(ctx, centerX, centerY, radius, -Math.PI / 2, progress - Math.PI / 2, color);
}
}
function drawDashboard(ctx, centerX, centerY, radius) {
radius = Math.min(radius, Math.min(width, height) * 0.5 - control.barThickness);
/* ! 计算开始和结束角度 */
const gapRad = Math.min(Math.max(control.gapDegree, 0), 295) * Math.PI / 180;
const startAngle = Math.PI * 0.5 + gapRad * 0.5;
const endAngle = Math.PI * 2.5 - gapRad * 0.5;
const color = getCurrentColor(ctx);
if (control.steps > 0) {
/*! 计算每个步骤的弧长,考虑仪表盘缺口和步进间隔 */
const gap = control.gap;
const availableAngle = endAngle - startAngle;
const totalGapAngle = (gap / radius) * (control.steps - 1);
const stepAngle = (availableAngle - totalGapAngle) / control.steps;
/*! 绘制背景圆环段 */
for (let i = 0; i < control.steps; i++) {
const stepStartAngle = startAngle + i * (stepAngle + gap / radius);
const stepEndAngle = stepStartAngle + stepAngle;
drawStrokeWithRadius(ctx, centerX, centerY, radius, stepStartAngle, stepEndAngle, control.colorTrack);
}
/*! 绘制已完成的步骤 */
for (let ii = 0; ii < control.currentStep; ii++) {
const stepStartAngle = startAngle + ii * (stepAngle + gap / radius);
const stepEndAngle = stepStartAngle + stepAngle;
drawStrokeWithRadius(ctx, centerX, centerY, radius, stepStartAngle, stepEndAngle, color);
}
} else {
/*! 非步骤条需要使用线帽 */
ctx.lineCap = control.strokeLineCap;
/*!绘制背景轨道 */
drawStrokeWithRadius(ctx, centerX, centerY, radius, startAngle, endAngle, control.colorTrack);
/*计算进度条角度 */
const progressRange = endAngle - startAngle;
const progress = control.percent * 0.01 * progressRange;
/*绘制进度 */
drawStrokeWithRadius(ctx, centerX, centerY, radius, startAngle, startAngle + progress, color);
}
}
onPaint: {
let ctx = getContext('2d');
let centerX = width / 2;
let centerY = height / 2;
let radius = Math.min(width, height) / 2 - control.barThickness;
/*! 清除画布 */
ctx.clearRect(0, 0, width, height);
switch (control.type) {
case DelProgress.Type_Line:
drawLine(ctx); break;
case DelProgress.Type_Circle:
drawCircle(ctx, centerX, centerY, radius); break;
case DelProgress.Type_Dashboard:
drawDashboard(ctx, centerX, centerY, radius); break;
default: break;
}
}
}
Loader {
id: __infoLoader
active: control.showInfo
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: control.type === DelProgress.Type_Line ? undefined : parent.horizontalCenter
anchors.right: control.type === DelProgress.Type_Line ? parent.right : undefined
sourceComponent: control.infoDelegate
}
}