aardio 文档
aardio 范例: BT 下载 - 自绘卡片风格
BT 下载 -自绘 listview 风格
// BT 下载 - 自绘卡片风格
// BT 下载 -自绘 listview 风格: https://www.aardio.com/zh-cn/docs/examples/Network/Transfer/bt.draw.listview.html
import fonts.fontAwesome;
import win.ui;
/*DSG{{*/
var winform = win.form(text="BT 下载 - 自绘卡片风格";right=720;bottom=560;bgcolor=0xF0F0F0)
winform.add(
btnNext={cls="plus";text='\uF054';left=200;top=510;right=240;bottom=540;color=0x3C3C3C;db=1;dl=1;font=LOGFONT(h=-14;name='FontAwesome');notify=1;z=6};
btnPrev={cls="plus";text='\uF053';left=90;top=510;right=130;bottom=540;color=0x3C3C3C;db=1;dl=1;font=LOGFONT(h=-14;name='FontAwesome');notify=1;z=4};
pie={cls="plus";text="0%";left=20;top=505;right=68;bottom=553;bgcolor=0x4A68E8;color=0xFFFFFF;db=1;dl=1;forecolor=0x4AD4A9;z=2};
plusList={cls="plus";left=0;top=0;right=720;bottom=495;db=1;dl=1;dr=1;dt=1;notify=1;z=1};
txtPage={cls="plus";text="1/1";left=135;top=510;right=195;bottom=540;db=1;dl=1;z=5};
txtUrl={cls="plus";left=258;top=517;right=700;bottom=543;align="left";border={bottom=1;color=0x909090};db=1;dl=1;dr=1;editable=1;notify=1;paddingTop=5;textPadding={left=4;right=28;bottom=1};z=3}
)
/*}}*/
// ========== 下载任务列表数据管理 ==========
var taskList = {
items = {};
gidToIndex = {};
selectedIndex = 0;
hoverIndex = 0;
currentPage = 1;
pageSize = 4;
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 = 0;
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 = "";
}
};
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, rcHeight, dpiScaleY){
var cardMargin = 10 * dpiScaleY;
var availHeight = rcHeight - cardMargin;
var cardHeight = (availHeight / owner.pageSize) - cardMargin;
var pageItems = owner.getPageItems();
for(i=1; #pageItems){
var yTop = cardMargin + (i - 1) * (cardHeight + cardMargin);
var yBottom = yTop + cardHeight;
if(y >= yTop && y <= yBottom){
return i, pageItems[i];
}
}
return 0, null;
};
};
var updatePageText = function(){
winform.txtPage.text = string.format("%d/%d", taskList.currentPage, taskList.getTotalPages());
};
// 翻页按钮
winform.btnPrev.skin({
background = { hover=0x15000000; active=0x25000000 };
color = { default=0xFF505050; hover=0xFF1976D2; active=0xFF1565C0; disabled=0xFFBBBBBB }
});
winform.btnNext.skin({
background = { hover=0x15000000; active=0x25000000 };
color = { default=0xFF505050; hover=0xFF1976D2; active=0xFF1565C0; disabled=0xFFBBBBBB }
});
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,backgroundColor,foregroundColor){
var brush = gdip.solidBrush(0xFFF5F5F5);
graphics.fillRectangle(brush, rc);
brush.delete();
};
winform.plusList.onDrawForeground = function(graphics,rc,foregroundRect,foregroundColor,color,font){
graphics.smoothingMode = 4;
graphics.textRenderingHint = 5;
var scaleX, scaleY, dpiScaleX, dpiScaleY = winform.getScale();
var rcWidth = rc.right - rc.left;
var rcHeight = rc.bottom - rc.top;
// 动态计算卡片高度,充分利用空间
var cardMargin = 10 * dpiScaleY;
var cardPadding = 14 * dpiScaleX;
var availHeight = rcHeight - cardMargin;
var cardHeight = (availHeight / taskList.pageSize) - cardMargin;
var cardRadius = 8;
var pageItems, startIdx = taskList.getPageItems();
// 创建字体
var fontFamily = gdip.family("Tahoma");//注意 XP 以后的系统 GDI+ 会自动将 Tahoma 修改为 "Segoe UI";
var titleFont = fontFamily.createFont(10, 1, 3);
var infoFont = fontFamily.createFont(9, 0, 3);
var percentFont = fontFamily.createFont(11, 1, 3);
var iconFamily = gdip.family("FontAwesome");
var iconFont = iconFamily.createFont(16, 0, 3);
var strFormat = gdip.stringFormat();
strFormat.trimming = 3;
strFormat.flags = 0x1000/*_StringFormatFlagsNoWrap*/;
for(i, item in pageItems){
var globalIdx = startIdx + i - 1;
var yPos = cardMargin + (i - 1) * (cardHeight + cardMargin);
var cardLeft = cardMargin;
var cardWidth = rcWidth - cardMargin * 2;
// 绘制卡片阴影
var shadowPath = gdip.path();
shadowPath.addRoundRect(::RECTF(cardLeft + 2, yPos + 3, cardWidth, cardHeight), cardRadius);
var shadowBrush = gdip.solidBrush(0x15000000);
graphics.fillPath(shadowBrush, shadowPath);
shadowBrush.delete();
shadowPath.delete();
// 卡片背景
var cardPath = gdip.path();
cardPath.addRoundRect(::RECTF(cardLeft, yPos, cardWidth, cardHeight), cardRadius);
var cardColor = 0xFFFFFFFF;
if(globalIdx == taskList.selectedIndex){
cardColor = 0xFFE8F4FD;
} elseif(globalIdx == taskList.hoverIndex){
cardColor = 0xFFFCFCFC;
}
var cardBrush = gdip.solidBrush(cardColor);
graphics.fillPath(cardBrush, cardPath);
cardBrush.delete();
cardPath.delete();
// ===== 状态配色 =====
var statusIcon, accentColor, statusText;
if(item.isComplete){
statusIcon = '\uF058';
accentColor = 0xFF43A047;
statusText = "已完成";
} elseif(item.isError){
statusIcon = '\uF057';
accentColor = 0xFFE53935;
statusText = item.status;
} elseif(item.isRemoving){
statusIcon = '\uF1DA';
accentColor = 0xFF9E9E9E;
statusText = "移除中";
} elseif(item.status == "下载中"){
statusIcon = '\uF019';
accentColor = 0xFF1E88E5;
statusText = null;
} elseif(item.status == "已暂停"){
statusIcon = '\uF04C';
accentColor = 0xFFFF9800;
statusText = "已暂停";
} else {
statusIcon = '\uF017';
accentColor = 0xFF9E9E9E;
statusText = item.status;
}
// 布局计算
var iconSize = 36 * dpiScaleX;
var contentLeft = cardLeft + cardPadding;
var contentTop = yPos + cardPadding;
var contentHeight = cardHeight - cardPadding * 2;
var contentWidth = cardWidth - cardPadding * 2;
// 左侧图标区域
var iconX = contentLeft;
var iconY = contentTop + (contentHeight - iconSize) / 2;
// 绘制图标背景圆
var iconBgPath = gdip.path();
iconBgPath.addEllipse(::RECTF(iconX, iconY, iconSize, iconSize));
var iconBgBrush = gdip.solidBrush((accentColor & 0x00FFFFFF) | 0x18000000);
graphics.fillPath(iconBgBrush, iconBgPath);
iconBgBrush.delete();
iconBgPath.delete();
// 绘制图标
var iconBrush = gdip.solidBrush(accentColor);
var iconRc = ::RECTF(iconX, iconY, iconSize, iconSize);
strFormat.align = 1;
strFormat.lineAlign = 1;
graphics.drawString(statusIcon, iconFont, iconRc, strFormat, iconBrush);
iconBrush.delete();
// 右侧信息区域
var infoLeft = iconX + iconSize + 12 * dpiScaleX;
var infoWidth = contentWidth - iconSize - 12 * dpiScaleX;
// 三行布局:标题、进度条+百分比、详情
var lineHeight = contentHeight / 3;
// ===== 第一行:文件名 =====
var titleY = contentTop;
var titleBrush = gdip.solidBrush(0xFF333333);
var titleRc = ::RECTF(infoLeft, titleY, infoWidth, lineHeight);
strFormat.align = 0;
strFormat.lineAlign = 1;
graphics.drawString(item.name, titleFont, titleRc, strFormat, titleBrush);
titleBrush.delete();
// ===== 第二行:进度条 + 百分比 =====
var progressY = contentTop + lineHeight;
var percentWidth = 45 * dpiScaleX;
var progressWidth = infoWidth - percentWidth - 8 * dpiScaleX;
var progressHeight = 5 * dpiScaleY;
var progressTop = progressY + (lineHeight - progressHeight) / 2;
// 进度条背景
var bgPath = gdip.path();
bgPath.addRoundRect(::RECTF(infoLeft, progressTop, progressWidth, progressHeight), progressHeight / 2);
var bgBrush = gdip.solidBrush(0xFFE0E0E0);
graphics.fillPath(bgBrush, bgPath);
bgBrush.delete();
bgPath.delete();
// 进度条前景
if(item.percent > 0){
var fgWidth = math.max(progressHeight, progressWidth * item.percent / 100);
var fgPath = gdip.path();
fgPath.addRoundRect(::RECTF(infoLeft, progressTop, fgWidth, progressHeight), progressHeight / 2);
var fgBrush = gdip.solidBrush(accentColor);
graphics.fillPath(fgBrush, fgPath);
fgBrush.delete();
fgPath.delete();
}
// 百分比文字(紧跟进度条右侧)
var percentX = infoLeft + progressWidth + 8 * dpiScaleX;
var percentBrush = gdip.solidBrush(accentColor);
var percentRc = ::RECTF(percentX, progressY, percentWidth, lineHeight);
strFormat.align = 0;
strFormat.lineAlign = 1;
var percentText = item.isComplete ? "完成" : (string.format("%.0f", item.percent) ++ "%");
graphics.drawString(percentText, percentFont, percentRc, strFormat, percentBrush);
percentBrush.delete();
// ===== 第三行:详情信息 =====
var infoY = contentTop + lineHeight * 2;
var grayBrush = gdip.solidBrush(0xFF888888);
strFormat.align = 0;
strFormat.lineAlign = 1;
var infoText = "";
if(statusText){
infoText = statusText;
if(#item.total) infoText = statusText ++ " · " ++ item.total;
} else {
if(#item.speed) infoText = "↓ " ++ item.speed;
if(#item.downloaded && #item.total){
if(#infoText) infoText = infoText ++ " · ";
infoText = infoText ++ item.downloaded ++ " / " ++ item.total;
}
if(item.connections > 0){
if(#infoText) infoText = infoText ++ " · ";
infoText = infoText ++ tostring(item.connections) ++ " 连接";
}
}
var infoRc = ::RECTF(infoLeft, infoY, infoWidth, lineHeight);
graphics.drawString(infoText, infoFont, infoRc, strFormat, grayBrush);
grayBrush.delete();
}
// 空状态
if(#pageItems == 0){
var emptyIconFont = iconFamily.createFont(32, 0, 3);
strFormat.align = 1;
strFormat.lineAlign = 1;
var emptyBrush = gdip.solidBrush(0xFFCCCCCC);
var iconRc = ::RECTF(0, rcHeight/2 - 40*dpiScaleY, rcWidth, 40*dpiScaleY);
graphics.drawString('\uF0ED', emptyIconFont, iconRc, strFormat, emptyBrush);
emptyBrush.delete();
var textBrush = gdip.solidBrush(0xFF999999);
var textRc = ::RECTF(0, rcHeight/2 + 5*dpiScaleY, rcWidth, 25*dpiScaleY);
graphics.drawString("输入下载链接开始下载", infoFont, textRc, strFormat, textBrush);
textBrush.delete();
emptyIconFont.delete();
}
strFormat.delete();
titleFont.delete();
infoFont.delete();
percentFont.delete();
iconFont.delete();
fontFamily.delete();
iconFamily.delete();
};
// ========== 鼠标交互 ==========
winform.plusList.onMouseMove = function(wParam, lParam){
var x, y = win.getMessagePos(lParam);
var scaleX, scaleY, dpiScaleX, dpiScaleY = winform.getScale();
var rc = owner.getClientRect();
var newHover = taskList.hitTest(x, y, rc.bottom - rc.top, dpiScaleY);
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 scaleX, scaleY, dpiScaleX, dpiScaleY = winform.getScale();
var rc = owner.getClientRect();
var clickIdx = taskList.hitTest(x, y, rc.bottom - rc.top, dpiScaleY);
if(clickIdx > 0 && clickIdx != taskList.selectedIndex){
taskList.selectedIndex = clickIdx;
owner.redraw();
}
};
// ========== 添加下载按钮 ==========
var btnAdd = winform.txtUrl.addCtrl(
cls="plus";
marginRight=0;marginBottom=2;width=26;
iconText = '\uF019';
iconStyle={
align="right";font=LOGFONT(h=-15;name='FontAwesome');padding={top=3}
}
);
btnAdd.skin({
color = {
default = 0xB0000000;
hover = 0xFF1976D2;
active = 0xFF1565C0;
};
});
winform.pie.setPieRange(0, 100);
winform.show();
// ========== aria2 集成 ==========
import process.aria2;
var aria2 = process.aria2();
aria2.startServer(maxConcurrentDownloads = 10);
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,tempName = aria2.taskName(gid);
taskList.addTask(gid, taskName || "未知文件");
if(tempName){
//获取真正的文件名
winform.setInterval(
function(){
taskName,tempName = aria2.taskName(gid)
if(!tempName){
taskList.updateTask(gid, {name = taskName});
return 0;//删除定时器
}
},2000
)
}
}
if(status){
taskList.updateTask(gid, {status = status});
}
}
updatePageText();
winform.plusList.redraw();
};
remove = function(gid){
taskList.removeByGid(gid);
updatePageText();
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 = tonumber(progress.connections) || 0;
percent = percent;
status = "下载中";
});
}
else {
var info = aria2.tellStatus(gid, "totalLength");
taskList.updateTask(gid, {
speed = "";
total = math.size64(info.totalLength).format();
connections = 0;
});
}
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 scaleX, scaleY, dpiScaleX, dpiScaleY = winform.getScale();
var rc = owner.getClientRect();
var clickIdx = taskList.hitTest(cx, cy, rc.bottom - rc.top, dpiScaleY);
if(clickIdx <= 0) return;
taskList.selectedIndex = clickIdx;
owner.redraw();
var pageItems = taskList.getPageItems();
var item = pageItems[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.text = "";
};
winform.txtUrl.editBox.onOk = function(){
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.ready(function(){
updateDownloadStatus(true);
winform.setInterval(updateDownloadStatus, 1500);
});
winform.onDestroy = function(){
aria2.stop();
};
win.loopMessage();
return winform,aria2;
Markdown 格式