aardio 文档
aardio 范例: aria2 下载 —— 如果 BT 下载没速度,先找个热门种子下载就可以了
BT 下载 -自绘卡片风格
// BT 下载 -自绘 listview 风格
// BT 下载 -自绘卡片风格: https://www.aardio.com/zh-cn/doc/example/Network/Transfer/bt.draw.html
import fonts.fontAwesome;
import win.ui;
/*DSG{{*/
var winform = win.form(text="aria2 下载 —— 如果 BT 下载没速度,先找个热门种子下载就可以了";right=921;bottom=537;bgcolor=0xFFFFFF)
winform.add(
btnNext={cls="plus";text='\uF054';left=200;top=490;right=240;bottom=520;color=0x3C3C3C;db=1;dl=1;font=LOGFONT(h=-14;name='FontAwesome');notify=1;z=6};
btnPrev={cls="plus";text='\uF053';left=90;top=490;right=130;bottom=520;color=0x3C3C3C;db=1;dl=1;font=LOGFONT(h=-14;name='FontAwesome');notify=1;z=4};
pie={cls="plus";text="100%";left=25;top=480;right=73;bottom=528;bgcolor=0x4A68E8;color=0xFFFFFF;db=1;dl=1;forecolor=0x4AD4A9;z=2};
plusList={cls="plus";left=12;top=7;right=912;bottom=471;db=1;dl=1;dr=1;dt=1;notify=1;z=1};
txtPage={cls="plus";text="1/1";left=135;top=490;right=195;bottom=520;db=1;dl=1;z=5};
txtUrl={cls="plus";text="https://webtorrent.io/torrents/big-buck-bunny.torrent";left=358;top=497;right=906;bottom=523;align="right";border={bottom=1;color=0xFF808080};db=1;dl=1;dr=1;editable="edit";notify=1;paddingTop=5;textPadding={right=24;bottom=1};z=3}
)
/*}}*/
// 下载任务列表数据管理
var taskList = {
items = {};
gidToIndex = {};
selectedIndex = 0;
hoverIndex = 0;
currentPage = 1;
pageSize = 8;
rowHeight = 50;
headerHeight = 40;
columns = {
{"文件", 0.32},
{"速度", 0.12},
{"已下载", 0.18},
{"状态", 0.26},
{"连接数", 0.12}
};
getTotalPages = function(){
// ★ 只计算未移除的任务
var count = 0;
for(i,item in owner.items){
if(!item.isRemoving) count++;
}
return math.max(1, math.ceil(count / owner.pageSize));
};
getPageItems = function(){
var visibleItems = {};
for(i,item in owner.items){
if(!item.isRemoving){
table.push(visibleItems, item);
}
}
var startIdx = (owner.currentPage - 1) * owner.pageSize + 1;
var endIdx = math.min(startIdx + owner.pageSize - 1, #visibleItems);
var result = {};
for(i=startIdx; endIdx){
table.push(result, visibleItems[i]);
}
return result, startIdx;
};
addTask = function(gid, name){
var item = {
gid = gid;
name = name || "未知文件";
speed = "";
downloaded = "";
total = "";
status = "等待中";
connections = "";
percent = 0;
isComplete = false;
isError = false;
isRemoving = false; // ★ 新增
};
table.push(owner.items, item);
owner.gidToIndex[gid] = #owner.items;
return #owner.items;
};
getItemByGid = function(gid){
var idx = owner.gidToIndex[gid];
if(idx) return owner.items[idx], idx;
};
// ★ 标记为正在移除(不真正删除)
markRemoving = function(gid){
var item = owner.getItemByGid(gid);
if(item){
item.isRemoving = true;
item.status = "正在移除...";
item.speed = "";
}
};
// ★ 真正删除(在 aria2 确认停止后调用)
removeByGid = function(gid){
var idx = owner.gidToIndex[gid];
if(!idx) return;
table.remove(owner.items, idx);
owner.gidToIndex[gid] = null;
for(i=idx; #owner.items){
owner.gidToIndex[owner.items[i].gid] = i;
}
if(owner.currentPage > owner.getTotalPages()){
owner.currentPage = owner.getTotalPages();
}
};
updateTask = function(gid, data){
var item = owner.getItemByGid(gid);
if(!item) return;
// ★ 正在移除的任务不更新(除非是要取消移除)
if(item.isRemoving && !data.isRemoving) return;
for(k,v in data){
item[k] = v;
}
};
hitTest = function(x, y, rc){
if(y < owner.headerHeight) return 0, null;
var row = math.floor((y - owner.headerHeight) / owner.rowHeight) + 1;
// ★ 基于可见任务计算
var pageItems = owner.getPageItems();
if(row > 0 && row <= #pageItems){
return row, pageItems[row];
}
return 0, null;
};
};
winform.afterDpiChanged = function(scaleX,scaleY,origScaleX,origScaleY){
taskList.rowHeight = 30 * scaleY;
taskList.headerHeight = 25 * scaleY;
}
var updatePageText = function(){
winform.txtPage.text = string.format("%d/%d", taskList.currentPage, taskList.getTotalPages());
};
// 翻页按钮样式
winform.btnPrev.skin({
background = { hover=0x20000000; active=0x30000000 };
color = { default=0xFF3C3C3C; hover=0xFF0078D4; active=0xFF005A9E; disabled=0xFFAAAAAA }
});
winform.btnNext.skin({
background = { hover=0x20000000; active=0x30000000 };
color = { default=0xFF3C3C3C; hover=0xFF0078D4; active=0xFF005A9E; disabled=0xFFAAAAAA }
});
winform.btnPrev.oncommand = function(){
if(taskList.currentPage > 1){
taskList.currentPage--;
taskList.selectedIndex = 0;
updatePageText();
winform.plusList.redraw();
}
};
winform.btnNext.oncommand = function(){
if(taskList.currentPage < taskList.getTotalPages()){
taskList.currentPage++;
taskList.selectedIndex = 0;
updatePageText();
winform.plusList.redraw();
}
};
winform.plusList.onDrawBackground = function(graphics, rc){
var brush = gdip.solidBrush(0xFFFFFFFF);
graphics.fillRectangle(brush, rc);
brush.delete();
var pen = gdip.pen(0xFFE0E0E0, 1);
graphics.drawRectangle(pen, rc.left, rc.top, rc.right - rc.left - 1, rc.bottom - rc.top - 1);
pen.delete();
};
winform.plusList.onDrawContent = function(graphics, rc){
graphics.smoothingMode = 4;
var scaleX, scaleY, dpiScaleX, dpiScaleY = winform.getScale();
var rcWidth = rc.right - rc.left;
var rcHeight = rc.bottom - rc.top;
var colWidths = {};
var totalRatio = 0;
for(i,col in taskList.columns){
totalRatio += col[2];
}
var availWidth = rcWidth - 20;
for(i,col in taskList.columns){
colWidths[i] = availWidth * col[2] / totalRatio;
}
// 创建字体
var fontFamily = gdip.family("Tahoma");//注意 XP 以后的系统 GDI+ 会自动将 Tahoma 修改为 "Segoe UI";
var headerFont = fontFamily.createFont(10, 0, 3/*_UnitPoint*/);
var itemFont = fontFamily.createFont(9, 0, 3/*_UnitPoint*/);
var smallFont = fontFamily.createFont(8, 0, 3/*_UnitPoint*/); // ★ 进度条内使用更小字体
var strFormat = gdip.stringformat();
strFormat.trimming = 3/*_StringTrimmingEllipsisCharacter*/;
// 绘制表头背景
var headerBrush = gdip.solidBrush(0xFFF8F8F8);
graphics.fillRectangle(headerBrush, 1, 1, rcWidth - 2, taskList.headerHeight - 1);
headerBrush.delete();
var headerPen = gdip.pen(0xFFE0E0E0, 1);
graphics.drawLine(headerPen, 0, taskList.headerHeight, rcWidth, taskList.headerHeight);
headerPen.delete();
// 绘制表头文字
var headerTextBrush = gdip.solidBrush(0xFF555555);
var xPos = 10;
for(i,col in taskList.columns){
var textRc = ::RECTF(xPos, 0, colWidths[i], taskList.headerHeight);
strFormat.lineAlign = 1/*_StringAlignmentCenter*/;
graphics.drawString(col[1], headerFont, textRc, strFormat, headerTextBrush);
xPos += colWidths[i];
}
headerTextBrush.delete();
var pageItems, startIdx = taskList.getPageItems();
var itemTextBrush = gdip.solidBrush(0xFF333333);
var grayTextBrush = gdip.solidBrush(0xFF888888);
// ★ 计算文本行高度(用于垂直居中)
var textHeight = 18 * dpiScaleY;
for(i, item in pageItems){
var globalIdx = startIdx + i - 1;
var yPos = taskList.headerHeight + (i - 1) * taskList.rowHeight;
if(globalIdx == taskList.selectedIndex){
var selBrush = gdip.solidBrush(0xFFE3F2FD);
graphics.fillRectangle(selBrush, 1, yPos, rcWidth - 2, taskList.rowHeight);
selBrush.delete();
}
elseif(globalIdx == taskList.hoverIndex){
var hoverBrush = gdip.solidBrush(0xFFFAFAFA);
graphics.fillRectangle(hoverBrush, 1, yPos, rcWidth - 2, taskList.rowHeight);
hoverBrush.delete();
}
var rowPen = gdip.pen(0xFFF0F0F0, 1);
graphics.drawLine(rowPen, 10, yPos + taskList.rowHeight, rcWidth - 10, yPos + taskList.rowHeight);
rowPen.delete();
xPos = 10;
// ★ 文本垂直居中
var textY = yPos + (taskList.rowHeight - textHeight) / 2;
// 第1列:文件名
var nameRc = ::RECTF(xPos, textY, colWidths[1] - 5, textHeight);
strFormat.lineAlign = 1;
strFormat.align = 0;
graphics.drawString(item.name, itemFont, nameRc, strFormat, itemTextBrush);
xPos += colWidths[1];
// 第2列:速度
var speedRc = ::RECTF(xPos, textY, colWidths[2] - 5, textHeight);
graphics.drawString(item.speed, itemFont, speedRc, strFormat, grayTextBrush);
xPos += colWidths[2];
// 第3列:已下载
var dlText = item.downloaded;
if(#item.total) dlText = item.downloaded + "/" + item.total;
var dlRc = ::RECTF(xPos, textY, colWidths[3] - 5, textHeight);
graphics.drawString(dlText, itemFont, dlRc, strFormat, grayTextBrush);
xPos += colWidths[3];
// 第4列:状态/进度条
var statusWidth = colWidths[4] - 10;
var progressHeight = 16 * dpiScaleY;
var progressY = yPos + (taskList.rowHeight - progressHeight) / 2;
if(item.isComplete){
var completeBrush = gdip.solidBrush(0xFF4CAF50);
var completeRc = ::RECTF(xPos, textY, statusWidth, textHeight);
graphics.drawString("✓ 已完成", itemFont, completeRc, strFormat, completeBrush);
completeBrush.delete();
}
elseif(item.isError){
var errorBrush = gdip.solidBrush(0xFFE53935);
var errorRc = ::RECTF(xPos, textY, statusWidth, textHeight);
graphics.drawString(item.status, itemFont, errorRc, strFormat, errorBrush);
errorBrush.delete();
}
elseif(item.percent > 0 || item.status == "下载中"){
var barRadius = 4; // ★ 略微增加圆角
// 进度条背景
var bgPath = gdip.path();
bgPath.addRoundRect(::RECTF(xPos, progressY, statusWidth, progressHeight), barRadius);
var bgBrush = gdip.solidBrush(0xFFE8E8E8);
graphics.fillPath(bgBrush, bgPath);
bgBrush.delete();
bgPath.delete();
// 进度条前景
if(item.percent > 0){
var fgWidth = statusWidth * item.percent / 100;
if(fgWidth > barRadius * 2){
var fgPath = gdip.path();
fgPath.addRoundRect(::RECTF(xPos, progressY, fgWidth, progressHeight), barRadius);
var p1 = ::POINTF(xPos, progressY);
var p2 = ::POINTF(xPos + statusWidth, progressY);
var fgBrush = gdip.lineBrush(p1, p2, 0xFF42A5F5, 0xFF1E88E5, 0);
graphics.fillPath(fgBrush, fgPath);
fgBrush.delete();
fgPath.delete();
}
}
// ★ 进度文字:使用更小字体,确保不会被截断
var percentText = string.format("%.1f%%", item.percent);
var percentRc = ::RECTF(xPos, progressY, statusWidth, progressHeight);
strFormat.align = 1/*_StringAlignmentCenter*/;
strFormat.lineAlign = 1/*_StringAlignmentCenter*/;
var percentBrush = gdip.solidBrush(item.percent > 50 ? 0xFFFFFFFF : 0xFF555555);
graphics.drawString(percentText, smallFont, percentRc, strFormat, percentBrush); // ★ 使用 smallFont
percentBrush.delete();
strFormat.align = 0;
}
else {
var statusRc = ::RECTF(xPos, textY, statusWidth, textHeight);
graphics.drawString(item.status, itemFont, statusRc, strFormat, grayTextBrush);
}
xPos += colWidths[4];
// 第5列:连接数
var connRc = ::RECTF(xPos, textY, colWidths[5] - 10, textHeight);
graphics.drawString(tostring(item.connections), itemFont, connRc, strFormat, grayTextBrush);
}
if(#pageItems == 0){
var emptyBrush = gdip.solidBrush(0xFFAAAAAA);
var emptyRc = ::RECTF(0, taskList.headerHeight, rcWidth, rcHeight - taskList.headerHeight);
strFormat.align = 1;
strFormat.lineAlign = 1;
graphics.drawString("暂无下载任务", itemFont, emptyRc, strFormat, emptyBrush);
emptyBrush.delete();
}
itemTextBrush.delete();
grayTextBrush.delete();
strFormat.delete();
headerFont.delete();
itemFont.delete();
smallFont.delete();
fontFamily.delete();
};
winform.plusList.onMouseMove = function(wParam, lParam){
var x, y = win.getMessagePos(lParam);
var rc = owner.getClientRect();
var newHover = taskList.hitTest(x, y, rc);
if(newHover != taskList.hoverIndex){
taskList.hoverIndex = newHover;
owner.redraw();
}
};
winform.plusList.onMouseLeave = function(){
if(taskList.hoverIndex != 0){
taskList.hoverIndex = 0;
owner.redraw();
}
};
winform.plusList.onMouseUp = function(wParam, lParam){
var x, y = win.getMessagePos(lParam);
var rc = owner.getClientRect();
var clickIdx = taskList.hitTest(x, y, rc);
if(clickIdx > 0 && clickIdx != taskList.selectedIndex){
taskList.selectedIndex = clickIdx;
owner.redraw();
}
};
var btnAdd = winform.txtUrl.addCtrl(
cls="plus";
marginRight=0;marginBottom=2;width=24;
iconText = '\uF019';
iconStyle={
align="right";font=LOGFONT(h=-15;name='FontAwesome');padding={top=3}
}
);
btnAdd.skin({
color = {
default = 0xC0000000;
hover = 0xFFFF0000;
active = 0xFF00FF00;
};
});
winform.pie.setPieRange(0, 100);
winform.show(); //先显示窗口避免等待
import process.aria2;
var aria2 = process.aria2();
aria2.startServer(maxConcurrentDownloads = 10);// 启动 aria2 下载服务进程
var aria2Ui = {
addTaskInfo = function(gid, url, status){
if(!gid){
import bencode;
var name = bencode.magnet.getName(url);
taskList.addTask("error_" + time.tick(), name || "未知文件");
var item = taskList.items[#taskList.items];
item.status = "出错了:" + (status || "未知错误");
item.isError = true;
}
else {
var existItem = taskList.getItemByGid(gid);
if(!existItem){
var taskName = aria2.taskName(gid);
taskList.addTask(gid, taskName || "未知文件");
}
if(status){
taskList.updateTask(gid, {status = status});
}
}
updatePageText();
winform.plusList.redraw();
};
remove = function(gid){
taskList.removeByGid(gid);
updatePageText();
winform.plusList.redraw();
};
updateStatus = function(gid, status){
var item = taskList.getItemByGid(gid);
if(!item){
owner.addTaskInfo(gid);
}
taskList.updateTask(gid, {status = status});
winform.plusList.redraw();
};
updateProgress = function(gid, progress){
var item = taskList.getItemByGid(gid);
if(item && item.isRemoving) return;
if(!item){
owner.addTaskInfo(gid);
item = taskList.getItemByGid(gid);
}
if(progress){
var percent = 0;
progress.totalLength = tonumber(progress.totalLength);
if( progress.totalLength > 0){
percent = progress.completedLength / progress.totalLength * 100;
}
taskList.updateTask(gid, {
speed = math.size64(progress.downloadSpeed).format() + "/s";
downloaded = math.size64(progress.completedLength).format();
total = math.size64(progress.totalLength).format();
connections = progress.connections;
percent = percent;
status = "下载中";
});
}
else {
var info = aria2.tellStatus(gid, "totalLength");
taskList.updateTask(gid, {
speed = "";
total = math.size64(info.totalLength).format();
connections = "";
});
}
winform.plusList.redraw();
};
};
aria2.onDownloadStart = function(task){
aria2Ui.addTaskInfo(task.gid, , "开始下载");
};
aria2.onDownloadPause = function(task){
taskList.updateTask(task.gid, {status = "已暂停"; speed = ""});
winform.plusList.redraw();
};
aria2.onDownloadStop = function(task){
var item = taskList.getItemByGid(task.gid);
if(item && item.isRemoving){
// ★ 确认已移除,真正删除
taskList.removeByGid(task.gid);
}
else {
taskList.updateTask(task.gid, {status = "已停止"; speed = ""});
}
updatePageText();
winform.plusList.redraw();
};
aria2.onDownloadComplete = function(task){
if(aria2.taskIsTorrent(task.gid)){
aria2Ui.remove(task.gid);
}
else {
taskList.updateTask(task.gid, {
isComplete = true;
speed = "";
percent = 100;
});
aria2Ui.updateProgress(task.gid);
}
};
aria2.onDownloadError = function(task){
var errMsg = aria2.taskErrorMessage(task.gid);
taskList.updateTask(task.gid, {
status = errMsg || "下载出错";
isError = true;
speed = "";
});
winform.plusList.redraw();
};
winform.plusList.onContextMenu = function(x, y){
var cx, cy = win.toClient(owner.hwnd, x, y);
var rc = owner.getClientRect();
var clickIdx = taskList.hitTest(cx, cy, rc);
if(clickIdx <= 0) return;
taskList.selectedIndex = clickIdx;
owner.redraw();
var item = taskList.items[clickIdx];
if(!item) return;
var gid = item.gid;
var statusInfo = aria2.tellStatus(gid, "status", "belongsTo");
if(!statusInfo) return;
import win.ui.menu;
var popmenu = win.ui.popmenu(winform);
popmenu.add('移除', function(){
aria2.taskRemove(gid);
taskList.markRemoving(gid);
updatePageText();
winform.plusList.redraw();
});
if(statusInfo.status == "active"){
popmenu.add('暂停', function(){
aria2.taskPause(gid);
});
}
if(statusInfo.status == "paused"){
popmenu.add('开始下载', function(){
aria2.taskUnpause(gid);
});
}
var path = aria2.taskFilePath(gid);
if(#path){
popmenu.add('浏览文件', function(){
raw.explore(path, "/select");
});
}
popmenu.add('复制链接', function(){
var url = aria2.taskUrl(gid);
if(url){
import win.clip;
win.clip.write(url);
}
});
popmenu.popup(x, y, true);
};
btnAdd.oncommand = function(){
var url = winform.txtUrl.text;
if(!#url){
winform.msgboxErr("请输入下载地址或种子文件路径");
winform.txtUrl.setFocus();
return;
}
aria2.taskAdd(url, function(gid, err){
if(err){
aria2Ui.addTaskInfo(null, url, err[["message"]]);
}
else {
aria2Ui.addTaskInfo(gid, url, "添加成功");
}
});
};
winform.txtUrl.editBox.onOk = function(ctrl, alt, shift){
btnAdd.oncommand();
return true;
};
import com.interface.ITaskbarList3;
var taskbar = com.interface.ITaskbarList3.Create();
updateDownloadStatus = function(init){
aria2.tellActive(function(result, err){
if(result){
var totalLength, completedLength = math.size64(), math.size64();
for(k, v in result){
totalLength.add(v.totalLength);
completedLength.add(v.completedLength);
aria2Ui.updateProgress(v.gid, v);
}
var pos = 0;
if(tonumber(totalLength) > 0){
pos = (completedLength / totalLength) * 100;
}
winform.pie.progressPos = pos;
winform.pie.text = math.round(pos,pos<10?1:0)+"%";
taskbar.SetProgressValue(winform.hwnd, pos, 100);
}
}, "gid", "status", "connections", "downloadSpeed", "totalLength", "completedLength");
if(init){
aria2.tellWaiting(0, 20, function(result, err){
if(result){
for(i, v in result){
aria2Ui.updateProgress(v.gid, v);
}
}
}, "gid", "status", "connections", "downloadSpeed", "totalLength", "completedLength");
}
};
aria2.onError = function(errMsg, rpcErr){
};
aria2.ready(function(){
updateDownloadStatus(true);
winform.setInterval(updateDownloadStatus, 1500);
});
winform.onDestroy = function(){
aria2.stop();
};
win.loopMessage();
Markdown 格式