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 格式