lib-images.js
· 58 KiB · JavaScript
Неформатований
'use strict';
class LibImages {
constructor() {
this.accessed = 0;
this.asyncBypass = 0;
this.blockWidth = 150;
this.cachePath = grSet.customLibraryDir ? $(`${grCfg.customLibraryDir}library-tree-cache\\`, undefined, true) : `${fb.ProfilePath}cache\\library\\library-tree-cache\\`;
this.cellWidth = 200;
this.column = 0;
this.columnWidth = 150;
this.database = this.newDatabase();
this.end = 1;
this.groupField = '';
this.items = [];
this.overlayHeight = 0;
this.panel = {};
this.preLoadItems = [];
this.rootNo = 4;
this.saveSize = 250;
this.shadow = null;
this.shadowStub = null;
this.start = 0;
this.toSave = [];
this.zooming = false;
this.bor = {
bot: 6,
cov: 16,
pad: 10,
side: 2
};
this.box = {
h: 100,
w: 100
};
this.cache = {};
this.cachesize = {
min: 20
};
this.stub = {
noImg: null,
root: null
};
this.style = {
image: 0,
rootComposite: libSet.rootNode && libSet.curRootImg == 3,
vertical: !libSet.albumArtFlowMode ? true : lib.ui.h - lib.panel.search.h > lib.ui.w - lib.ui.sbar.w,
y: 25
};
this.im = {
offset: 0,
y: 0,
w: 120
};
this.interval = {
cache: 1,
preLoad: 7
};
this.labels = { statistics: libSet.itemShowStatistics ? 1 : 0 }
this.letter = {
albumArtYearAuto: libSet.albumArtYearAuto,
no: 1,
show: libSet.albumArtLetter,
w: 0
};
this.mask = {
fade: null,
circular: null
};
this.row = {
h: 80
};
this.text = {
x: 0,
y1: 0,
y2: 0,
h: 20,
w: 20
};
this.timer = {
load: null,
preLoad: null,
save: null
};
this.drawDebounce = $Lib.debounce(() => {
lib.panel.treePaint();
}, 500);
this.loadThrottle = $Lib.throttle(() => {
if (!lib.panel.imgView) return;
this.getImages();
}, 40);
this.rootDebounce = $Lib.debounce(() => {
this.checkRootImg();
}, 250, {
leading: true,
trailing: true
});
this.sizeDebounce = $Lib.debounce(() => {
if (!lib.panel.imgView) return;
this.clearCache();
this.metrics();
if (lib.sbar.scroll > lib.sbar.max_scroll) lib.sbar.checkScroll(lib.sbar.max_scroll);
}, 100);
this.setRoot();
this.setNoArtist();
this.setNoCover();
}
// * METHODS * //
async get_album_art_async(handle, art_id, key, ix) {
const result = await utils.GetAlbumArtAsyncV2(0, handle, art_id, false);
const o = this.cache[key];
const saveName = `${libMD5.hashStr(result.path)}.jpg`;
if (o && o.img == 'called') this.cacheIt(result.image, key, ix, saveName);
}
async load_image_async(key, image_path, ix, rawCache) {
const image = Date.now() - this.asyncBypass > 5000 ? await gdi.LoadImageAsyncV2(0, image_path) : gdi.Image(image_path);
const o = this.cache[key];
if (o && o.img == 'called') !rawCache ? this.cacheIt(image, key, ix) : this.cacheItPreLoad(image, key, ix);
}
cacheIt(image, key, ix, saveName) {
try {
if (!image) {
if (this.style.rootComposite && ix < this.rootNo) this.rootDebounce();
if (this.albumArtDiskCache && !this.database[key]) {
this.toSave.unshift({
key,
image: null,
folder: this.cacheFolder,
saveName: 'noAlbumArt',
setKeyOnly: true
});
}
}
if (image) {
if (this.albumArtDiskCache && saveName) {
if (!this.database[key] && $Lib.file(this.cacheFolder + saveName)) {
this.toSave.unshift({
key,
image: null,
folder: this.cacheFolder,
saveName,
setKeyOnly: true
});
}
if (!this.database[key] || !$Lib.file(this.cacheFolder + saveName)) {
image = this.format(image, 1, 'default', this.saveSize, this.saveSize, false, 'save');
this.toSave.unshift({
key,
image: image.Clone(0, 0, image.Width, image.Height),
folder: this.cacheFolder,
saveName,
setKeyOnly: false
});
}
}
this.checkCache();
this.format(image, libSet.artId, ['default', 'crop', 'circular'][this.style.image], this.im.w, this.im.w, libSet.albumArtLabelType == 3, 'display', ix, key);
if (this.style.rootComposite && ix < this.rootNo) this.rootDebounce();
}
if (!this.timer.save && this.toSave.length) {
this.timer.save = setInterval(() => {
const ln = this.toSave.length;
if (ln) {
if (this.toSave[ln - 1].setKeyOnly) {
this.database[this.toSave[ln - 1].key] = this.toSave[ln - 1].saveName;
$Lib.save(`${this.toSave[ln - 1].folder}database.dat`, JSON.stringify(this.database, null, 3), true);
} else {
const saved = this.toSave[ln - 1].image.SaveAs(this.toSave[ln - 1].folder + this.toSave[ln - 1].saveName, 'image/jpeg');
if (saved) {
this.database[this.toSave[ln - 1].key] = this.toSave[ln - 1].saveName;
$Lib.save(`${this.toSave[ln - 1].folder}database.dat`, JSON.stringify(this.database, null, 3), true);
}
}
this.toSave.pop();
}
if (!this.toSave.length) {
clearInterval(this.timer.save);
this.timer.save = null;
}
}, 1000);
}
}
catch (e) {
$Lib.trace(`unable to load thumbnail image: ${key}`);
}
this.drawDebounce();
}
cacheItPreLoad(image, key, ix) {
try {
if (image) {
this.checkCache();
this.format(image, libSet.artId, ['default', 'crop', 'circular'][this.style.image], this.im.w, this.im.w, libSet.albumArtLabelType == 3, 'displayPreload', ix, key);
}
if (this.style.rootComposite && ix < this.rootNo) this.rootDebounce();
} catch (e) {
$Lib.trace(`unable to load thumbnail image: ${key}`);
}
lib.panel.treePaint();
}
checkCache() {
if (!this.memoryLimit()) return;
const ln = this.columns * lib.panel.rows * 3;
if (this.toSave.length > ln) this.toSave.length = ln;
this.preLoadItems = [];
clearInterval(this.timer.preLoad);
this.timer.preLoad = null;
this.items = [];
clearInterval(this.timer.load);
this.timer.load = null;
let keys = Object.keys(this.cache);
const cacheLength = keys.length;
if (lib.pop.tree.length) {
const o = this.cache[lib.pop.tree[0].key];
if (o) o.accessed = Infinity;
}
this.cache = this.sortCache(this.cache, 'accessed');
keys = Object.keys(this.cache);
const numToRemove = Math.round((cacheLength - this.cachesize.min) / 2);
if (numToRemove > 0) {
for (let i = 0; i < numToRemove; i++) this.trimCache(keys[i]);
}
}
checkNowPlaying(item) {
if (!libSet.highLightNowplaying) return false;
return !item.root && lib.pop.inRange(lib.pop.nowp, item.item);
}
checkRootImg() {
const key = lib.pop.tree.length ? lib.pop.tree[0].key : null;
if (!key) return;
let o = this.cache[key];
const imgsAvailable = Math.min(Math.round((this.panel.h + this.row.h) / this.row.h) * this.columns, lib.pop.tree.length) - 1;
const n = Math.max(Math.min(Math.floor(Math.sqrt(imgsAvailable)), Infinity), 2); // auto set collage size: limited by no imgs available (per screen): reduce by changing infinity
const cells = Math.pow(n, 2);
this.rootNo = n * n + 1;
if (!o) {
this.cache[key] = {
img: 'called',
accessed: ++this.accessed
};
}
o = this.cache[key];
o.img = $Lib.gr(this.cellWidth * n, this.cellWidth * n, true, g => this.createCollage(g, this.cellWidth, this.cellWidth, n, n, cells));
if (o.img) {
if (this.style.image == 2) this.circularMask(o.img, o.img.Width, o.img.Height);
o.img = o.img.Resize(this.im.w, this.im.w, 7);
if (libSet.albumArtLabelType == 3) this.fadeMask(o.img, o.img.Width, o.img.Height);
}
lib.panel.treePaint();
}
checkTooltip(gr, item, x, y1, y2, y3, w, tt1, tt2, tt3, font1, font2, font3) {
if (lib.panel.colMarker) {
if (tt1) tt1 = tt1.replace(Regex.LibMarkerColor, '');
if (tt2) tt2 = tt2.replace(Regex.LibMarkerColor, '');
}
let text = tt1 || '';
if (tt2 && (lib.panel.lines == 2 || lib.panel.lines == 1 && this.labels.statistics)) text += `\n${tt2}`;
if (tt3 && this.labels.statistics) text += `\n${tt3}`;
item.tt = {
text,
x,
y1,
y2,
y3,
w,
1: tt1 ? gr.CalcTextWidth(tt1, font1) > w ? tt1 : false : false,
2: tt2 ? gr.CalcTextWidth(tt2, font2) > w ? tt2 : false : false,
3: tt3 ? gr.CalcTextWidth(tt3, font3) > w ? tt3 : false : false
};
}
circularMask(image, w, h) {
image.ApplyMask(this.mask.circular.Resize(w, h));
}
clearCache() {
this.accessed = 0;
this.cache = {};
this.cachesize = {
min: 20
};
this.items = [];
}
createCollage(g, cellWidth, cellHeight, rows, columns, cells) {
let x = 0;
let y = 0;
for (let row = 0; row < rows; row++) {
for (let column = 0; column < columns; column++) {
const idx = column + row * columns + 1;
if (idx <= cells) {
let img = lib.pop.tree.length && lib.pop.tree[idx] ? this.getRootImg(lib.pop.tree[idx].key) : null;
if (!img) img = this.stub.noImg;
if (img) {
let cx = 0;
let cy = 0;
let cw = img.Width;
let ch = img.Height;
if (libSet.albumArtLabelType == 3) {
if (this.style.image != 2) {
ch -= this.overlayHeight;
} else {
cx = cw * 0.1;
cy = ch * 0.1;
cw *= 0.8;
ch = (ch - this.overlayHeight) * 0.8;
}
} else if (this.style.image == 2) {
cx = cw * 0.1;
cy = ch * 0.1;
cw *= 0.8;
ch *= 0.8;
}
img = img.Clone(cx, cy, cw, ch);
img = this.format(img, libSet.artId, 'crop', this.cellWidth, this.cellWidth, false, 'root');
g.DrawImage(img, x, y, img.Width, img.Height, 0, 0, img.Width, img.Height);
}
x += cellWidth;
}
}
x = 0;
y += cellHeight;
}
x = 0;
y = 0;
for (let column = 0; column < columns; column++) {
x += cellWidth;
if (this.style.image != 2) g.DrawLine(x, 0, x, cellWidth * columns, lib.ui.l.w, lib.ui.col.rootBlend);
}
x = 0;
y = 0;
for (let row = 0; row < rows; row++) {
y += cellHeight;
if (this.style.image != 2) g.DrawLine(x, y, cellWidth * columns, y, lib.ui.l.w, lib.ui.col.rootBlend);
}
if (this.style.image != 2) g.DrawRect(0, 0, cellWidth * columns - 1, cellWidth * columns - 1, 1, lib.ui.col.rootBlend);
}
createImages() {
this.mask.circular = $Lib.gr(500, 500, true, g => {
g.FillSolidRect(0, 0, 500, 500, RGB(255, 255, 255));
g.SetSmoothingMode(2);
g.FillEllipse(1, 1, 498, 498, RGBA(0, 0, 0, 255));
g.SetSmoothingMode(0);
});
this.mask.fade = $Lib.gr(500, 500, true, g => {
g.FillSolidRect(0, 0, 500, 500, RGB(220, 220, 220));
});
}
draw(gr) {
if (!lib.panel.imgView) return;
let box_x;
let box_y;
let iw;
let ih;
this.getItemsToDraw();
this.column = 0;
for (let i = this.start; i < this.end; i++) {
const row = this.style.vertical ? Math.floor(i / this.columns) : 0;
box_x = this.style.vertical ? Math.floor(lib.ui.x + this.panel.x + this.column * this.columnWidth + this.bor.side) : Math.floor(lib.ui.x + this.panel.x + i * this.columnWidth + this.bor.side - lib.sbar.delta + (libSet.albumArtFlowMode ? SCALE(18) : 0));
box_y = this.style.vertical ? Math.floor(lib.ui.y + this.panel.y + row * this.row.h - lib.sbar.delta) : lib.ui.y + this.style.y;
if (box_y >= 0 - this.row.h && box_y < this.panel.y + this.panel.h) {
const item = lib.pop.tree[i];
lib.pop.getItemCount(item);
const grp = item.grp;
const lot = item.lot;
const statistics = this.labels.statistics ? (!item.root && this.labels.counts ? item.count + (item.count && item._statistics ? ' | ' : '') : '') + item._statistics : '';
const cur_img = !this.zooming ? this.getImg(item.key) : null;
const nowp = this.checkNowPlaying(item);
// const grpCol = this.getGrpCol(item, nowp, lib.pop.highlight.text && i == lib.pop.m.i);
// const lotCol = this.getLotCol(item, nowp, lib.pop.highlight.text && i == lib.pop.m.i);
const coversRightBottom = ['coversLabelsRight', 'coversLabelsBottom'].includes(grSet.libraryDesign) || libSet.albumArtLabelType === 2;
const updatedNowpBg = pl.col.header_nowplaying_bg !== null; // * Wait until nowplaying bg has a new color to prevent flashing
const colNowPlaying = grSet.libraryBgImg ? RGBtoRGBA(lib.ui.col.nowPlayingBg, grSet.libraryBgRowOpacity) : lib.ui.col.nowPlayingBg;
const colRowStripes = grSet.libraryBgImg ? RGBtoRGBA(lib.ui.col.rowStripes, grSet.libraryBgRowOpacity) : lib.ui.col.rowStripes;
this.drawSelBg(gr, cur_img, box_x, box_y, i, nowp);
// * Now playing bg for labels overlay mode ( album art )
if (this.labels.overlay && !item.root && lib.pop.inRange(lib.pop.nowp, item.item) && updatedNowpBg) {
gr.FillSolidRect(box_x, box_y, this.box.w, this.box.h, colNowPlaying);
}
// * Now playing bg selection with now playing deactivated ( album art )
if (item.sel && libSet.albumArtShow && !coversRightBottom && updatedNowpBg) {
gr.FillSolidRect(box_x, box_y, this.box.w, this.box.h, colNowPlaying);
}
// * Now playing bg selection with now playing deactivated
if (!lib.pop.highlight.nowPlaying && item.sel && coversRightBottom && updatedNowpBg) {
gr.FillSolidRect(lib.ui.x, box_y, lib.sbar.w ? lib.ui.w - SCALE(42) : lib.ui.w, this.box.h, colNowPlaying);
gr.FillSolidRect(lib.ui.x, box_y, lib.ui.sz.sideMarker, this.box.h, lib.ui.col.sideMarker);
}
// * Marker selection with now playing active
if (lib.pop.highlight.nowPlaying && item.sel && grSet.libraryDesign !== 'flowMode') {
gr.DrawRect(lib.ui.x, box_y, lib.sbar.w ? lib.ui.w - SCALE(42) - 1 : lib.ui.w, this.box.h, 1, lib.ui.col.selectionFrame);
gr.FillSolidRect(lib.ui.x, box_y, lib.ui.sz.sideMarker, this.box.h + 1, lib.ui.col.sideMarker);
}
// * Hide DrawRect gaps when all songs are completely selected and mask lines when selecting now playing
if ((['white', 'black', 'cream'].includes(grSet.theme) && !grSet.styleBlackAndWhite2) && (lib.pop.highlight.nowPlaying && item.sel && !item.root && lib.pop.inRange(lib.pop.nowp, item.item) && coversRightBottom) && updatedNowpBg) {
gr.DrawRect(lib.ui.x, box_y, lib.pop.fullLineSelection ? lib.sbar.w ? lib.ui.w - SCALE(42) : lib.ui.w : lib.ui.w + lib.ui.sz.margin + box_x - lib.ui.x - lib.ui.sz.sideMarker, this.box.h, 1, lib.ui.col.nowPlayingBg);
}
this.im.y = this.labels.overlay ? this.im.offset + box_y + libSet.thumbNailGapCompact / 2 : this.im.offset + box_y;
if (lib.pop.rowStripes && this.labels.right) {
if (i % 2 == 0) gr.FillSolidRect(0, box_y + 1, lib.panel.tree.stripe.w, this.row.h, colRowStripes /*ui.col.bg1*/);
else gr.FillSolidRect(0, box_y, lib.panel.tree.stripe.w, this.row.h, lib.ui.col.bg2);
}
let x1 = 0;
const x2 = Math.round(box_x + (this.bor.cov) / 2);
let y1 = 0;
let y2 = this.im.y + 2 + this.im.w - this.overlayHeight;
if (cur_img) {
iw = cur_img.Width;
ih = cur_img.Height;
x1 = box_x + Math.round((this.box.w - iw) / 2);
y1 = this.im.y + 1 + this.im.w - ih;
const w = iw;
const h = ih;
if (this.style.dropShadow && this.shadow) {
if (this.style.image) {
gr.DrawImage(this.shadow, x1, y1, this.shadow.Width, this.shadow.Height, 0, 0, this.shadow.Width, this.shadow.Height);
} else {
gr.DrawImage(this.shadow, x1, y1, Math.ceil(w * 1.15), Math.ceil(h * 1.15), 0, 0, this.shadow.Width, this.shadow.Height);
}
} else if (this.style.dropGrad) {
if (this.style.image != 2) {
FillGradRect(gr, x1 + w, y1, 4 * $Lib.scale, h, 0, RGBA(0, 0, 0, 56), 0);
FillGradRect(gr, x1, y1 + h, w, 4 * $Lib.scale, 90, RGBA(0, 0, 0, 56), 0);
} else {
gr.SetSmoothingMode(4);
gr.DrawEllipse(x1, y1, iw, ih, 4 * $Lib.scale, RGBA(0, 0, 0, 32));
gr.SetSmoothingMode(0);
}
}
gr.DrawImage(cur_img, x1, y1, w, h, grSet.libraryThumbnailBorder === 'border' ? 0 : 1, grSet.libraryThumbnailBorder === 'border' ? 0 : 1, iw, ih);
if (this.labels.overlayDark) {
if (item.sel || nowp) gr.FillSolidRect(x2, y2, this.im.w, this.overlayHeight, RGBA(150, 150, 150, 150));
gr.FillSolidRect(x2, y2, this.im.w, this.overlayHeight, this.getSelBgCol(item, nowp));
}
if (grSet.libraryThumbnailBorder !== 'none' && (!item.sel || !this.labels.overlay || this.style.image != 2)) {
if (this.style.image != 2) gr.DrawRect(x1, y1, iw - 1, ih - 1, 1, lib.ui.col.imgBor);
else {
gr.SetSmoothingMode(2);
gr.DrawEllipse(x1, y1, iw - 1, ih - 1, 1, lib.ui.col.imgBor);
gr.SetSmoothingMode(0);
}
}
gr.SetSmoothingMode(0);
}
else {
iw = this.im.w;
ih = this.im.w;
x1 = box_x + Math.round((this.box.w - iw) / 2);
y1 = this.im.y + 2 + iw - ih;
if (!item.root) {
if (this.style.dropShadowStub && this.shadowStub) {
gr.DrawImage(this.shadowStub, x1, y1, this.shadowStub.Width, this.shadowStub.Height, 0, 0, this.shadowStub.Width, this.shadowStub.Height);
} else if (this.style.dropGradStub) {
if (this.style.image != 2) {
FillGradRect(gr, x1 + iw - 2 * $Lib.scale, y1, 6 * $Lib.scale, ih, 0, RGBA(0, 0, 0, 56), 0);
FillGradRect(gr, x1, y1 + ih - 2 * $Lib.scale, iw, 6 * $Lib.scale, 90, RGBA(0, 0, 0, 56), 0);
} else {
gr.SetSmoothingMode(2);
gr.DrawEllipse(x1, y1, iw, ih, 4 * $Lib.scale, RGBA(0, 0, 0, 32));
gr.SetSmoothingMode(0);
}
}
this.stub.noImg && gr.DrawImage(this.stub.noImg, x1, y1, iw, ih, 0, 0, iw, ih);
}
else if (!this.style.rootComposite && this.stub.root) gr.DrawImage(this.stub.root, x1, y1, iw, ih, 0, 0, iw, ih);
if (this.labels.overlay) {
FillGradRect(gr, x1, y2 - 1, iw / 2, lib.ui.l.w, 1, RGBA(0, 0, 0, 0), lib.ui.col.imgBor);
FillGradRect(gr, x1 + iw / 2, y2 - 1, iw / 2, lib.ui.l.w, 1, lib.ui.col.imgBor, RGBA(0, 0, 0, 0));
}
if (this.labels.overlayDark) {
if (item.sel || nowp) gr.FillSolidRect(x2, y2, this.im.w, this.overlayHeight, RGBA(150, 150, 150, 150));
gr.FillSolidRect(x2, y2, this.im.w, this.overlayHeight, this.getSelBgCol(item, nowp));
}
}
this.drawItemOverlay(gr, item, x1, y1, iw, ih);
if (i == lib.pop.m.i) {
if (lib.pop.highlight.row == 3 || lib.pop.highlight.row == 2 && (((this.labels.overlay || this.labels.hide) && this.style.image != 2))) {
if (!libSet.frameImage) this.drawFrame(gr, box_x, box_y, /*ui.col.frameImgSel*/ lib.ui.col.selectionFrame2, !this.labels.overlay && !this.labels.hide ? 'stnd' : 'thick');
else this.drawImageFrame(gr, x1, y1, iw, ih, /*ui.col.frameImgSel*/ lib.ui.col.selectionFrame2);
} // else if (lib.pop.highlight.row == 1 && !lib.sbar.draw_timer) gr.FillSolidRect(lib.ui.l.w, y1, lib.ui.sz.sideMarker, this.im.w, lib.ui.col.sideMarker);
}
if (item.sel) {
// if (this.labels.overlay && this.style.image != 2) this.drawFrame(gr, box_x, box_y, /* lib.ui.col.frameImgSel */ lib.ui.col.selectionFrame2, 'thick');
// else if (this.labels.hide && lib.pop.highlight.row == 3 && libSet.frameImage) this.drawImageFrame(gr, x1, y1, iw, ih, /* lib.ui.col.frameImgSel */ lib.ui.col.selectionFrame2);
if (this.labels.hide && lib.pop.highlight.row == 3 && libSet.frameImage) this.drawImageFrame(gr, x1, y1, iw, ih, /* lib.ui.col.frameImgSel */ lib.ui.col.selectionFrame2);
}
if (!this.labels.hide) {
const x = box_x + this.text.x;
let type = 0;
const txt_c =
lib.pop.highlight.nowPlaying && !item.root && lib.pop.inRange(lib.pop.nowp, item.item) && updatedNowpBg ? lib.ui.col.text_nowp :
item.sel ? lib.ui.col.textSel :
lib.pop.m.i === i ? lib.ui.col.text_h : lib.ui.col.text;
if (lib.panel.colMarker) type = item.sel ? 2 : lib.pop.highlight.text && i == lib.pop.m.i ? 1 : 0;
if (!this.labels.overlay) {
y1 = this.im.y + this.text.y1;
y2 = this.im.y + this.text.y2;
const y3 = this.im.y + this.text.y3;
if (lib.panel.lines == 2) {
this.checkTooltip(gr, item, x, y1, y2, y3, this.text.w, grp, lot, statistics, lib.ui.font.group, /*ui.font.lot,*/ lib.ui.font.group, lib.ui.font.statistics);
!lib.panel.colMarker ? gr.GdiDrawText(grp, lib.ui.font.group, txt_c /*grpCol*/, x, y1, this.text.w, this.text.h, !this.labels.right && !item.tt[1] ? lib.panel.cc : lib.panel.lc) : lib.pop.cusCol(gr, grp, item, x, y1, this.text.w, this.text.h, type, nowp, lib.ui.font.group, lib.ui.font.groupEllipsisSpace, 'group');
!lib.panel.colMarker ? gr.GdiDrawText(lot, lib.ui.font.lot, txt_c /*lotCol*/, x, y2, this.text.w, this.text.h, !this.labels.right && !item.tt[2] ? lib.panel.cc : lib.panel.lc) : lib.pop.cusCol(gr, lot, item, x, y2, this.text.w, this.text.h, type, nowp, lib.ui.font.lot, lib.ui.font.lotEllipsisSpace, 'lott');
if (statistics) gr.GdiDrawText(statistics, lib.ui.font.statistics, txt_c /*lotCol*/, x, y3, this.text.w, this.text.h, !this.labels.right && !item.tt[2] ? lib.panel.cc : lib.panel.lc);
} else {
this.checkTooltip(gr, item, x, y1, statistics ? y2 : -1, -1, this.text.w, grp, statistics, false, lib.ui.font.group, lib.ui.font.statistics);
!lib.panel.colMarker ? gr.GdiDrawText(grp, lib.ui.font.group, txt_c /*grpCol*/, x, y1, this.text.w, this.text.h, !this.labels.right && !item.tt[1] ? lib.panel.cc : lib.panel.lc) : lib.pop.cusCol(gr, grp, item, x, y1, this.text.w, this.text.h, type, nowp, lib.ui.font.group, lib.ui.font.mainEllipsisSpace, 'group');
if (statistics) gr.GdiDrawText(statistics, lib.ui.font.statistics, txt_c /*lotCol*/, x, y2, this.text.w, this.text.h, !this.labels.right && !item.tt[2] ? lib.panel.cc : lib.panel.lc);
}
} else {
y1 = this.im.y + this.text.y1;
y2 = y1 + this.text.h * (this.labels.statistics ? 0.93 : 0.9);
const y3 = y2 + this.text.h * 0.95;
if (lib.panel.lines == 2) {
this.checkTooltip(gr, item, x, y1, y2, y3, this.text.w, grp, lot, statistics, lib.ui.font.group, /*ui.font.lot,*/ lib.ui.font.group, lib.ui.font.statistics);
!lib.panel.colMarker ? gr.GdiDrawText(grp, lib.ui.font.group, txt_c /*grpCol*/, x, y1, this.text.w, this.text.h, !item.tt[1] ? lib.panel.cc : lib.panel.lc) : lib.pop.cusCol(gr, grp, item, x, y1, this.text.w, this.text.h, type, nowp, lib.ui.font.group, lib.ui.font.groupEllipsisSpace, 'lott');
!lib.panel.colMarker ? gr.GdiDrawText(lot, lib.ui.font.lot, txt_c /*lotCol*/, x, y2, this.text.w, this.text.h, !item.tt[2] ? lib.panel.cc : lib.panel.lc) : lib.pop.cusCol(gr, lot, item, x, y2, this.text.w, this.text.h, type, nowp, lib.ui.font.lot, lib.ui.font.lotEllipsisSpace, 'group');
if (statistics) gr.GdiDrawText(statistics, lib.ui.font.statistics, txt_c /*lotCol*/, x, y3, this.text.w, this.text.h, !item.tt[3] ? lib.panel.cc : lib.panel.lc);
} else {
this.checkTooltip(gr, item, x, y1, statistics ? y2 : -1, -1, this.text.w, grp, statistics, false, lib.ui.font.group, /*ui.font.lot,*/ lib.ui.font.group, lib.ui.font.statistics);
!lib.panel.colMarker ? gr.GdiDrawText(grp, lib.ui.font.group, txt_c /*grpCol*/, x, y1, this.text.w, this.text.h, !item.tt[1] ? lib.panel.cc : lib.panel.lc) : lib.pop.cusCol(gr, grp, item, x, y1, this.text.w, this.text.h, type, nowp, lib.ui.font.group, lib.ui.font.groupEllipsisSpace, 'group');
if (statistics) gr.GdiDrawText(statistics, lib.ui.font.statistics, txt_c /*lotCol*/, x, y2, this.text.w, this.text.h, !item.tt[2] ? lib.panel.cc : lib.panel.lc);
}
}
}
}
if (this.column == this.columns - 1) this.column = 0;
else this.column++;
}
lib.ui.drawTopBarUnderlay(gr);
}
drawFrame(gr, box_x, box_y, col, weight) {
let x;
let y;
let w;
let h;
let l_w;
switch (weight) {
case 'stnd':
x = !this.labels.right ? box_x + 1 : lib.ui.sz.pad + 1;
y = box_y + (!this.labels.right ? 1 : 1);
w = !this.labels.right ? this.box.w - 2 : lib.panel.tree.sel.w;
h = this.box.h - (!this.labels.right ? 2 : 0);
l_w = 2;
break;
case 'thick':
x = box_x + Math.round((this.box.w - this.im.w) / 2) + 1;
y = this.im.y + 2;
w = this.im.w;
h = this.im.w - 2;
l_w = 4;
break;
}
gr.DrawRect(x, y, w, h, l_w, col);
}
drawImageFrame(gr, x, y, w, h, col) {
const l_w = 3;
gr.SetSmoothingMode(2);
if (this.style.image != 2) gr.DrawRect(x + 1, y + 1, w - l_w / 2 - 1, h - l_w / 2 - 1, l_w, col);
else gr.DrawEllipse(x, y, w - l_w / 2, h - l_w / 2, l_w, col);
gr.SetSmoothingMode(0);
}
drawItemOverlay(gr, item, x, y, w) {
if (item.root) return;
switch (libSet.itemOverlayType) {
case 1: {
if (!item.count) break;
let count_w = Math.max(gr.CalcTextWidth(`${item.count} `, lib.ui.font.tracks), 8);
const count_h = Math.max(gr.CalcTextHeight(item.count, lib.ui.font.tracks), 8);
let count_x = x + (this.style.image != 2 ? w - count_w - 3 : (w - count_w - 2) / 2);
const count_y = y + (this.style.image != 2 ? 0 : count_h / 1.67);
let count = item.count;
let count_h2 = count_h;
if (count_w > this.im.w) {
count = item.count.split(' ');
count_h2 = count_h * 2;
count_w = Math.max(gr.CalcTextWidth(count[0], lib.ui.font.tracks), gr.CalcTextWidth(count[1], lib.ui.font.tracks));
count_x = x + (this.style.image != 2 ? w - count_w - 3 : (w - count_w - 2) / 2);
gr.SetSmoothingMode(2);
gr.FillSolidRect(count_x, count_y, count_w + 2, count_h2, RGBA(0, 0, 0, 115));
gr.GdiDrawText(count[0], lib.ui.font.tracks, RGB(255, 255, 255), count_x + 1, count_y, count_w, count_h, this.style.image != 2 ? lib.panel.rc : lib.panel.cc);
gr.GdiDrawText(count[1], lib.ui.font.tracks, RGB(255, 255, 255), count_x + 1, count_y + count_h, count_w, count_h, this.style.image != 2 ? lib.panel.rc : lib.panel.cc);
gr.SetSmoothingMode(0);
} else {
gr.SetSmoothingMode(2);
gr.FillSolidRect(count_x, count_y, count_w + 2, count_h2, RGBA(0, 0, 0, 115));
gr.GdiDrawText(count, lib.ui.font.tracks, RGB(255, 255, 255), count_x + 1, count_y, count_w, count_h, lib.panel.cc);
gr.SetSmoothingMode(0);
}
break;
}
case 2: {
if (!item.year) break;
const year_w = Math.max(gr.CalcTextWidth(`${item.year} `, lib.ui.font.tracks), 8);
const year_h = Math.max(gr.CalcTextHeight(item.year, lib.ui.font.tracks), 8);
const year_x = x + (this.style.image != 2 ? 0 : (w - year_w - 2) / 2);
const year_y = y + (this.style.image != 2 ? 0 : year_h / 1.67);
gr.SetSmoothingMode(2);
gr.FillSolidRect(year_x, year_y, year_w + 2, year_h, RGBA(0, 0, 0, 115));
gr.GdiDrawText(item.year, lib.ui.font.tracks, RGB(255, 255, 255), year_x + 1, year_y, year_w, year_h, lib.panel.cc);
gr.SetSmoothingMode(0);
break;
}
}
}
drawSelBg(gr, cur_img, box_x, box_y, i, nowpOrSel) {
if (this.labels.hide && (this.style.image != 2 || lib.pop.highlight.row == 3 && libSet.frameImage)) return;
let x;
let y;
let w;
let h;
switch (true) {
case nowpOrSel:
// col = lib.ui.col.imgBgSel;
switch (this.labels.overlay || this.labels.hide) {
case true:
x = box_x + Math.round((this.box.w - (cur_img ? cur_img.Width + 1 : this.im.w + 2)) / 2);
y = box_y + (cur_img ? libSet.thumbNailGapCompact / 2 + this.im.w - cur_img.Height + 1 : libSet.thumbNailGapCompact / 2 + 2);
w = cur_img ? cur_img.Width : this.im.w;
h = cur_img ? cur_img.Height : this.im.w;
break;
case false:
x = !this.labels.right ? box_x : lib.ui.x;
y = box_y + (!this.labels.right ? 1 : 1);
w = !this.labels.right ? this.box.w : lib.panel.tree.sel.w;
h = this.box.h - 1;
break;
}
break;
case lib.pop.highlight.row == 2 && i == lib.pop.m.i:
// col = lib.ui.col.bg_h;
if ((this.labels.overlay || this.labels.hide) && this.style.image == 2) {
x = box_x + Math.round((this.box.w - (cur_img ? cur_img.Width : this.im.w)) / 2);
y = box_y + (cur_img ? this.im.w - cur_img.Height : 0);
w = cur_img ? cur_img.Width : this.im.w;
h = cur_img ? cur_img.Height : this.im.w;
} else {
x = !this.labels.right ? box_x : lib.ui.sz.pad;
y = box_y + ((this.labels.overlay || this.labels.hide) ? 0 : (!this.labels.right ? 2 : 1));
w = !this.labels.right ? this.box.w : lib.panel.tree.sel.w;
h = this.box.h + ((this.labels.overlay || this.labels.hide) ? 2 : 0);
}
break;
}
x = this.labels.overlay ? box_x + Math.round((this.box.w - (cur_img ? cur_img.Width + 1 : this.im.w + 2)) / 2) : !this.labels.right ? box_x : lib.ui.x;
y = this.labels.overlay ? box_y + (cur_img ? libSet.thumbNailGapCompact / 2 + this.im.w - cur_img.Height + 1 : libSet.thumbNailGapCompact / 2 + 2) : box_y + (!this.labels.right ? 1 : 1);
const coversRight = grSet.libraryDesign === 'coversLabelsRight' || libSet.albumArtLabelType === 2;
const colNowPlaying = grSet.libraryBgImg ? RGBtoRGBA(lib.ui.col.nowPlayingBg, grSet.libraryBgRowOpacity) : lib.ui.col.nowPlayingBg;
gr.FillSolidRect(x, coversRight ? y - 1 : y, w - (lib.sbar.w && coversRight ? SCALE(42) : 0), coversRight ? h + 2 : h, colNowPlaying);
if ((grSet.theme !== 'white' && grSet.theme !== 'black') && (!libSet.albumArtShow || libSet.albumArtShow && coversRight) ||
(grSet.styleBlackAndWhite || grSet.styleBlackAndWhite2) && libSet.albumArtShow && coversRight) {
gr.FillSolidRect(x, y - 1, lib.ui.sz.sideMarker, h + 2, lib.ui.col.sideMarker);
}
}
fadeMask(image, w, h) {
const mask = $Lib.gr(w, h, true, g => g.DrawImage(this.mask.fade, 0, h - this.overlayHeight, w, this.overlayHeight, 0, 0, this.mask.fade.Width, this.mask.fade.Height));
image.ApplyMask(mask);
}
format(image, n, type, w, h, fade, caller, i, key) {
let ix = 0;
let iy = 0;
let iw = image.Width;
let ih = image.Height;
switch (type) {
case 'crop':
case 'circular': {
const s1 = iw / w;
const s2 = ih / h;
const r = s1 / s2;
if (this.needTrim(n, r)) {
if (s1 > s2) {
iw = Math.round(w * s2);
ix = Math.round((image.Width - iw) / 2);
} else {
ih = Math.round(h * s1);
iy = Math.round((image.Height - ih) / 8);
}
image = image.Clone(ix, iy, iw, ih);
}
image = image.Resize(w, h, 7);
if (type == 'circular') this.circularMask(image, image.Width, image.Height);
break;
}
default: {
const sc = caller != 'save' ? Math.min(h / ih, w / iw) : Math.max(h / ih, w / iw);
const im_w = Math.round(iw * sc);
const im_h = Math.round(ih * sc);
image = image.Resize(im_w, im_h, 7);
break;
}
}
if (fade) this.fadeMask(image, image.Width, image.Height);
if (caller.startsWith('display')) {
this.cache[key] = {
img: image,
accessed: caller == 'display' ? ++this.accessed : 0
};
}
else return image;
}
getCurrentDatabase() {
this.albumArtDiskCache = libSet.albumArtDiskCache;
if (!this.albumArtDiskCache) return;
const cacheFolder = this.cacheFolder;
$Lib.buildPth(this.cachePath);
this.saveSize = this.im.w > 500 ? 750 : this.im.w > 250 ? 500 : 250;
this.interval = {
cache: this.saveSize == 250 ? 1 : this.saveSize == 500 ? 4 : 9,
preLoad: this.saveSize == 250 ? (libSet.albumArtLabelType != 3 ? 7 : 15) : this.saveSize == 500 ? 20 : 45
}
this.cacheFolder = `${this.cachePath + ['front', 'back', 'disc', 'icon', 'artist'][libSet.artId] + (this.saveSize == 250 ? '' : this.saveSize)}\\`;
$Lib.create(this.cacheFolder);
this.database = $Lib.jsonParse(`${this.cacheFolder}database.dat`, this.newDatabase(), 'file');
if (this.cacheFolder != cacheFolder) {
this.preLoadItems = [];
clearInterval(this.timer.preLoad);
this.timer.preLoad = null;
this.items = [];
clearInterval(this.timer.load);
this.timer.load = null;
this.toSave = [];
clearInterval(this.timer.save);
this.timer.save = null;
}
}
getField(handle, name, arr) {
const f = handle.GetFileInfo();
if (f) {
for (let i = 0; i < f.MetaCount; ++i) {
let fullName = '';
for (let j = 0; j < f.MetaValueCount(i); ++j) {
fullName += f.MetaValue(i, j) + (j < f.MetaValueCount(i) - 1 ? ', ' : '');
if (f.MetaValue(i, j) == name || fullName == name) arr.push(f.MetaName(i).toLowerCase());
}
}
}
}
getGrpCol(item, nowp, hover) {
return nowp ? lib.ui.col.nowp : hover ? (lib.panel.textDiffHighlight ? lib.ui.col.nowp : lib.ui.col.text_h) : item.sel ? !this.labels.overlayDark ? lib.ui.col.textSel : lib.ui.col.text : !this.labels.overlayDark ? lib.ui.col.text : RGB(240, 240, 240);
}
getImages() {
const extraRows = this.albumArtDiskCache ? lib.panel.rows * 2 : lib.panel.rows; // will load any extra including those after any preLoad
if (!lib.panel.imgView) return;
this.items = [];
let begin = this.start == 0 ? libSet.rootNode ? 1 : 0 : this.start;
const end = this.end != 0 ? Math.min(this.end + this.columns * extraRows, lib.pop.tree.length) : this.end;
for (let i = begin; i < end; i++) {
if (!lib.pop.tree[i]) continue;
const key = lib.pop.tree[i].key;
if (key && !this.cache[key]) {
this.items.push({
ix: i,
handle: lib.pop.tree[i].handle,
key
});
}
}
begin = Math.max(libSet.rootNode ? 1 : 0, begin - this.columns * extraRows);
let i = end;
while (i--) {
if (i < begin) break;
if (!lib.pop.tree[i]) continue;
const key = lib.pop.tree[i].key;
if (key && !this.cache[key]) {
this.items.push({
ix: i,
handle: lib.pop.tree[i].handle,
key
});
}
}
if (!this.items.length) return;
let interval = !lib.sbar.bar.isDragging && !lib.sbar.touch.dn ? 5 : 50;
const allCached = this.albumArtDiskCache ? this.items.every(v => v.key && this.database[v.key]) : false;
if (allCached) interval = this.interval.cache;
clearInterval(this.timer.load);
this.timer.load = null;
let j = 0;
this.timer.load = setInterval(() => {
if (j < this.items.length) {
const v = this.items[j];
const key = v.key;
if (!this.cache[key]) {
if (this.albumArtDiskCache && $Lib.file(this.cacheFolder + this.database[key])) {
this.cache[key] = {
img: 'called',
accessed: ++this.accessed
};
this.load_image_async(key, this.cacheFolder + this.database[key], v.ix);
} else {
this.cache[key] = {
img: 'called',
accessed: ++this.accessed
};
if (v.handle) this.get_album_art_async(v.handle, libSet.artId, key, v.ix);
}
}
j++;
} else {
clearInterval(this.timer.load);
this.timer.load = null;
}
}, interval);
}
getImg(key) {
const o = this.cache[key];
if (!o || o.img == 'called') return undefined;
o.accessed = ++this.accessed;
return o.img;
}
getItem(i) {
if (!lib.pop.tree[i]) {
return null;
}
const key = lib.pop.tree[i].key;
if (!this.cache[key] && this.database[key] && this.database[key] != 'noAlbumArt') {
if ($Lib.file(this.cacheFolder + this.database[key])) { // cacheItPreload if file exists
return {
ix: i,
key
}
}
}
return null;
}
getItemsToDraw(preLoad) {
switch (true) {
case this.style.vertical:
if (lib.pop.tree.length <= lib.panel.rows * this.columns) {
this.start = 0;
this.end = lib.pop.tree.length;
} else {
this.start = Math.round(lib.sbar.delta / this.row.h) * this.columns;
this.start = $Lib.clamp(this.start, 0, this.start - this.columns);
this.end = Math.ceil((lib.sbar.delta + this.panel.h) / this.row.h) * this.columns;
this.end = Math.min(this.end, lib.pop.tree.length);
}
break;
case !this.style.vertical:
if (lib.pop.tree.length <= lib.panel.rows) {
this.start = 0;
this.end = lib.pop.tree.length;
} else {
this.start = Math.round(lib.sbar.delta / this.blockWidth);
this.end = Math.min(this.start + lib.panel.rows + 2, lib.pop.tree.length);
this.start = $Lib.clamp(this.start, 0, this.start - 1);
}
break;
}
this.albumArtDiskCache ? (preLoad ? this.preLoad() : this.getImages()) : this.loadThrottle();
}
getLotCol(item, nowp, hover) {
return nowp ? lib.ui.col.nowp : hover ? (lib.panel.textDiffHighlight ? lib.ui.col.nowp : lib.ui.col.text_h) : item.sel ? !this.labels.overlayDark ? lib.ui.col.selBlend : lib.ui.col.lotBlend : !this.labels.overlayDark ? lib.ui.col.lotBlend : RGB(220, 220, 220);
}
getMostFrequentField(arr) {
const counts = arr.reduce((a, c) => {
a[c] = (a[c] || 0) + 1;
return a;
}, {});
const maxCount = Math.max(...Object.values(counts));
const mostFrequent = Object.keys(counts).filter(k => counts[k] === maxCount);
return lib.panel.grp[libSet.viewBy].type.includes(mostFrequent[0]) ? mostFrequent[0] : '';
}
getShadow() {
const xy = this.im.w * 0.02;
let wh = this.style.image ? this.im.w * 0.985 : this.im.w; // draw at actual size if possible as faster; regular have to be resized during draw
const sz = this.im.w * 1.17;
if (this.style.image != 2) {
this.shadow = $Lib.gr(sz, sz, true, g => g.FillSolidRect(xy, xy, wh, wh, RGBA(0, 0, 0, 128)));
this.shadow.StackBlur(5);
} else {
this.shadow = $Lib.gr(sz, sz, true, g => g.FillEllipse(xy, xy, wh, wh, RGBA(0, 0, 0, 128)));
this.shadow.StackBlur(4);
}
wh = this.im.w * 0.985; // always drawn at actual size
if (libSet.artId == 4) {
if (libSet.curNoArtistImg == 0 || libSet.curNoArtistImg == 2 || this.style.image == 2) {
this.shadowStub = $Lib.gr(sz, sz, true, g => g.FillEllipse(xy, xy, wh, wh, RGBA(0, 0, 0, 128)));
this.shadowStub.StackBlur(4);
} else if (libSet.curNoArtistImg != 4) {
this.shadowStub = $Lib.gr(sz, sz, true, g => g.FillSolidRect(xy, xy, wh, wh, RGBA(0, 0, 0, 128)));
this.shadowStub.StackBlur(5);
} else {
this.shadowStub = null;
}
} else if (libSet.curNoCoverImg > 2) {
this.shadowStub = $Lib.gr(sz, sz, true, g => g.FillSolidRect(xy, xy, wh, wh, RGBA(0, 0, 0, 128)));
this.shadowStub.StackBlur(5);
} else {
this.shadowStub = null;
}
}
getRootImg(key) {
const o = this.cache[key];
if (!o || o.img == 'called') return undefined;
o.accessed = ++this.accessed;
return o.img;
}
getSelBgCol(item, nowp) {
return nowp || item.sel ? this.albumArtShowLabels ? lib.ui.col.imgBgSel : lib.ui.col.imgOverlaySel : RGBA(0, 0, 0, 175);
}
getStyle() {
switch (libSet.artId) {
case 0:
return libSet.imgStyleFront;
case 1:
return libSet.imgStyleBack;
case 2:
return libSet.imgStyleDisc;
case 3:
return libSet.imgStyleIcon;
case 4:
return libSet.imgStyleArtist;
}
}
load() {
const albumArtGrpNames = $Lib.jsonParse(libSet.albumArtGrpNames, {});
const fields = [];
const mod = lib.pop.tree.length < 1000 ? 1 : lib.pop.tree.length < 3500 ? Math.round(lib.pop.tree.length / 1000) : 3;
const tf_d = FbTitleFormat('[$year(%date%)]');
this.groupField = albumArtGrpNames[`${lib.panel.grp[libSet.viewBy].type.trim()}${lib.panel.lines}`];
lib.pop.tree.forEach((v, i) => {
const item = v.item[0].start;
if (item >= lib.panel.list.Count) return;
const handle = lib.panel.list[item];
v.handle = handle;
const arr = lib.pop.tree[i].name.split('^@^');
v.grp = lib.panel.lines == 1 || !libSet.albumArtFlipLabels ? arr[0] : arr[1];
v.lot = lib.panel.lines == 2 ? !libSet.albumArtFlipLabels ? arr[1] : arr[0] : '';
v.key = libMD5.hashStr(handle.Path + handle.SubSong + (lib.panel.lines == 1 ? (arr[0] || 'Unknown') : (`${arr[0] || 'Unknown'} - ${arr[1] || 'Unknown'}`)) + libSet.artId);
if (libSet.itemOverlayType == 2) v.year = tf_d.EvalWithMetadb(handle).replace('0000', '');
if (!this.groupField && !lib.panel.folderView && i % mod === 0) this.getField(handle, lib.panel.lines == 1 || libSet.albumArtFlipLabels ? v.grp : v.lot, fields);
});
if (!this.groupField && !lib.panel.folderView) {
this.groupField = this.getMostFrequentField(fields) || '项';
this.groupField = $Lib.titlecase(this.groupField);
}
if (libSet.rootNode) {
if (!lib.pop.tree[0]) return;
if (!this.groupField) this.groupField = '项';
const plurals = this.groupField.split(' ').map(v => pluralize(v));
const pluralField = plurals.join(' ').replace(Regex.LibTypesPlural, '$1 ').replace(Regex.LibSimilarArtist, '$1s ').replace(Regex.LibYearsAlbums, '年份 - 专辑');
lib.pop.tree[0].key = lib.pop.tree[0].name;
const ln1 = lib.pop.tree.length - 1;
const ln2 = lib.panel.list.Count;
const nm = `${!libSet.showSource ? '全部' : lib.panel.sourceName} (${ln1}${ln1 > 1 ? ` ${pluralField}` : ` ${this.groupField}`})`;
if (libSet.rootNode == 3) lib.pop.tree[0].grp = nm;
else if (lib.panel.lines == 1) lib.pop.tree[0].grp = lib.panel.rootName + (libSet.nodeCounts ? ` (${libSet.nodeCounts == 2 && libSet.rootNode != 3 ? ln1 + (ln1 > 1 ? ` ${pluralField}` : ` ${this.groupField}`) : ln2 + (ln2 > 1 ? ' 个音轨' : ' 个音轨')})` : '');
if (lib.panel.lines == 2) {
if (libSet.rootNode != 3) lib.pop.tree[0].grp = lib.panel.rootName;
lib.pop.tree[0].lot = libSet.nodeCounts == 2 && libSet.rootNode != 3 ? ln1 + (ln1 > 1 ? ` ${pluralField}` : ` ${this.groupField}`) : ln2 + (ln2 > 1 ? ' 个音轨' : ' 个音轨');
}
}
this.metrics();
lib.panel.treePaint();
}
memoryLimit() {
if (!window.JsMemoryStats) return;
const limit = !libSet.memoryLimit ? window.JsMemoryStats.TotalMemoryLimit * 0.5 : Math.min(libSet.memoryLimit * 1048576, window.JsMemoryStats.TotalMemoryLimit * 0.8);
return window.JsMemoryStats.TotalMemoryUsage > limit;
}
metrics() {
if (!lib.ui.w || !lib.ui.h) return;
$Lib.gr(1, 1, false, g => {
const lineSpacing = this.labels.hide || this.labels.overlay ? Math.max(libSet.verticalAlbumArtPad - 2, 0) : libSet.verticalAlbumArtPad;
this.letter.w = Math.round(g.CalcTextWidth('W', lib.ui.font.main));
this.text.h = Math.max(Math.round(g.CalcTextHeight('String', lib.ui.font.group)) + lineSpacing, Math.round(g.CalcTextHeight('String', lib.ui.font.lot)) + lineSpacing, 10);
});
this.style = {
dropShadow: libSet.albumArtDropShadow && libSet.albumArtLabelType != 3,
dropShadowStub: libSet.albumArtDropShadow && libSet.albumArtLabelType != 3 && (libSet.artId == 4 || libSet.curNoCoverImg > 2),
image: this.getStyle(),
rootComposite: libSet.rootNode && libSet.curRootImg == 3,
vertical: !libSet.albumArtFlowMode ? true : lib.ui.h - lib.panel.search.h > lib.ui.w - lib.ui.sbar.w
}
this.style.dropGrad = libSet.albumArtDropShadow && !this.style.dropShadow;
this.style.dropGradStub = libSet.albumArtDropShadow && !this.style.dropShadowStub;
this.letter.show = libSet.albumArtLetter;
this.letter.no = libSet.albumArtLetterNo;
this.letter.albumArtYearAuto = libSet.albumArtYearAuto;
switch (this.style.vertical) {
case true: {
this.labels = {
hide: !libSet.albumArtLabelType,
bottom: libSet.albumArtLabelType == 1 || libSet.albumArtFlowMode && libSet.albumArtLabelType == 2,
right: !libSet.albumArtFlowMode ? libSet.albumArtLabelType == 2 : false,
overlay: libSet.albumArtLabelType == 3 || libSet.albumArtLabelType == 4,
overlayDark: libSet.albumArtLabelType == 4,
flip: libSet.albumArtFlipLabels,
statistics: libSet.itemShowStatistics ? 1 : 0
};
this.bor.pad = !this.labels.hide && !this.labels.overlay ? (libSet.thumbNailGapStnd == 0 ? Math.round(this.text.h * (!this.labels.right && grSet.libraryThumbnailSize !== 'playlist' ? 1.05 : 0.75)) : libSet.thumbNailGapStnd - Math.round(2 * $Lib.scale)) : libSet.thumbNailGapCompact;
this.im.offset = Math.round(!this.labels.hide && !this.labels.overlay ? this.bor.pad / 2 : -2);
if (this.labels.hide || this.labels.overlay) {
this.panel.y = lib.panel.search.h + Math.round(this.bor.pad / 2);
this.bor.bot = 0;
this.bor.side = 0;
this.bor.cov = libSet.thumbNailGapCompact;
} else {
this.panel.y = lib.panel.search.h;
this.bor.cov = Math.round(this.bor.pad / 2);
this.bor.side = Math.round(2 * $Lib.scale);
this.bor.bot = this.bor.side * 2;
}
const margin = libSet.margin;
this.panel.x = (libSet.sbarShow != 2 ? Math.max(margin, lib.ui.sbar.w) : margin) + lib.ui.l.w - SCALE(3);
this.panel.w = lib.ui.w - lib.ui.l.w * 2 - (lib.ui.sbar.type == 0 || libSet.sbarShow != 2 ? Math.max(margin, lib.ui.sbar.w) * 2 + SCALE(20) : (margin * 2 + lib.ui.sbar.w) + SCALE(20));
this.panel.h = lib.ui.h - this.panel.y;
this.blockWidth = grSet.libraryThumbnailSize === 'playlist' ? pl.thumbnail_size + (this.bor.side * 2 + this.bor.cov * 2) :
Math.round(lib.ui.row.h * 4 * $Lib.scale * libSet.zoomImg / 100 *
[// Thumbnail size
0.66, // Mini
1, // Small
1.5, // Regular
1.75, // Medium
2.5, // Large
3, // XL
3.5, // XXL
5 // MAX
][libSet.thumbNailSize]);
this.columns = libSet.albumArtFlowMode || this.labels.right ? 1 : Math.max(Math.floor(this.panel.w / this.blockWidth), 1);
let gap = this.panel.w - this.columns * this.blockWidth;
gap = Math.floor(gap / this.columns);
this.columnWidth = !this.labels.right ? $Lib.clamp(this.blockWidth + (grSet.libraryThumbnailSize === 'playlist' ? 0 : gap), 10, Math.min(this.panel.w, this.panel.h)) : $Lib.clamp(this.blockWidth, 10, Math.min(this.panel.w, this.panel.h));
this.overlayHeight = !this.labels.overlay ? 0 : (lib.panel.lines != 2 ? this.text.h * (1.2 + this.labels.statistics) : Math.round(this.text.h * (2.1 + this.labels.statistics)));
this.im.w = Math.round(Math.max(this.columnWidth - this.bor.side * 2 - this.bor.cov * 2 - (this.labels.hide || this.labels.overlay ? 1 : 0), 10));
if (this.labels.hide || this.labels.overlay) {
this.im.w = Math.round(Math.max(this.columnWidth - this.bor.cov, 10));
this.row.h = this.im.w + this.bor.cov;
} else {
this.im.w = Math.round(Math.max(this.columnWidth - this.bor.cov * 2 - this.bor.side * 2, 10));
this.row.h = !this.labels.right ? this.im.w + this.text.h * (lib.panel.lines + this.labels.statistics) + this.bor.cov * 2 + this.bor.side * 2 : this.im.w + this.bor.pad + 2;
}
if (this.row.h > this.panel.h) {
this.im.w -= this.row.h - this.panel.h;
this.im.w = Math.max(this.im.w, 10);
this.row.h = this.panel.h;
}
this.box.w = this.columnWidth - this.bor.side * 2;
this.box.h = this.row.h - (!this.labels.right ? this.bor.side * 2 : 0);
lib.panel.rows = Math.max(Math.floor(this.panel.h / this.row.h));
lib.sbar.metrics(lib.sbar.x, lib.sbar.y, lib.sbar.w, lib.sbar.h, lib.panel.rows, this.row.h, this.style.vertical);
lib.sbar.setRows(Math.ceil(lib.pop.tree.length / this.columns));
break;
}
case false: { // only H-Flow
this.labels = {
hide: !libSet.albumArtLabelType,
bottom: libSet.albumArtLabelType == 1 || libSet.albumArtLabelType == 2,
right: false,
overlay: libSet.albumArtLabelType == 3 || libSet.albumArtLabelType == 4,
overlayDark: libSet.albumArtLabelType == 4,
flip: libSet.albumArtFlipLabels,
statistics: libSet.itemShowStatistics ? 1 : 0
};
this.bor.pad = !this.labels.hide && !this.labels.overlay ? (libSet.thumbNailGapStnd == 0 ? Math.round(this.text.h * 1.05) : libSet.thumbNailGapStnd - Math.round(2 * $Lib.scale)) : libSet.thumbNailGapCompact;
this.im.offset = Math.round(!this.labels.hide && !this.labels.overlay ? this.bor.pad / 2 : -2);
if (this.labels.hide || this.labels.overlay) {
this.bor.bot = 0;
this.bor.side = 0;
this.bor.cov = libSet.thumbNailGapCompact;
} else {
this.bor.cov = Math.round(this.bor.pad / 2);
this.bor.side = Math.round(2 * $Lib.scale);
this.bor.bot = this.bor.side * 2;
}
this.panel.x = 0;
const spacer = this.letter.show ? (this.labels.bottom ? this.text.h * 0.5 - this.bor.pad / 4 : this.text.h * 0.75) : (this.labels.bottom ? 0 : Math.round(this.bor.pad / 2));
this.panel.y = lib.panel.search.h + spacer - SCALE(3);
this.panel.h = lib.ui.h - this.panel.y - lib.ui.l.w * 3 - spacer - lib.ui.sbar.w - SCALE(34);
this.panel.w = lib.ui.w;
if (!this.labels.hide && !this.labels.overlay) {
this.row.h = this.panel.h;
const extra = this.text.h * (lib.panel.lines + this.labels.statistics) + this.bor.cov * 2 + this.bor.side * 2;
this.im.w = Math.min(this.panel.h - extra, this.panel.h - extra);
this.im.w = $Lib.clamp(this.im.w, 10, Math.round(this.panel.w - (this.bor.cov * 2 + this.bor.side * 2)));
this.blockWidth = this.im.w + this.bor.cov * 2 + this.bor.side * 2;
this.row.h = this.im.w + extra;
} else {
const extra = this.bor.cov;
this.im.w = Math.min(this.panel.h - extra, this.panel.h - extra);
this.im.w = $Lib.clamp(this.im.w, 10, Math.round(this.panel.w - this.bor.cov));
this.blockWidth = this.im.w + this.bor.cov;
this.row.h = this.im.w + extra;
}
this.columns = Math.max(Math.floor(this.panel.w / this.blockWidth), 1);
this.overlayHeight = !this.labels.overlay ? 0 : (lib.panel.lines != 2 ? this.text.h * (1.2 + this.labels.statistics) : Math.round(this.text.h * (2.1 + this.labels.statistics)));
this.box.w = this.blockWidth - this.bor.side * 2;
this.box.h = this.row.h - this.bor.bot;
lib.panel.rows = Math.max(Math.floor(this.panel.w / this.blockWidth));
this.columnWidth = this.blockWidth;
lib.sbar.metrics(lib.sbar.x, lib.sbar.y, lib.ui.w, lib.ui.sbar.w, lib.panel.rows, this.blockWidth, this.style.vertical);
lib.sbar.setRows(Math.ceil(lib.pop.tree.length));
break;
}
}
this.cellWidth = Math.max(200, this.im.w / 2);
this.labels.counts = libSet.itemOverlayType != 1 && libSet.nodeCounts;
this.style.y = this.style.vertical ? Math.floor(this.panel.y + (!this.labels.hide && !this.labels.overlay ? libSet.thumbNailGapStnd / 2 : libSet.thumbNailGapCompact / 2)) : this.panel.y;
if (this.style.dropShadow) this.getShadow();
if (!this.labels.hide) {
if (!this.labels.overlay) {
this.text.x = !this.labels.right ? Math.round((this.box.w - this.im.w) / 2) : Math.max(Math.round((this.box.w - this.im.w) / 2), 5 * $Lib.scale) * 2 + this.im.w;
this.text.y1 = !this.labels.right ? this.im.w + Math.round(this.bor.cov * 0.5) : Math.round((this.im.w - this.text.h * lib.panel.lines) / 2) - (this.labels.statistics ? this.text.h / 2 : 0);
this.text.y2 = !this.labels.right ? Math.round(this.text.y1 + this.text.h * 0.95) : this.text.y1 + this.text.h;
this.text.y3 = !this.labels.right ? Math.round(this.text.y2 + this.text.h * 0.95) : this.text.y2 + this.text.h;
this.text.w = !this.labels.right ? this.im.w : this.panel.w - this.text.x - 12;
} else {
this.text.x = Math.round(10 + (libSet.thumbNailGapCompact - 3) / 2);
this.text.y1 = Math.round(this.im.w - this.overlayHeight + 2 + (this.overlayHeight - this.text.h * (lib.panel.lines + this.labels.statistics)) / 2);
this.text.w = this.box.w - 20 - libSet.thumbNailGapCompact - 6;
libSet.thumbNailGapCompact = 22;
}
}
this.cachesize.min = lib.panel.rows * this.columns * 3 + (this.albumArtDiskCache ? lib.panel.rows * 2 : lib.panel.rows) * this.columns * 2;
this.createImages();
this.getCurrentDatabase();
if (libSet.albumArtPreLoad && !this.zooming && this.albumArtDiskCache) this.getItemsToDraw(true);
this.setNoArtist();
this.setNoCover();
this.setRoot();
if (this.style.rootComposite) this.checkRootImg();
const stub = libSet.artId != 4 ? this.no_cover_img : this.no_artist_img;
if (stub) this.stub.noImg = this.format(stub, libSet.artId, ['default', 'default', 'circular'][this.style.image], this.im.w, this.im.w, libSet.albumArtLabelType == 3, 'noImg');
if (this.root_img) this.stub.root = this.format(this.root_img, libSet.artId, 'default', this.im.w, this.im.w, libSet.albumArtLabelType == 3, 'root');
lib.panel.treePaint();
}
needTrim(n, ratio) {
return n || Math.abs(ratio - 1) >= 0.05;
}
newDatabase() {
return {
'-----------group key------------': '-----------image key------------.jpg'
};
}
on_key_down() {
this.zooming = lib.vk.k('zoom');
if (this.zooming) {
clearInterval(this.timer.preLoad);
this.timer.preLoad = null;
}
}
on_key_up() {
if (this.zooming && this.zooming != lib.vk.k('zoom')) {
this.zooming = false;
if (libSet.albumArtPreLoad && this.albumArtDiskCache && lib.panel.imgView) this.metrics();
lib.panel.treePaint();
}
}
preLoad() {
if (!lib.panel.imgView) return;
this.preLoadItems = [];
const begin = this.start == 0 ? libSet.rootNode ? 1 : 0 : this.start;
const end = this.end != 0 ? Math.min(this.end + this.columns, lib.pop.tree.length) : this.end;
for (let i = begin; i < end; i++) {
const v = this.getItem(i);
if (v) {
const key = v.key;
if (!this.cache[key]) {
this.cache[key] = {
img: 'called',
accessed: ++this.accessed
};
this.load_image_async(key, this.cacheFolder + this.database[key], v.ix, true);
}
}
}
const upBegin = this.start == 0 ? libSet.rootNode ? 1 : 0 : this.start - 1;
const upEnd = libSet.rootNode ? 1 : 0;
const downBegin = this.end != 0 ? Math.min(this.end + 1 + this.columns, lib.pop.tree.length) : this.end;
const downEnd = lib.pop.tree.length;
const doPreload = () => {
clearInterval(this.timer.preLoad);
this.timer.preLoad = null;
let i = downBegin;
let j = upBegin;
if (i < downEnd || j > upEnd) {
this.timer.preLoad = setInterval(() => {
let v = null;
if (i < downEnd || j > upEnd) { // interleave
if (i < downEnd) v = this.getItem(i++);
if (v) {
const key = v.key;
if (!this.cache[key]) {
this.cache[key] = {
img: 'called',
accessed: 0
};
this.load_image_async(key, this.cacheFolder + this.database[key], v.ix, true);
}
}
if (j > upEnd) v = this.getItem(j--);
if (v) {
const key = v.key;
if (!this.cache[key]) {
this.cache[key] = {
img: 'called',
accessed: 0
};
this.load_image_async(key, this.cacheFolder + this.database[key], v.ix, true);
}
}
} else {
clearInterval(this.timer.preLoad);
this.timer.preLoad = null;
}
}, this.interval.preLoad);
}
};
doPreload();
}
refresh(items) {
let itemsToRemove = [];
if (items === 'all') {
if (!libSet.albumArtDiskCache) return;
const continue_confirmation = (status, confirmed) => {
if (confirmed) {
try {
this.clearCache();
const app = new ActiveXObject('Shell.Application');
app.NameSpace(10).MoveHere(this.cachePath); // remove all saved images & databases if albumArtDiskCache
} catch (e) {
$Lib.trace('无法清空图片缓存:可以在windows资源管理器中清空'); // Wine fix
}
}
};
const caption = '刷新全部图片';
const prompt = '此操作将重置曲库(library tree)缩略图磁盘缓存\n\n继续?';
const wsh = lib.popUpBox.isHtmlDialogSupported() ? lib.popUpBox.confirm(caption, prompt, '是', '否', false, 'center', continue_confirmation) : true;
if (wsh) continue_confirmation('ok', $Lib.wshPopup(prompt, caption));
return;
}
const allSelected = lib.pop.sel_items.length == fb.GetLibraryItems().Count;
const base = this.cachePath + ['front', 'back', 'disc', 'icon', 'artist'][libSet.artId];
const databases = [`${base}\\database.dat`, `${base}500\\database.dat`, `${base}750\\database.dat`];
if (allSelected) {
this.clearCache(); // full clear of working cache
if (!libSet.albumArtDiskCache) return;
this.database = this.newDatabase(); // full clear of databases for current image type
databases.forEach(v => {
if ($Lib.file(v)) $Lib.save(v, JSON.stringify(this.newDatabase(), null, 3), true);
});
return;
}
// refresh selected images
items.Convert().forEach(v => {
const item = lib.panel.list.Find(v);
let ind = -1;
lib.pop.tree.forEach((v, j) => {
if (!v.root && lib.pop.inRange(item, v.item)) ind = j;
});
if (ind != -1) itemsToRemove.push(lib.pop.tree[ind].key);
});
itemsToRemove = [...new Set(itemsToRemove)];
itemsToRemove.forEach(v => this.trimCache(v)); // clear working cache of selected keys: won't check if same images are used with other keys
if (!libSet.albumArtDiskCache) return;
let imgsToRemove = itemsToRemove.map(v => this.database[v]);
imgsToRemove = [...new Set(imgsToRemove)];
databases.forEach(v => {
if ($Lib.file(v)) {
const cur_db = v == `${this.cacheFolder}database.dat`;
const imgDatabase = $Lib.jsonParse(v, this.newDatabase(), 'file');
Object.entries(imgDatabase).forEach(w => { // clear working cache & database of all keys that reference a particular image: this should always work even if images in use
if (w[0] != '-----------group key------------') {
if (imgsToRemove.includes(w[1]) || !$Lib.file(this.cacheFolder + w[1])) { // images are refreshed as loaded, overwriting existing
if (cur_db) this.trimCache(w[0]);
delete imgDatabase[w[0]];
}
}
});
Object.entries(imgDatabase).forEach(w => {
if (w[0] != '-----------group key------------') {
if (w[1] != 'noAlbumArt' && !$Lib.file(this.cacheFolder + w[1])) delete imgDatabase[w[0]];
}
}); // remove any user deleted images from database
$Lib.save(v, JSON.stringify(imgDatabase, null, 3), true);
}
});
this.getCurrentDatabase();
}
setNoArtist() {
this.artist_images = lib_my_utils.getImageAssets('noArtist').sort();
libSet.curNoArtistImg = $Lib.clamp($Lib.value(libSet.curNoArtistImg, 0, 0), 0, this.artist_images.length - 1);
const artistImages = this.artist_images.map(v => ({
name: utils.SplitFilePath(v)[1],
path: `file://${v.replace('noArtist', 'noArtist/small')}`
}));
this.no_artist_img = gdi.Image(this.artist_images[libSet.curNoArtistImg]);
libSet.noArtistImages = JSON.stringify(artistImages);
}
setNoCover() {
this.cover_images = lib_my_utils.getImageAssets('noCover').sort();
libSet.curNoCoverImg = $Lib.clamp($Lib.value(libSet.curNoCoverImg, 0, 0), 0, this.cover_images.length - 1);
const coverImages = this.cover_images.map(v => ({
name: utils.SplitFilePath(v)[1],
path: `file://${v.replace('noCover', 'noCover/small')}`
}));
this.no_cover_img = gdi.Image(this.cover_images[libSet.curNoCoverImg]);
libSet.noCoverImages = JSON.stringify(coverImages);
}
setRoot() {
this.root_images = lib_my_utils.getImageAssets('root').sort();
libSet.curRootImg = $Lib.clamp($Lib.value(libSet.curRootImg, 0, 0), 0, this.root_images.length - 1);
const rootImages = this.root_images.map(v => ({
name: utils.SplitFilePath(v)[1],
path: `file://${v.replace('root', 'root/small')}`
}));
if (libSet.rootNode && libSet.curRootImg == 3) {
this.style.rootComposite = true;
this.root_img = null;
} else {
this.style.rootComposite = false;
this.root_img = gdi.Image(this.root_images[libSet.curRootImg]);
}
libSet.rootImages = JSON.stringify(rootImages);
}
sort(data, prop) {
data.sort((a, b) => a[prop] - b[prop]);
return data;
}
sortCache(o, prop) {
const sorted = {};
Object.keys(o).sort((a, b) => o[a][prop] - o[b][prop]).forEach(key => sorted[key] = o[key]);
return sorted;
}
trimCache(key) {
delete this.cache[key];
}
}
/**
* The instance of `LibImages` class for library image operations.
* @typedef {LibImages}
* @global
*/
const libImg = new LibImages();
| 1 | 'use strict'; |
| 2 | |
| 3 | class LibImages { |
| 4 | constructor() { |
| 5 | this.accessed = 0; |
| 6 | this.asyncBypass = 0; |
| 7 | this.blockWidth = 150; |
| 8 | this.cachePath = grSet.customLibraryDir ? $(`${grCfg.customLibraryDir}library-tree-cache\\`, undefined, true) : `${fb.ProfilePath}cache\\library\\library-tree-cache\\`; |
| 9 | this.cellWidth = 200; |
| 10 | this.column = 0; |
| 11 | this.columnWidth = 150; |
| 12 | this.database = this.newDatabase(); |
| 13 | this.end = 1; |
| 14 | this.groupField = ''; |
| 15 | this.items = []; |
| 16 | this.overlayHeight = 0; |
| 17 | this.panel = {}; |
| 18 | this.preLoadItems = []; |
| 19 | this.rootNo = 4; |
| 20 | this.saveSize = 250; |
| 21 | this.shadow = null; |
| 22 | this.shadowStub = null; |
| 23 | this.start = 0; |
| 24 | this.toSave = []; |
| 25 | this.zooming = false; |
| 26 | |
| 27 | this.bor = { |
| 28 | bot: 6, |
| 29 | cov: 16, |
| 30 | pad: 10, |
| 31 | side: 2 |
| 32 | }; |
| 33 | |
| 34 | this.box = { |
| 35 | h: 100, |
| 36 | w: 100 |
| 37 | }; |
| 38 | |
| 39 | this.cache = {}; |
| 40 | |
| 41 | this.cachesize = { |
| 42 | min: 20 |
| 43 | }; |
| 44 | |
| 45 | this.stub = { |
| 46 | noImg: null, |
| 47 | root: null |
| 48 | }; |
| 49 | |
| 50 | this.style = { |
| 51 | image: 0, |
| 52 | rootComposite: libSet.rootNode && libSet.curRootImg == 3, |
| 53 | vertical: !libSet.albumArtFlowMode ? true : lib.ui.h - lib.panel.search.h > lib.ui.w - lib.ui.sbar.w, |
| 54 | y: 25 |
| 55 | }; |
| 56 | |
| 57 | this.im = { |
| 58 | offset: 0, |
| 59 | y: 0, |
| 60 | w: 120 |
| 61 | }; |
| 62 | |
| 63 | this.interval = { |
| 64 | cache: 1, |
| 65 | preLoad: 7 |
| 66 | }; |
| 67 | |
| 68 | this.labels = { statistics: libSet.itemShowStatistics ? 1 : 0 } |
| 69 | |
| 70 | this.letter = { |
| 71 | albumArtYearAuto: libSet.albumArtYearAuto, |
| 72 | no: 1, |
| 73 | show: libSet.albumArtLetter, |
| 74 | w: 0 |
| 75 | }; |
| 76 | |
| 77 | this.mask = { |
| 78 | fade: null, |
| 79 | circular: null |
| 80 | }; |
| 81 | |
| 82 | this.row = { |
| 83 | h: 80 |
| 84 | }; |
| 85 | |
| 86 | this.text = { |
| 87 | x: 0, |
| 88 | y1: 0, |
| 89 | y2: 0, |
| 90 | h: 20, |
| 91 | w: 20 |
| 92 | }; |
| 93 | |
| 94 | this.timer = { |
| 95 | load: null, |
| 96 | preLoad: null, |
| 97 | save: null |
| 98 | }; |
| 99 | |
| 100 | this.drawDebounce = $Lib.debounce(() => { |
| 101 | lib.panel.treePaint(); |
| 102 | }, 500); |
| 103 | |
| 104 | this.loadThrottle = $Lib.throttle(() => { |
| 105 | if (!lib.panel.imgView) return; |
| 106 | this.getImages(); |
| 107 | }, 40); |
| 108 | |
| 109 | this.rootDebounce = $Lib.debounce(() => { |
| 110 | this.checkRootImg(); |
| 111 | }, 250, { |
| 112 | leading: true, |
| 113 | trailing: true |
| 114 | }); |
| 115 | |
| 116 | this.sizeDebounce = $Lib.debounce(() => { |
| 117 | if (!lib.panel.imgView) return; |
| 118 | this.clearCache(); |
| 119 | this.metrics(); |
| 120 | if (lib.sbar.scroll > lib.sbar.max_scroll) lib.sbar.checkScroll(lib.sbar.max_scroll); |
| 121 | }, 100); |
| 122 | |
| 123 | this.setRoot(); |
| 124 | this.setNoArtist(); |
| 125 | this.setNoCover(); |
| 126 | } |
| 127 | |
| 128 | // * METHODS * // |
| 129 | |
| 130 | async get_album_art_async(handle, art_id, key, ix) { |
| 131 | const result = await utils.GetAlbumArtAsyncV2(0, handle, art_id, false); |
| 132 | const o = this.cache[key]; |
| 133 | const saveName = `${libMD5.hashStr(result.path)}.jpg`; |
| 134 | if (o && o.img == 'called') this.cacheIt(result.image, key, ix, saveName); |
| 135 | } |
| 136 | |
| 137 | async load_image_async(key, image_path, ix, rawCache) { |
| 138 | const image = Date.now() - this.asyncBypass > 5000 ? await gdi.LoadImageAsyncV2(0, image_path) : gdi.Image(image_path); |
| 139 | const o = this.cache[key]; |
| 140 | if (o && o.img == 'called') !rawCache ? this.cacheIt(image, key, ix) : this.cacheItPreLoad(image, key, ix); |
| 141 | } |
| 142 | |
| 143 | cacheIt(image, key, ix, saveName) { |
| 144 | try { |
| 145 | if (!image) { |
| 146 | if (this.style.rootComposite && ix < this.rootNo) this.rootDebounce(); |
| 147 | if (this.albumArtDiskCache && !this.database[key]) { |
| 148 | this.toSave.unshift({ |
| 149 | key, |
| 150 | image: null, |
| 151 | folder: this.cacheFolder, |
| 152 | saveName: 'noAlbumArt', |
| 153 | setKeyOnly: true |
| 154 | }); |
| 155 | } |
| 156 | } |
| 157 | if (image) { |
| 158 | if (this.albumArtDiskCache && saveName) { |
| 159 | if (!this.database[key] && $Lib.file(this.cacheFolder + saveName)) { |
| 160 | this.toSave.unshift({ |
| 161 | key, |
| 162 | image: null, |
| 163 | folder: this.cacheFolder, |
| 164 | saveName, |
| 165 | setKeyOnly: true |
| 166 | }); |
| 167 | } |
| 168 | if (!this.database[key] || !$Lib.file(this.cacheFolder + saveName)) { |
| 169 | image = this.format(image, 1, 'default', this.saveSize, this.saveSize, false, 'save'); |
| 170 | this.toSave.unshift({ |
| 171 | key, |
| 172 | image: image.Clone(0, 0, image.Width, image.Height), |
| 173 | folder: this.cacheFolder, |
| 174 | saveName, |
| 175 | setKeyOnly: false |
| 176 | }); |
| 177 | } |
| 178 | } |
| 179 | |
| 180 | this.checkCache(); |
| 181 | this.format(image, libSet.artId, ['default', 'crop', 'circular'][this.style.image], this.im.w, this.im.w, libSet.albumArtLabelType == 3, 'display', ix, key); |
| 182 | if (this.style.rootComposite && ix < this.rootNo) this.rootDebounce(); |
| 183 | } |
| 184 | |
| 185 | if (!this.timer.save && this.toSave.length) { |
| 186 | this.timer.save = setInterval(() => { |
| 187 | const ln = this.toSave.length; |
| 188 | if (ln) { |
| 189 | if (this.toSave[ln - 1].setKeyOnly) { |
| 190 | this.database[this.toSave[ln - 1].key] = this.toSave[ln - 1].saveName; |
| 191 | $Lib.save(`${this.toSave[ln - 1].folder}database.dat`, JSON.stringify(this.database, null, 3), true); |
| 192 | } else { |
| 193 | const saved = this.toSave[ln - 1].image.SaveAs(this.toSave[ln - 1].folder + this.toSave[ln - 1].saveName, 'image/jpeg'); |
| 194 | if (saved) { |
| 195 | this.database[this.toSave[ln - 1].key] = this.toSave[ln - 1].saveName; |
| 196 | $Lib.save(`${this.toSave[ln - 1].folder}database.dat`, JSON.stringify(this.database, null, 3), true); |
| 197 | } |
| 198 | } |
| 199 | this.toSave.pop(); |
| 200 | } |
| 201 | if (!this.toSave.length) { |
| 202 | clearInterval(this.timer.save); |
| 203 | this.timer.save = null; |
| 204 | } |
| 205 | }, 1000); |
| 206 | } |
| 207 | } |
| 208 | catch (e) { |
| 209 | $Lib.trace(`unable to load thumbnail image: ${key}`); |
| 210 | } |
| 211 | this.drawDebounce(); |
| 212 | } |
| 213 | |
| 214 | cacheItPreLoad(image, key, ix) { |
| 215 | try { |
| 216 | if (image) { |
| 217 | this.checkCache(); |
| 218 | this.format(image, libSet.artId, ['default', 'crop', 'circular'][this.style.image], this.im.w, this.im.w, libSet.albumArtLabelType == 3, 'displayPreload', ix, key); |
| 219 | } |
| 220 | if (this.style.rootComposite && ix < this.rootNo) this.rootDebounce(); |
| 221 | } catch (e) { |
| 222 | $Lib.trace(`unable to load thumbnail image: ${key}`); |
| 223 | } |
| 224 | lib.panel.treePaint(); |
| 225 | } |
| 226 | |
| 227 | checkCache() { |
| 228 | if (!this.memoryLimit()) return; |
| 229 | const ln = this.columns * lib.panel.rows * 3; |
| 230 | if (this.toSave.length > ln) this.toSave.length = ln; |
| 231 | this.preLoadItems = []; |
| 232 | clearInterval(this.timer.preLoad); |
| 233 | this.timer.preLoad = null; |
| 234 | this.items = []; |
| 235 | clearInterval(this.timer.load); |
| 236 | this.timer.load = null; |
| 237 | let keys = Object.keys(this.cache); |
| 238 | const cacheLength = keys.length; |
| 239 | if (lib.pop.tree.length) { |
| 240 | const o = this.cache[lib.pop.tree[0].key]; |
| 241 | if (o) o.accessed = Infinity; |
| 242 | } |
| 243 | this.cache = this.sortCache(this.cache, 'accessed'); |
| 244 | keys = Object.keys(this.cache); |
| 245 | const numToRemove = Math.round((cacheLength - this.cachesize.min) / 2); |
| 246 | if (numToRemove > 0) { |
| 247 | for (let i = 0; i < numToRemove; i++) this.trimCache(keys[i]); |
| 248 | } |
| 249 | } |
| 250 | |
| 251 | checkNowPlaying(item) { |
| 252 | if (!libSet.highLightNowplaying) return false; |
| 253 | return !item.root && lib.pop.inRange(lib.pop.nowp, item.item); |
| 254 | } |
| 255 | |
| 256 | checkRootImg() { |
| 257 | const key = lib.pop.tree.length ? lib.pop.tree[0].key : null; |
| 258 | if (!key) return; |
| 259 | let o = this.cache[key]; |
| 260 | const imgsAvailable = Math.min(Math.round((this.panel.h + this.row.h) / this.row.h) * this.columns, lib.pop.tree.length) - 1; |
| 261 | const n = Math.max(Math.min(Math.floor(Math.sqrt(imgsAvailable)), Infinity), 2); // auto set collage size: limited by no imgs available (per screen): reduce by changing infinity |
| 262 | const cells = Math.pow(n, 2); |
| 263 | this.rootNo = n * n + 1; |
| 264 | if (!o) { |
| 265 | this.cache[key] = { |
| 266 | img: 'called', |
| 267 | accessed: ++this.accessed |
| 268 | }; |
| 269 | } |
| 270 | o = this.cache[key]; |
| 271 | o.img = $Lib.gr(this.cellWidth * n, this.cellWidth * n, true, g => this.createCollage(g, this.cellWidth, this.cellWidth, n, n, cells)); |
| 272 | if (o.img) { |
| 273 | if (this.style.image == 2) this.circularMask(o.img, o.img.Width, o.img.Height); |
| 274 | o.img = o.img.Resize(this.im.w, this.im.w, 7); |
| 275 | if (libSet.albumArtLabelType == 3) this.fadeMask(o.img, o.img.Width, o.img.Height); |
| 276 | } |
| 277 | lib.panel.treePaint(); |
| 278 | } |
| 279 | |
| 280 | checkTooltip(gr, item, x, y1, y2, y3, w, tt1, tt2, tt3, font1, font2, font3) { |
| 281 | if (lib.panel.colMarker) { |
| 282 | if (tt1) tt1 = tt1.replace(Regex.LibMarkerColor, ''); |
| 283 | if (tt2) tt2 = tt2.replace(Regex.LibMarkerColor, ''); |
| 284 | } |
| 285 | let text = tt1 || ''; |
| 286 | if (tt2 && (lib.panel.lines == 2 || lib.panel.lines == 1 && this.labels.statistics)) text += `\n${tt2}`; |
| 287 | if (tt3 && this.labels.statistics) text += `\n${tt3}`; |
| 288 | item.tt = { |
| 289 | text, |
| 290 | x, |
| 291 | y1, |
| 292 | y2, |
| 293 | y3, |
| 294 | w, |
| 295 | 1: tt1 ? gr.CalcTextWidth(tt1, font1) > w ? tt1 : false : false, |
| 296 | 2: tt2 ? gr.CalcTextWidth(tt2, font2) > w ? tt2 : false : false, |
| 297 | 3: tt3 ? gr.CalcTextWidth(tt3, font3) > w ? tt3 : false : false |
| 298 | }; |
| 299 | } |
| 300 | |
| 301 | circularMask(image, w, h) { |
| 302 | image.ApplyMask(this.mask.circular.Resize(w, h)); |
| 303 | } |
| 304 | |
| 305 | clearCache() { |
| 306 | this.accessed = 0; |
| 307 | this.cache = {}; |
| 308 | this.cachesize = { |
| 309 | min: 20 |
| 310 | }; |
| 311 | this.items = []; |
| 312 | } |
| 313 | |
| 314 | createCollage(g, cellWidth, cellHeight, rows, columns, cells) { |
| 315 | let x = 0; |
| 316 | let y = 0; |
| 317 | for (let row = 0; row < rows; row++) { |
| 318 | for (let column = 0; column < columns; column++) { |
| 319 | const idx = column + row * columns + 1; |
| 320 | if (idx <= cells) { |
| 321 | let img = lib.pop.tree.length && lib.pop.tree[idx] ? this.getRootImg(lib.pop.tree[idx].key) : null; |
| 322 | if (!img) img = this.stub.noImg; |
| 323 | if (img) { |
| 324 | let cx = 0; |
| 325 | let cy = 0; |
| 326 | let cw = img.Width; |
| 327 | let ch = img.Height; |
| 328 | if (libSet.albumArtLabelType == 3) { |
| 329 | if (this.style.image != 2) { |
| 330 | ch -= this.overlayHeight; |
| 331 | } else { |
| 332 | cx = cw * 0.1; |
| 333 | cy = ch * 0.1; |
| 334 | cw *= 0.8; |
| 335 | ch = (ch - this.overlayHeight) * 0.8; |
| 336 | } |
| 337 | } else if (this.style.image == 2) { |
| 338 | cx = cw * 0.1; |
| 339 | cy = ch * 0.1; |
| 340 | cw *= 0.8; |
| 341 | ch *= 0.8; |
| 342 | } |
| 343 | img = img.Clone(cx, cy, cw, ch); |
| 344 | img = this.format(img, libSet.artId, 'crop', this.cellWidth, this.cellWidth, false, 'root'); |
| 345 | g.DrawImage(img, x, y, img.Width, img.Height, 0, 0, img.Width, img.Height); |
| 346 | } |
| 347 | x += cellWidth; |
| 348 | } |
| 349 | } |
| 350 | x = 0; |
| 351 | y += cellHeight; |
| 352 | } |
| 353 | x = 0; |
| 354 | y = 0; |
| 355 | for (let column = 0; column < columns; column++) { |
| 356 | x += cellWidth; |
| 357 | if (this.style.image != 2) g.DrawLine(x, 0, x, cellWidth * columns, lib.ui.l.w, lib.ui.col.rootBlend); |
| 358 | } |
| 359 | x = 0; |
| 360 | y = 0; |
| 361 | for (let row = 0; row < rows; row++) { |
| 362 | y += cellHeight; |
| 363 | if (this.style.image != 2) g.DrawLine(x, y, cellWidth * columns, y, lib.ui.l.w, lib.ui.col.rootBlend); |
| 364 | } |
| 365 | if (this.style.image != 2) g.DrawRect(0, 0, cellWidth * columns - 1, cellWidth * columns - 1, 1, lib.ui.col.rootBlend); |
| 366 | } |
| 367 | |
| 368 | createImages() { |
| 369 | this.mask.circular = $Lib.gr(500, 500, true, g => { |
| 370 | g.FillSolidRect(0, 0, 500, 500, RGB(255, 255, 255)); |
| 371 | g.SetSmoothingMode(2); |
| 372 | g.FillEllipse(1, 1, 498, 498, RGBA(0, 0, 0, 255)); |
| 373 | g.SetSmoothingMode(0); |
| 374 | }); |
| 375 | this.mask.fade = $Lib.gr(500, 500, true, g => { |
| 376 | g.FillSolidRect(0, 0, 500, 500, RGB(220, 220, 220)); |
| 377 | }); |
| 378 | } |
| 379 | |
| 380 | draw(gr) { |
| 381 | if (!lib.panel.imgView) return; |
| 382 | let box_x; |
| 383 | let box_y; |
| 384 | let iw; |
| 385 | let ih; |
| 386 | this.getItemsToDraw(); |
| 387 | this.column = 0; |
| 388 | for (let i = this.start; i < this.end; i++) { |
| 389 | const row = this.style.vertical ? Math.floor(i / this.columns) : 0; |
| 390 | box_x = this.style.vertical ? Math.floor(lib.ui.x + this.panel.x + this.column * this.columnWidth + this.bor.side) : Math.floor(lib.ui.x + this.panel.x + i * this.columnWidth + this.bor.side - lib.sbar.delta + (libSet.albumArtFlowMode ? SCALE(18) : 0)); |
| 391 | box_y = this.style.vertical ? Math.floor(lib.ui.y + this.panel.y + row * this.row.h - lib.sbar.delta) : lib.ui.y + this.style.y; |
| 392 | if (box_y >= 0 - this.row.h && box_y < this.panel.y + this.panel.h) { |
| 393 | const item = lib.pop.tree[i]; |
| 394 | lib.pop.getItemCount(item); |
| 395 | const grp = item.grp; |
| 396 | const lot = item.lot; |
| 397 | const statistics = this.labels.statistics ? (!item.root && this.labels.counts ? item.count + (item.count && item._statistics ? ' | ' : '') : '') + item._statistics : ''; |
| 398 | const cur_img = !this.zooming ? this.getImg(item.key) : null; |
| 399 | const nowp = this.checkNowPlaying(item); |
| 400 | // const grpCol = this.getGrpCol(item, nowp, lib.pop.highlight.text && i == lib.pop.m.i); |
| 401 | // const lotCol = this.getLotCol(item, nowp, lib.pop.highlight.text && i == lib.pop.m.i); |
| 402 | const coversRightBottom = ['coversLabelsRight', 'coversLabelsBottom'].includes(grSet.libraryDesign) || libSet.albumArtLabelType === 2; |
| 403 | const updatedNowpBg = pl.col.header_nowplaying_bg !== null; // * Wait until nowplaying bg has a new color to prevent flashing |
| 404 | const colNowPlaying = grSet.libraryBgImg ? RGBtoRGBA(lib.ui.col.nowPlayingBg, grSet.libraryBgRowOpacity) : lib.ui.col.nowPlayingBg; |
| 405 | const colRowStripes = grSet.libraryBgImg ? RGBtoRGBA(lib.ui.col.rowStripes, grSet.libraryBgRowOpacity) : lib.ui.col.rowStripes; |
| 406 | |
| 407 | this.drawSelBg(gr, cur_img, box_x, box_y, i, nowp); |
| 408 | |
| 409 | // * Now playing bg for labels overlay mode ( album art ) |
| 410 | if (this.labels.overlay && !item.root && lib.pop.inRange(lib.pop.nowp, item.item) && updatedNowpBg) { |
| 411 | gr.FillSolidRect(box_x, box_y, this.box.w, this.box.h, colNowPlaying); |
| 412 | } |
| 413 | // * Now playing bg selection with now playing deactivated ( album art ) |
| 414 | if (item.sel && libSet.albumArtShow && !coversRightBottom && updatedNowpBg) { |
| 415 | gr.FillSolidRect(box_x, box_y, this.box.w, this.box.h, colNowPlaying); |
| 416 | } |
| 417 | // * Now playing bg selection with now playing deactivated |
| 418 | if (!lib.pop.highlight.nowPlaying && item.sel && coversRightBottom && updatedNowpBg) { |
| 419 | gr.FillSolidRect(lib.ui.x, box_y, lib.sbar.w ? lib.ui.w - SCALE(42) : lib.ui.w, this.box.h, colNowPlaying); |
| 420 | gr.FillSolidRect(lib.ui.x, box_y, lib.ui.sz.sideMarker, this.box.h, lib.ui.col.sideMarker); |
| 421 | } |
| 422 | // * Marker selection with now playing active |
| 423 | if (lib.pop.highlight.nowPlaying && item.sel && grSet.libraryDesign !== 'flowMode') { |
| 424 | gr.DrawRect(lib.ui.x, box_y, lib.sbar.w ? lib.ui.w - SCALE(42) - 1 : lib.ui.w, this.box.h, 1, lib.ui.col.selectionFrame); |
| 425 | gr.FillSolidRect(lib.ui.x, box_y, lib.ui.sz.sideMarker, this.box.h + 1, lib.ui.col.sideMarker); |
| 426 | } |
| 427 | // * Hide DrawRect gaps when all songs are completely selected and mask lines when selecting now playing |
| 428 | if ((['white', 'black', 'cream'].includes(grSet.theme) && !grSet.styleBlackAndWhite2) && (lib.pop.highlight.nowPlaying && item.sel && !item.root && lib.pop.inRange(lib.pop.nowp, item.item) && coversRightBottom) && updatedNowpBg) { |
| 429 | gr.DrawRect(lib.ui.x, box_y, lib.pop.fullLineSelection ? lib.sbar.w ? lib.ui.w - SCALE(42) : lib.ui.w : lib.ui.w + lib.ui.sz.margin + box_x - lib.ui.x - lib.ui.sz.sideMarker, this.box.h, 1, lib.ui.col.nowPlayingBg); |
| 430 | } |
| 431 | |
| 432 | this.im.y = this.labels.overlay ? this.im.offset + box_y + libSet.thumbNailGapCompact / 2 : this.im.offset + box_y; |
| 433 | if (lib.pop.rowStripes && this.labels.right) { |
| 434 | if (i % 2 == 0) gr.FillSolidRect(0, box_y + 1, lib.panel.tree.stripe.w, this.row.h, colRowStripes /*ui.col.bg1*/); |
| 435 | else gr.FillSolidRect(0, box_y, lib.panel.tree.stripe.w, this.row.h, lib.ui.col.bg2); |
| 436 | } |
| 437 | let x1 = 0; |
| 438 | const x2 = Math.round(box_x + (this.bor.cov) / 2); |
| 439 | let y1 = 0; |
| 440 | let y2 = this.im.y + 2 + this.im.w - this.overlayHeight; |
| 441 | if (cur_img) { |
| 442 | iw = cur_img.Width; |
| 443 | ih = cur_img.Height; |
| 444 | x1 = box_x + Math.round((this.box.w - iw) / 2); |
| 445 | y1 = this.im.y + 1 + this.im.w - ih; |
| 446 | const w = iw; |
| 447 | const h = ih; |
| 448 | if (this.style.dropShadow && this.shadow) { |
| 449 | if (this.style.image) { |
| 450 | gr.DrawImage(this.shadow, x1, y1, this.shadow.Width, this.shadow.Height, 0, 0, this.shadow.Width, this.shadow.Height); |
| 451 | } else { |
| 452 | gr.DrawImage(this.shadow, x1, y1, Math.ceil(w * 1.15), Math.ceil(h * 1.15), 0, 0, this.shadow.Width, this.shadow.Height); |
| 453 | } |
| 454 | } else if (this.style.dropGrad) { |
| 455 | if (this.style.image != 2) { |
| 456 | FillGradRect(gr, x1 + w, y1, 4 * $Lib.scale, h, 0, RGBA(0, 0, 0, 56), 0); |
| 457 | FillGradRect(gr, x1, y1 + h, w, 4 * $Lib.scale, 90, RGBA(0, 0, 0, 56), 0); |
| 458 | } else { |
| 459 | gr.SetSmoothingMode(4); |
| 460 | gr.DrawEllipse(x1, y1, iw, ih, 4 * $Lib.scale, RGBA(0, 0, 0, 32)); |
| 461 | gr.SetSmoothingMode(0); |
| 462 | } |
| 463 | } |
| 464 | |
| 465 | gr.DrawImage(cur_img, x1, y1, w, h, grSet.libraryThumbnailBorder === 'border' ? 0 : 1, grSet.libraryThumbnailBorder === 'border' ? 0 : 1, iw, ih); |
| 466 | |
| 467 | if (this.labels.overlayDark) { |
| 468 | if (item.sel || nowp) gr.FillSolidRect(x2, y2, this.im.w, this.overlayHeight, RGBA(150, 150, 150, 150)); |
| 469 | gr.FillSolidRect(x2, y2, this.im.w, this.overlayHeight, this.getSelBgCol(item, nowp)); |
| 470 | } |
| 471 | if (grSet.libraryThumbnailBorder !== 'none' && (!item.sel || !this.labels.overlay || this.style.image != 2)) { |
| 472 | if (this.style.image != 2) gr.DrawRect(x1, y1, iw - 1, ih - 1, 1, lib.ui.col.imgBor); |
| 473 | else { |
| 474 | gr.SetSmoothingMode(2); |
| 475 | gr.DrawEllipse(x1, y1, iw - 1, ih - 1, 1, lib.ui.col.imgBor); |
| 476 | gr.SetSmoothingMode(0); |
| 477 | } |
| 478 | } |
| 479 | gr.SetSmoothingMode(0); |
| 480 | } |
| 481 | else { |
| 482 | iw = this.im.w; |
| 483 | ih = this.im.w; |
| 484 | x1 = box_x + Math.round((this.box.w - iw) / 2); |
| 485 | y1 = this.im.y + 2 + iw - ih; |
| 486 | if (!item.root) { |
| 487 | if (this.style.dropShadowStub && this.shadowStub) { |
| 488 | gr.DrawImage(this.shadowStub, x1, y1, this.shadowStub.Width, this.shadowStub.Height, 0, 0, this.shadowStub.Width, this.shadowStub.Height); |
| 489 | } else if (this.style.dropGradStub) { |
| 490 | if (this.style.image != 2) { |
| 491 | FillGradRect(gr, x1 + iw - 2 * $Lib.scale, y1, 6 * $Lib.scale, ih, 0, RGBA(0, 0, 0, 56), 0); |
| 492 | FillGradRect(gr, x1, y1 + ih - 2 * $Lib.scale, iw, 6 * $Lib.scale, 90, RGBA(0, 0, 0, 56), 0); |
| 493 | } else { |
| 494 | gr.SetSmoothingMode(2); |
| 495 | gr.DrawEllipse(x1, y1, iw, ih, 4 * $Lib.scale, RGBA(0, 0, 0, 32)); |
| 496 | gr.SetSmoothingMode(0); |
| 497 | } |
| 498 | } |
| 499 | this.stub.noImg && gr.DrawImage(this.stub.noImg, x1, y1, iw, ih, 0, 0, iw, ih); |
| 500 | } |
| 501 | else if (!this.style.rootComposite && this.stub.root) gr.DrawImage(this.stub.root, x1, y1, iw, ih, 0, 0, iw, ih); |
| 502 | |
| 503 | if (this.labels.overlay) { |
| 504 | FillGradRect(gr, x1, y2 - 1, iw / 2, lib.ui.l.w, 1, RGBA(0, 0, 0, 0), lib.ui.col.imgBor); |
| 505 | FillGradRect(gr, x1 + iw / 2, y2 - 1, iw / 2, lib.ui.l.w, 1, lib.ui.col.imgBor, RGBA(0, 0, 0, 0)); |
| 506 | } |
| 507 | if (this.labels.overlayDark) { |
| 508 | if (item.sel || nowp) gr.FillSolidRect(x2, y2, this.im.w, this.overlayHeight, RGBA(150, 150, 150, 150)); |
| 509 | gr.FillSolidRect(x2, y2, this.im.w, this.overlayHeight, this.getSelBgCol(item, nowp)); |
| 510 | } |
| 511 | } |
| 512 | this.drawItemOverlay(gr, item, x1, y1, iw, ih); |
| 513 | if (i == lib.pop.m.i) { |
| 514 | if (lib.pop.highlight.row == 3 || lib.pop.highlight.row == 2 && (((this.labels.overlay || this.labels.hide) && this.style.image != 2))) { |
| 515 | if (!libSet.frameImage) this.drawFrame(gr, box_x, box_y, /*ui.col.frameImgSel*/ lib.ui.col.selectionFrame2, !this.labels.overlay && !this.labels.hide ? 'stnd' : 'thick'); |
| 516 | else this.drawImageFrame(gr, x1, y1, iw, ih, /*ui.col.frameImgSel*/ lib.ui.col.selectionFrame2); |
| 517 | } // else if (lib.pop.highlight.row == 1 && !lib.sbar.draw_timer) gr.FillSolidRect(lib.ui.l.w, y1, lib.ui.sz.sideMarker, this.im.w, lib.ui.col.sideMarker); |
| 518 | } |
| 519 | if (item.sel) { |
| 520 | // if (this.labels.overlay && this.style.image != 2) this.drawFrame(gr, box_x, box_y, /* lib.ui.col.frameImgSel */ lib.ui.col.selectionFrame2, 'thick'); |
| 521 | // else if (this.labels.hide && lib.pop.highlight.row == 3 && libSet.frameImage) this.drawImageFrame(gr, x1, y1, iw, ih, /* lib.ui.col.frameImgSel */ lib.ui.col.selectionFrame2); |
| 522 | if (this.labels.hide && lib.pop.highlight.row == 3 && libSet.frameImage) this.drawImageFrame(gr, x1, y1, iw, ih, /* lib.ui.col.frameImgSel */ lib.ui.col.selectionFrame2); |
| 523 | } |
| 524 | if (!this.labels.hide) { |
| 525 | const x = box_x + this.text.x; |
| 526 | let type = 0; |
| 527 | |
| 528 | const txt_c = |
| 529 | lib.pop.highlight.nowPlaying && !item.root && lib.pop.inRange(lib.pop.nowp, item.item) && updatedNowpBg ? lib.ui.col.text_nowp : |
| 530 | item.sel ? lib.ui.col.textSel : |
| 531 | lib.pop.m.i === i ? lib.ui.col.text_h : lib.ui.col.text; |
| 532 | |
| 533 | if (lib.panel.colMarker) type = item.sel ? 2 : lib.pop.highlight.text && i == lib.pop.m.i ? 1 : 0; |
| 534 | if (!this.labels.overlay) { |
| 535 | y1 = this.im.y + this.text.y1; |
| 536 | y2 = this.im.y + this.text.y2; |
| 537 | const y3 = this.im.y + this.text.y3; |
| 538 | if (lib.panel.lines == 2) { |
| 539 | this.checkTooltip(gr, item, x, y1, y2, y3, this.text.w, grp, lot, statistics, lib.ui.font.group, /*ui.font.lot,*/ lib.ui.font.group, lib.ui.font.statistics); |
| 540 | !lib.panel.colMarker ? gr.GdiDrawText(grp, lib.ui.font.group, txt_c /*grpCol*/, x, y1, this.text.w, this.text.h, !this.labels.right && !item.tt[1] ? lib.panel.cc : lib.panel.lc) : lib.pop.cusCol(gr, grp, item, x, y1, this.text.w, this.text.h, type, nowp, lib.ui.font.group, lib.ui.font.groupEllipsisSpace, 'group'); |
| 541 | !lib.panel.colMarker ? gr.GdiDrawText(lot, lib.ui.font.lot, txt_c /*lotCol*/, x, y2, this.text.w, this.text.h, !this.labels.right && !item.tt[2] ? lib.panel.cc : lib.panel.lc) : lib.pop.cusCol(gr, lot, item, x, y2, this.text.w, this.text.h, type, nowp, lib.ui.font.lot, lib.ui.font.lotEllipsisSpace, 'lott'); |
| 542 | if (statistics) gr.GdiDrawText(statistics, lib.ui.font.statistics, txt_c /*lotCol*/, x, y3, this.text.w, this.text.h, !this.labels.right && !item.tt[2] ? lib.panel.cc : lib.panel.lc); |
| 543 | } else { |
| 544 | this.checkTooltip(gr, item, x, y1, statistics ? y2 : -1, -1, this.text.w, grp, statistics, false, lib.ui.font.group, lib.ui.font.statistics); |
| 545 | !lib.panel.colMarker ? gr.GdiDrawText(grp, lib.ui.font.group, txt_c /*grpCol*/, x, y1, this.text.w, this.text.h, !this.labels.right && !item.tt[1] ? lib.panel.cc : lib.panel.lc) : lib.pop.cusCol(gr, grp, item, x, y1, this.text.w, this.text.h, type, nowp, lib.ui.font.group, lib.ui.font.mainEllipsisSpace, 'group'); |
| 546 | if (statistics) gr.GdiDrawText(statistics, lib.ui.font.statistics, txt_c /*lotCol*/, x, y2, this.text.w, this.text.h, !this.labels.right && !item.tt[2] ? lib.panel.cc : lib.panel.lc); |
| 547 | } |
| 548 | } else { |
| 549 | y1 = this.im.y + this.text.y1; |
| 550 | y2 = y1 + this.text.h * (this.labels.statistics ? 0.93 : 0.9); |
| 551 | const y3 = y2 + this.text.h * 0.95; |
| 552 | if (lib.panel.lines == 2) { |
| 553 | this.checkTooltip(gr, item, x, y1, y2, y3, this.text.w, grp, lot, statistics, lib.ui.font.group, /*ui.font.lot,*/ lib.ui.font.group, lib.ui.font.statistics); |
| 554 | !lib.panel.colMarker ? gr.GdiDrawText(grp, lib.ui.font.group, txt_c /*grpCol*/, x, y1, this.text.w, this.text.h, !item.tt[1] ? lib.panel.cc : lib.panel.lc) : lib.pop.cusCol(gr, grp, item, x, y1, this.text.w, this.text.h, type, nowp, lib.ui.font.group, lib.ui.font.groupEllipsisSpace, 'lott'); |
| 555 | !lib.panel.colMarker ? gr.GdiDrawText(lot, lib.ui.font.lot, txt_c /*lotCol*/, x, y2, this.text.w, this.text.h, !item.tt[2] ? lib.panel.cc : lib.panel.lc) : lib.pop.cusCol(gr, lot, item, x, y2, this.text.w, this.text.h, type, nowp, lib.ui.font.lot, lib.ui.font.lotEllipsisSpace, 'group'); |
| 556 | if (statistics) gr.GdiDrawText(statistics, lib.ui.font.statistics, txt_c /*lotCol*/, x, y3, this.text.w, this.text.h, !item.tt[3] ? lib.panel.cc : lib.panel.lc); |
| 557 | } else { |
| 558 | this.checkTooltip(gr, item, x, y1, statistics ? y2 : -1, -1, this.text.w, grp, statistics, false, lib.ui.font.group, /*ui.font.lot,*/ lib.ui.font.group, lib.ui.font.statistics); |
| 559 | !lib.panel.colMarker ? gr.GdiDrawText(grp, lib.ui.font.group, txt_c /*grpCol*/, x, y1, this.text.w, this.text.h, !item.tt[1] ? lib.panel.cc : lib.panel.lc) : lib.pop.cusCol(gr, grp, item, x, y1, this.text.w, this.text.h, type, nowp, lib.ui.font.group, lib.ui.font.groupEllipsisSpace, 'group'); |
| 560 | if (statistics) gr.GdiDrawText(statistics, lib.ui.font.statistics, txt_c /*lotCol*/, x, y2, this.text.w, this.text.h, !item.tt[2] ? lib.panel.cc : lib.panel.lc); |
| 561 | } |
| 562 | } |
| 563 | } |
| 564 | } |
| 565 | if (this.column == this.columns - 1) this.column = 0; |
| 566 | else this.column++; |
| 567 | } |
| 568 | lib.ui.drawTopBarUnderlay(gr); |
| 569 | } |
| 570 | |
| 571 | drawFrame(gr, box_x, box_y, col, weight) { |
| 572 | let x; |
| 573 | let y; |
| 574 | let w; |
| 575 | let h; |
| 576 | let l_w; |
| 577 | switch (weight) { |
| 578 | case 'stnd': |
| 579 | x = !this.labels.right ? box_x + 1 : lib.ui.sz.pad + 1; |
| 580 | y = box_y + (!this.labels.right ? 1 : 1); |
| 581 | w = !this.labels.right ? this.box.w - 2 : lib.panel.tree.sel.w; |
| 582 | h = this.box.h - (!this.labels.right ? 2 : 0); |
| 583 | l_w = 2; |
| 584 | break; |
| 585 | case 'thick': |
| 586 | x = box_x + Math.round((this.box.w - this.im.w) / 2) + 1; |
| 587 | y = this.im.y + 2; |
| 588 | w = this.im.w; |
| 589 | h = this.im.w - 2; |
| 590 | l_w = 4; |
| 591 | break; |
| 592 | } |
| 593 | gr.DrawRect(x, y, w, h, l_w, col); |
| 594 | } |
| 595 | |
| 596 | drawImageFrame(gr, x, y, w, h, col) { |
| 597 | const l_w = 3; |
| 598 | gr.SetSmoothingMode(2); |
| 599 | if (this.style.image != 2) gr.DrawRect(x + 1, y + 1, w - l_w / 2 - 1, h - l_w / 2 - 1, l_w, col); |
| 600 | else gr.DrawEllipse(x, y, w - l_w / 2, h - l_w / 2, l_w, col); |
| 601 | gr.SetSmoothingMode(0); |
| 602 | } |
| 603 | |
| 604 | drawItemOverlay(gr, item, x, y, w) { |
| 605 | if (item.root) return; |
| 606 | switch (libSet.itemOverlayType) { |
| 607 | case 1: { |
| 608 | if (!item.count) break; |
| 609 | let count_w = Math.max(gr.CalcTextWidth(`${item.count} `, lib.ui.font.tracks), 8); |
| 610 | const count_h = Math.max(gr.CalcTextHeight(item.count, lib.ui.font.tracks), 8); |
| 611 | let count_x = x + (this.style.image != 2 ? w - count_w - 3 : (w - count_w - 2) / 2); |
| 612 | const count_y = y + (this.style.image != 2 ? 0 : count_h / 1.67); |
| 613 | let count = item.count; |
| 614 | let count_h2 = count_h; |
| 615 | if (count_w > this.im.w) { |
| 616 | count = item.count.split(' '); |
| 617 | count_h2 = count_h * 2; |
| 618 | count_w = Math.max(gr.CalcTextWidth(count[0], lib.ui.font.tracks), gr.CalcTextWidth(count[1], lib.ui.font.tracks)); |
| 619 | count_x = x + (this.style.image != 2 ? w - count_w - 3 : (w - count_w - 2) / 2); |
| 620 | gr.SetSmoothingMode(2); |
| 621 | gr.FillSolidRect(count_x, count_y, count_w + 2, count_h2, RGBA(0, 0, 0, 115)); |
| 622 | gr.GdiDrawText(count[0], lib.ui.font.tracks, RGB(255, 255, 255), count_x + 1, count_y, count_w, count_h, this.style.image != 2 ? lib.panel.rc : lib.panel.cc); |
| 623 | gr.GdiDrawText(count[1], lib.ui.font.tracks, RGB(255, 255, 255), count_x + 1, count_y + count_h, count_w, count_h, this.style.image != 2 ? lib.panel.rc : lib.panel.cc); |
| 624 | gr.SetSmoothingMode(0); |
| 625 | } else { |
| 626 | gr.SetSmoothingMode(2); |
| 627 | gr.FillSolidRect(count_x, count_y, count_w + 2, count_h2, RGBA(0, 0, 0, 115)); |
| 628 | gr.GdiDrawText(count, lib.ui.font.tracks, RGB(255, 255, 255), count_x + 1, count_y, count_w, count_h, lib.panel.cc); |
| 629 | gr.SetSmoothingMode(0); |
| 630 | } |
| 631 | break; |
| 632 | } |
| 633 | case 2: { |
| 634 | if (!item.year) break; |
| 635 | const year_w = Math.max(gr.CalcTextWidth(`${item.year} `, lib.ui.font.tracks), 8); |
| 636 | const year_h = Math.max(gr.CalcTextHeight(item.year, lib.ui.font.tracks), 8); |
| 637 | const year_x = x + (this.style.image != 2 ? 0 : (w - year_w - 2) / 2); |
| 638 | const year_y = y + (this.style.image != 2 ? 0 : year_h / 1.67); |
| 639 | gr.SetSmoothingMode(2); |
| 640 | gr.FillSolidRect(year_x, year_y, year_w + 2, year_h, RGBA(0, 0, 0, 115)); |
| 641 | gr.GdiDrawText(item.year, lib.ui.font.tracks, RGB(255, 255, 255), year_x + 1, year_y, year_w, year_h, lib.panel.cc); |
| 642 | gr.SetSmoothingMode(0); |
| 643 | break; |
| 644 | } |
| 645 | } |
| 646 | } |
| 647 | |
| 648 | drawSelBg(gr, cur_img, box_x, box_y, i, nowpOrSel) { |
| 649 | if (this.labels.hide && (this.style.image != 2 || lib.pop.highlight.row == 3 && libSet.frameImage)) return; |
| 650 | let x; |
| 651 | let y; |
| 652 | let w; |
| 653 | let h; |
| 654 | switch (true) { |
| 655 | case nowpOrSel: |
| 656 | // col = lib.ui.col.imgBgSel; |
| 657 | switch (this.labels.overlay || this.labels.hide) { |
| 658 | case true: |
| 659 | x = box_x + Math.round((this.box.w - (cur_img ? cur_img.Width + 1 : this.im.w + 2)) / 2); |
| 660 | y = box_y + (cur_img ? libSet.thumbNailGapCompact / 2 + this.im.w - cur_img.Height + 1 : libSet.thumbNailGapCompact / 2 + 2); |
| 661 | w = cur_img ? cur_img.Width : this.im.w; |
| 662 | h = cur_img ? cur_img.Height : this.im.w; |
| 663 | break; |
| 664 | case false: |
| 665 | x = !this.labels.right ? box_x : lib.ui.x; |
| 666 | y = box_y + (!this.labels.right ? 1 : 1); |
| 667 | w = !this.labels.right ? this.box.w : lib.panel.tree.sel.w; |
| 668 | h = this.box.h - 1; |
| 669 | break; |
| 670 | } |
| 671 | break; |
| 672 | case lib.pop.highlight.row == 2 && i == lib.pop.m.i: |
| 673 | // col = lib.ui.col.bg_h; |
| 674 | if ((this.labels.overlay || this.labels.hide) && this.style.image == 2) { |
| 675 | x = box_x + Math.round((this.box.w - (cur_img ? cur_img.Width : this.im.w)) / 2); |
| 676 | y = box_y + (cur_img ? this.im.w - cur_img.Height : 0); |
| 677 | w = cur_img ? cur_img.Width : this.im.w; |
| 678 | h = cur_img ? cur_img.Height : this.im.w; |
| 679 | } else { |
| 680 | x = !this.labels.right ? box_x : lib.ui.sz.pad; |
| 681 | y = box_y + ((this.labels.overlay || this.labels.hide) ? 0 : (!this.labels.right ? 2 : 1)); |
| 682 | w = !this.labels.right ? this.box.w : lib.panel.tree.sel.w; |
| 683 | h = this.box.h + ((this.labels.overlay || this.labels.hide) ? 2 : 0); |
| 684 | } |
| 685 | break; |
| 686 | } |
| 687 | |
| 688 | x = this.labels.overlay ? box_x + Math.round((this.box.w - (cur_img ? cur_img.Width + 1 : this.im.w + 2)) / 2) : !this.labels.right ? box_x : lib.ui.x; |
| 689 | y = this.labels.overlay ? box_y + (cur_img ? libSet.thumbNailGapCompact / 2 + this.im.w - cur_img.Height + 1 : libSet.thumbNailGapCompact / 2 + 2) : box_y + (!this.labels.right ? 1 : 1); |
| 690 | const coversRight = grSet.libraryDesign === 'coversLabelsRight' || libSet.albumArtLabelType === 2; |
| 691 | |
| 692 | const colNowPlaying = grSet.libraryBgImg ? RGBtoRGBA(lib.ui.col.nowPlayingBg, grSet.libraryBgRowOpacity) : lib.ui.col.nowPlayingBg; |
| 693 | gr.FillSolidRect(x, coversRight ? y - 1 : y, w - (lib.sbar.w && coversRight ? SCALE(42) : 0), coversRight ? h + 2 : h, colNowPlaying); |
| 694 | |
| 695 | if ((grSet.theme !== 'white' && grSet.theme !== 'black') && (!libSet.albumArtShow || libSet.albumArtShow && coversRight) || |
| 696 | (grSet.styleBlackAndWhite || grSet.styleBlackAndWhite2) && libSet.albumArtShow && coversRight) { |
| 697 | gr.FillSolidRect(x, y - 1, lib.ui.sz.sideMarker, h + 2, lib.ui.col.sideMarker); |
| 698 | } |
| 699 | } |
| 700 | |
| 701 | fadeMask(image, w, h) { |
| 702 | const mask = $Lib.gr(w, h, true, g => g.DrawImage(this.mask.fade, 0, h - this.overlayHeight, w, this.overlayHeight, 0, 0, this.mask.fade.Width, this.mask.fade.Height)); |
| 703 | image.ApplyMask(mask); |
| 704 | } |
| 705 | |
| 706 | format(image, n, type, w, h, fade, caller, i, key) { |
| 707 | let ix = 0; |
| 708 | let iy = 0; |
| 709 | let iw = image.Width; |
| 710 | let ih = image.Height; |
| 711 | switch (type) { |
| 712 | case 'crop': |
| 713 | case 'circular': { |
| 714 | const s1 = iw / w; |
| 715 | const s2 = ih / h; |
| 716 | const r = s1 / s2; |
| 717 | if (this.needTrim(n, r)) { |
| 718 | if (s1 > s2) { |
| 719 | iw = Math.round(w * s2); |
| 720 | ix = Math.round((image.Width - iw) / 2); |
| 721 | } else { |
| 722 | ih = Math.round(h * s1); |
| 723 | iy = Math.round((image.Height - ih) / 8); |
| 724 | } |
| 725 | image = image.Clone(ix, iy, iw, ih); |
| 726 | } |
| 727 | image = image.Resize(w, h, 7); |
| 728 | if (type == 'circular') this.circularMask(image, image.Width, image.Height); |
| 729 | break; |
| 730 | } |
| 731 | |
| 732 | default: { |
| 733 | const sc = caller != 'save' ? Math.min(h / ih, w / iw) : Math.max(h / ih, w / iw); |
| 734 | const im_w = Math.round(iw * sc); |
| 735 | const im_h = Math.round(ih * sc); |
| 736 | image = image.Resize(im_w, im_h, 7); |
| 737 | break; |
| 738 | } |
| 739 | } |
| 740 | if (fade) this.fadeMask(image, image.Width, image.Height); |
| 741 | if (caller.startsWith('display')) { |
| 742 | this.cache[key] = { |
| 743 | img: image, |
| 744 | accessed: caller == 'display' ? ++this.accessed : 0 |
| 745 | }; |
| 746 | } |
| 747 | else return image; |
| 748 | } |
| 749 | |
| 750 | getCurrentDatabase() { |
| 751 | this.albumArtDiskCache = libSet.albumArtDiskCache; |
| 752 | if (!this.albumArtDiskCache) return; |
| 753 | const cacheFolder = this.cacheFolder; |
| 754 | $Lib.buildPth(this.cachePath); |
| 755 | this.saveSize = this.im.w > 500 ? 750 : this.im.w > 250 ? 500 : 250; |
| 756 | this.interval = { |
| 757 | cache: this.saveSize == 250 ? 1 : this.saveSize == 500 ? 4 : 9, |
| 758 | preLoad: this.saveSize == 250 ? (libSet.albumArtLabelType != 3 ? 7 : 15) : this.saveSize == 500 ? 20 : 45 |
| 759 | } |
| 760 | this.cacheFolder = `${this.cachePath + ['front', 'back', 'disc', 'icon', 'artist'][libSet.artId] + (this.saveSize == 250 ? '' : this.saveSize)}\\`; |
| 761 | $Lib.create(this.cacheFolder); |
| 762 | this.database = $Lib.jsonParse(`${this.cacheFolder}database.dat`, this.newDatabase(), 'file'); |
| 763 | if (this.cacheFolder != cacheFolder) { |
| 764 | this.preLoadItems = []; |
| 765 | clearInterval(this.timer.preLoad); |
| 766 | this.timer.preLoad = null; |
| 767 | this.items = []; |
| 768 | clearInterval(this.timer.load); |
| 769 | this.timer.load = null; |
| 770 | this.toSave = []; |
| 771 | clearInterval(this.timer.save); |
| 772 | this.timer.save = null; |
| 773 | } |
| 774 | } |
| 775 | |
| 776 | getField(handle, name, arr) { |
| 777 | const f = handle.GetFileInfo(); |
| 778 | if (f) { |
| 779 | for (let i = 0; i < f.MetaCount; ++i) { |
| 780 | let fullName = ''; |
| 781 | for (let j = 0; j < f.MetaValueCount(i); ++j) { |
| 782 | fullName += f.MetaValue(i, j) + (j < f.MetaValueCount(i) - 1 ? ', ' : ''); |
| 783 | if (f.MetaValue(i, j) == name || fullName == name) arr.push(f.MetaName(i).toLowerCase()); |
| 784 | } |
| 785 | } |
| 786 | } |
| 787 | } |
| 788 | |
| 789 | getGrpCol(item, nowp, hover) { |
| 790 | return nowp ? lib.ui.col.nowp : hover ? (lib.panel.textDiffHighlight ? lib.ui.col.nowp : lib.ui.col.text_h) : item.sel ? !this.labels.overlayDark ? lib.ui.col.textSel : lib.ui.col.text : !this.labels.overlayDark ? lib.ui.col.text : RGB(240, 240, 240); |
| 791 | } |
| 792 | |
| 793 | getImages() { |
| 794 | const extraRows = this.albumArtDiskCache ? lib.panel.rows * 2 : lib.panel.rows; // will load any extra including those after any preLoad |
| 795 | |
| 796 | if (!lib.panel.imgView) return; |
| 797 | this.items = []; |
| 798 | let begin = this.start == 0 ? libSet.rootNode ? 1 : 0 : this.start; |
| 799 | const end = this.end != 0 ? Math.min(this.end + this.columns * extraRows, lib.pop.tree.length) : this.end; |
| 800 | for (let i = begin; i < end; i++) { |
| 801 | if (!lib.pop.tree[i]) continue; |
| 802 | const key = lib.pop.tree[i].key; |
| 803 | if (key && !this.cache[key]) { |
| 804 | this.items.push({ |
| 805 | ix: i, |
| 806 | handle: lib.pop.tree[i].handle, |
| 807 | key |
| 808 | }); |
| 809 | } |
| 810 | } |
| 811 | |
| 812 | begin = Math.max(libSet.rootNode ? 1 : 0, begin - this.columns * extraRows); |
| 813 | |
| 814 | let i = end; |
| 815 | while (i--) { |
| 816 | if (i < begin) break; |
| 817 | if (!lib.pop.tree[i]) continue; |
| 818 | const key = lib.pop.tree[i].key; |
| 819 | if (key && !this.cache[key]) { |
| 820 | this.items.push({ |
| 821 | ix: i, |
| 822 | handle: lib.pop.tree[i].handle, |
| 823 | key |
| 824 | }); |
| 825 | } |
| 826 | } |
| 827 | if (!this.items.length) return; |
| 828 | |
| 829 | let interval = !lib.sbar.bar.isDragging && !lib.sbar.touch.dn ? 5 : 50; |
| 830 | const allCached = this.albumArtDiskCache ? this.items.every(v => v.key && this.database[v.key]) : false; |
| 831 | if (allCached) interval = this.interval.cache; |
| 832 | |
| 833 | clearInterval(this.timer.load); |
| 834 | this.timer.load = null; |
| 835 | let j = 0; |
| 836 | this.timer.load = setInterval(() => { |
| 837 | if (j < this.items.length) { |
| 838 | const v = this.items[j]; |
| 839 | const key = v.key; |
| 840 | if (!this.cache[key]) { |
| 841 | if (this.albumArtDiskCache && $Lib.file(this.cacheFolder + this.database[key])) { |
| 842 | this.cache[key] = { |
| 843 | img: 'called', |
| 844 | accessed: ++this.accessed |
| 845 | }; |
| 846 | this.load_image_async(key, this.cacheFolder + this.database[key], v.ix); |
| 847 | } else { |
| 848 | this.cache[key] = { |
| 849 | img: 'called', |
| 850 | accessed: ++this.accessed |
| 851 | }; |
| 852 | if (v.handle) this.get_album_art_async(v.handle, libSet.artId, key, v.ix); |
| 853 | } |
| 854 | } |
| 855 | j++; |
| 856 | } else { |
| 857 | clearInterval(this.timer.load); |
| 858 | this.timer.load = null; |
| 859 | } |
| 860 | }, interval); |
| 861 | } |
| 862 | |
| 863 | getImg(key) { |
| 864 | const o = this.cache[key]; |
| 865 | if (!o || o.img == 'called') return undefined; |
| 866 | o.accessed = ++this.accessed; |
| 867 | return o.img; |
| 868 | } |
| 869 | |
| 870 | getItem(i) { |
| 871 | if (!lib.pop.tree[i]) { |
| 872 | return null; |
| 873 | } |
| 874 | const key = lib.pop.tree[i].key; |
| 875 | if (!this.cache[key] && this.database[key] && this.database[key] != 'noAlbumArt') { |
| 876 | if ($Lib.file(this.cacheFolder + this.database[key])) { // cacheItPreload if file exists |
| 877 | return { |
| 878 | ix: i, |
| 879 | key |
| 880 | } |
| 881 | } |
| 882 | } |
| 883 | return null; |
| 884 | } |
| 885 | |
| 886 | getItemsToDraw(preLoad) { |
| 887 | switch (true) { |
| 888 | case this.style.vertical: |
| 889 | if (lib.pop.tree.length <= lib.panel.rows * this.columns) { |
| 890 | this.start = 0; |
| 891 | this.end = lib.pop.tree.length; |
| 892 | } else { |
| 893 | this.start = Math.round(lib.sbar.delta / this.row.h) * this.columns; |
| 894 | this.start = $Lib.clamp(this.start, 0, this.start - this.columns); |
| 895 | this.end = Math.ceil((lib.sbar.delta + this.panel.h) / this.row.h) * this.columns; |
| 896 | this.end = Math.min(this.end, lib.pop.tree.length); |
| 897 | } |
| 898 | break; |
| 899 | case !this.style.vertical: |
| 900 | if (lib.pop.tree.length <= lib.panel.rows) { |
| 901 | this.start = 0; |
| 902 | this.end = lib.pop.tree.length; |
| 903 | } else { |
| 904 | this.start = Math.round(lib.sbar.delta / this.blockWidth); |
| 905 | this.end = Math.min(this.start + lib.panel.rows + 2, lib.pop.tree.length); |
| 906 | this.start = $Lib.clamp(this.start, 0, this.start - 1); |
| 907 | } |
| 908 | break; |
| 909 | } |
| 910 | this.albumArtDiskCache ? (preLoad ? this.preLoad() : this.getImages()) : this.loadThrottle(); |
| 911 | } |
| 912 | |
| 913 | getLotCol(item, nowp, hover) { |
| 914 | return nowp ? lib.ui.col.nowp : hover ? (lib.panel.textDiffHighlight ? lib.ui.col.nowp : lib.ui.col.text_h) : item.sel ? !this.labels.overlayDark ? lib.ui.col.selBlend : lib.ui.col.lotBlend : !this.labels.overlayDark ? lib.ui.col.lotBlend : RGB(220, 220, 220); |
| 915 | } |
| 916 | |
| 917 | getMostFrequentField(arr) { |
| 918 | const counts = arr.reduce((a, c) => { |
| 919 | a[c] = (a[c] || 0) + 1; |
| 920 | return a; |
| 921 | }, {}); |
| 922 | const maxCount = Math.max(...Object.values(counts)); |
| 923 | const mostFrequent = Object.keys(counts).filter(k => counts[k] === maxCount); |
| 924 | return lib.panel.grp[libSet.viewBy].type.includes(mostFrequent[0]) ? mostFrequent[0] : ''; |
| 925 | } |
| 926 | |
| 927 | getShadow() { |
| 928 | const xy = this.im.w * 0.02; |
| 929 | let wh = this.style.image ? this.im.w * 0.985 : this.im.w; // draw at actual size if possible as faster; regular have to be resized during draw |
| 930 | const sz = this.im.w * 1.17; |
| 931 | if (this.style.image != 2) { |
| 932 | this.shadow = $Lib.gr(sz, sz, true, g => g.FillSolidRect(xy, xy, wh, wh, RGBA(0, 0, 0, 128))); |
| 933 | this.shadow.StackBlur(5); |
| 934 | } else { |
| 935 | this.shadow = $Lib.gr(sz, sz, true, g => g.FillEllipse(xy, xy, wh, wh, RGBA(0, 0, 0, 128))); |
| 936 | this.shadow.StackBlur(4); |
| 937 | } |
| 938 | wh = this.im.w * 0.985; // always drawn at actual size |
| 939 | if (libSet.artId == 4) { |
| 940 | if (libSet.curNoArtistImg == 0 || libSet.curNoArtistImg == 2 || this.style.image == 2) { |
| 941 | this.shadowStub = $Lib.gr(sz, sz, true, g => g.FillEllipse(xy, xy, wh, wh, RGBA(0, 0, 0, 128))); |
| 942 | this.shadowStub.StackBlur(4); |
| 943 | } else if (libSet.curNoArtistImg != 4) { |
| 944 | this.shadowStub = $Lib.gr(sz, sz, true, g => g.FillSolidRect(xy, xy, wh, wh, RGBA(0, 0, 0, 128))); |
| 945 | this.shadowStub.StackBlur(5); |
| 946 | } else { |
| 947 | this.shadowStub = null; |
| 948 | } |
| 949 | } else if (libSet.curNoCoverImg > 2) { |
| 950 | this.shadowStub = $Lib.gr(sz, sz, true, g => g.FillSolidRect(xy, xy, wh, wh, RGBA(0, 0, 0, 128))); |
| 951 | this.shadowStub.StackBlur(5); |
| 952 | } else { |
| 953 | this.shadowStub = null; |
| 954 | } |
| 955 | } |
| 956 | |
| 957 | getRootImg(key) { |
| 958 | const o = this.cache[key]; |
| 959 | if (!o || o.img == 'called') return undefined; |
| 960 | o.accessed = ++this.accessed; |
| 961 | return o.img; |
| 962 | } |
| 963 | |
| 964 | getSelBgCol(item, nowp) { |
| 965 | return nowp || item.sel ? this.albumArtShowLabels ? lib.ui.col.imgBgSel : lib.ui.col.imgOverlaySel : RGBA(0, 0, 0, 175); |
| 966 | } |
| 967 | |
| 968 | getStyle() { |
| 969 | switch (libSet.artId) { |
| 970 | case 0: |
| 971 | return libSet.imgStyleFront; |
| 972 | case 1: |
| 973 | return libSet.imgStyleBack; |
| 974 | case 2: |
| 975 | return libSet.imgStyleDisc; |
| 976 | case 3: |
| 977 | return libSet.imgStyleIcon; |
| 978 | case 4: |
| 979 | return libSet.imgStyleArtist; |
| 980 | } |
| 981 | } |
| 982 | |
| 983 | load() { |
| 984 | const albumArtGrpNames = $Lib.jsonParse(libSet.albumArtGrpNames, {}); |
| 985 | const fields = []; |
| 986 | const mod = lib.pop.tree.length < 1000 ? 1 : lib.pop.tree.length < 3500 ? Math.round(lib.pop.tree.length / 1000) : 3; |
| 987 | const tf_d = FbTitleFormat('[$year(%date%)]'); |
| 988 | this.groupField = albumArtGrpNames[`${lib.panel.grp[libSet.viewBy].type.trim()}${lib.panel.lines}`]; |
| 989 | |
| 990 | lib.pop.tree.forEach((v, i) => { |
| 991 | const item = v.item[0].start; |
| 992 | if (item >= lib.panel.list.Count) return; |
| 993 | const handle = lib.panel.list[item]; |
| 994 | v.handle = handle; |
| 995 | const arr = lib.pop.tree[i].name.split('^@^'); |
| 996 | v.grp = lib.panel.lines == 1 || !libSet.albumArtFlipLabels ? arr[0] : arr[1]; |
| 997 | v.lot = lib.panel.lines == 2 ? !libSet.albumArtFlipLabels ? arr[1] : arr[0] : ''; |
| 998 | v.key = libMD5.hashStr(handle.Path + handle.SubSong + (lib.panel.lines == 1 ? (arr[0] || 'Unknown') : (`${arr[0] || 'Unknown'} - ${arr[1] || 'Unknown'}`)) + libSet.artId); |
| 999 | if (libSet.itemOverlayType == 2) v.year = tf_d.EvalWithMetadb(handle).replace('0000', ''); |
| 1000 | if (!this.groupField && !lib.panel.folderView && i % mod === 0) this.getField(handle, lib.panel.lines == 1 || libSet.albumArtFlipLabels ? v.grp : v.lot, fields); |
| 1001 | }); |
| 1002 | if (!this.groupField && !lib.panel.folderView) { |
| 1003 | this.groupField = this.getMostFrequentField(fields) || '项'; |
| 1004 | this.groupField = $Lib.titlecase(this.groupField); |
| 1005 | } |
| 1006 | |
| 1007 | if (libSet.rootNode) { |
| 1008 | if (!lib.pop.tree[0]) return; |
| 1009 | if (!this.groupField) this.groupField = '项'; |
| 1010 | const plurals = this.groupField.split(' ').map(v => pluralize(v)); |
| 1011 | const pluralField = plurals.join(' ').replace(Regex.LibTypesPlural, '$1 ').replace(Regex.LibSimilarArtist, '$1s ').replace(Regex.LibYearsAlbums, '年份 - 专辑'); |
| 1012 | lib.pop.tree[0].key = lib.pop.tree[0].name; |
| 1013 | const ln1 = lib.pop.tree.length - 1; |
| 1014 | const ln2 = lib.panel.list.Count; |
| 1015 | const nm = `${!libSet.showSource ? '全部' : lib.panel.sourceName} (${ln1}${ln1 > 1 ? ` ${pluralField}` : ` ${this.groupField}`})`; |
| 1016 | if (libSet.rootNode == 3) lib.pop.tree[0].grp = nm; |
| 1017 | else if (lib.panel.lines == 1) lib.pop.tree[0].grp = lib.panel.rootName + (libSet.nodeCounts ? ` (${libSet.nodeCounts == 2 && libSet.rootNode != 3 ? ln1 + (ln1 > 1 ? ` ${pluralField}` : ` ${this.groupField}`) : ln2 + (ln2 > 1 ? ' 个音轨' : ' 个音轨')})` : ''); |
| 1018 | if (lib.panel.lines == 2) { |
| 1019 | if (libSet.rootNode != 3) lib.pop.tree[0].grp = lib.panel.rootName; |
| 1020 | lib.pop.tree[0].lot = libSet.nodeCounts == 2 && libSet.rootNode != 3 ? ln1 + (ln1 > 1 ? ` ${pluralField}` : ` ${this.groupField}`) : ln2 + (ln2 > 1 ? ' 个音轨' : ' 个音轨'); |
| 1021 | } |
| 1022 | } |
| 1023 | this.metrics(); |
| 1024 | lib.panel.treePaint(); |
| 1025 | } |
| 1026 | |
| 1027 | memoryLimit() { |
| 1028 | if (!window.JsMemoryStats) return; |
| 1029 | const limit = !libSet.memoryLimit ? window.JsMemoryStats.TotalMemoryLimit * 0.5 : Math.min(libSet.memoryLimit * 1048576, window.JsMemoryStats.TotalMemoryLimit * 0.8); |
| 1030 | return window.JsMemoryStats.TotalMemoryUsage > limit; |
| 1031 | } |
| 1032 | |
| 1033 | metrics() { |
| 1034 | if (!lib.ui.w || !lib.ui.h) return; |
| 1035 | $Lib.gr(1, 1, false, g => { |
| 1036 | const lineSpacing = this.labels.hide || this.labels.overlay ? Math.max(libSet.verticalAlbumArtPad - 2, 0) : libSet.verticalAlbumArtPad; |
| 1037 | this.letter.w = Math.round(g.CalcTextWidth('W', lib.ui.font.main)); |
| 1038 | this.text.h = Math.max(Math.round(g.CalcTextHeight('String', lib.ui.font.group)) + lineSpacing, Math.round(g.CalcTextHeight('String', lib.ui.font.lot)) + lineSpacing, 10); |
| 1039 | }); |
| 1040 | this.style = { |
| 1041 | dropShadow: libSet.albumArtDropShadow && libSet.albumArtLabelType != 3, |
| 1042 | dropShadowStub: libSet.albumArtDropShadow && libSet.albumArtLabelType != 3 && (libSet.artId == 4 || libSet.curNoCoverImg > 2), |
| 1043 | image: this.getStyle(), |
| 1044 | rootComposite: libSet.rootNode && libSet.curRootImg == 3, |
| 1045 | vertical: !libSet.albumArtFlowMode ? true : lib.ui.h - lib.panel.search.h > lib.ui.w - lib.ui.sbar.w |
| 1046 | } |
| 1047 | |
| 1048 | this.style.dropGrad = libSet.albumArtDropShadow && !this.style.dropShadow; |
| 1049 | this.style.dropGradStub = libSet.albumArtDropShadow && !this.style.dropShadowStub; |
| 1050 | |
| 1051 | this.letter.show = libSet.albumArtLetter; |
| 1052 | this.letter.no = libSet.albumArtLetterNo; |
| 1053 | this.letter.albumArtYearAuto = libSet.albumArtYearAuto; |
| 1054 | |
| 1055 | switch (this.style.vertical) { |
| 1056 | case true: { |
| 1057 | this.labels = { |
| 1058 | hide: !libSet.albumArtLabelType, |
| 1059 | bottom: libSet.albumArtLabelType == 1 || libSet.albumArtFlowMode && libSet.albumArtLabelType == 2, |
| 1060 | right: !libSet.albumArtFlowMode ? libSet.albumArtLabelType == 2 : false, |
| 1061 | overlay: libSet.albumArtLabelType == 3 || libSet.albumArtLabelType == 4, |
| 1062 | overlayDark: libSet.albumArtLabelType == 4, |
| 1063 | flip: libSet.albumArtFlipLabels, |
| 1064 | statistics: libSet.itemShowStatistics ? 1 : 0 |
| 1065 | }; |
| 1066 | this.bor.pad = !this.labels.hide && !this.labels.overlay ? (libSet.thumbNailGapStnd == 0 ? Math.round(this.text.h * (!this.labels.right && grSet.libraryThumbnailSize !== 'playlist' ? 1.05 : 0.75)) : libSet.thumbNailGapStnd - Math.round(2 * $Lib.scale)) : libSet.thumbNailGapCompact; |
| 1067 | this.im.offset = Math.round(!this.labels.hide && !this.labels.overlay ? this.bor.pad / 2 : -2); |
| 1068 | |
| 1069 | if (this.labels.hide || this.labels.overlay) { |
| 1070 | this.panel.y = lib.panel.search.h + Math.round(this.bor.pad / 2); |
| 1071 | this.bor.bot = 0; |
| 1072 | this.bor.side = 0; |
| 1073 | this.bor.cov = libSet.thumbNailGapCompact; |
| 1074 | } else { |
| 1075 | this.panel.y = lib.panel.search.h; |
| 1076 | this.bor.cov = Math.round(this.bor.pad / 2); |
| 1077 | this.bor.side = Math.round(2 * $Lib.scale); |
| 1078 | this.bor.bot = this.bor.side * 2; |
| 1079 | } |
| 1080 | |
| 1081 | const margin = libSet.margin; |
| 1082 | this.panel.x = (libSet.sbarShow != 2 ? Math.max(margin, lib.ui.sbar.w) : margin) + lib.ui.l.w - SCALE(3); |
| 1083 | this.panel.w = lib.ui.w - lib.ui.l.w * 2 - (lib.ui.sbar.type == 0 || libSet.sbarShow != 2 ? Math.max(margin, lib.ui.sbar.w) * 2 + SCALE(20) : (margin * 2 + lib.ui.sbar.w) + SCALE(20)); |
| 1084 | this.panel.h = lib.ui.h - this.panel.y; |
| 1085 | |
| 1086 | this.blockWidth = grSet.libraryThumbnailSize === 'playlist' ? pl.thumbnail_size + (this.bor.side * 2 + this.bor.cov * 2) : |
| 1087 | Math.round(lib.ui.row.h * 4 * $Lib.scale * libSet.zoomImg / 100 * |
| 1088 | [// Thumbnail size |
| 1089 | 0.66, // Mini |
| 1090 | 1, // Small |
| 1091 | 1.5, // Regular |
| 1092 | 1.75, // Medium |
| 1093 | 2.5, // Large |
| 1094 | 3, // XL |
| 1095 | 3.5, // XXL |
| 1096 | 5 // MAX |
| 1097 | ][libSet.thumbNailSize]); |
| 1098 | |
| 1099 | this.columns = libSet.albumArtFlowMode || this.labels.right ? 1 : Math.max(Math.floor(this.panel.w / this.blockWidth), 1); |
| 1100 | let gap = this.panel.w - this.columns * this.blockWidth; |
| 1101 | gap = Math.floor(gap / this.columns); |
| 1102 | this.columnWidth = !this.labels.right ? $Lib.clamp(this.blockWidth + (grSet.libraryThumbnailSize === 'playlist' ? 0 : gap), 10, Math.min(this.panel.w, this.panel.h)) : $Lib.clamp(this.blockWidth, 10, Math.min(this.panel.w, this.panel.h)); |
| 1103 | this.overlayHeight = !this.labels.overlay ? 0 : (lib.panel.lines != 2 ? this.text.h * (1.2 + this.labels.statistics) : Math.round(this.text.h * (2.1 + this.labels.statistics))); |
| 1104 | this.im.w = Math.round(Math.max(this.columnWidth - this.bor.side * 2 - this.bor.cov * 2 - (this.labels.hide || this.labels.overlay ? 1 : 0), 10)); |
| 1105 | |
| 1106 | if (this.labels.hide || this.labels.overlay) { |
| 1107 | this.im.w = Math.round(Math.max(this.columnWidth - this.bor.cov, 10)); |
| 1108 | this.row.h = this.im.w + this.bor.cov; |
| 1109 | } else { |
| 1110 | this.im.w = Math.round(Math.max(this.columnWidth - this.bor.cov * 2 - this.bor.side * 2, 10)); |
| 1111 | this.row.h = !this.labels.right ? this.im.w + this.text.h * (lib.panel.lines + this.labels.statistics) + this.bor.cov * 2 + this.bor.side * 2 : this.im.w + this.bor.pad + 2; |
| 1112 | } |
| 1113 | if (this.row.h > this.panel.h) { |
| 1114 | this.im.w -= this.row.h - this.panel.h; |
| 1115 | this.im.w = Math.max(this.im.w, 10); |
| 1116 | this.row.h = this.panel.h; |
| 1117 | } |
| 1118 | this.box.w = this.columnWidth - this.bor.side * 2; |
| 1119 | this.box.h = this.row.h - (!this.labels.right ? this.bor.side * 2 : 0); |
| 1120 | lib.panel.rows = Math.max(Math.floor(this.panel.h / this.row.h)); |
| 1121 | lib.sbar.metrics(lib.sbar.x, lib.sbar.y, lib.sbar.w, lib.sbar.h, lib.panel.rows, this.row.h, this.style.vertical); |
| 1122 | lib.sbar.setRows(Math.ceil(lib.pop.tree.length / this.columns)); |
| 1123 | break; |
| 1124 | } |
| 1125 | case false: { // only H-Flow |
| 1126 | this.labels = { |
| 1127 | hide: !libSet.albumArtLabelType, |
| 1128 | bottom: libSet.albumArtLabelType == 1 || libSet.albumArtLabelType == 2, |
| 1129 | right: false, |
| 1130 | overlay: libSet.albumArtLabelType == 3 || libSet.albumArtLabelType == 4, |
| 1131 | overlayDark: libSet.albumArtLabelType == 4, |
| 1132 | flip: libSet.albumArtFlipLabels, |
| 1133 | statistics: libSet.itemShowStatistics ? 1 : 0 |
| 1134 | }; |
| 1135 | this.bor.pad = !this.labels.hide && !this.labels.overlay ? (libSet.thumbNailGapStnd == 0 ? Math.round(this.text.h * 1.05) : libSet.thumbNailGapStnd - Math.round(2 * $Lib.scale)) : libSet.thumbNailGapCompact; |
| 1136 | this.im.offset = Math.round(!this.labels.hide && !this.labels.overlay ? this.bor.pad / 2 : -2); |
| 1137 | if (this.labels.hide || this.labels.overlay) { |
| 1138 | this.bor.bot = 0; |
| 1139 | this.bor.side = 0; |
| 1140 | this.bor.cov = libSet.thumbNailGapCompact; |
| 1141 | } else { |
| 1142 | this.bor.cov = Math.round(this.bor.pad / 2); |
| 1143 | this.bor.side = Math.round(2 * $Lib.scale); |
| 1144 | this.bor.bot = this.bor.side * 2; |
| 1145 | } |
| 1146 | this.panel.x = 0; |
| 1147 | const spacer = this.letter.show ? (this.labels.bottom ? this.text.h * 0.5 - this.bor.pad / 4 : this.text.h * 0.75) : (this.labels.bottom ? 0 : Math.round(this.bor.pad / 2)); |
| 1148 | this.panel.y = lib.panel.search.h + spacer - SCALE(3); |
| 1149 | this.panel.h = lib.ui.h - this.panel.y - lib.ui.l.w * 3 - spacer - lib.ui.sbar.w - SCALE(34); |
| 1150 | |
| 1151 | this.panel.w = lib.ui.w; |
| 1152 | if (!this.labels.hide && !this.labels.overlay) { |
| 1153 | this.row.h = this.panel.h; |
| 1154 | const extra = this.text.h * (lib.panel.lines + this.labels.statistics) + this.bor.cov * 2 + this.bor.side * 2; |
| 1155 | this.im.w = Math.min(this.panel.h - extra, this.panel.h - extra); |
| 1156 | this.im.w = $Lib.clamp(this.im.w, 10, Math.round(this.panel.w - (this.bor.cov * 2 + this.bor.side * 2))); |
| 1157 | this.blockWidth = this.im.w + this.bor.cov * 2 + this.bor.side * 2; |
| 1158 | this.row.h = this.im.w + extra; |
| 1159 | } else { |
| 1160 | const extra = this.bor.cov; |
| 1161 | this.im.w = Math.min(this.panel.h - extra, this.panel.h - extra); |
| 1162 | this.im.w = $Lib.clamp(this.im.w, 10, Math.round(this.panel.w - this.bor.cov)); |
| 1163 | this.blockWidth = this.im.w + this.bor.cov; |
| 1164 | this.row.h = this.im.w + extra; |
| 1165 | } |
| 1166 | this.columns = Math.max(Math.floor(this.panel.w / this.blockWidth), 1); |
| 1167 | this.overlayHeight = !this.labels.overlay ? 0 : (lib.panel.lines != 2 ? this.text.h * (1.2 + this.labels.statistics) : Math.round(this.text.h * (2.1 + this.labels.statistics))); |
| 1168 | this.box.w = this.blockWidth - this.bor.side * 2; |
| 1169 | this.box.h = this.row.h - this.bor.bot; |
| 1170 | lib.panel.rows = Math.max(Math.floor(this.panel.w / this.blockWidth)); |
| 1171 | this.columnWidth = this.blockWidth; |
| 1172 | |
| 1173 | lib.sbar.metrics(lib.sbar.x, lib.sbar.y, lib.ui.w, lib.ui.sbar.w, lib.panel.rows, this.blockWidth, this.style.vertical); |
| 1174 | lib.sbar.setRows(Math.ceil(lib.pop.tree.length)); |
| 1175 | break; |
| 1176 | } |
| 1177 | } |
| 1178 | |
| 1179 | this.cellWidth = Math.max(200, this.im.w / 2); |
| 1180 | this.labels.counts = libSet.itemOverlayType != 1 && libSet.nodeCounts; |
| 1181 | this.style.y = this.style.vertical ? Math.floor(this.panel.y + (!this.labels.hide && !this.labels.overlay ? libSet.thumbNailGapStnd / 2 : libSet.thumbNailGapCompact / 2)) : this.panel.y; |
| 1182 | if (this.style.dropShadow) this.getShadow(); |
| 1183 | |
| 1184 | if (!this.labels.hide) { |
| 1185 | if (!this.labels.overlay) { |
| 1186 | this.text.x = !this.labels.right ? Math.round((this.box.w - this.im.w) / 2) : Math.max(Math.round((this.box.w - this.im.w) / 2), 5 * $Lib.scale) * 2 + this.im.w; |
| 1187 | this.text.y1 = !this.labels.right ? this.im.w + Math.round(this.bor.cov * 0.5) : Math.round((this.im.w - this.text.h * lib.panel.lines) / 2) - (this.labels.statistics ? this.text.h / 2 : 0); |
| 1188 | this.text.y2 = !this.labels.right ? Math.round(this.text.y1 + this.text.h * 0.95) : this.text.y1 + this.text.h; |
| 1189 | this.text.y3 = !this.labels.right ? Math.round(this.text.y2 + this.text.h * 0.95) : this.text.y2 + this.text.h; |
| 1190 | this.text.w = !this.labels.right ? this.im.w : this.panel.w - this.text.x - 12; |
| 1191 | } else { |
| 1192 | this.text.x = Math.round(10 + (libSet.thumbNailGapCompact - 3) / 2); |
| 1193 | this.text.y1 = Math.round(this.im.w - this.overlayHeight + 2 + (this.overlayHeight - this.text.h * (lib.panel.lines + this.labels.statistics)) / 2); |
| 1194 | this.text.w = this.box.w - 20 - libSet.thumbNailGapCompact - 6; |
| 1195 | libSet.thumbNailGapCompact = 22; |
| 1196 | } |
| 1197 | } |
| 1198 | |
| 1199 | this.cachesize.min = lib.panel.rows * this.columns * 3 + (this.albumArtDiskCache ? lib.panel.rows * 2 : lib.panel.rows) * this.columns * 2; |
| 1200 | this.createImages(); |
| 1201 | this.getCurrentDatabase(); |
| 1202 | if (libSet.albumArtPreLoad && !this.zooming && this.albumArtDiskCache) this.getItemsToDraw(true); |
| 1203 | this.setNoArtist(); |
| 1204 | this.setNoCover(); |
| 1205 | this.setRoot(); |
| 1206 | if (this.style.rootComposite) this.checkRootImg(); |
| 1207 | const stub = libSet.artId != 4 ? this.no_cover_img : this.no_artist_img; |
| 1208 | if (stub) this.stub.noImg = this.format(stub, libSet.artId, ['default', 'default', 'circular'][this.style.image], this.im.w, this.im.w, libSet.albumArtLabelType == 3, 'noImg'); |
| 1209 | if (this.root_img) this.stub.root = this.format(this.root_img, libSet.artId, 'default', this.im.w, this.im.w, libSet.albumArtLabelType == 3, 'root'); |
| 1210 | lib.panel.treePaint(); |
| 1211 | } |
| 1212 | |
| 1213 | needTrim(n, ratio) { |
| 1214 | return n || Math.abs(ratio - 1) >= 0.05; |
| 1215 | } |
| 1216 | |
| 1217 | newDatabase() { |
| 1218 | return { |
| 1219 | '-----------group key------------': '-----------image key------------.jpg' |
| 1220 | }; |
| 1221 | } |
| 1222 | |
| 1223 | on_key_down() { |
| 1224 | this.zooming = lib.vk.k('zoom'); |
| 1225 | if (this.zooming) { |
| 1226 | clearInterval(this.timer.preLoad); |
| 1227 | this.timer.preLoad = null; |
| 1228 | } |
| 1229 | } |
| 1230 | |
| 1231 | on_key_up() { |
| 1232 | if (this.zooming && this.zooming != lib.vk.k('zoom')) { |
| 1233 | this.zooming = false; |
| 1234 | if (libSet.albumArtPreLoad && this.albumArtDiskCache && lib.panel.imgView) this.metrics(); |
| 1235 | lib.panel.treePaint(); |
| 1236 | } |
| 1237 | } |
| 1238 | |
| 1239 | preLoad() { |
| 1240 | if (!lib.panel.imgView) return; |
| 1241 | this.preLoadItems = []; |
| 1242 | const begin = this.start == 0 ? libSet.rootNode ? 1 : 0 : this.start; |
| 1243 | const end = this.end != 0 ? Math.min(this.end + this.columns, lib.pop.tree.length) : this.end; |
| 1244 | for (let i = begin; i < end; i++) { |
| 1245 | const v = this.getItem(i); |
| 1246 | |
| 1247 | if (v) { |
| 1248 | const key = v.key; |
| 1249 | if (!this.cache[key]) { |
| 1250 | this.cache[key] = { |
| 1251 | img: 'called', |
| 1252 | accessed: ++this.accessed |
| 1253 | }; |
| 1254 | this.load_image_async(key, this.cacheFolder + this.database[key], v.ix, true); |
| 1255 | } |
| 1256 | } |
| 1257 | } |
| 1258 | |
| 1259 | const upBegin = this.start == 0 ? libSet.rootNode ? 1 : 0 : this.start - 1; |
| 1260 | const upEnd = libSet.rootNode ? 1 : 0; |
| 1261 | const downBegin = this.end != 0 ? Math.min(this.end + 1 + this.columns, lib.pop.tree.length) : this.end; |
| 1262 | const downEnd = lib.pop.tree.length; |
| 1263 | |
| 1264 | const doPreload = () => { |
| 1265 | clearInterval(this.timer.preLoad); |
| 1266 | this.timer.preLoad = null; |
| 1267 | let i = downBegin; |
| 1268 | let j = upBegin; |
| 1269 | if (i < downEnd || j > upEnd) { |
| 1270 | this.timer.preLoad = setInterval(() => { |
| 1271 | let v = null; |
| 1272 | if (i < downEnd || j > upEnd) { // interleave |
| 1273 | if (i < downEnd) v = this.getItem(i++); |
| 1274 | if (v) { |
| 1275 | const key = v.key; |
| 1276 | if (!this.cache[key]) { |
| 1277 | this.cache[key] = { |
| 1278 | img: 'called', |
| 1279 | accessed: 0 |
| 1280 | }; |
| 1281 | this.load_image_async(key, this.cacheFolder + this.database[key], v.ix, true); |
| 1282 | } |
| 1283 | } |
| 1284 | |
| 1285 | if (j > upEnd) v = this.getItem(j--); |
| 1286 | if (v) { |
| 1287 | const key = v.key; |
| 1288 | if (!this.cache[key]) { |
| 1289 | this.cache[key] = { |
| 1290 | img: 'called', |
| 1291 | accessed: 0 |
| 1292 | }; |
| 1293 | this.load_image_async(key, this.cacheFolder + this.database[key], v.ix, true); |
| 1294 | } |
| 1295 | } |
| 1296 | } else { |
| 1297 | clearInterval(this.timer.preLoad); |
| 1298 | this.timer.preLoad = null; |
| 1299 | } |
| 1300 | }, this.interval.preLoad); |
| 1301 | } |
| 1302 | }; |
| 1303 | |
| 1304 | doPreload(); |
| 1305 | } |
| 1306 | |
| 1307 | refresh(items) { |
| 1308 | let itemsToRemove = []; |
| 1309 | if (items === 'all') { |
| 1310 | if (!libSet.albumArtDiskCache) return; |
| 1311 | const continue_confirmation = (status, confirmed) => { |
| 1312 | if (confirmed) { |
| 1313 | try { |
| 1314 | this.clearCache(); |
| 1315 | const app = new ActiveXObject('Shell.Application'); |
| 1316 | app.NameSpace(10).MoveHere(this.cachePath); // remove all saved images & databases if albumArtDiskCache |
| 1317 | } catch (e) { |
| 1318 | $Lib.trace('无法清空图片缓存:可以在windows资源管理器中清空'); // Wine fix |
| 1319 | } |
| 1320 | } |
| 1321 | }; |
| 1322 | const caption = '刷新全部图片'; |
| 1323 | const prompt = '此操作将重置曲库(library tree)缩略图磁盘缓存\n\n继续?'; |
| 1324 | const wsh = lib.popUpBox.isHtmlDialogSupported() ? lib.popUpBox.confirm(caption, prompt, '是', '否', false, 'center', continue_confirmation) : true; |
| 1325 | if (wsh) continue_confirmation('ok', $Lib.wshPopup(prompt, caption)); |
| 1326 | return; |
| 1327 | } |
| 1328 | |
| 1329 | const allSelected = lib.pop.sel_items.length == fb.GetLibraryItems().Count; |
| 1330 | const base = this.cachePath + ['front', 'back', 'disc', 'icon', 'artist'][libSet.artId]; |
| 1331 | const databases = [`${base}\\database.dat`, `${base}500\\database.dat`, `${base}750\\database.dat`]; |
| 1332 | if (allSelected) { |
| 1333 | this.clearCache(); // full clear of working cache |
| 1334 | if (!libSet.albumArtDiskCache) return; |
| 1335 | this.database = this.newDatabase(); // full clear of databases for current image type |
| 1336 | databases.forEach(v => { |
| 1337 | if ($Lib.file(v)) $Lib.save(v, JSON.stringify(this.newDatabase(), null, 3), true); |
| 1338 | }); |
| 1339 | return; |
| 1340 | } |
| 1341 | |
| 1342 | // refresh selected images |
| 1343 | items.Convert().forEach(v => { |
| 1344 | const item = lib.panel.list.Find(v); |
| 1345 | let ind = -1; |
| 1346 | lib.pop.tree.forEach((v, j) => { |
| 1347 | if (!v.root && lib.pop.inRange(item, v.item)) ind = j; |
| 1348 | }); |
| 1349 | if (ind != -1) itemsToRemove.push(lib.pop.tree[ind].key); |
| 1350 | }); |
| 1351 | itemsToRemove = [...new Set(itemsToRemove)]; |
| 1352 | itemsToRemove.forEach(v => this.trimCache(v)); // clear working cache of selected keys: won't check if same images are used with other keys |
| 1353 | if (!libSet.albumArtDiskCache) return; |
| 1354 | let imgsToRemove = itemsToRemove.map(v => this.database[v]); |
| 1355 | imgsToRemove = [...new Set(imgsToRemove)]; |
| 1356 | databases.forEach(v => { |
| 1357 | if ($Lib.file(v)) { |
| 1358 | const cur_db = v == `${this.cacheFolder}database.dat`; |
| 1359 | const imgDatabase = $Lib.jsonParse(v, this.newDatabase(), 'file'); |
| 1360 | Object.entries(imgDatabase).forEach(w => { // clear working cache & database of all keys that reference a particular image: this should always work even if images in use |
| 1361 | if (w[0] != '-----------group key------------') { |
| 1362 | if (imgsToRemove.includes(w[1]) || !$Lib.file(this.cacheFolder + w[1])) { // images are refreshed as loaded, overwriting existing |
| 1363 | if (cur_db) this.trimCache(w[0]); |
| 1364 | delete imgDatabase[w[0]]; |
| 1365 | } |
| 1366 | } |
| 1367 | }); |
| 1368 | Object.entries(imgDatabase).forEach(w => { |
| 1369 | if (w[0] != '-----------group key------------') { |
| 1370 | if (w[1] != 'noAlbumArt' && !$Lib.file(this.cacheFolder + w[1])) delete imgDatabase[w[0]]; |
| 1371 | } |
| 1372 | }); // remove any user deleted images from database |
| 1373 | $Lib.save(v, JSON.stringify(imgDatabase, null, 3), true); |
| 1374 | } |
| 1375 | }); |
| 1376 | this.getCurrentDatabase(); |
| 1377 | } |
| 1378 | |
| 1379 | setNoArtist() { |
| 1380 | this.artist_images = lib_my_utils.getImageAssets('noArtist').sort(); |
| 1381 | libSet.curNoArtistImg = $Lib.clamp($Lib.value(libSet.curNoArtistImg, 0, 0), 0, this.artist_images.length - 1); |
| 1382 | const artistImages = this.artist_images.map(v => ({ |
| 1383 | name: utils.SplitFilePath(v)[1], |
| 1384 | path: `file://${v.replace('noArtist', 'noArtist/small')}` |
| 1385 | })); |
| 1386 | this.no_artist_img = gdi.Image(this.artist_images[libSet.curNoArtistImg]); |
| 1387 | libSet.noArtistImages = JSON.stringify(artistImages); |
| 1388 | } |
| 1389 | |
| 1390 | setNoCover() { |
| 1391 | this.cover_images = lib_my_utils.getImageAssets('noCover').sort(); |
| 1392 | libSet.curNoCoverImg = $Lib.clamp($Lib.value(libSet.curNoCoverImg, 0, 0), 0, this.cover_images.length - 1); |
| 1393 | const coverImages = this.cover_images.map(v => ({ |
| 1394 | name: utils.SplitFilePath(v)[1], |
| 1395 | path: `file://${v.replace('noCover', 'noCover/small')}` |
| 1396 | })); |
| 1397 | this.no_cover_img = gdi.Image(this.cover_images[libSet.curNoCoverImg]); |
| 1398 | libSet.noCoverImages = JSON.stringify(coverImages); |
| 1399 | } |
| 1400 | |
| 1401 | setRoot() { |
| 1402 | this.root_images = lib_my_utils.getImageAssets('root').sort(); |
| 1403 | libSet.curRootImg = $Lib.clamp($Lib.value(libSet.curRootImg, 0, 0), 0, this.root_images.length - 1); |
| 1404 | const rootImages = this.root_images.map(v => ({ |
| 1405 | name: utils.SplitFilePath(v)[1], |
| 1406 | path: `file://${v.replace('root', 'root/small')}` |
| 1407 | })); |
| 1408 | if (libSet.rootNode && libSet.curRootImg == 3) { |
| 1409 | this.style.rootComposite = true; |
| 1410 | this.root_img = null; |
| 1411 | } else { |
| 1412 | this.style.rootComposite = false; |
| 1413 | this.root_img = gdi.Image(this.root_images[libSet.curRootImg]); |
| 1414 | } |
| 1415 | libSet.rootImages = JSON.stringify(rootImages); |
| 1416 | } |
| 1417 | |
| 1418 | sort(data, prop) { |
| 1419 | data.sort((a, b) => a[prop] - b[prop]); |
| 1420 | return data; |
| 1421 | } |
| 1422 | |
| 1423 | sortCache(o, prop) { |
| 1424 | const sorted = {}; |
| 1425 | Object.keys(o).sort((a, b) => o[a][prop] - o[b][prop]).forEach(key => sorted[key] = o[key]); |
| 1426 | return sorted; |
| 1427 | } |
| 1428 | |
| 1429 | trimCache(key) { |
| 1430 | delete this.cache[key]; |
| 1431 | } |
| 1432 | } |
| 1433 | |
| 1434 | /** |
| 1435 | * The instance of `LibImages` class for library image operations. |
| 1436 | * @typedef {LibImages} |
| 1437 | * @global |
| 1438 | */ |
| 1439 | const libImg = new LibImages(); |
| 1440 |
lib-populate.js
· 89 KiB · JavaScript
Неформатований
'use strict';
class LibPopulate {
constructor() {
this.alt_dbl_clicked = false;
this.childCount = 0;
this.clicked_on = 'none';
this.cur = [];
this.cur_ix = 0;
this.customSort = FbTitleFormat(libSet.customSort);
this.dbl_clicked = false;
this.expandedTracks = 0;
this.expandLmt = 500;
this.hotKeys = $Lib.split(libSet.hotKeys, 0);
this.hand = false;
this.id = '';
this.inlineRoot = libSet.rootNode && (libSet.inlineRoot || libSet.facetView);
this.is_focused = false;
this.last_sel = -1;
this.lbtnDn = false;
this.libItems = false;
this.mbtn_dbl_clicked = false;
this.nd = [];
this.nowp = -1;
this.rows = 0;
this.sel_items = [];
this.selection_holder = fb.AcquireUiSelectionHolder();
this.selList = null;
this.setFocus = false;
this.specialChar = '[' + [Unicode.Apostrophe,
Unicode.SoftHyphen, Unicode.ArmenianHyphen, Unicode.Hyphen, Unicode.NonBreakingHyphen,
Unicode.FigureDash, Unicode.EnDash, Unicode.EmDash, Unicode.SmallEmDash
].join('') + ']';
this.sy_sz = 8;
this.tree = [];
this.cache = {
standard: {},
filter: {},
search: {}
}
this.highlight = {};
this.getMainMenuIndex = {
add: parseFloat(this.hotKeys[3]),
collapseAll: parseFloat(this.hotKeys[1]),
insert: parseFloat(this.hotKeys[5]),
new: parseFloat(this.hotKeys[7]),
searchClear: parseFloat(this.hotKeys[11]),
searchFocus: parseFloat(this.hotKeys[9])
};
this.last_pressed_coord = {
x: undefined,
y: undefined
};
this.m = {
br: -1,
cur_br: 0,
i: -1
};
this.row = {
cur: 0,
i: -1,
lineMax: [],
note_w: 0
};
this.tf = {
added: FbTitleFormat(libSet.tfAdded),
bitrate: FbTitleFormat('%bitrate%'),
bytes: FbTitleFormat('%path%|%filesize%'),
date: FbTitleFormat(libSet.tfDate),
firstPlayed: FbTitleFormat(libSet.tfFirstPlayed),
lastPlayed: FbTitleFormat(libSet.tfLastPlayed),
pc: FbTitleFormat(libSet.tfPc),
popularity: FbTitleFormat(libSet.tfPopularity),
rating: FbTitleFormat(libSet.tfRating)
};
this.triangle = {
expand: null,
highlight: null,
select: null
};
this.collator = new Intl.Collator(undefined, {
sensitivity: 'accent',
numeric: true
});
this.setActions();
this.setValues();
}
// * METHODS * //
activateTooltip(value) {
if (!grSet.showTooltipLibrary && !grSet.showTooltipTruncated || libTooltip.Text == value) return;
this.checkTooltipFont('tree');
if (grSet.showStyledTooltips) {
grm.ui.styledTooltipText = value;
} else {
libTooltip.Text = value;
libTooltip.Activate();
}
}
add(x, y, pl) {
if (y < lib.panel.search.h) return;
const ix = this.get_ix(x, y, true, false);
lib.panel.pos = ix;
if (ix < this.tree.length && ix >= 0) {
if (this.check_ix(this.tree[ix], x, y, true)) {
this.clearSelected();
this.tree[ix].sel = true;
this.getTreeSel();
this.load(this.sel_items, true, true, false, pl, false);
lib.lib.treeState(false, libSet.rememberTree);
}
}
}
addItems(arr, item) {
item.forEach(v => {
for (let i = v.start; i <= v.end; i++) arr.push(i);
});
}
arrayToRange(array) {
return array.slice().sort((a, b) => a - b).reduce((ranges, value) => {
const lastIndex = ranges.length - 1;
if (lastIndex === -1 || ranges[lastIndex].max !== value - 1) {
ranges.push({ min: value, max: value });
} else {
ranges[lastIndex].max = value;
}
return ranges;
}, []).map((range) => range.min !== range.max ? `${range.min}-${range.max}` : range.min.toString());
}
branch(br, base, node, block) {
if (!br || br.track || !lib.lib.initialised || lib.lib.list.Count != lib.lib.libNode.length) return;
const ix = this.showTracks ? 2 : 3;
const l = base ? 0 : this.rootNode ? br.level : br.level + 1;
if (base) node = false;
let i = 0;
let n = '';
let n_o = '#get_branch#';
let nU = '';
this.range(br.item).forEach(v => {
n = lib.lib.node[v][l];
nU = n.toUpperCase();
if (n_o != nU) {
n_o = nU;
if (lib.panel.multiPrefix) n = lib.lib.prefixes(n);
br.child[i] = {
nm: n,
sel: false,
child: [],
track: l > lib.lib.node[v].length - ix,
item: [v],
srt: lib.lib.sort(n)
};
i++;
} else br.child[i - 1].item.push(v);
});
this.condense(br.child);
this.buildTree(lib.lib.root, 0, node, true, block);
}
branchChange(br) {
const arr = br.level == 0 ? lib.lib.root : this.tree[br.par].child;
this.childCount = 0;
this.getChildCount(arr, br.ix);
arr.forEach(v => v.child = []);
return this.childCount;
}
branchCount(br, base, node, block, key, type) {
if (!br || !lib.lib.node.length) return;
if (this.cache[type][key]) return this.cache[type][key].value;
const l = base ? 0 : this.rootNode ? br.level : br.level + 1;
const b = [];
let n = '';
let n_o = '#get_branch#';
let nU = '';
if (base) node = false;
const full = !!br.root;
this.range(br.item).forEach(v => {
if (l < lib.lib.node[v].length) {
n = lib.lib.node[v][l];
nU = n.toUpperCase();
if (n_o != nU) {
n_o = nU;
if (lib.panel.multiPrefix) n = lib.lib.prefixes(n);
b.push({
nm: n,
srt: lib.lib.sort(n)
});
}
}
});
if (!lib.panel.multiProcess && (!node || node && !full)) this.merge(b, true);
if (lib.panel.multiProcess) {
const multi_cond = [];
const multi_obj = [];
const multi_rem = [];
const nm_arr = [];
let h = -1;
let j = 0;
let multi = [];
n = '';
n_o = '#condense#';
nU = '';
b.forEach((v, i) => {
if (v.nm.includes('@@')) {
multi = this.getAllCombinations(v.nm);
multi_rem.push(i);
multi.forEach(w => {
multi_obj.push({
nm: w.join(''),
srt: lib.lib.sort(w.join(''))
});
});
}
});
let i = multi_rem.length;
while (i--) b.splice(multi_rem[i], 1);
this.sort(multi_obj);
multi_obj.forEach(v => {
n = v.nm;
nU = n.toUpperCase();
if (n_o != nU) {
n_o = nU;
multi_cond[j] = {
nm: n,
srt: v.srt
};
j++;
}
});
b.forEach(v => {
v.nm = v.nm.replace(Regex.LibMarkerMultiProcess, '');
nm_arr.push(v.nm);
});
multi_cond.forEach((v, i) => {
h = nm_arr.indexOf(v.nm);
if (h != -1) multi_cond.splice(i, 1);
});
multi_cond.forEach((v, i) => b.splice(i + 1, 0, {
nm: v.nm,
srt: v.srt
}));
this.merge(b, true);
}
this.cache[type][key] = {
value: b.length,
items: []
}
return b.length;
}
buildTree(br, level, node, full, block) {
const l = !this.rootNode ? level : level - 1;
let i = 0;
let j = 0;
if (!br[0].sorted) {
switch (lib.panel.multiProcess) {
case false:
if (!node || node && !full) this.merge(br);
break;
case true: {
const multi_cond = [];
const multi_obj = [];
const multi_rem = [];
const nm_arr = [];
let h = -1;
let multi = [];
let n = '';
let n_o = '#condense#';
let nU = '';
br.forEach((v, i) => {
if (v.nm.includes('@@') || v.nm.includes(lib.panel.softSplitter)) {
multi = this.getAllCombinations(v.nm);
multi_rem.push(i);
multi.forEach(w => {
multi_obj.push({
nm: w.join(''),
item: this.copy(v.item),
track: v.track,
srt: lib.lib.sort(w.join(''))
});
});
}
});
i = multi_rem.length;
while (i--) br.splice(multi_rem[i], 1);
this.sort(multi_obj);
multi_obj.forEach(v => {
n = v.nm;
nU = n.toUpperCase();
if (n_o != nU) {
n_o = nU;
multi_cond[j] = {
nm: n,
item: this.copy(v.item),
track: v.track,
srt: v.srt
};
j++;
} else v.item.forEach(v => multi_cond[j - 1].item.push(v));
});
br.forEach(v => {
v.nm = v.nm.replace(Regex.LibMarkerMultiProcess, '');
nm_arr.push(v.nm);
});
multi_cond.forEach((v, i) => {
h = nm_arr.indexOf(v.nm);
if (h != -1) {
v.item.forEach(v => br[h].item.push(v));
multi_cond.splice(i, 1);
}
});
multi_cond.forEach((v, i) => br.splice(i + 1, 0, {
nm: v.nm,
sel: false,
track: v.track,
child: [],
item: this.copy(v.item),
srt: v.srt
}));
this.merge(br);
break;
}
}
this.sort(br);
br[0].sorted = true;
}
const br_l = br.length;
const par = this.tree.length - 1;
if (level == 0) this.clearTree();
// * Apply View By Folder Hide if View by Folder Structure is active
if (lib.panel.folderView) lib.men.setViewByFolderHide(this.tree, libSet.viewByFolderHide);
br.forEach((v, i) => {
j = this.tree.length;
const item = this.tree[j] = v;
item.top = !i;
item.bot = i == br_l - 1;
item.count = '';
item.ix = j;
item.level = level;
item.par = par;
switch (true) {
case libSet.facetView:
if (!item.root) item.track = true;
break;
case l != -1 && !this.showTracks:
this.range(item.item).some(v => {
if (lib.lib.node[v] && (lib.lib.node[v].length == l + 1 || lib.lib.node[v].length == l + 2)) return item.track = true;
});
break;
case l == 0 && lib.lib.node[item.item[0].start] && lib.lib.node[item.item[0].start].length == 1:
item.track = true;
break;
}
if (lib.ui.col.counts && (!item.track || !this.showTracks)) {
const str = `@!#${lib.ui.col.counts}\`${this.highlight.text ? lib.ui.col.text_h : lib.ui.col.counts}\`${lib.ui.col.textSel}@!#`;
if (!item.nm.endsWith(str)) item.nm += str;
}
item.name = !lib.panel.noDisplay ? item.nm : item.nm.replace(Regex.LibMarkerNoDisplayContent, '');
if (v.child.length > 0) this.buildTree(v.child, level + 1, node, !!item.root);
});
if (lib.ui.style.squareNode && lib.ui.col.line) {
this.row.lineMax = [];
this.tree.forEach(v => {
const depth = !this.inlineRoot ? v.level : Math.max(v.level - 1, 0)
this.row.lineMax[depth] = v.ix
});
}
if (this.rootNode == 3) this.tree[0].name = this.tree[0].child.length > 1 ? lib.panel.rootName.replace('#^^^^#', this.tree[0].child.length) : lib.panel.rootName1;
lib.find.initials = null;
if (!block) {
lib.sbar.setRows(this.tree.length);
lib.panel.treePaint();
}
}
butTooltipFont() {
return [grFont.fontDefault, 15 * $Lib.scale * libSet.zoomTooltipBut / 100, 0];
}
calcStatistics(v) {
const key = `stat${this.getKey(v)}`;
const type = lib.panel.search.txt ? 'search' : libSet.filterBy ? 'filter' : 'standard';
if (this.cache[type][key]) return this.cache[type][key].value;
const handleList = new FbMetadbHandleList();
let items = [];
this.addItems(items, v.item);
items = [...new Set(items)].sort(this.numSort)
items.some(w => {
if (w >= lib.panel.list.Count) return true;
handleList.Add(lib.panel.list[w]);
});
let date = '';
let dates;
let indices;
let ln;
let n;
let tf;
let value;
let values;
switch (libSet.itemShowStatistics) {
case 1: // bitrate
values = this.tf.bitrate.EvalWithMetadbs(handleList);
if (values.length == 1) {
value = Number(values[0]) || '';
} else {
let lengths = FbTitleFormat('%length_seconds_fp%').EvalWithMetadbs(handleList)
const total = values.map((v, i) => v * lengths[i]);
let totals = total.map(v => parseFloat(v) || '');
totals = totals.filter((v, i) => v && lengths[i] ? v : lengths.splice(i, 1));
totals = totals.reduce((a, b) => a + b, 0);
lengths = lengths.map(v => parseFloat(v)).reduce((a, b) => a + b, 0);
value = Number(Math.round(totals / lengths)) || '';
}
if (lib.panel.imgView && value) value = `${value} kbps`;
this.cache[type][key] = {
value,
items
}
return value;
case 2: { // duration
const duration = utils.FormatDuration(handleList.CalcTotalDuration());
this.cache[type][key] = {
value: duration,
items: []
}
return duration;
}
case 3: { // total size
const bytes = this.tf.bytes.EvalWithMetadbs(handleList);
let size = [...new Set(bytes)];
size = size.map(v => {
const a = v.split('|');
return a[a.length - 1];
});
if (!size.length) return '';
size = size.map(v => parseInt(v)).reduce((a, b) => a + b, 0);
const formattedBytes = this.formatBytes(size);
this.cache[type][key] = {
value: formattedBytes,
items
}
return formattedBytes;
}
case 4: // rating
case 5: // popularity
tf = libSet.itemShowStatistics == 4 ? this.tf.rating : this.tf.popularity;
values = tf.EvalWithMetadbs(handleList);
values = this.getNumbers(values);
values = values.filter(Boolean)
ln = values.length;
if (!ln) return '';
values = values.map(v => parseFloat(v)).reduce((a, b) => a + b, 0);
value = Math.ceil(values / ln);
if (lib.panel.imgView && this.label) value = (libSet.itemShowStatistics == 4 ? '等级 ' : '热门 ') + value;
this.cache[type][key] = {
value,
items
}
return value;
case 6: // date (first release)
case 9: // firstPlayed
case 10: // lastPlayed
case 11: // added
tf =
libSet.itemShowStatistics == 6 ? this.tf.date :
libSet.itemShowStatistics == 9 ? this.tf.firstPlayed :
libSet.itemShowStatistics == 10 ? this.tf.lastPlayed :
this.tf.added;
dates = tf.EvalWithMetadbs(handleList);
dates = dates.filter(v => v !== '');
ln = dates.length;
if (ln) {
if (ln == 1) date = dates[0];
else {
date = libSet.itemShowStatistics == 6 || libSet.itemShowStatistics == 9 || libSet.itemShowStatistics == 11 ?
dates.reduce((pre, cur) => Date.parse(pre) > Date.parse(cur) ? cur : pre) :
dates.reduce((pre, cur) => Date.parse(cur) > Date.parse(pre) ? cur : pre)
}
}
if (!date) return '';
if (lib.panel.imgView && this.label) date = ['', '', '', '', '', '', (v.root ? '首次发行 ' : ''), '', '', '首次播放 ', '最近播放 ', '添加时间 '][libSet.itemShowStatistics] + date;
this.cache[type][key] = {
value: date,
items
}
return date;
case 7: { // queue
let index = '';
indices = [];
const queueHandles = plman.GetPlaybackQueueHandles();
handleList.Convert().forEach(h => {
const j = queueHandles.Find(h);
if (j != -1) indices.push(j + 1);
});
ln = indices.length;
if (ln) {
if (ln == 1) index = indices[0];
else {
index = this.arrayToRange(indices);
index.join();
}
}
if (!index) return '';
if (lib.panel.imgView && this.label) index = `Queue ${index}`;
this.cache[type][key] = {
value: index,
items
}
return index;
}
case 8: { // playcount
let playcount = this.tf.pc.EvalWithMetadbs(handleList);
const played = [...new Set(playcount)];
n = played.map(v => {
const a = v.split('|');
return a[a.length - 1];
});
playcount = this.getNumbers(n);
if (!playcount.length) return '';
playcount = n.map(v => parseInt(v)).reduce((a, b) => a + b, 0);
if (lib.panel.imgView) playcount = `${(this.label ? '已播放 ' : '') + playcount} 次`;
this.cache[type][key] = {
value: playcount,
items
}
return playcount;
}
}
}
checkAutoHeight() {
if (lib.panel.pn_h_auto && !lib.panel.imgView && libSet.pn_h == libSet.pn_h_min && this.tree[0]) this.clearChild(this.tree[0]);
}
check_ix(br, x, y, type) {
if (lib.panel.imgView) return true;
if (!br) return false;
x -= lib.ui.x;
const level = !this.inlineRoot ? br.level : Math.max(br.level - 1, 0);
const icon_w = this.inlineRoot && br.ix == 0 ? 0 : lib.ui.icon.w + (!this.fullLineSelection ? lib.ui.l.wf : 0);
return type ? (x >= Math.round(this.treeIndent * level + lib.ui.sz.margin) && x < Math.round(this.treeIndent * level + lib.ui.sz.margin) + br.w + icon_w) :
(x >= Math.round(this.treeIndent * level + lib.ui.sz.margin) + icon_w) && x < Math.min(Math.round(this.treeIndent * level + lib.ui.sz.margin) + icon_w + br.w, lib.panel.tree.w);
}
checkNode(gr) {
if (lib.sbar.draw_timer || this.nodeStyle != 7) return;
try {
lib.ui.style.symb.SetPartAndStateID(2, 1);
lib.ui.style.symb.SetPartAndStateID(2, 2);
lib.ui.style.symb.DrawThemeBackground(gr, -lib.ui.sz.node, -lib.ui.sz.node, lib.ui.sz.node, lib.ui.sz.node);
} catch (e) {
libSet.nodeStyle = 0;
this.nodeStyle = 0;
}
}
checkRow(x, y) {
this.m.br = -1;
const im = this.get_ix(x, y, true, false);
if (im >= this.tree.length || im < 0) return -1;
if (lib.panel.imgView) return im;
const item = this.tree[im];
const level = !this.inlineRoot ? item.level : Math.max(item.level - 1, 0);
if (x < Math.round(this.treeIndent * level) + lib.ui.icon.w + lib.ui.sz.margin + lib.ui.x + lib.ui.w && (!item.track || item.root)) this.m.br = im;
return im;
}
check_tooltip(ix, x, y) {
if (this.lbtnDn || lib.sbar.draw_timer) return;
const item = this.tree[ix];
let text = '';
if (!item) return;
switch (true) {
case !lib.panel.imgView: {
const trace1 = item.tt && item.tt.needed && x >= item.tt.x && x <= item.tt.x + item.tt.w && y >= item.tt.y && y <= item.tt.y + lib.ui.row.h;
const trace2 = item.stats_tt && item.stats_tt.needed && x >= item.stats_tt.x + item.stats_tt.w && x <= lib.ui.w - lib.ui.sz.marginRight && y >= item.stats_tt.y && y <= item.stats_tt.y + lib.ui.row.h * 0.9;
if (trace2) {
text = this.statisticsShow ?
(item.statistics !== undefined ? `${this.statistics[this.statisticsShow]}: ${item.statistics}` : '') :
(item.count ? `${['', '首', '项'][this.nodeCounts]}:${item.count}` : '');
} else if (trace1) {
text = (!lib.panel.colMarker ? item.name : item.name.replace(Regex.LibMarkerColor, '')) + (!this.countsRight || this.statisticsShow ? item.count : '');
text = text.replace(/&/g, '&&');
}
if (text != libTooltip.Text) this.deactivateTooltip();
if (!trace1 && !trace2 || !item.tt && !item.stats_tt) {
this.deactivateTooltip();
return;
}
break;
}
case lib.panel.imgView: {
let trace1 = false;
let trace2 = false;
let trace3 = false;
if (!libImg.labels.hide) {
if (!item.tt) {
this.deactivateTooltip();
return;
}
trace1 = x >= item.tt.x && x <= item.tt.x + item.tt.w && y >= item.tt.y1 && y <= item.tt.y1 + libImg.text.h;
trace2 = item.tt.y2 == -1 ? false : x >= item.tt.x && x <= item.tt.x + item.tt.w && y >= item.tt.y2 && y <= item.tt.y2 + libImg.text.h;
trace3 = item.tt.y3 == -1 ? false : x >= item.tt.x && x <= item.tt.x + item.tt.w && y >= item.tt.y3 && y <= item.tt.y3 + libImg.text.h;
text = trace1 || trace2 || trace3 ? item.tt.text : '';
if (lib.panel.colMarker) text = text.replace(Regex.LibMarkerColor, '');
text = text.replace(/&/g, '&&');
if (text != libTooltip.Text) this.deactivateTooltip();
if (!trace1 && !trace2 && !trace3 || !item.tt[1] && !item.tt[2] && !item.tt[3]) {
this.deactivateTooltip();
return;
}
} else {
text = lib.panel.lines == 2 ? !libSet.albumArtFlipLabels ? `${item.grp}\n${item.lot}` : `${item.lot}\n${item.grp}` : item.grp;
if (lib.panel.colMarker) text = text.replace(Regex.LibMarkerColor, '');
text = text.replace(/&/g, '&&');
if (text != libTooltip.Text) this.deactivateTooltip();
}
break;
}
}
this.activateTooltip(text);
lib.timer.tooltipLib();
}
checkTooltip(item, x, y, txt_w, w) {
item.tt = {
needed: txt_w > w,
x,
y,
w
};
item.stats_tt = {
needed: !this.tooltipStatistics || !this.statisticsShow || item.root ? false : [false, true, false, true, true, true, true, true, true, true, true, true][this.statisticsShow] && item.statistics !== undefined,
x,
y: y + lib.ui.row.h * 0.1,
w
};
}
checkTooltipFont(type) {
switch (type) {
case 'btn': {
const newTooltipFont = this.butTooltipFont();
if ($Lib.equal(this.cur, newTooltipFont)) return;
this.cur = newTooltipFont;
break;
}
case 'tree': {
const newTooltipFont = this.treeTooltipFont();
if ($Lib.equal(this.cur, newTooltipFont)) return;
this.cur = newTooltipFont;
break;
}
}
libTooltip.SetFont(this.cur[0], this.cur[1], this.cur[2]);
}
clearSelected() {
this.tree.forEach(v => v.sel = false);
}
clearTree() {
if (lib.panel.imgView && this.tree.length) libImg.trimCache(this.tree[0].key);
this.tree = [];
}
clearChild(br) {
br.child = [];
this.buildTree(lib.lib.root, 0, true, true);
}
clickedOn(x, y, item) {
if (lib.panel.imgView) return 'text';
if (this.inlineRoot && item.ix == 0) return this.check_ix(item, x, y, false) ? 'text' : 'none';
const level = !this.inlineRoot ? item.level : Math.max(item.level - 1, 0);
return x < lib.ui.x + Math.round(this.treeIndent * level) + lib.ui.icon.w + lib.ui.sz.margin ? 'node' : this.check_ix(item, x, y, false) ? 'text' : 'none';
}
collapseAll() {
let ic = this.get_ix(lib.ui.x, lib.ui.y + lib.panel.tree.y + lib.ui.row.h / 2, true, false);
if (ic >= this.tree.length || ic < 0) return;
let j = this.tree[ic].level;
if (this.rootNode) j -= 1;
if (this.tree[ic].level != 0) {
const par = this.tree[ic].par;
const pr_pr = [];
for (let m = 1; m < j + 1; m++) {
pr_pr[m] = m == 1 ? par : this.tree[pr_pr[m - 1]].par;
ic = pr_pr[m];
}
}
const nm = this.tree[ic].srt[0].toUpperCase();
this.tree.forEach(v => {
if (!v.root) v.child = [];
});
this.buildTree(lib.lib.root, 0);
let scr_pos = false;
this.tree.some((v, i) => {
if (v.srt[0].toUpperCase() == nm) {
lib.sbar.checkScroll(i * lib.ui.row.h);
return scr_pos = true;
}
});
if (!scr_pos) {
lib.sbar.reset();
lib.panel.treePaint();
}
lib.lib.treeState(false, libSet.rememberTree);
}
condense(child) {
child.forEach(v => {
if (typeof v.item[0] !== 'number') return;
v.item = this.createRanges(v.item);
});
}
copy(item) {
return item.map(v => v);
}
createImages() {
if (!lib.ui.w || !lib.ui.h) return;
if (!this.nodeStyle) {
const sz = lib.ui.sz.node;
const ln_w = Math.max(Math.floor(sz / 9), 1);
let plus = true;
let hot = false;
let sy_w = ln_w;
const x = 0;
const y = 0;
if (((sz - ln_w * 3) / 2) % 1 != 0) sy_w = ln_w > 1 ? ln_w - 1 : ln_w + 1;
for (let j = 0; j < 4; j++) {
this.nd[j] = $Lib.gr(sz, sz, true, g => {
hot = j > 1;
plus = !j || j == 2;
if (grSet.libraryDesign !== 'reborn') {
g.FillSolidRect(x, y, sz, sz, RGB(145, 145, 145));
if (!hot) FillGradRect(g, x + ln_w, y + ln_w, sz - ln_w * 2, sz - ln_w * 2, 91, plus ? lib.ui.col.icon_e[0] : lib.ui.col.icon_c[0], plus ? lib.ui.col.icon_e[1] : lib.ui.col.icon_c[1], 1.0);
else FillGradRect(g, x + ln_w, y + ln_w, sz - ln_w * 2, sz - ln_w * 2, 91, lib.ui.col.icon_h[0], lib.ui.col.icon_h[1], 1.0);
// const x_o = [x, x + sz - ln_w, x, x + sz - ln_w];
// const y_o = [y, y, y + sz - ln_w, y + sz - ln_w];
for (let i = 0; i < 4; i++) { // g.FillSolidRect(x_o[i], y_o[i], ln_w, ln_w, RGB(186, 187, 188));
if (grSet.libraryDesign === 'traditional') g.FillSolidRect(x, y, sz, sz, lib.ui.col.iconPlusBg);
}
} else if (libSet.nodeStyle === 0) {
g.DrawRect(x, y, sz - 1, sz - 1, 1, lib.ui.col.iconPlusBg);
}
if (plus) g.FillSolidRect(Math.floor(x + (sz - sy_w) / 2), y + ln_w + Math.min(ln_w, sy_w), sy_w, sz - ln_w * 2 - Math.min(ln_w, sy_w) * 2, !hot ? lib.ui.col.iconPlus : lib.ui.col.iconPlus_h);
g.FillSolidRect(x + ln_w + Math.min(ln_w, sy_w), Math.floor(y + (sz - sy_w) / 2), sz - ln_w * 2 - Math.min(ln_w, sy_w) * 2, sy_w, !hot ? (plus ? lib.ui.col.iconMinus_e : lib.ui.col.iconMinus_c) : lib.ui.col.iconMinus_h);
});
}
} else {
let lightCol = lib.ui.isLightCol(lib.ui.col.icon_h);
$Lib.gr(1, 1, false, g => {
const h = this.nodeStyle != 7 ? g.CalcTextHeight('String', lib.ui.icon.font) / 15 : g.CalcTextHeight('String', lib.ui.font.main) / 20;
this.sy_sz = Math.floor(Math.max(8 * libSet.zoomNode / 100 * h, 5));
});
const sz = Math.max(Math.round(this.sy_sz * 1.666667), 1);
this.triangle.highlight = $Lib.gr(sz, sz, true, g => {
g.SetSmoothingMode(4);
g.FillPolygon(lib.ui.col.icon_h, 1, [sz, 0, sz, sz, 0, sz]);
g.SetSmoothingMode(0);
});
lightCol = lib.ui.isLightCol(lib.ui.col.icon_e);
this.triangle.expand = $Lib.gr(sz, sz, true, g => {
g.SetSmoothingMode(4);
g.FillPolygon(lib.ui.col.icon_e & (lightCol ? 0xC0ffffff : 0xBAffffff), 1, [sz, 0, sz, sz, 0, sz]);
g.SetSmoothingMode(0);
});
this.triangle.select = $Lib.gr(sz, sz, true, g => {
g.SetSmoothingMode(4);
g.FillPolygon(lib.ui.col.textSel & (lightCol ? 0xC0ffffff : 0xBAffffff), 1, [sz, 0, sz, sz, 0, sz]);
g.SetSmoothingMode(0);
});
}
}
createRanges(arr) {
const ret = [];
let start;
let end;
for (let i = 0; i < arr.length; i++) {
start = end = arr[i];
while (arr[i + 1] == end + 1) {
end++;
i++;
}
ret.push(start == end ? {
start,
end: start,
count: 1
} : {
start,
end,
count: end - start + 1
});
}
return ret;
}
cusCol(gr, text, item, item_x, item_y, w, h, type, np, font, ellipsisSpace, cus) {
if (!text) return;
let col = [];
let col_x = [];
let col_w = [];
let w_arr = [];
let x = 0;
if (item[cus] && item[cus].id == this.id && !this.highlight.nowPlayingIndicator) {
col = item[cus].col;
col_x = item[cus].col_x;
col_w = item[cus].col_w;
text = item[cus].txt;
w_arr = item[cus].txt_w;
} else {
text = text.split('@!#');
text.forEach((v, i) => {
if (i % 2 == 0) w_arr[i] = gr.CalcTextWidth(text[i], font);
});
text.forEach((v, i) => {
if (i % 2 == 0) {
const cur_w = x + w_arr[i];
const next_text = !!text[i + 2];
let ellipsis_corr = 0;
const roomForEllipsis = !(next_text && cur_w < w && w - cur_w < ellipsisSpace);
if (!roomForEllipsis) {
text[i + 2] = '';
ellipsis_corr = ellipsisSpace;
}
col[i] = i > 0 ? (text[i - 1]).split('`') : (!lib.panel.imgView || !libImg.labels.overlayDark ? lib.ui.col.txtArr : [RGB(240, 240, 240), lib.ui.col.text_h, lib.ui.col.text]);
col_x[i] = x;
col_w[i] = w - x - ellipsis_corr > ellipsis_corr ? w - x - ellipsis_corr : w - x;
x += w_arr[i];
}
});
item[cus] = {
id: this.id,
txt: text,
col,
col_x,
col_w,
txt_w: w_arr
};
}
text.forEach((v, i) => {
if (i % 2 == 0 && text[i]) {
gr.GdiDrawText(text[i], font, !np ? col[i][type] : lib.ui.col.nowp, item_x + col_x[i], item_y, col_w[i], h, lib.panel.lc);
}
});
}
deactivateTooltip() {
if (!libTooltip.Text && !grSet.showStyledTooltips || lib.but.trace) return;
libTooltip.Text = '';
lib.but.tooltipLib.delay = false;
libTooltip.Deactivate();
}
dragDrop(x, y) {
x -= lib.ui.x; y -= lib.ui.y;
if (!this.lbtnDn) return;
const drag_diff = !libSet.touchControl ? Math.sqrt((Math.pow(this.last_pressed_coord.x - x, 2) + Math.pow(this.last_pressed_coord.y - y, 2))) : Math.abs(x - this.last_pressed_coord.x);
if (drag_diff > 7) {
if (libSet.touchControl) {
const ix = this.get_ix(x, y, true, false);
const item = this.tree[ix];
if (lib.ui.id.dragDrop != ix || ix >= this.tree.length || ix < 0) return;
if (!item.sel && !lib.vk.k('ctrl')) this.setTreeSel(ix, item.sel);
}
this.last_pressed_coord = {
x: undefined,
y: undefined
};
const handleList = this.getHandleList('newItems');
this.sortIfNeeded(handleList);
if (grm.ui.displayLibrarySplit()) { // * Drag and drop action from Library to Playlist in split layout
grm.ui.librarySplitDragDrop(handleList);
} else {
fb.DoDragDrop(0, handleList, handleList.Count ? 1 | 4 : 0);
}
this.lbtnDn = false;
}
}
draw(gr) { // * Heavily modified
if (lib.lib.empty) return gr.DrawString(lib.lib.empty, lib.ui.font.main, lib.ui.col.text, lib.ui.x, grm.ui.wh * 0.5 - lib.ui.y - lib.panel.search.h * 0.5, lib.panel.tree.w, lib.ui.row.h * 3, Stringformat.Align_Center);
if (!this.tree.length || !lib.panel.draw) return gr.GdiDrawText(this.libItems && !lib.panel.search.txt && !libSet.filterBy && libSet.libSource ? '加载中...\n\n' : lib.lib.none, lib.ui.font.main, lib.ui.col.text, lib.ui.x + lib.ui.sz.margin, lib.ui.y + lib.panel.search.h, lib.panel.tree.w, lib.ui.row.h * 3);
if (lib.panel.imgView) return;
const b = $Lib.clamp(Math.round(lib.sbar.delta / lib.ui.row.h + 0.4), 0, this.tree.length - 1);
// const bar_x = this.nodeStyle && this.nodeStyle < 5 ? 0 : lib.ui.sz.pad;
const f = Math.min(b + lib.panel.rows, this.tree.length);
const nm = [];
const nowp_c = [];
const updatedNowpBg = pl.col.header_nowplaying_bg !== null; // * Wait until nowplaying bg has a new color to prevent flashing
const colNowPlaying = grSet.libraryBgImg ? RGBtoRGBA(lib.ui.col.nowPlayingBg, grSet.libraryBgRowOpacity) : lib.ui.col.nowPlayingBg;
const colRowStripes = grSet.libraryBgImg ? RGBtoRGBA(lib.ui.col.rowStripes, grSet.libraryBgRowOpacity) : lib.ui.col.rowStripes;
const row = [];
const y1 = Math.round(lib.panel.search.h - lib.sbar.delta + lib.panel.node_y) + Math.floor(lib.ui.sz.node / 2);
let i = 0;
let item_x = 0;
let item_y = 0;
let sel_x = 0;
let sel_w = 0;
let level = 0;
this.checkNode(gr);
if (!lib.ui.style.squareNode) gr.SetTextRenderingHint(5);
this.rows = 0;
if (lib.ui.style.squareNode && lib.ui.col.line) {
level = !this.inlineRoot ? this.tree[b].level : Math.max(this.tree[b].level - 1, 0);
for (let j = 0; j <= level; j++) row[j] = b;
}
for (i = b; i < f; i++) {
const item = this.tree[i];
this.getItemCount(item);
nm[i] = item.name + (i || this.rootNode != 3 || this.nodeCounts == 1 && (this.countsRight || this.statisticsShow) ? (!this.countsRight || this.statisticsShow ? item.count : '') : '');
const counts = !this.statisticsShow ? item.count : item.statistics;
if (this.highlight.nowPlayingShow && !item.root && this.inRange(this.nowp, item.item)) nowp_c.push(i);
item.np = nowp_c.includes(i) || lib.panel.textDiffHighlight && this.m.i == i ? `${Unicode.BeamedEighthNotes} ` : '';
if (item.np && item.id != this.id) this.row.note_w = gr.CalcTextWidth(item.np, lib.ui.font.main);
if (item.id != this.id || this.highlight.nowPlayingIndicator) {
let itemName = !lib.panel.colMarker ? nm[i] : nm[i].replace(Regex.LibMarkerColor, '');
if (item.np && this.highlight.nowPlayingIndicator && item.track) itemName = item.np + itemName;
item.name_w = gr.CalcTextWidth(itemName, lib.ui.font.main, true);
item.count_w = this.nodeCounts && this.countsRight || this.statisticsShow ?
gr.CalcTextWidth(counts || '000', lib.ui.font.small) + (counts ? lib.ui.row.h * 0.2 : 0) : 0;
if (!this.fullLineSelection) {
item.w = item.name_w;
item.id = this.id;
}
}
level = !this.inlineRoot ? item.level : Math.max(item.level - 1, 0);
if (this.highlight.nowPlaying && !item.root && this.inRange(this.nowp, item.item)) nowp_c.push(i);
item_y = Math.round(lib.ui.y + lib.ui.row.h * i + lib.panel.search.h - lib.sbar.delta);
if (item_y < lib.panel.filter.y) {
this.rows++;
if (this.rowStripes) {
const width = lib.sbar.w && libSet.sbarShow !== 0 ? lib.ui.w - SCALE(42) : lib.ui.w + 1;
if (i % 2 == 0) gr.FillSolidRect(lib.ui.x, item_y + 1, width, lib.ui.row.h - 2, colRowStripes /*ui.col.bg1*/);
else gr.FillSolidRect(lib.ui.x, item_y, width, lib.ui.row.h, lib.ui.col.bg2);
}
if ((item.sel || this.highlight.nowPlaying) && (lib.ui.col.bgSel != 0 || grCol.primary != 0)) {
const icon_w = !this.inlineRoot || i ? lib.ui.icon.w : 0;
item_x = Math.round(lib.ui.x + this.treeIndent * level + lib.ui.sz.margin) + icon_w;
sel_x = Math.round(item_x - lib.ui.sz.sel);
if (this.inlineRoot && !i) sel_x = Math.max(sel_x - lib.ui.sz.sel, 0);
sel_w = Math.min(item.name_w + lib.ui.sz.sel * 2, lib.ui.x + lib.panel.tree.w - sel_x - item.count_w - 1);
if (this.fullLineSelection) {
sel_x = lib.ui.x;
sel_w = lib.sbar.w && libSet.sbarShow !== 0 ? lib.ui.w - SCALE(42) : lib.ui.w + 1;
}
if (!nowp_c.includes(i)) {
if (this.fullLineSelection && this.sbarShow === 1 && lib.ui.sbar.type === 2 && (this.highlight.row || !this.fullLineSelection)) {
gr.FillSolidRect(sel_x, item_y, sel_w + lib.ui.l.w, lib.ui.row.h, lib.ui.col.bgSel);
gr.FillSolidRect(sel_x, item_y, sel_w + lib.ui.l.w, lib.ui.l.w, lib.ui.col.bgSelframe);
gr.FillSolidRect(sel_x, item_y + lib.ui.row.h, sel_w + lib.ui.l.w, lib.ui.l.w, lib.ui.col.bgSelframe);
}
}
// * Now playing bg selection
else if (this.highlight.nowPlaying && updatedNowpBg) {
gr.FillSolidRect(grSet.libraryDesign === 'traditional' ? item_x - SCALE(2) : lib.ui.x, item_y, grSet.libraryDesign === 'traditional' && this.fullLineSelection ? sel_w - item_x - lib.ui.sz.margin - lib.ui.sz.node + lib.ui.l.w : grSet.libraryDesign === 'traditional' && !this.fullLineSelection ? sel_w + sel_x - item_x + SCALE(2) : !this.fullLineSelection ? sel_w + lib.ui.sz.margin + sel_x - lib.ui.x - lib.ui.sz.sideMarker : sel_w, lib.ui.row.h, colNowPlaying);
if (grSet.libraryDesign !== 'traditional') {
gr.FillSolidRect(lib.ui.x, item_y, lib.ui.sz.sideMarker, lib.ui.row.h, lib.ui.col.sideMarker);
}
}
// * Marker selection with now playing active
if (item.sel && this.highlight.nowPlaying) {
if (grSet.libraryDesign !== 'traditional') {
if (!this.inRange(this.nowp, item.item) || item.root) gr.DrawRect(this.fullLineSelection ? sel_x : lib.ui.x, item_y, this.fullLineSelection ? sel_w - 1 : sel_w + lib.ui.sz.margin + sel_x - lib.ui.x - lib.ui.sz.sideMarker, lib.ui.row.h, 1, lib.ui.col.selectionFrame);
gr.FillSolidRect(lib.ui.x, this.highlight.nowPlaying && !item.root && this.inRange(this.nowp, item.item) ? item_y + 1 : item_y, lib.ui.sz.sideMarker, this.highlight.nowPlaying && !item.root && this.inRange(this.nowp, item.item) ? lib.ui.row.h - 1 : lib.ui.row.h + 1, lib.ui.col.sideMarker);
}
else if (grSet.libraryDesign === 'traditional') {
gr.FillSolidRect(item_x - SCALE(2), item_y, grSet.libraryDesign === 'traditional' && this.fullLineSelection ? sel_w - item_x - lib.ui.sz.margin - lib.ui.sz.node + lib.ui.l.w : sel_w, lib.ui.row.h, colNowPlaying);
}
}
// * Marker selection with now playing deactivated
if (item.sel && !this.highlight.nowPlaying && updatedNowpBg) {
gr.FillSolidRect(grSet.libraryDesign === 'traditional' && this.fullLineSelection ? item_x - SCALE(2) : sel_x, item_y, grSet.libraryDesign === 'traditional' && this.fullLineSelection ? sel_w - item_x - lib.ui.sz.margin - lib.ui.sz.node + lib.ui.l.w : sel_w, lib.ui.row.h, colNowPlaying);
if (grSet.libraryDesign !== 'traditional') {
gr.FillSolidRect(lib.ui.x, item_y, lib.ui.w, lib.ui.row.h, colNowPlaying);
gr.FillSolidRect(lib.ui.x, item_y, lib.ui.sz.sideMarker, lib.ui.row.h, lib.ui.col.sideMarker);
}
}
}
}
}
for (i = b; i < f; i++) {
const item = this.tree[i];
const level = !this.inlineRoot ? item.level : Math.max(item.level - 1, 0);
item_y = Math.round(lib.ui.y + lib.ui.row.h * i + lib.panel.search.h - lib.sbar.delta);
if (item_y < lib.panel.filter.y) {
item_x = Math.round(lib.ui.x + this.treeIndent * level + lib.ui.sz.margin);
if (this.inlineRoot && !item.level) item_x = lib.ui.x + lib.ui.sz.marginSearch;
if ((this.fullLineSelection && this.row.i == i || this.m.i == i)) {
sel_x = Math.round(item_x - lib.ui.sz.sel);
if (!this.inlineRoot || item.level) sel_x += lib.ui.icon.w;
sel_w = Math.min(item.name_w + lib.ui.sz.sel * 2, lib.ui.x + lib.panel.tree.w - sel_x - item.count_w - 1);
if (this.fullLineSelection) {
sel_x = lib.ui.x;
sel_w = lib.sbar.w ? lib.ui.w - SCALE(42) : lib.ui.w + 1;
}
if (this.highlight.row == 3 && (this.fullLineSelection && this.sbarShow == 1 && lib.ui.sbar.type == 2)) {
gr.DrawLine(sel_x, item_y, sel_w, item_y, lib.ui.l.w, lib.ui.col.frame);
gr.DrawLine(sel_x, item_y + lib.ui.row.h, sel_w, item_y + lib.ui.row.h, lib.ui.l.w, lib.ui.col.frame);
}
}
if (lib.ui.style.squareNode && lib.ui.col.line) {
if (item.top) row[level] = i;
const ff = lib.sbar.rows_drawn == this.rows || i < lib.sbar.rows_drawn ? f : f - 1;
if (item.bot || i === ff - 1) {
for (let depth = (i === ff - 1 ? 0 : level); depth <= level; depth++) {
if (row[depth] !== undefined && (this.inlineRoot || !item.root)) {
const start = row[depth];
let end = i + (item.bot && depth === level ? 0.5 : 1);
if (item_y >= lib.panel.filter.y) end -= 1;
const l_x = Math.round(lib.ui.x + this.treeIndent * depth + lib.ui.sz.margin) + Math.floor(lib.ui.sz.node / 2) - lib.ui.l.wf;
let l_y = Math.round(lib.ui.y + lib.ui.row.h * start + lib.panel.search.h - lib.sbar.delta);
let l_h = Math.ceil(lib.ui.row.h * (end - start)) + lib.ui.l.wc;
if (!start) {
l_y += lib.ui.row.h / 2;
l_h -= lib.ui.row.h / 2;
}
if (i <= this.row.lineMax[depth] && (!this.inlineRoot || item.level) && grSet.libraryDesign === 'traditional') gr.FillSolidRect(l_x, l_y, lib.ui.l.w, l_h, lib.ui.col.line);
}
}
if (item.bot) row[level] = undefined;
}
}
}
}
for (i = b; i < f; i++) {
const item = this.tree[i];
const level = !this.inlineRoot ? item.level : Math.max(item.level - 1, 0);
item_y = Math.round(lib.ui.y + lib.ui.row.h * i + lib.panel.search.h - lib.sbar.delta);
if (item_y < lib.panel.filter.y) {
item_x = Math.round(lib.ui.x + this.treeIndent * level + lib.ui.sz.margin);
if (this.inlineRoot && !item.level) item_x = lib.ui.x + lib.ui.sz.marginSearch;
if (lib.ui.style.squareNode) {
if (!item.track && (!this.inlineRoot || item.level)) {
const y2 = lib.ui.y + lib.ui.row.h * i + y1 - lib.ui.l.wf;
if (lib.ui.col.line) if (grSet.libraryDesign !== 'reborn') gr.FillSolidRect(item_x + lib.ui.sz.node, y2, lib.ui.l.s1, lib.ui.l.w, lib.ui.col.line);
this.drawNode(gr, item.child.length < 1 ? this.m.br != i ? 0 : 2 : this.m.br != i ? 1 : 3, item_x, item_y + lib.panel.node_y);
} else if (lib.ui.col.line && (!this.inlineRoot || item.level)) {
if (grSet.libraryDesign !== 'reborn') {
const y2 = Math.round(lib.ui.y + lib.panel.search.h - lib.sbar.delta) + Math.ceil(lib.ui.row.h * (i + 0.5)) - lib.ui.l.wf;
gr.FillSolidRect(item_x + lib.ui.l.s2, y2, lib.ui.l.s3, lib.ui.l.w, lib.ui.col.line);
}
}
} else if (!item.track && (!this.inlineRoot || item.level)) this.drawNode(gr, item, item_x, item_y, item.child.length < 1, this.m.br == i, item.sel);
if (!this.inlineRoot || item.level) item_x += lib.ui.icon.w + (!this.fullLineSelection ? lib.ui.l.wf : 0);
const w = lib.ui.x + lib.panel.tree.w - item_x - lib.ui.sz.sel - item.count_w;
this.checkTooltip(item, item_x, item_y, item.name_w, w);
if (this.fullLineSelection && item.id != this.id) {
item.w = lib.ui.x + lib.panel.tree.w - item_x - HD_4K(25, 45);
item.id = this.id;
}
if (item.np && this.highlight.nowPlayingSidemarker) {
gr.FillSolidRect(lib.ui.l.w, item_y, lib.ui.sz.sideMarker, lib.ui.row.h, lib.ui.col.nowp);
}
if (item.np && this.highlight.nowPlayingIndicator && item.track) nm[i] = item.np + nm[i];
const np = item.np && this.highlight.nowPlaying;
// const txt_co = np && !item.sel ? lib.ui.col.nowp : item.sel && this.fullLineSelection ? (this.highlight.row ? lib.ui.col.textSel : lib.ui.col.text) : this.m.i == i && this.highlight.text ? lib.ui.col.text_h : lib.ui.col.counts || lib.ui.col.count;
const type = item.sel ? (this.highlight.row || !this.fullLineSelection ? 2 : 0) : this.m.i == i && this.highlight.text ? 1 : 0;
const txt_c = // np && !item.sel ? lib.ui.col.nowp : lib.ui.col.txtArr[type];
this.highlight.nowPlaying && !item.root && this.inRange(this.nowp, item.item) && updatedNowpBg ? lib.ui.col.text_nowp :
item.sel ? lib.ui.col.textSel :
this.m.i === i ? lib.ui.col.text_h : lib.ui.col.text;
!lib.panel.colMarker ? gr.GdiDrawText(nm[i], lib.ui.font.main, txt_c, item_x, item_y, w, lib.ui.row.h, lib.panel.lc) : this.cusCol(gr, nm[i], item, item_x, item_y, w, lib.ui.row.h, type, np, lib.ui.font.main, lib.ui.font.mainEllipsisSpace, 'text');
if (this.countsRight || this.statisticsShow) {
const scrollbar = libSet.sbarShow !== 0 && lib.sbar.w === SCALE(12) && lib.sbar.scrollable_lines > 0;
const x = lib.panel.tree.w - item_x + (grSet.libraryLayout === 'split' ? 0 : lib.ui.x) - (scrollbar ? SCALE(24) : 0);
gr.GdiDrawText(!this.statisticsShow ? item.count : item.statistics, !item.root || !this.label ? lib.ui.font.small : lib.ui.font.label, txt_c, item_x, item_y, x, lib.ui.row.h, lib.panel.rc);
}
}
}
}
drawNode(gr, item, x, y, parent, hover, sel) {
const selCol = sel && this.fullLineSelection && this.highlight.row;
const y2 = Math.round(y);
const ix = this.get_ix(x, y, true, false);
if (ix >= this.tree.length || ix < 0) return;
const itemtr = this.tree[ix];
const nowp = this.highlight.nowPlaying && !itemtr.root && this.inRange(this.nowp, itemtr.item);
const icon_c = nowp ? lib.ui.col.text_nowp : hover ? lib.ui.col.iconPlus_h : selCol ? lib.ui.col.iconPlus_sel : lib.ui.col.iconPlus;
switch (this.nodeStyle) {
case 0: // * Squares - Traditional design
if (!this.highlight.node && item > 1) item -= 2;
x = Math.round(x);
y = Math.round(y);
if (this.nd[item]) gr.DrawImage(this.nd[item], x, y, this.nd[item].Width, this.nd[item].Height, 0, 0, this.nd[item].Width, this.nd[item].Height);
break;
case 1:
case 2: // * Angles/Arrows - Modern design
if (!this.highlight.row && this.fullLineSelection) x += lib.ui.l.w;
if (parent) {
if (hover) {
gr.DrawString(lib.ui.icon.expand, lib.ui.icon.font, icon_c, x, y2, lib.ui.x + lib.panel.tree.w - x, lib.ui.row.h, lib.panel.s_lc);
gr.DrawString(lib.ui.icon.expand, lib.ui.icon.font, icon_c, x + 1, y2, lib.ui.x + lib.panel.tree.w - x, lib.ui.row.h, lib.panel.s_lc);
} else {
gr.DrawString(lib.ui.icon.expand, lib.ui.icon.font, icon_c, x, y2, lib.ui.x + lib.panel.tree.w - x, lib.ui.row.h, lib.panel.s_lc);
if (this.nodeStyle === 1) gr.DrawString(lib.ui.icon.expand, lib.ui.icon.font, icon_c, x + 1, y2, lib.ui.x + lib.panel.tree.w - x, lib.ui.row.h, lib.panel.s_lc);
}
} else {
gr.DrawString(lib.ui.icon.collapse, lib.ui.icon.font, icon_c, x - lib.ui.icon.offset, y2, lib.ui.x + lib.panel.tree.w - x, lib.ui.row.h, lib.panel.s_lc);
gr.DrawString(lib.ui.icon.collapse, lib.ui.icon.font, icon_c, x - lib.ui.icon.offset, y2 + 1, lib.ui.x + lib.panel.tree.w - x, lib.ui.row.h, lib.panel.s_lc);
}
break;
case 3: case 4: { // * Triangle - Ultra-modern design
if (!this.highlight.row && this.fullLineSelection) x += lib.ui.l.w;
// const y3 = Math.round(y + (lib.ui.row.h - this.sy_sz) / 2 - 2);
gr.SetSmoothingMode(4);
if (parent) {
if (hover) {
gr.DrawString(lib.ui.icon.expand2, lib.ui.icon.font, icon_c, x, y2, lib.ui.x + lib.panel.tree.w - x, lib.ui.row.h, lib.panel.s_lc);
} else {
gr.DrawString(lib.ui.icon.expand, lib.ui.icon.font, icon_c, x, y2, lib.ui.x + lib.panel.tree.w - x, lib.ui.row.h, lib.panel.s_lc);
if (this.nodeStyle === 3) gr.DrawString(lib.ui.icon.expand, lib.ui.icon.font, icon_c, x + 1, y2, lib.ui.x + lib.panel.tree.w - x, lib.ui.row.h, lib.panel.s_lc);
}
} else {
gr.DrawString(lib.ui.icon.expand2, lib.ui.icon.font, icon_c, x, y2, lib.ui.x + lib.panel.tree.w - x, lib.ui.row.h, lib.panel.s_lc);
}
gr.SetSmoothingMode(0);
break;
}
case 5: // * Custom node - Georgia-ReBORN design ( Clean +|- )
if (parent) { // Plus
if (hover) {
gr.DrawString(lib.ui.icon.expand, lib.ui.icon.font, icon_c, x, y2 - HD_4K(1, -1), lib.ui.x + lib.panel.tree.w - x, lib.ui.row.h, lib.panel.s_lc);
gr.DrawString(lib.ui.icon.expand, lib.ui.icon.font, icon_c, x, y2 - HD_4K(1, -1), lib.ui.x + lib.panel.tree.w - x + 2, lib.ui.row.h + 2, lib.panel.s_lc);
} else gr.DrawString(lib.ui.icon.expand, lib.ui.icon.font, icon_c, x, y2 - (DetectWine() ? HD_4K(0, -1) : HD_4K(1, -1)), lib.ui.x + lib.panel.tree.w - x, lib.ui.row.h, lib.panel.s_lc);
} else { // Minus
gr.DrawString(lib.ui.icon.collapse, lib.ui.icon.font, icon_c, x - lib.ui.icon.offset, y2 - (DetectWine() ? HD_4K(1, -1) : HD_4K(1, 0)), lib.ui.x + lib.panel.tree.w - x, lib.ui.row.h, lib.panel.s_lc);
gr.DrawString(lib.ui.icon.collapse, lib.ui.icon.font, icon_c, x - lib.ui.icon.offset, y2 - HD_4K(1, -1), lib.ui.x + lib.panel.tree.w - x, lib.ui.row.h + 2, lib.panel.s_lc);
}
break;
case 7:
if (item > 1) item -= 2;
lib.ui.style.symb.SetPartAndStateID(2, !item ? 1 : 2);
lib.ui.style.symb.DrawThemeBackground(gr, x, y, lib.ui.sz.node, lib.ui.sz.node);
break;
default:
if (parent) {
if (hover) {
gr.DrawString(lib.ui.icon.expand, lib.ui.icon.font, selCol ? lib.ui.col.textSel : this.highlight.node ? lib.ui.col.icon_h : lib.ui.col.icon_e, x, y + this.iconVerticalPad, lib.ui.x + lib.panel.tree.w - x, lib.ui.row.h, lib.panel.s_lc);
} else gr.DrawString(lib.ui.icon.expand, lib.ui.icon.font, !selCol ? lib.ui.col.icon_c : lib.ui.col.textSel, x, y + this.iconVerticalPad, lib.ui.x + lib.panel.tree.w - x, lib.ui.row.h, lib.panel.s_lc);
} else {
if (hover) {
gr.DrawString(lib.ui.icon.collapse, lib.ui.icon.font, selCol ? lib.ui.col.textSel : this.highlight.node ? lib.ui.col.icon_h : lib.ui.col.icon_c, x - lib.ui.icon.offset, y + this.iconVerticalPad, lib.ui.x + lib.panel.tree.w - x, lib.ui.row.h, lib.panel.s_lc);
} else gr.DrawString(lib.ui.icon.collapse, lib.ui.icon.font, !selCol ? lib.ui.col.icon_e : lib.ui.col.textSel, x - lib.ui.icon.offset, y + this.iconVerticalPad, lib.ui.x + lib.panel.tree.w - x, lib.ui.row.h, lib.panel.s_lc);
}
break;
}
}
expand(ie, nm) {
let h = 0;
let m = 0;
if (ie) this.tree[ie].sel = true;
if (!this.tree.some(v => v.sel)) return;
if (libSet.autoCollapse) {
const parent = [];
const pr_pr = [];
let par = 0;
this.tree.forEach((v, j, arr) => {
if (v.sel) {
j = v.level;
if (this.rootNode) j -= 1;
if (v.level != 0) {
par = v.par;
for (m = 1; m < j + 1; m++) {
pr_pr[m] = m == 1 ? par : arr[pr_pr[m - 1]].par;
parent.push(pr_pr[m]);
}
}
}
});
this.tree.forEach((v, i) => {
if (!parent.includes(i) && !v.sel && !v.root) v.child = [];
});
this.buildTree(lib.lib.root, 0);
}
const start_l = this.tree.length;
let nm_n = '';
let nodes = -1;
m = this.tree.length;
this.expandedTracks = 0;
this.expandLmt = lib.men.treeExpandLimit;
while (m--) {
if (this.tree[m].sel) {
this.expandNodes(this.tree[m], !(!this.rootNode || m));
nodes++;
}
}
lib.sbar.setRows(this.tree.length);
lib.panel.treePaint();
if (nm) {
this.tree.some((v, i, arr) => {
nm_n = (v.level ? arr[v.par].srt[0] : '') + v.srt[0];
nm_n = nm_n.toUpperCase();
if (nm_n == nm) {
h = i;
return true;
}
});
} else {
this.tree.some((v, i) => {
if (v.sel) {
h = i;
return true;
}
});
}
const new_items = this.tree.length - start_l + nodes;
const b = Math.round(lib.sbar.scroll / lib.ui.row.h + 0.4);
const n = Math.max(h - b, this.rootNode ? 1 : 0);
let scrollChk = false;
if (n + 1 + new_items > this.rows) {
scrollChk = true;
if (new_items > this.rows - 2) lib.sbar.checkScroll(h * lib.ui.row.h);
else lib.sbar.checkScroll(Math.min(h * lib.ui.row.h, (h + 1 - lib.sbar.rows_drawn + new_items) * lib.ui.row.h));
}
if (lib.sbar.scroll > h * lib.ui.row.h) {
scrollChk = true;
lib.sbar.checkScroll(h * lib.ui.row.h);
}
if (!scrollChk) lib.sbar.scrollRound();
lib.lib.treeState(false, libSet.rememberTree);
}
expandCollapse(x, y, item, ix) {
const expanded = item.child.length > 0 ? 1 : 0;
switch (expanded) {
case 0: {
let n = 0;
if (libSet.autoCollapse) {
n = this.branchChange(item, false, true);
lib.sbar.checkScroll(lib.sbar.scroll - n * lib.ui.row.h, 'step');
}
const row = this.getRowNumber(y);
this.branch(item, !!item.root, true);
if (!ix) lib.panel.setHeight(true);
n = 2;
if (item.child.length == 1 && libSet.treeAutoExpandSingle) {
this.branch(item.child[0], false, true);
n += item.child[0].child.length;
}
if (libSet.autoCollapse) ix = item.ix;
if (row + n + item.child.length > this.rows) {
if (item.child.length > (this.rows - n)) lib.sbar.checkScroll(ix * lib.ui.row.h);
else lib.sbar.checkScroll(Math.min(ix * lib.ui.row.h, (ix + n - lib.sbar.rows_drawn + item.child.length) * lib.ui.row.h));
}
break;
}
case 1: {
this.clearChild(item);
if (!ix && this.tree.length == 1) lib.panel.setHeight(false);
const b = $Lib.clamp(Math.round(lib.sbar.delta / lib.ui.row.h + 0.4), 0, this.tree.length - 1);
const f = Math.min(b + lib.panel.rows, this.tree.length);
if (f - b < lib.panel.rows) lib.sbar.checkScroll((this.tree.length - lib.panel.rows) * lib.ui.row.h);
break;
}
}
if (lib.sbar.scroll > ix * lib.ui.row.h) lib.sbar.checkScroll(ix * lib.ui.row.h);
}
expandNodes(obj, am) {
this.branch(obj, !!am, true, true);
if (obj.child) {
obj.child.some(v => {
if (v.track) this.expandedTracks++;
if (this.expandedTracks >= this.expandLmt) return true;
if (!v.track) this.expandNodes(v);
});
}
}
fixMarkers(n) {
while (n.includes('@!##!#')) { // $colour
n = n.replace('@!##!#', '<!>');
n = n.replace('@!#', '~#~');
}
n = n.replace(Regex.LibMarkerExcl, '@!##!#').replace(Regex.LibMarkerTildeHash, '#!#@!#');
while (n.includes('#@##!#')) { // $nodisplay
n = n.replace('#@##!#', '<!>');
n = n.replace('#@#', '~#~');
}
n = n.replace(Regex.LibMarkerExcl, '#@##!#').replace(Regex.LibMarkerTildeHash, '#!##@#');
return n;
}
focusShow(i) {
this.setTreeSel(i);
lib.panel.treePaint();
this.showItem(i);
}
formatBytes(bytes, decimals = 1) {
if (!+bytes) return '0 字节'
const k = 1024
const dm = decimals < 0 ? 0 : decimals
const sizes = ['字节', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`
}
getAllCombinations(n) {
n = this.fixMarkers(n);
return n.includes('^@^') && n.includes(lib.panel.softSplitter) ? this.imgView(n) : this.getCombos(n);
}
getCombos(n) {
const combinations = [];
const divisors = [];
const arraysToCombine = [];
n = n.replace(RegExp(`(#!#|)${lib.panel.softSplitter}(#!#|)`, 'g'), '@@').split('#!#');
const ln = n.length;
let i = 0;
for (i = 0; i < ln; i++) {
n[i] = n[i].split('@@');
if (n[i] != '') arraysToCombine.push(n[i]);
}
const arraysToCombineLength = arraysToCombine.length;
for (i = arraysToCombineLength - 1; i >= 0; i--) divisors[i] = divisors[i + 1] ? divisors[i + 1] * arraysToCombine[i + 1].length : 1;
const getPermutation = (n, arraysToCombine) => {
const result = [];
let cur_array;
for (let j = 0; j < arraysToCombineLength; j++) {
cur_array = arraysToCombine[j];
result.push(cur_array[Math.floor(n / divisors[j]) % cur_array.length]);
}
return result;
};
let numPerms = arraysToCombine[0].length;
for (i = 1; i < arraysToCombineLength; i++) numPerms *= arraysToCombine[i].length;
for (i = 0; i < numPerms; i++) combinations.push(getPermutation(i, arraysToCombine));
return this.removeDuplicateArr(combinations);
}
getChildCount(arr, ix) {
arr.forEach(v => {
if (v.child && ix > v.ix) {
this.childCount += v.child.length;
if (!v.track) this.getChildCount(v.child, ix);
}
});
}
getItemCount(v) {
const prop = !lib.panel.imgView ? 'statistics' : '_statistics';
if (this.statisticsShow && v[prop] == null) v[prop] = v.root && this.label && !lib.panel.imgView ? this.label : this.calcStatistics(v);
if (v.count === '' && this.nodeCounts) {
if (!lib.panel.imgView) {
if (v.root && this.label) {
if (!this.statisticsShow) v.count = this.label;
} else {
const type = lib.panel.search.txt ? 'search' : libSet.filterBy ? 'filter' : 'standard';
const key = this.getKey(v);
v.count = !v.track || !this.showTracks ? (v.name ? ' ' : '') + (this.nodeCounts == 1 ? `(${this.trackCount(v.item)})` : this.nodeCounts == 2 ? `(${this.branchCount(v, !!v.root, true, false, key, type)})` : '') : '';
if (!this.showTracks && v.count == `${v.name ? ' ' : ''}(0)`) v.count = '';
if (this.countsRight && !this.statisticsShow) v.count = v.count.replace(Regex.PunctParen, '');
}
} else {
const getTracks = [true, true, true, true, false, false, true, false, false, false, false][libSet.itemShowStatistics];
if (getTracks) {
v.count = this.trackCount(v.item);
v.count += v.count > 1 ? ' 首' : ' 首';
}
const getItemCount = !v.root && libSet.itemOverlayType != 1 && libSet.albumArtLabelType == 2 && !libSet.itemShowStatistics && (lib.pop.nodeCounts == 1 || lib.pop.nodeCounts == 2);
if (getItemCount) {
const count = v.count.replace(Regex.NumNonDigits, '');
if (lib.panel.lines == 1 || libSet.albumArtFlipLabels) v.grp += ` (${count})`;
else v.lot += ` (${count})`;
}
}
}
}
getHandleList(n) {
if (n == 'newItems') this.getTreeSel();
const handleList = new FbMetadbHandleList();
this.sel_items.some(v => {
if (v >= lib.panel.list.Count) return true;
handleList.Add(lib.panel.list[v]);
});
return handleList;
}
get_ix(x, y, simple, type) {
let ix;
y -= lib.ui.y - 1; // - 1 = workaround to adjust and fix background color selection ( text_nowp in drawNode() ) to draw correct colored nodes in tree when option "Nowplaying in highlight" is active
x -= lib.ui.x;
if (lib.panel.imgView) {
if (y > libImg.panel.y && y < libImg.panel.y + libImg.panel.h && x > libImg.panel.x && x < libImg.panel.x + libImg.panel.w) {
const row_ix = libImg.style.vertical ? Math.ceil((y + lib.sbar.delta - libImg.panel.y) / libImg.row.h) - 1 : 0;
const column_ix = libImg.style.vertical ? (!libImg.labels.right && !libSet.albumArtFlowMode ? Math.ceil((x - libImg.panel.x) / libImg.columnWidth) - 1 : 0) : Math.ceil((x + lib.sbar.delta - libImg.panel.x) / libImg.columnWidth) - 1;
ix = (row_ix * libImg.columns) + column_ix;
return ix > this.tree.length - 1 ? -1 : ix;
}
return -1;
}
ix = y > lib.panel.tree.y && y < lib.panel.tree.y + this.rows * lib.ui.row.h ? Math.round((y + lib.sbar.delta - lib.panel.search.h - lib.ui.row.h * 0.5) / lib.ui.row.h) : -1;
if (simple) return ix;
return this.tree.length > ix && ix >= 0 && x < lib.panel.tree.w && y > lib.panel.tree.y && y < lib.panel.tree.y + this.rows * lib.ui.row.h && this.check_ix(this.tree[ix], x + lib.ui.x, y + lib.ui.y, type) ? ix : -1;
}
getKey(v) {
const level = v.level;
const o = {
a: v.nm || '',
b: level != 0 ? this.tree[v.par].nm : '',
c: level > 1 ? this.tree[this.tree[v.par].par].nm : '',
d: level > 2 ? this.tree[this.tree[this.tree[v.par].par].par].nm : ''
}
return level + (level > 2 ? o.d : '') + (level > 1 ? o.c : '') + (level > 0 ? o.b : '') + o.a;
}
getNowplaying(handle, stop) {
if (stop) {
lib.panel.treePaint();
return this.nowp = -1;
}
if (!handle && fb.IsPlaying) handle = fb.GetNowPlaying();
if (!handle) return this.nowp = -1;
this.nowp = lib.panel.list.Find(handle);
lib.panel.treePaint();
}
getNumbers(arr) { // test [0, '0', "0", "0.5", 10, '10', "", '', '-', null, true, false, 'Oh']
return arr.filter(v => Number(v)); // gives ["0.5", 10, "10", true]
//return arr.filter(v => parseFloat(v) == v); // gives [0, "0", "0", "0.5", 10, "10"]
//return arr.filter(v => Number(v) && parseFloat(v) == v); // gives ["0.5", 10, "10"]
}
getRowNumber(y) {
return Math.round((y - lib.panel.tree.y - lib.ui.row.h * 0.5) / lib.ui.row.h);
}
getTreeSel() {
lib.panel.treePaint();
this.sel_items = [];
this.tree.forEach(v => {
if (v.sel) this.addItems(this.sel_items, v.item);
});
this.uniq(this.sel_items);
}
imgView(n) {
let a; let b; const c = [];
n = n.split('^@^');
if (n[0]) a = this.getCombos(n[0]);
if (n[1]) b = this.getCombos(n[1]);
a.forEach(v => b.forEach(w => c.push([`${v.join('')}^@^${w.join('')}`])));
return this.removeDuplicateArr(c);
}
isDate(n) {
return isNaN(n) && !isNaN(Date.parse(n));
}
inRange(num, item) {
return item.some(v => {
const end = v.end;
const start = v.start;
return num >= Math.min(start, end) && num <= Math.max(start, end);
});
}
lbtn_dblclk(x, y) {
if (this.autoPlay.click > 2 && libSet.libSource) return;
if (lib.vk.k('alt')) {
this.mbtnDblClickOrAltDblClick(x, y, '', 'alt');
return;
}
this.dbl_clicked = true;
if (y < lib.panel.search.h) return;
const ix = this.get_ix(x, y, true, false);
if (ix >= this.tree.length || ix < 0) return;
const item = this.tree[ix];
switch (this.clicked_on) {
case 'node':
this.expandCollapse(x, y, item, ix);
break;
case 'text':
if (!this.check_ix(item, x, y, false)) return;
if (this.dblClickAction == 3) {
const handleList = new FbMetadbHandleList();
this.range(item.item).forEach(v => {
if (v < lib.panel.list.Count) handleList.Add(lib.panel.list[v]);
});
if (handleList.Count) plman.FlushPlaybackQueue();
for (let i = 0; i < handleList.Count; i++) {
plman.AddItemToPlaybackQueue(handleList[i]);
}
fb.Play();
return;
}
if (!libSet.libSource) {
plman.ExecutePlaylistDefaultAction($Lib.pl_active, this.range(item.item)[0]);
//plman.ExecutePlaylistDefaultAction($.pl_active, plman.GetPlaylistFocusItemIndex($.pl_active));//Asion 修正来源选择列表模式下搜索内容播放不正常
/*const plsIdx = libSet.fixedPlaylist ? plman.FindPlaylist(libSet.fixedPlaylistName) : $.pl_active;
if (plsIdx !== -1) {
const idx = lib.panel.search.txt.length
? plman.GetPlaylistItems(plsIdx).Find(lib.panel.list[this.range(item.item)[0]])
: this.range(item.item)[0];
if (idx !== -1) { plman.ExecutePlaylistDefaultAction(plsIdx, idx); }
} */
return;
}
if (!this.dblClickAction && !this.autoFill.mouse && !this.autoPlay.click) return this.send(item, x, y);
if (this.dblClickAction === 2 && !item.track && !lib.panel.imgView) {
this.expandCollapse(x, y, item, ix);
lib.lib.treeState(false, libSet.rememberTree);
}
if (!this.dblClickAction || this.autoPlay.click === 2) return;
if (this.dblClickAction !== 2 && this.dblClickAction !== 3 || this.dblClickAction === 2 && item.track || this.dblClickAction === 2 && lib.panel.imgView) {
if (!this.autoFill.mouse) this.send(item, x, y);
let pl_stnd_idx = plman.FindOrCreatePlaylist(libSet.libPlaylist.replace(Regex.TFLibViewName, lib.panel.viewName), false);
if (libSet.sendToCur) pl_stnd_idx = plman.ActivePlaylist;
else plman.ActivePlaylist = pl_stnd_idx;
plman.ActivePlaylist = pl_stnd_idx;
const c = (plman.PlaybackOrder === 3 || plman.PlaybackOrder === 4) ? Math.ceil(plman.PlaylistItemCount(pl_stnd_idx) * Math.random() - 1) : 0;
plman.ExecutePlaylistDefaultAction(pl_stnd_idx, c);
}
if (libSet.dblClickAction === 3) {
if (plman.PlaylistItemCount(plman.ActivePlaylist) <= 0) {
lib.panel.pos = 0;
this.setTreeSel(this.tree[lib.panel.pos].ix);
this.setPlaylist(lib.panel.pos, this.tree[lib.panel.pos]);
this.load(lib.panel.pos, true, true, true, false, false);
}
plman.ExecutePlaylistDefaultAction($Lib.pl_active, this.sel_items[0]);
this.load(this.sel_items, true, false, true, false, false);
if (!item.root) {
setTimeout(() => { fb.RunMainMenuCommand('编辑/撤消'); }, 100);
}
}
break;
}
}
lbtn_dn(x, y) {
this.lbtnDn = false;
this.dbl_clicked = false;
if (y < lib.panel.search.h) return;
const ix = this.get_ix(x, y, true, false);
if (ix >= this.tree.length || ix < 0) return;
this.deactivateTooltip();
if (libSet.touchControl) {
lib.ui.id.dragDrop = lib.ui.id.touch_dn = ix;
}
const item = this.tree[ix];
this.clicked_on = this.clickedOn(x, y, item);
switch (this.clicked_on) {
case 'node':
lib.panel.pos = ix;
this.expandCollapse(x, y, item, ix);
this.checkRow(x, y);
break;
case 'text':
this.last_pressed_coord.x = x - lib.ui.x;
this.last_pressed_coord.y = y - lib.ui.y;
this.lbtnDn = true;
lib.panel.pos = ix;
if (libSet.touchControl) break;
if (lib.vk.k('alt')) {
this.alt_dbl_clicked = false;
if (libSet.altClickAction == 2) {
return;
}
if (this.autoFill.mouse) return;
}
if (!item.sel && !lib.vk.k('ctrl')) this.setTreeSel(ix, item.sel);
break;
}
lib.lib.treeState(false, libSet.rememberTree);
}
lbtn_up(x, y) {
if (lib.lib.empty && libSet.libSource == 1 && !libSet.fixedPlaylist && y > lib.panel.search.h) fb.RunMainMenuCommand('媒体库/配置');
this.last_pressed_coord = {
x: undefined,
y: undefined
};
this.lbtnDn = false;
if (y < lib.panel.search.h || x < lib.ui.x || this.dbl_clicked || lib.but.Dn) return;
const ix = this.get_ix(x, y, true, false);
lib.panel.pos = ix;
if (ix >= this.tree.length || ix < 0) return;
if (libSet.touchControl && (this.autoFill.mouse || this.autoPlay.click) && lib.ui.id.touch_dn != ix) return;
const item = this.tree[ix];
if (this.clicked_on != 'text') return;
if (!libSet.libSource) return this.setPlaylistSelection(ix, item);
if (lib.vk.k('alt')) {
if (grSet.addTracksPlaylistSwitch) {
grm.button.btn.library.enabled = false;
grm.button.btn.library.changeButtonState(ButtonState.Default);
grm.ui.displayLibrary = false;
grm.ui.displayPlaylist = true;
if (!grSet.playlistAutoScrollNowPlaying) grm.ui.setPlaylistSize();
setTimeout(() => {
if (pl.playlist.is_scrollbar_available) {
pl.playlist.scrollbar.scroll_to_end();
}
}, 500);
window.Repaint();
}
this.mbtnUpOrAltClickUp(x, y, '', 'alt'); // {
return;
}
if (!lib.vk.k('ctrl')) {
this.clearSelected();
if (!item.sel) this.setTreeSel(ix, item.sel);
} else this.setTreeSel(ix, item.sel);
if (this.autoFill.mouse || this.autoPlay.click) {
window.Repaint(true);
this.send(item, x, y);
} else {
lib.panel.treePaint();
}
this.track(this.autoFill.mouse || this.autoPlay.click);
lib.lib.treeState(false, libSet.rememberTree);
}
leave() {
this.deactivateTooltip();
lib.panel.m.x = -1;
lib.panel.m.y = -1;
if (lib.men.r_up) return;
this.m.br = -1;
this.m.cur_br = 0;
this.m.i = -1;
this.cur_ix = 0;
this.row.i = -1;
this.row.cur = 0;
lib.panel.treePaint();
}
leftKeyCheckScroll() {
const row = (lib.panel.pos * lib.ui.row.h - lib.sbar.scroll) / lib.ui.row.h;
if (lib.sbar.scroll > lib.panel.pos * lib.ui.row.h) lib.sbar.checkScroll(lib.panel.pos * lib.ui.row.h);
else if (row - lib.sbar.rows_drawn > 0) {
lib.sbar.checkScroll((lib.panel.pos + 3 - lib.sbar.rows_drawn) * lib.ui.row.h);
}
else lib.sbar.scrollRound();
lib.lib.treeState(false, libSet.rememberTree);
}
load(list, isArray, add, autoPlay, def_pl, insert) {
let np_item = -1;
let pid = -1;
const pl_stnd = libSet.libPlaylist.replace(Regex.TFLibViewName, lib.panel.viewName);
let pl_stnd_idx = plman.FindOrCreatePlaylist(pl_stnd, true);
if (!def_pl && plman.ActivePlaylist != -1) pl_stnd_idx = plman.ActivePlaylist;
else if (libSet.activateOnChange) plman.ActivePlaylist = pl_stnd_idx;
if (autoPlay == 4 && plman.PlaylistItemCount(pl_stnd_idx) || autoPlay == 3 && fb.IsPlaying) {
autoPlay = false;
add = true;
}
const items = isArray ? this.getHandleList() : list.Clone();
this.sortIfNeeded(items);
this.selList = items.Clone();
this.selection_holder.SetSelection(this.selList);
const plnIsValid = pl_stnd_idx != -1 && pl_stnd_idx < plman.PlaylistCount;
const pllockRemoveOrAdd = plnIsValid ? plman.GetPlaylistLockedActions(pl_stnd_idx).includes('RemoveItems') || plman.GetPlaylistLockedActions(pl_stnd_idx).includes('ReplaceItems') || plman.GetPlaylistLockedActions(pl_stnd_idx).includes('AddItems') : false;
if (!add && pllockRemoveOrAdd) return;
if (fb.IsPlaying && !add) {
if (libSet.actionMode == 1) {
const pl_playing = `${libSet.libPlaylist} (正在播放)`;
const pl_playing_idx = plman.FindOrCreatePlaylist(pl_playing, false);
if (plman.PlayingPlaylist == pl_stnd_idx) {
plman.RenamePlaylist(pl_stnd_idx, pl_playing);
plman.RenamePlaylist(pl_playing_idx, pl_stnd);
plman.SetPlaylistSelection(pl_playing_idx, $Lib.range(0, plman.PlaylistItemCount(pl_playing_idx) - 1), true);
plman.RemovePlaylistSelection(pl_playing_idx, false);
plman.InsertPlaylistItems(pl_playing_idx, 0, items, false);
plman.MovePlaylist(pl_playing_idx, pl_stnd_idx);
plman.MovePlaylist(pl_stnd_idx + 1, pl_playing_idx);
} else {
plman.SetPlaylistSelection(pl_stnd_idx, $Lib.range(0, plman.PlaylistItemCount(pl_stnd_idx) - 1), true);
plman.RemovePlaylistSelection(pl_stnd_idx, false);
plman.InsertPlaylistItems(pl_stnd_idx, 0, items, false);
}
plman.ActivePlaylist = pl_stnd_idx;
} else if (fb.GetNowPlaying()) {
np_item = items.Find(fb.GetNowPlaying());
let pl_chk = true;
let np;
if (np_item != -1) {
np = plman.GetPlayingItemLocation();
if (np.IsValid) {
if (np.PlaylistIndex != pl_stnd_idx) pl_chk = false;
else pid = np.PlaylistItemIndex;
}
if (pl_chk && pid == -1 && items.Count < 5000) {
if (lib.ui.dui) plman.SetActivePlaylistContext();
const start = Date.now();
for (let i = 0; i < 20; i++) {
if (Date.now() - start > 300) break;
fb.RunMainMenuCommand('编辑/撤消');
np = plman.GetPlayingItemLocation();
if (np.IsValid) {
pid = np.PlaylistItemIndex;
if (pid != -1) break;
}
}
}
}
if (pid != -1) {
plman.ClearPlaylistSelection(pl_stnd_idx);
plman.SetPlaylistSelectionSingle(pl_stnd_idx, pid, true);
plman.RemovePlaylistSelection(pl_stnd_idx, true);
const it = items.Clone();
items.RemoveRange(np_item, items.Count);
it.RemoveRange(0, np_item + 1);
if (plman.PlaylistItemCount(pl_stnd_idx) < 5000) plman.UndoBackup(pl_stnd_idx);
plman.InsertPlaylistItems(pl_stnd_idx, 0, items);
plman.InsertPlaylistItems(pl_stnd_idx, plman.PlaylistItemCount(pl_stnd_idx), it);
} else {
if (plman.PlaylistItemCount(pl_stnd_idx) < 5000) plman.UndoBackup(pl_stnd_idx);
plman.ClearPlaylist(pl_stnd_idx);
plman.InsertPlaylistItems(pl_stnd_idx, 0, items);
}
}
} else if (!add) {
if (plman.PlaylistItemCount(pl_stnd_idx) < 5000) plman.UndoBackup(pl_stnd_idx);
plman.ClearPlaylist(pl_stnd_idx);
plman.InsertPlaylistItems(pl_stnd_idx, 0, items);
plman.SetPlaylistFocusItem(pl_stnd_idx, 0);
} else {
if (plman.PlaylistItemCount(pl_stnd_idx) < 5000) plman.UndoBackup(pl_stnd_idx);
plman.InsertPlaylistItems(pl_stnd_idx, !insert ? plman.PlaylistItemCount(pl_stnd_idx) : plman.GetPlaylistFocusItemIndex(pl_stnd_idx), items, true);
const f_ix = !insert || plman.GetPlaylistFocusItemIndex(pl_stnd_idx) == -1 ? plman.PlaylistItemCount(pl_stnd_idx) - items.Count : plman.GetPlaylistFocusItemIndex(pl_stnd_idx) - items.Count;
plman.SetPlaylistFocusItem(pl_stnd_idx, f_ix);
plman.EnsurePlaylistItemVisible(pl_stnd_idx, f_ix);
}
if (autoPlay) {
const c = (plman.PlaybackOrder == 3 || plman.PlaybackOrder == 4) ? Math.ceil(plman.PlaylistItemCount(pl_stnd_idx) * Math.random() - 1) : 0;
plman.ExecutePlaylistDefaultAction(pl_stnd_idx, c);
}
}
mbtn_dn() {
this.mbtn_dbl_clicked = false;
}
mbtnDblClickOrAltDblClick(x, y, mask, type) {
this[`${type}_dbl_clicked`] = true;
if (type == 'mbtn' && (libSet.actionMode == 2 || libSet.mbtnClickAction == 2) || type == 'alt' && libSet.altClickAction == 2) {
const ix = this.get_ix(x, y, true, false);
if (ix < this.tree.length && ix >= 0) {
const handleList = new FbMetadbHandleList();
this.range(this.tree[ix].item).forEach(v => {
if (v < lib.panel.list.Count) handleList.Add(lib.panel.list[v]);
});
const queueHandles = plman.GetPlaybackQueueHandles();
let remove = [];
for (let i = 0; i < handleList.Count; i++) {
for (let k = 0; k < queueHandles.Count; k++) {
if (handleList[i].Compare(queueHandles[k])) {
remove.push(k);
}
}
}
remove = [...new Set(remove)];
plman.RemoveItemsFromPlaybackQueue(remove);
}
}
}
mbtnUpOrAltClickUp(x, y, mask, type) {
if (this[`${type}_dbl_clicked`]) return;
if (type == 'mbtn' && (libSet.actionMode == 2 || libSet.mbtnClickAction == 2) || type == 'alt' && libSet.altClickAction == 2) {
setTimeout(() => { // timeout: wait & see if double click, but adds a little lag to single click: timeout can be commented out
if (this[`${type}_dbl_clicked`]) return;
const ix = this.get_ix(x, y, true, false);
if (ix < this.tree.length && ix >= 0) {
const handleList = new FbMetadbHandleList();
this.range(this.tree[ix].item).forEach(v => {
if (v < lib.panel.list.Count) handleList.Add(lib.panel.list[v]);
});
const add = new FbMetadbHandleList();
handleList.Convert().forEach(h => {
let found = false;
plman.GetPlaybackQueueHandles().Convert().forEach(q => {
if (h.Compare(q)) found = true;
});
if (!found) add.Add(h);
});
const ap = plman.ActivePlaylist;
const plItems = plman.GetPlaylistItems(ap);
add.Convert().forEach(v => {
const ix = plItems.Find(v);
if (ix != -1) {
plman.AddPlaylistItemToPlaybackQueue(ap, ix);
} else plman.AddItemToPlaybackQueue(v);
});
}
}, 180);
} else {
if (!libSet.libSource) return;
this.add(x, y, !libSet[`${type}ClickAction`]);
}
}
merge(m, mergeBrCount) {
if (!libSet.libSource && !lib.panel.multiProcess) return;
const seen = {};
for (let i = 0; i < m.length; i++) {
const v = m[i].srt[0].toUpperCase();
if (seen[v] === undefined) seen[v] = i;
else {
if (!mergeBrCount) m[i].item.forEach(w => m[seen[v]].item.push(w));
m.splice(i, 1);
i--;
}
}
}
move(x, y) {
if (lib.but.Dn) return;
const ix = this.get_ix(x, y, false, false);
this.row.i = this.checkRow(x, y);
this.m.i = -1;
if (ix != -1) {
this.m.i = ix;
this.check_tooltip(ix, x, y);
} else if (this.countsRight || this.statisticsShow) {
this.check_tooltip(this.row.i, x, y);
} else this.deactivateTooltip();
if (!libSet.mousePointerOnly) {
if (this.highlight.node || lib.panel.imgView) {
if (ix != -1 || this.inlineRoot && !this.m.br) this.hand = true;
} else if (this.m.br != -1 && !(this.inlineRoot && !this.m.br)) this.hand = true;
}
SetCursor(this.hand ? 'Hand' : !lib.but.Dn && y > lib.ui.y && y < lib.ui.y + lib.panel.search.h && libSet.searchShow && x > lib.ui.x + lib.but.q.h + lib.but.margin && x < lib.panel.search.x + lib.panel.search.w ? 'IBeam' : 'Arrow');
const same = this.m.i == this.cur_ix && this.m.br == this.m.cur_br && this.row.i == this.row.cur;
if (same && !lib.sbar.touch.dn) return;
if (!lib.sbar.draw_timer && !same) lib.panel.treePaint();
this.cur_ix = this.m.i;
this.m.cur_br = this.m.br;
this.row.cur = this.row.i;
}
notifySelection(list) {
if (list === undefined) list = this.getHandleList('newItems');
window.NotifyOthers(window.Name, list);
if (list.Count) return true;
}
nowPlayingShow() {
if (this.nowp != -1) {
let np_i = -1;
for (let i = 0; i < this.tree.length; i++) {
const v = this.tree[i];
if (this.inRange(this.nowp, v.item)) {
np_i = i;
if (!v.root) {
if (lib.panel.imgView) i = this.tree.length;
else if (!v.track) this.branch(this.tree[np_i]);
}
}
}
if (!lib.panel.imgView && !this.tree[np_i].root) {
this.clearSelected();
if (!this.highlight.nowPlaying) this.tree[np_i].sel = true;
}
if (np_i != -1) this.showItem(np_i, 'np');
}
}
numSort(a, b) {
return a - b;
}
on_char(code) {
if (lib.panel.search.active) return;
switch (code) {
case lib.vk.copy: {
const handleList = this.getHandleList('newItems');
fb.CopyHandleListToClipboard(handleList);
break;
}
case lib.vk.selAll:
this.tree.forEach(v => {
if (!v.root) v.sel = true;
});
this.getTreeSel();
if (!this.sel_items.length) return;
this.setPlaylist();
break;
case lib.vk.eFocusSearch:
case lib.vk.lFocusSearch:
if (libSet.searchShow) lib.search.focus();
break;
case lib.vk.insert:
this.getTreeSel();
if (!this.sel_items.length) return;
this.load(this.sel_items, true, true, false, false, true);
break;
}
}
on_focus(p_is_focused) {
this.is_focused = p_is_focused;
if (p_is_focused && this.selList && this.selList.Count) this.selection_holder.SetSelection(this.selList);
}
setPlaylist(ix, item) {
if (libSet.libSource) {
if (this.autoFill.key) this.load(this.sel_items, true, false, false, !libSet.sendToCur, false);
this.track(true);
} else if (this.autoFill.key) this.setPlaylistSelection(ix, item);
}
on_key_down(vkey) {
if (vkey == lib.vk.collapseAll && !lib.panel.imgView) this.collapseAll();
if (vkey == lib.vk.expand && !lib.panel.imgView) {
const isSel = this.tree.some(v => v.sel);
this.expand();
if (isSel) {
lib.panel.setHeight(true);
this.checkAutoHeight();
}
}
if (lib.panel.search.active) return;
if (lib.vk.k('enter')) {
if (!this.sel_items.length) return;
if (!libSet.libSource) {
if (this.autoPlay.send) plman.ExecutePlaylistDefaultAction($Lib.pl_active, this.sel_items[0]);
return;
}
switch (true) {
case lib.vk.k('shift'):
return this.load(this.sel_items, true, true, false, false, false);
case lib.vk.k('ctrl'):
return this.sendToNewPlaylist();
default:
return this.load(this.sel_items, true, false, this.autoPlay.send, false, false);
}
}
let item = -1;
switch (vkey) {
case lib.vk.left:
if (lib.panel.imgView) lib.panel.pos -= 1;
lib.panel.pos = $Lib.clamp(lib.panel.pos, 0, this.tree.length - 1);
this.row.i = -1;
this.m.i = -1;
if (lib.panel.imgView) {
item = this.tree[lib.panel.pos];
this.m.i = lib.panel.pos;
this.showItem(item.ix, 'left');
this.setPlaylist(lib.panel.pos, item);
break;
}
// !imgView
if ((this.tree[lib.panel.pos].level == (this.rootNode ? 1 : 0)) && this.tree[lib.panel.pos].child.length < 1) {
item = this.tree[lib.panel.pos];
this.m.i = lib.panel.pos = item.ix;
this.leftKeyCheckScroll();
break;
}
if (this.tree[lib.panel.pos].child.length > 0) {
item = this.tree[lib.panel.pos];
this.clearChild(item);
this.setTreeSel(item.ix);
this.m.i = lib.panel.pos = item.ix;
} else {
item = this.tree[this.tree[lib.panel.pos].par];
if (item) this.clearChild(item);
this.setTreeSel(item.ix);
this.m.i = lib.panel.pos = item.ix;
}
lib.panel.treePaint();
this.setPlaylist(lib.panel.pos, item);
lib.sbar.setRows(this.tree.length);
this.leftKeyCheckScroll();
break;
case lib.vk.right: {
if (lib.panel.imgView) lib.panel.pos += 1;
lib.panel.pos = $Lib.clamp(lib.panel.pos, 0, this.tree.length - 1);
this.row.i = -1;
this.m.i = -1;
item = this.tree[lib.panel.pos];
if (lib.panel.imgView) {
this.m.i = lib.panel.pos;
this.showItem(item.ix, 'right');
this.setPlaylist(lib.panel.pos, item);
break;
}
// !imgView
if (item.child.length) {
lib.panel.pos++;
lib.panel.pos = $Lib.clamp(lib.panel.pos, !this.rootNode ? 0 : 1, this.tree.length - 1);
this.upDnKeyCheckScroll(vkey);
break;
}
if (libSet.autoCollapse) this.branchChange(item, false, true);
this.branch(item, !!item.root, true);
let n = 2;
if (item.child.length == 1 && libSet.treeAutoExpandSingle) {
this.branch(item.child[0], false, true);
n += item.child[0].child.length;
}
this.setTreeSel(item.ix);
lib.panel.treePaint();
this.m.i = lib.panel.pos = item.ix;
this.setPlaylist(lib.panel.pos, item);
lib.sbar.setRows(this.tree.length);
const row = (lib.panel.pos * lib.ui.row.h - lib.sbar.scroll) / lib.ui.row.h;
if (row + n + item.child.length > lib.sbar.rows_drawn || row < 0) {
if (item.child.length > (lib.sbar.rows_drawn - n) || row < 0) lib.sbar.checkScroll(lib.panel.pos * lib.ui.row.h);
else lib.sbar.checkScroll(Math.min(lib.panel.pos * lib.ui.row.h, (lib.panel.pos + n - lib.sbar.rows_drawn + item.child.length) * lib.ui.row.h));
} else lib.sbar.scrollRound();
lib.lib.treeState(false, libSet.rememberTree);
break;
}
case lib.vk.pgUp:
if (this.tree.length == 0) break;
if (lib.panel.imgView) {
lib.panel.pos = lib.panel.pos - libImg.columns * (lib.panel.rows - 1);
lib.panel.pos = $Lib.clamp(lib.panel.pos, 0, this.tree.length - 1);
} else lib.panel.pos = Math.max(Math.round(lib.sbar.scroll / lib.ui.row.h + 0.4) - Math.floor(lib.panel.rows) + 1, !this.rootNode ? 0 : 1);
lib.sbar.pageThrottle(1);
this.setTreeSel(this.tree[lib.panel.pos].ix);
lib.panel.treePaint();
this.setPlaylist(lib.panel.pos, this.tree[lib.panel.pos]);
lib.lib.treeState(false, libSet.rememberTree);
break;
case lib.vk.pgDn:
if (this.tree.length == 0) break;
if (lib.panel.imgView) {
lib.panel.pos = lib.panel.pos + libImg.columns * (lib.panel.rows - 1);
lib.panel.pos = $Lib.clamp(lib.panel.pos, 0, this.tree.length - 1);
} else lib.panel.pos = Math.min(Math.round(lib.sbar.scroll / lib.ui.row.h + 0.4) + Math.floor(lib.panel.rows) * 2 - 2, this.tree.length - 1);
lib.sbar.pageThrottle(-1);
this.setTreeSel(this.tree[lib.panel.pos].ix);
lib.panel.treePaint();
this.setPlaylist(lib.panel.pos, this.tree[lib.panel.pos]);
lib.lib.treeState(false, libSet.rememberTree);
break;
case lib.vk.home:
if (this.tree.length == 0) break;
lib.panel.pos = !this.rootNode ? 0 : 1;
lib.sbar.checkScroll(0, 'full');
this.setTreeSel(this.tree[lib.panel.pos].ix);
lib.panel.treePaint();
this.setPlaylist(lib.panel.pos, this.tree[lib.panel.pos]);
lib.lib.treeState(false, libSet.rememberTree);
break;
case lib.vk.end:
if (this.tree.length == 0) break;
lib.panel.pos = this.tree.length - 1;
lib.sbar.scrollToEnd();
this.setTreeSel(this.tree[lib.panel.pos].ix);
lib.panel.treePaint();
this.setPlaylist(lib.panel.pos, this.tree[lib.panel.pos]);
lib.lib.treeState(false, libSet.rememberTree);
break;
case lib.vk.dn:
case lib.vk.up:
if (this.tree.length == 0) break;
if ((lib.panel.pos == 0 && vkey == lib.vk.up) || (lib.panel.pos == this.tree.length - 1 && vkey == lib.vk.dn)) {
this.setTreeSel(-1);
break;
}
this.row.i = -1;
this.m.i = -1;
if (!lib.panel.imgView) {
if (vkey == lib.vk.dn) lib.panel.pos++;
if (vkey == lib.vk.up) lib.panel.pos--;
} else {
if (vkey == lib.vk.dn) lib.panel.pos += libImg.columns;
if (vkey == lib.vk.up) lib.panel.pos -= libImg.columns;
}
lib.panel.pos = $Lib.clamp(lib.panel.pos, !this.rootNode ? 0 : 1, this.tree.length - 1);
if (lib.panel.imgView) {
item = this.tree[lib.panel.pos];
this.m.i = lib.panel.pos;
this.showItem(item.ix, vkey == lib.vk.up ? 'up' : 'down');
this.setPlaylist(lib.panel.pos, item);
return;
}
this.upDnKeyCheckScroll(vkey);
break;
}
}
on_main_menu(index) {
if (index == this.getMainMenuIndex.add) {
this.getTreeSel();
if (!this.sel_items.length) return;
this.load(this.sel_items, true, true, false, false, false);
}
if (index == this.getMainMenuIndex.collapseAll) this.collapseAll();
if (index == this.getMainMenuIndex.insert) {
this.getTreeSel();
if (!this.sel_items.length) return;
this.load(this.sel_items, true, true, false, false, true);
}
if (index == this.getMainMenuIndex.new) {
this.getTreeSel();
if (!this.sel_items.length) return;
this.sendToNewPlaylist();
}
if (index == this.getMainMenuIndex.searchClear && libSet.searchShow) lib.search.clear();
if (index == this.getMainMenuIndex.searchFocus && this.is_focused && libSet.searchShow) lib.search.focus();
}
range(item) {
const items = [];
item.forEach(v => {
for (let i = v.start; i <= v.end; i++) items.push(i);
});
return items;
}
removeDuplicateArr(arr) {
const t = {};
return arr.filter(v => !(t[v] = v in t));
}
send(item, x, y) {
if (!this.check_ix(item, x, y, false)) return;
if (lib.vk.k('ctrl') || lib.vk.k('shift')) this.load(this.sel_items, true, false, false, !libSet.sendToCur, false);
else this.load(this.sel_items, true, false, this.autoPlay.click, !libSet.sendToCur, false);
}
sendToNewPlaylist() {
const names = this.tree.filter(v => v.sel).map(v => v.name);
plman.ActivePlaylist = plman.CreatePlaylist(plman.PlaylistCount, [...new Set(names)].join('; '));
this.load(this.sel_items, true, false, this.autoPlay.send, false, false);
}
setActions() {
this.autoPlay = {
click: libSet.clickAction < 2 ? false : libSet.clickAction,
send: libSet.autoPlay
};
this.autoFill = {
mouse: libSet.actionMode == 2 ? false : libSet.clickAction == 1 || libSet.actionMode == 1,
key: libSet.keyAction
};
this.dblClickAction = libSet.actionMode == 1 ? 1 : libSet.actionMode == 2 ? 3 : libSet.dblClickAction;
}
setPlaylistSelection(ix, item) {
this.clearSelected();
if (!item.sel) this.setTreeSel(ix, item.sel);
lib.panel.treePaint();
plman.ClearPlaylistSelection($Lib.pl_active);
let items = [];
if (lib.panel.search.txt || libSet.filterBy || lib.panel.multiProcess) {
const hl = this.getHandleList();
hl.Convert().forEach(h => {
const i = lib.lib.full_list.Find(h);
if (i != -1) items.push(i);
});
} else {
items = this.range(item.item);
}
plman.SetPlaylistSelection($Lib.pl_active, items, true);
this.setFocus = true;
plman.SetPlaylistFocusItem($Lib.pl_active, items[0]);
this.track(false);
lib.lib.treeState(false, libSet.rememberTree);
}
setPos(pos) {
this.m.i = this.row.i = lib.panel.pos = pos;
}
setTreeSel(idx, state) {
const sel_type = idx == -1 ? 0 : lib.vk.k('shift') && this.last_sel > -1 && libSet.libSource ? 1 : lib.vk.k('ctrl') ? 2 : !state ? 3 : 0;
switch (sel_type) {
case 0:
this.clearSelected();
this.sel_items = [];
break;
case 1: {
const direction = (idx > this.last_sel) ? 1 : -1;
if (!lib.vk.k('ctrl')) this.clearSelected();
for (let i = this.last_sel; ; i += direction) {
this.tree[i].sel = true;
if (i == idx) break;
}
this.getTreeSel();
lib.panel.treePaint();
break;
}
case 2:
this.tree[idx].sel = !this.tree[idx].sel;
this.getTreeSel();
this.last_sel = idx;
break;
case 3:
this.sel_items = [];
this.clearSelected();
this.tree[idx].sel = true;
this.addItems(this.sel_items, this.tree[idx].item);
this.uniq(this.sel_items);
this.last_sel = idx;
break;
}
}
setValues() {
this.countsRight = libSet.countsRight;
this.fullLineSelection = libSet.fullLineSelection;
this.highlight = {
node: libSet.highLightNode,
nowPlaying: libSet.highLightNowplaying,
nowPlayingIndicator: libSet.nowPlayingIndicator,
nowPlayingSidemarker: libSet.nowPlayingSidemarker,
nowPlayingShow: libSet.highLightNowplaying || libSet.nowPlayingIndicator || libSet.nowPlayingSidemarker,
row: libSet.highLightRow,
text: libSet.highLightText
};
this.iconVerticalPad = libSet.iconVerticalPad;
this.nodeCounts = libSet.nodeCounts;
this.nodeStyle = libSet.nodeStyle;
this.rootNode = libSet.rootNode;
this.rowStripes = libSet.rowStripes;
this.sbarShow = libSet.sbarShow;
this.showTracks = !libSet.facetView ? libSet.showTracks : false;
this.statistics = ['', '比特率', '持续时间', '合计大小', '等级', '热门', '日期', '播放队列', '播放次数', '首次播放', '最近播放', '添加时间'];
this.statisticsShow = libSet.itemShowStatistics;
this.label = !libSet.labelStatistics ? '' : this.statisticsShow ? this.statistics[this.statisticsShow] : '';
this.tooltipStatistics = libSet.tooltipStatistics;
this.treeIndent = libSet.treeIndent;
this.imgGetItemCount = libSet.itemOverlayType != 1 && libSet.albumArtLabelType == 2 && !this.statisticsShow && (this.nodeCounts == 1 || this.nodeCounts == 2);
}
showItem(i, type) {
if (!lib.panel.imgView) {
lib.sbar.checkScroll(i * lib.ui.row.h - Math.round(lib.sbar.rows_drawn / 2 - 1) * lib.ui.row.h, 'full');
return;
}
this.m.i = -1;
const b = $Lib.clamp(Math.round(lib.sbar.delta / lib.sbar.row.h), 0, lib.panel.rows - 1);
const f = Math.min(b + Math.floor(libImg.panel.h / lib.sbar.row.h), lib.panel.rows);
this.setTreeSel(i);
lib.panel.treePaint();
const row1 = Math.floor(i / (libImg.style.vertical ? libImg.columns : 1));
if (row1 <= b || row1 >= f) {
switch (type) {
case 'np':
case 'focus': {
const delta = (libImg.style.vertical ? libImg.panel.h : libImg.panel.w) / 2 > lib.sbar.row.h ? Math.floor((libImg.style.vertical ? libImg.panel.h : libImg.panel.w) / 2) : 0;
const deltaRows = Math.floor(delta / lib.sbar.row.h) * lib.sbar.row.h;
lib.sbar.checkScroll((libImg.style.vertical ? row1 : i) * lib.sbar.row.h - deltaRows, 'full');
break;
}
case 'up':
case 'down':
case 'left':
case 'right': {
const row2 = (row1 * lib.sbar.row.h - lib.sbar.scroll) / lib.sbar.row.h;
if (lib.sbar.rows_drawn - row2 < 1 || row2 < 0) lib.sbar.checkScroll((row1 + 1) * lib.sbar.row.h - lib.sbar.rows_drawn * lib.sbar.row.h);
else if (row2 < 1 & (type == 'up' || type == 'left')) lib.sbar.checkScroll(row1 * lib.sbar.row.h);
}
}
}
lib.lib.treeState(false, libSet.rememberTree);
}
sort(data) {
// 修复:ppt 和 panel 改为从 this 获取(作用域问题)
if (!this.ppt?.libSource && !this.panel?.multiProcess) return;
this.specialCharSort(data);
// 一次排序完成:类型排序 + 原有字段排序 + 字符串自然排序
data.sort((a, b) => {
// ============= 1. 优先按【字符类型】排序(核心规则) =============
const nameA = (a.srt[0] || '').trim();
const nameB = (b.srt[0] || '').trim();
// 字符类型判断:符号(0) → 数字(1) → 英文(2) → 中文(3) → 其他(4)
const getCharType = (str) => {
if (!str) return 4; // 空字符串排最后
const char = str.charAt(0); // 取第一个字符判断类型
const code = char.charCodeAt(0);
// 中文字符
if (code >= 0x4e00 && code <= 0x9fff) return 3;
// 数字 0-9
if (code >= 48 && code <= 57) return 1;
// 英文字母 A-Z a-z
if ((code >= 65 && code <= 90) || (code >= 97 && code <= 122)) return 2;
// 符号(非中文、非数字、非字母)
return 0;
};
const typeA = getCharType(nameA);
const typeB = getCharType(nameB);
// 类型不同 → 按类型排序
if (typeA !== typeB) return typeA - typeB;
// ============= 2. 类型相同 → 按原有 collator 字段排序 =============
const collatorCompare = this.collator.compare(a.srt[2], b.srt[2]);
if (collatorCompare !== 0) return collatorCompare;
// ============= 3. 再按 srt[3] 布尔值排序 =============
const flagCompare = (a.srt[3] && !b.srt[3]) ? 1 : (!a.srt[3] && b.srt[3]) ? -1 : 0;
if (flagCompare !== 0) return flagCompare;
// ============= 4. 最后按字符串本身自然排序 =============
return nameA.localeCompare(nameB, undefined, { numeric: true, sensitivity: 'base' });
});
}
sortIfNeeded(items) {
if (lib.panel.multiProcess && !libSet.customSort.length) items.OrderByFormat(lib.panel.playlistSort, 1);
else if (libSet.customSort.length) items.OrderByFormat(this.customSort, 1);
}
sortViewByFolder(data) {
// Use Intl.Collator with numeric: true to sort file/folder names (srt[0]) naturally,
// matching Windows Explorer's alphanumeric order (e.g., "2.01" < "2.15", "1985a" < "1986 -").
// This fixes incorrect track and folder sorting in View by Folder Structure.
const naturalCollator = new Intl.Collator(undefined, { numeric: true });
data.sort((a, b) => {
const nameCompare = naturalCollator.compare(a.srt[0], b.srt[0]);
if (nameCompare !== 0) return nameCompare;
// Fallback to metadata sorting (srt[2], srt[3]) for identical file/folder names.
return this.collator.compare(a.srt[2], b.srt[2]) || (a.srt[3] && !b.srt[3] ? 1 : 0);
});
}
specialCharHas(name) {
return RegExp(this.specialChar).test(name);
}
specialCharIsLeading(name) {
return RegExp(`^${this.specialChar}`).test(name);
}
specialCharPad(name) {
return name.replace(RegExp(`${this.specialChar}+`, 'g'), v => (`${v} `).slice(0, 5));
}
specialCharSort(data) {
const removed = [];
const filtered = data.filter((v, i) => {
const f = this.specialCharHas(v.srt[0]);
if (f) {
v.srt[1] = this.specialCharPad(v.srt[0]);
v.srt[2] = this.specialCharStrip(v.srt[0]);
v.srt[3] = this.specialCharIsLeading(v.srt[0]);
removed.push(i);
}
return f;
});
removed.reverse();
removed.forEach(v => data.splice(v, 1));
const sorted = filtered.sort((a, b) => this.collator.compare(a.srt[1], b.srt[1]));
sorted.forEach(v => data.push(v));
}
specialCharStrip(name) {
let [str1, ...str2] = name.split(' ');
str2 = str2.join(' ');
if (this.isDate(str1)) return `${str1} ${str2.replace(RegExp(this.specialChar, 'g'), '')}`;
return name.replace(RegExp(this.specialChar, 'g'), '');
}
track(plLoaded) {
const list = this.getHandleList();
this.notifySelection(list);
if (!plLoaded) this.selection_holder.SetSelection(list);
}
trackCount(item) {
return item.reduce((a, b) => a + b.count, 0);
}
treeTooltipFont() {
const libraryFontSize = SCALE((RES._4K ? grSet.libraryFontSize_layout - 0 : grSet.libraryFontSize_layout) || 14);
return !lib.panel.imgView ? [lib.ui.font.main.Name, /* ui.font.main.Size */ libraryFontSize + 3, lib.ui.font.main.Style] : [lib.ui.font.group.Name, /* ui.font.group.Size */ libraryFontSize + 3, lib.ui.font.group.Style];
}
uniq(arr) {
this.sel_items = [...new Set(arr)].sort(this.numSort);
}
upDnKeyCheckScroll(vkey) {
const row = (lib.panel.pos * lib.ui.row.h - lib.sbar.scroll) / lib.ui.row.h;
if (lib.sbar.rows_drawn - row < 3 || row < 0) lib.sbar.checkScroll((lib.panel.pos + 3) * lib.ui.row.h - lib.sbar.rows_drawn * lib.ui.row.h);
else if (row < 2 && vkey == lib.vk.up) lib.sbar.checkScroll((lib.panel.pos - 1) * lib.ui.row.h);
this.m.i = lib.panel.pos;
this.setTreeSel(lib.panel.pos);
lib.panel.treePaint();
this.setPlaylist(lib.panel.pos, this.tree[lib.panel.pos]);
lib.lib.treeState(false, libSet.rememberTree);
}
}
| 1 | 'use strict'; |
| 2 | |
| 3 | class LibPopulate { |
| 4 | constructor() { |
| 5 | this.alt_dbl_clicked = false; |
| 6 | this.childCount = 0; |
| 7 | this.clicked_on = 'none'; |
| 8 | this.cur = []; |
| 9 | this.cur_ix = 0; |
| 10 | this.customSort = FbTitleFormat(libSet.customSort); |
| 11 | this.dbl_clicked = false; |
| 12 | this.expandedTracks = 0; |
| 13 | this.expandLmt = 500; |
| 14 | this.hotKeys = $Lib.split(libSet.hotKeys, 0); |
| 15 | this.hand = false; |
| 16 | this.id = ''; |
| 17 | this.inlineRoot = libSet.rootNode && (libSet.inlineRoot || libSet.facetView); |
| 18 | this.is_focused = false; |
| 19 | this.last_sel = -1; |
| 20 | this.lbtnDn = false; |
| 21 | this.libItems = false; |
| 22 | this.mbtn_dbl_clicked = false; |
| 23 | this.nd = []; |
| 24 | this.nowp = -1; |
| 25 | this.rows = 0; |
| 26 | this.sel_items = []; |
| 27 | this.selection_holder = fb.AcquireUiSelectionHolder(); |
| 28 | this.selList = null; |
| 29 | this.setFocus = false; |
| 30 | this.specialChar = '[' + [Unicode.Apostrophe, |
| 31 | Unicode.SoftHyphen, Unicode.ArmenianHyphen, Unicode.Hyphen, Unicode.NonBreakingHyphen, |
| 32 | Unicode.FigureDash, Unicode.EnDash, Unicode.EmDash, Unicode.SmallEmDash |
| 33 | ].join('') + ']'; |
| 34 | this.sy_sz = 8; |
| 35 | this.tree = []; |
| 36 | |
| 37 | this.cache = { |
| 38 | standard: {}, |
| 39 | filter: {}, |
| 40 | search: {} |
| 41 | } |
| 42 | |
| 43 | this.highlight = {}; |
| 44 | |
| 45 | this.getMainMenuIndex = { |
| 46 | add: parseFloat(this.hotKeys[3]), |
| 47 | collapseAll: parseFloat(this.hotKeys[1]), |
| 48 | insert: parseFloat(this.hotKeys[5]), |
| 49 | new: parseFloat(this.hotKeys[7]), |
| 50 | searchClear: parseFloat(this.hotKeys[11]), |
| 51 | searchFocus: parseFloat(this.hotKeys[9]) |
| 52 | }; |
| 53 | |
| 54 | this.last_pressed_coord = { |
| 55 | x: undefined, |
| 56 | y: undefined |
| 57 | }; |
| 58 | |
| 59 | this.m = { |
| 60 | br: -1, |
| 61 | cur_br: 0, |
| 62 | i: -1 |
| 63 | }; |
| 64 | |
| 65 | this.row = { |
| 66 | cur: 0, |
| 67 | i: -1, |
| 68 | lineMax: [], |
| 69 | note_w: 0 |
| 70 | }; |
| 71 | |
| 72 | this.tf = { |
| 73 | added: FbTitleFormat(libSet.tfAdded), |
| 74 | bitrate: FbTitleFormat('%bitrate%'), |
| 75 | bytes: FbTitleFormat('%path%|%filesize%'), |
| 76 | date: FbTitleFormat(libSet.tfDate), |
| 77 | firstPlayed: FbTitleFormat(libSet.tfFirstPlayed), |
| 78 | lastPlayed: FbTitleFormat(libSet.tfLastPlayed), |
| 79 | pc: FbTitleFormat(libSet.tfPc), |
| 80 | popularity: FbTitleFormat(libSet.tfPopularity), |
| 81 | rating: FbTitleFormat(libSet.tfRating) |
| 82 | }; |
| 83 | |
| 84 | this.triangle = { |
| 85 | expand: null, |
| 86 | highlight: null, |
| 87 | select: null |
| 88 | }; |
| 89 | |
| 90 | this.collator = new Intl.Collator(undefined, { |
| 91 | sensitivity: 'accent', |
| 92 | numeric: true |
| 93 | }); |
| 94 | |
| 95 | this.setActions(); |
| 96 | this.setValues(); |
| 97 | } |
| 98 | |
| 99 | // * METHODS * // |
| 100 | |
| 101 | activateTooltip(value) { |
| 102 | if (!grSet.showTooltipLibrary && !grSet.showTooltipTruncated || libTooltip.Text == value) return; |
| 103 | this.checkTooltipFont('tree'); |
| 104 | if (grSet.showStyledTooltips) { |
| 105 | grm.ui.styledTooltipText = value; |
| 106 | } else { |
| 107 | libTooltip.Text = value; |
| 108 | libTooltip.Activate(); |
| 109 | } |
| 110 | } |
| 111 | |
| 112 | add(x, y, pl) { |
| 113 | if (y < lib.panel.search.h) return; |
| 114 | const ix = this.get_ix(x, y, true, false); |
| 115 | lib.panel.pos = ix; |
| 116 | if (ix < this.tree.length && ix >= 0) { |
| 117 | if (this.check_ix(this.tree[ix], x, y, true)) { |
| 118 | this.clearSelected(); |
| 119 | this.tree[ix].sel = true; |
| 120 | this.getTreeSel(); |
| 121 | this.load(this.sel_items, true, true, false, pl, false); |
| 122 | lib.lib.treeState(false, libSet.rememberTree); |
| 123 | } |
| 124 | } |
| 125 | } |
| 126 | |
| 127 | addItems(arr, item) { |
| 128 | item.forEach(v => { |
| 129 | for (let i = v.start; i <= v.end; i++) arr.push(i); |
| 130 | }); |
| 131 | } |
| 132 | |
| 133 | arrayToRange(array) { |
| 134 | return array.slice().sort((a, b) => a - b).reduce((ranges, value) => { |
| 135 | const lastIndex = ranges.length - 1; |
| 136 | if (lastIndex === -1 || ranges[lastIndex].max !== value - 1) { |
| 137 | ranges.push({ min: value, max: value }); |
| 138 | } else { |
| 139 | ranges[lastIndex].max = value; |
| 140 | } |
| 141 | return ranges; |
| 142 | }, []).map((range) => range.min !== range.max ? `${range.min}-${range.max}` : range.min.toString()); |
| 143 | } |
| 144 | |
| 145 | branch(br, base, node, block) { |
| 146 | if (!br || br.track || !lib.lib.initialised || lib.lib.list.Count != lib.lib.libNode.length) return; |
| 147 | const ix = this.showTracks ? 2 : 3; |
| 148 | const l = base ? 0 : this.rootNode ? br.level : br.level + 1; |
| 149 | if (base) node = false; |
| 150 | let i = 0; |
| 151 | let n = ''; |
| 152 | let n_o = '#get_branch#'; |
| 153 | let nU = ''; |
| 154 | this.range(br.item).forEach(v => { |
| 155 | n = lib.lib.node[v][l]; |
| 156 | nU = n.toUpperCase(); |
| 157 | if (n_o != nU) { |
| 158 | n_o = nU; |
| 159 | if (lib.panel.multiPrefix) n = lib.lib.prefixes(n); |
| 160 | br.child[i] = { |
| 161 | nm: n, |
| 162 | sel: false, |
| 163 | child: [], |
| 164 | track: l > lib.lib.node[v].length - ix, |
| 165 | item: [v], |
| 166 | srt: lib.lib.sort(n) |
| 167 | }; |
| 168 | i++; |
| 169 | } else br.child[i - 1].item.push(v); |
| 170 | }); |
| 171 | this.condense(br.child); |
| 172 | this.buildTree(lib.lib.root, 0, node, true, block); |
| 173 | } |
| 174 | |
| 175 | branchChange(br) { |
| 176 | const arr = br.level == 0 ? lib.lib.root : this.tree[br.par].child; |
| 177 | this.childCount = 0; |
| 178 | this.getChildCount(arr, br.ix); |
| 179 | arr.forEach(v => v.child = []); |
| 180 | return this.childCount; |
| 181 | } |
| 182 | |
| 183 | branchCount(br, base, node, block, key, type) { |
| 184 | if (!br || !lib.lib.node.length) return; |
| 185 | if (this.cache[type][key]) return this.cache[type][key].value; |
| 186 | const l = base ? 0 : this.rootNode ? br.level : br.level + 1; |
| 187 | const b = []; |
| 188 | let n = ''; |
| 189 | let n_o = '#get_branch#'; |
| 190 | let nU = ''; |
| 191 | if (base) node = false; |
| 192 | const full = !!br.root; |
| 193 | this.range(br.item).forEach(v => { |
| 194 | if (l < lib.lib.node[v].length) { |
| 195 | n = lib.lib.node[v][l]; |
| 196 | nU = n.toUpperCase(); |
| 197 | if (n_o != nU) { |
| 198 | n_o = nU; |
| 199 | if (lib.panel.multiPrefix) n = lib.lib.prefixes(n); |
| 200 | b.push({ |
| 201 | nm: n, |
| 202 | srt: lib.lib.sort(n) |
| 203 | }); |
| 204 | } |
| 205 | } |
| 206 | }); |
| 207 | if (!lib.panel.multiProcess && (!node || node && !full)) this.merge(b, true); |
| 208 | if (lib.panel.multiProcess) { |
| 209 | const multi_cond = []; |
| 210 | const multi_obj = []; |
| 211 | const multi_rem = []; |
| 212 | const nm_arr = []; |
| 213 | let h = -1; |
| 214 | let j = 0; |
| 215 | let multi = []; |
| 216 | n = ''; |
| 217 | n_o = '#condense#'; |
| 218 | nU = ''; |
| 219 | b.forEach((v, i) => { |
| 220 | if (v.nm.includes('@@')) { |
| 221 | multi = this.getAllCombinations(v.nm); |
| 222 | multi_rem.push(i); |
| 223 | multi.forEach(w => { |
| 224 | multi_obj.push({ |
| 225 | nm: w.join(''), |
| 226 | srt: lib.lib.sort(w.join('')) |
| 227 | }); |
| 228 | }); |
| 229 | } |
| 230 | }); |
| 231 | let i = multi_rem.length; |
| 232 | while (i--) b.splice(multi_rem[i], 1); |
| 233 | this.sort(multi_obj); |
| 234 | multi_obj.forEach(v => { |
| 235 | n = v.nm; |
| 236 | nU = n.toUpperCase(); |
| 237 | if (n_o != nU) { |
| 238 | n_o = nU; |
| 239 | multi_cond[j] = { |
| 240 | nm: n, |
| 241 | srt: v.srt |
| 242 | }; |
| 243 | j++; |
| 244 | } |
| 245 | }); |
| 246 | b.forEach(v => { |
| 247 | v.nm = v.nm.replace(Regex.LibMarkerMultiProcess, ''); |
| 248 | nm_arr.push(v.nm); |
| 249 | }); |
| 250 | multi_cond.forEach((v, i) => { |
| 251 | h = nm_arr.indexOf(v.nm); |
| 252 | if (h != -1) multi_cond.splice(i, 1); |
| 253 | }); |
| 254 | multi_cond.forEach((v, i) => b.splice(i + 1, 0, { |
| 255 | nm: v.nm, |
| 256 | srt: v.srt |
| 257 | })); |
| 258 | this.merge(b, true); |
| 259 | } |
| 260 | this.cache[type][key] = { |
| 261 | value: b.length, |
| 262 | items: [] |
| 263 | } |
| 264 | return b.length; |
| 265 | } |
| 266 | |
| 267 | buildTree(br, level, node, full, block) { |
| 268 | const l = !this.rootNode ? level : level - 1; |
| 269 | let i = 0; |
| 270 | let j = 0; |
| 271 | if (!br[0].sorted) { |
| 272 | switch (lib.panel.multiProcess) { |
| 273 | case false: |
| 274 | if (!node || node && !full) this.merge(br); |
| 275 | break; |
| 276 | case true: { |
| 277 | const multi_cond = []; |
| 278 | const multi_obj = []; |
| 279 | const multi_rem = []; |
| 280 | const nm_arr = []; |
| 281 | let h = -1; |
| 282 | let multi = []; |
| 283 | let n = ''; |
| 284 | let n_o = '#condense#'; |
| 285 | let nU = ''; |
| 286 | br.forEach((v, i) => { |
| 287 | if (v.nm.includes('@@') || v.nm.includes(lib.panel.softSplitter)) { |
| 288 | multi = this.getAllCombinations(v.nm); |
| 289 | multi_rem.push(i); |
| 290 | multi.forEach(w => { |
| 291 | multi_obj.push({ |
| 292 | nm: w.join(''), |
| 293 | item: this.copy(v.item), |
| 294 | track: v.track, |
| 295 | srt: lib.lib.sort(w.join('')) |
| 296 | }); |
| 297 | }); |
| 298 | } |
| 299 | }); |
| 300 | i = multi_rem.length; |
| 301 | while (i--) br.splice(multi_rem[i], 1); |
| 302 | this.sort(multi_obj); |
| 303 | multi_obj.forEach(v => { |
| 304 | n = v.nm; |
| 305 | nU = n.toUpperCase(); |
| 306 | if (n_o != nU) { |
| 307 | n_o = nU; |
| 308 | multi_cond[j] = { |
| 309 | nm: n, |
| 310 | item: this.copy(v.item), |
| 311 | track: v.track, |
| 312 | srt: v.srt |
| 313 | }; |
| 314 | j++; |
| 315 | } else v.item.forEach(v => multi_cond[j - 1].item.push(v)); |
| 316 | }); |
| 317 | br.forEach(v => { |
| 318 | v.nm = v.nm.replace(Regex.LibMarkerMultiProcess, ''); |
| 319 | nm_arr.push(v.nm); |
| 320 | }); |
| 321 | multi_cond.forEach((v, i) => { |
| 322 | h = nm_arr.indexOf(v.nm); |
| 323 | if (h != -1) { |
| 324 | v.item.forEach(v => br[h].item.push(v)); |
| 325 | multi_cond.splice(i, 1); |
| 326 | } |
| 327 | }); |
| 328 | multi_cond.forEach((v, i) => br.splice(i + 1, 0, { |
| 329 | nm: v.nm, |
| 330 | sel: false, |
| 331 | track: v.track, |
| 332 | child: [], |
| 333 | item: this.copy(v.item), |
| 334 | srt: v.srt |
| 335 | })); |
| 336 | this.merge(br); |
| 337 | break; |
| 338 | } |
| 339 | } |
| 340 | this.sort(br); |
| 341 | br[0].sorted = true; |
| 342 | } |
| 343 | const br_l = br.length; |
| 344 | const par = this.tree.length - 1; |
| 345 | if (level == 0) this.clearTree(); |
| 346 | |
| 347 | // * Apply View By Folder Hide if View by Folder Structure is active |
| 348 | if (lib.panel.folderView) lib.men.setViewByFolderHide(this.tree, libSet.viewByFolderHide); |
| 349 | |
| 350 | br.forEach((v, i) => { |
| 351 | j = this.tree.length; |
| 352 | const item = this.tree[j] = v; |
| 353 | item.top = !i; |
| 354 | item.bot = i == br_l - 1; |
| 355 | item.count = ''; |
| 356 | item.ix = j; |
| 357 | item.level = level; |
| 358 | item.par = par; |
| 359 | switch (true) { |
| 360 | case libSet.facetView: |
| 361 | if (!item.root) item.track = true; |
| 362 | break; |
| 363 | case l != -1 && !this.showTracks: |
| 364 | this.range(item.item).some(v => { |
| 365 | if (lib.lib.node[v] && (lib.lib.node[v].length == l + 1 || lib.lib.node[v].length == l + 2)) return item.track = true; |
| 366 | }); |
| 367 | break; |
| 368 | case l == 0 && lib.lib.node[item.item[0].start] && lib.lib.node[item.item[0].start].length == 1: |
| 369 | item.track = true; |
| 370 | break; |
| 371 | } |
| 372 | if (lib.ui.col.counts && (!item.track || !this.showTracks)) { |
| 373 | const str = `@!#${lib.ui.col.counts}\`${this.highlight.text ? lib.ui.col.text_h : lib.ui.col.counts}\`${lib.ui.col.textSel}@!#`; |
| 374 | if (!item.nm.endsWith(str)) item.nm += str; |
| 375 | } |
| 376 | item.name = !lib.panel.noDisplay ? item.nm : item.nm.replace(Regex.LibMarkerNoDisplayContent, ''); |
| 377 | if (v.child.length > 0) this.buildTree(v.child, level + 1, node, !!item.root); |
| 378 | }); |
| 379 | if (lib.ui.style.squareNode && lib.ui.col.line) { |
| 380 | this.row.lineMax = []; |
| 381 | this.tree.forEach(v => { |
| 382 | const depth = !this.inlineRoot ? v.level : Math.max(v.level - 1, 0) |
| 383 | this.row.lineMax[depth] = v.ix |
| 384 | }); |
| 385 | } |
| 386 | if (this.rootNode == 3) this.tree[0].name = this.tree[0].child.length > 1 ? lib.panel.rootName.replace('#^^^^#', this.tree[0].child.length) : lib.panel.rootName1; |
| 387 | lib.find.initials = null; |
| 388 | if (!block) { |
| 389 | lib.sbar.setRows(this.tree.length); |
| 390 | lib.panel.treePaint(); |
| 391 | } |
| 392 | } |
| 393 | |
| 394 | butTooltipFont() { |
| 395 | return [grFont.fontDefault, 15 * $Lib.scale * libSet.zoomTooltipBut / 100, 0]; |
| 396 | } |
| 397 | |
| 398 | calcStatistics(v) { |
| 399 | const key = `stat${this.getKey(v)}`; |
| 400 | const type = lib.panel.search.txt ? 'search' : libSet.filterBy ? 'filter' : 'standard'; |
| 401 | if (this.cache[type][key]) return this.cache[type][key].value; |
| 402 | const handleList = new FbMetadbHandleList(); |
| 403 | let items = []; |
| 404 | this.addItems(items, v.item); |
| 405 | items = [...new Set(items)].sort(this.numSort) |
| 406 | items.some(w => { |
| 407 | if (w >= lib.panel.list.Count) return true; |
| 408 | handleList.Add(lib.panel.list[w]); |
| 409 | }); |
| 410 | let date = ''; |
| 411 | let dates; |
| 412 | let indices; |
| 413 | let ln; |
| 414 | let n; |
| 415 | let tf; |
| 416 | let value; |
| 417 | let values; |
| 418 | switch (libSet.itemShowStatistics) { |
| 419 | case 1: // bitrate |
| 420 | values = this.tf.bitrate.EvalWithMetadbs(handleList); |
| 421 | if (values.length == 1) { |
| 422 | value = Number(values[0]) || ''; |
| 423 | } else { |
| 424 | let lengths = FbTitleFormat('%length_seconds_fp%').EvalWithMetadbs(handleList) |
| 425 | const total = values.map((v, i) => v * lengths[i]); |
| 426 | let totals = total.map(v => parseFloat(v) || ''); |
| 427 | totals = totals.filter((v, i) => v && lengths[i] ? v : lengths.splice(i, 1)); |
| 428 | totals = totals.reduce((a, b) => a + b, 0); |
| 429 | lengths = lengths.map(v => parseFloat(v)).reduce((a, b) => a + b, 0); |
| 430 | value = Number(Math.round(totals / lengths)) || ''; |
| 431 | } |
| 432 | if (lib.panel.imgView && value) value = `${value} kbps`; |
| 433 | this.cache[type][key] = { |
| 434 | value, |
| 435 | items |
| 436 | } |
| 437 | return value; |
| 438 | case 2: { // duration |
| 439 | const duration = utils.FormatDuration(handleList.CalcTotalDuration()); |
| 440 | this.cache[type][key] = { |
| 441 | value: duration, |
| 442 | items: [] |
| 443 | } |
| 444 | return duration; |
| 445 | } |
| 446 | case 3: { // total size |
| 447 | const bytes = this.tf.bytes.EvalWithMetadbs(handleList); |
| 448 | let size = [...new Set(bytes)]; |
| 449 | size = size.map(v => { |
| 450 | const a = v.split('|'); |
| 451 | return a[a.length - 1]; |
| 452 | }); |
| 453 | if (!size.length) return ''; |
| 454 | size = size.map(v => parseInt(v)).reduce((a, b) => a + b, 0); |
| 455 | const formattedBytes = this.formatBytes(size); |
| 456 | this.cache[type][key] = { |
| 457 | value: formattedBytes, |
| 458 | items |
| 459 | } |
| 460 | return formattedBytes; |
| 461 | } |
| 462 | case 4: // rating |
| 463 | case 5: // popularity |
| 464 | tf = libSet.itemShowStatistics == 4 ? this.tf.rating : this.tf.popularity; |
| 465 | values = tf.EvalWithMetadbs(handleList); |
| 466 | values = this.getNumbers(values); |
| 467 | values = values.filter(Boolean) |
| 468 | ln = values.length; |
| 469 | if (!ln) return ''; |
| 470 | values = values.map(v => parseFloat(v)).reduce((a, b) => a + b, 0); |
| 471 | value = Math.ceil(values / ln); |
| 472 | if (lib.panel.imgView && this.label) value = (libSet.itemShowStatistics == 4 ? '等级 ' : '热门 ') + value; |
| 473 | this.cache[type][key] = { |
| 474 | value, |
| 475 | items |
| 476 | } |
| 477 | return value; |
| 478 | case 6: // date (first release) |
| 479 | case 9: // firstPlayed |
| 480 | case 10: // lastPlayed |
| 481 | case 11: // added |
| 482 | tf = |
| 483 | libSet.itemShowStatistics == 6 ? this.tf.date : |
| 484 | libSet.itemShowStatistics == 9 ? this.tf.firstPlayed : |
| 485 | libSet.itemShowStatistics == 10 ? this.tf.lastPlayed : |
| 486 | this.tf.added; |
| 487 | dates = tf.EvalWithMetadbs(handleList); |
| 488 | dates = dates.filter(v => v !== ''); |
| 489 | ln = dates.length; |
| 490 | if (ln) { |
| 491 | if (ln == 1) date = dates[0]; |
| 492 | else { |
| 493 | date = libSet.itemShowStatistics == 6 || libSet.itemShowStatistics == 9 || libSet.itemShowStatistics == 11 ? |
| 494 | dates.reduce((pre, cur) => Date.parse(pre) > Date.parse(cur) ? cur : pre) : |
| 495 | dates.reduce((pre, cur) => Date.parse(cur) > Date.parse(pre) ? cur : pre) |
| 496 | } |
| 497 | } |
| 498 | if (!date) return ''; |
| 499 | if (lib.panel.imgView && this.label) date = ['', '', '', '', '', '', (v.root ? '首次发行 ' : ''), '', '', '首次播放 ', '最近播放 ', '添加时间 '][libSet.itemShowStatistics] + date; |
| 500 | this.cache[type][key] = { |
| 501 | value: date, |
| 502 | items |
| 503 | } |
| 504 | return date; |
| 505 | case 7: { // queue |
| 506 | let index = ''; |
| 507 | indices = []; |
| 508 | const queueHandles = plman.GetPlaybackQueueHandles(); |
| 509 | handleList.Convert().forEach(h => { |
| 510 | const j = queueHandles.Find(h); |
| 511 | if (j != -1) indices.push(j + 1); |
| 512 | }); |
| 513 | ln = indices.length; |
| 514 | if (ln) { |
| 515 | if (ln == 1) index = indices[0]; |
| 516 | else { |
| 517 | index = this.arrayToRange(indices); |
| 518 | index.join(); |
| 519 | } |
| 520 | } |
| 521 | if (!index) return ''; |
| 522 | if (lib.panel.imgView && this.label) index = `Queue ${index}`; |
| 523 | this.cache[type][key] = { |
| 524 | value: index, |
| 525 | items |
| 526 | } |
| 527 | return index; |
| 528 | } |
| 529 | case 8: { // playcount |
| 530 | let playcount = this.tf.pc.EvalWithMetadbs(handleList); |
| 531 | const played = [...new Set(playcount)]; |
| 532 | n = played.map(v => { |
| 533 | const a = v.split('|'); |
| 534 | return a[a.length - 1]; |
| 535 | }); |
| 536 | playcount = this.getNumbers(n); |
| 537 | if (!playcount.length) return ''; |
| 538 | playcount = n.map(v => parseInt(v)).reduce((a, b) => a + b, 0); |
| 539 | if (lib.panel.imgView) playcount = `${(this.label ? '已播放 ' : '') + playcount} 次`; |
| 540 | this.cache[type][key] = { |
| 541 | value: playcount, |
| 542 | items |
| 543 | } |
| 544 | return playcount; |
| 545 | } |
| 546 | } |
| 547 | } |
| 548 | |
| 549 | checkAutoHeight() { |
| 550 | if (lib.panel.pn_h_auto && !lib.panel.imgView && libSet.pn_h == libSet.pn_h_min && this.tree[0]) this.clearChild(this.tree[0]); |
| 551 | } |
| 552 | |
| 553 | check_ix(br, x, y, type) { |
| 554 | if (lib.panel.imgView) return true; |
| 555 | if (!br) return false; |
| 556 | x -= lib.ui.x; |
| 557 | const level = !this.inlineRoot ? br.level : Math.max(br.level - 1, 0); |
| 558 | const icon_w = this.inlineRoot && br.ix == 0 ? 0 : lib.ui.icon.w + (!this.fullLineSelection ? lib.ui.l.wf : 0); |
| 559 | return type ? (x >= Math.round(this.treeIndent * level + lib.ui.sz.margin) && x < Math.round(this.treeIndent * level + lib.ui.sz.margin) + br.w + icon_w) : |
| 560 | (x >= Math.round(this.treeIndent * level + lib.ui.sz.margin) + icon_w) && x < Math.min(Math.round(this.treeIndent * level + lib.ui.sz.margin) + icon_w + br.w, lib.panel.tree.w); |
| 561 | } |
| 562 | |
| 563 | checkNode(gr) { |
| 564 | if (lib.sbar.draw_timer || this.nodeStyle != 7) return; |
| 565 | try { |
| 566 | lib.ui.style.symb.SetPartAndStateID(2, 1); |
| 567 | lib.ui.style.symb.SetPartAndStateID(2, 2); |
| 568 | lib.ui.style.symb.DrawThemeBackground(gr, -lib.ui.sz.node, -lib.ui.sz.node, lib.ui.sz.node, lib.ui.sz.node); |
| 569 | } catch (e) { |
| 570 | libSet.nodeStyle = 0; |
| 571 | this.nodeStyle = 0; |
| 572 | } |
| 573 | } |
| 574 | |
| 575 | checkRow(x, y) { |
| 576 | this.m.br = -1; |
| 577 | const im = this.get_ix(x, y, true, false); |
| 578 | if (im >= this.tree.length || im < 0) return -1; |
| 579 | if (lib.panel.imgView) return im; |
| 580 | const item = this.tree[im]; |
| 581 | const level = !this.inlineRoot ? item.level : Math.max(item.level - 1, 0); |
| 582 | if (x < Math.round(this.treeIndent * level) + lib.ui.icon.w + lib.ui.sz.margin + lib.ui.x + lib.ui.w && (!item.track || item.root)) this.m.br = im; |
| 583 | return im; |
| 584 | } |
| 585 | |
| 586 | check_tooltip(ix, x, y) { |
| 587 | if (this.lbtnDn || lib.sbar.draw_timer) return; |
| 588 | const item = this.tree[ix]; |
| 589 | let text = ''; |
| 590 | if (!item) return; |
| 591 | switch (true) { |
| 592 | case !lib.panel.imgView: { |
| 593 | const trace1 = item.tt && item.tt.needed && x >= item.tt.x && x <= item.tt.x + item.tt.w && y >= item.tt.y && y <= item.tt.y + lib.ui.row.h; |
| 594 | const trace2 = item.stats_tt && item.stats_tt.needed && x >= item.stats_tt.x + item.stats_tt.w && x <= lib.ui.w - lib.ui.sz.marginRight && y >= item.stats_tt.y && y <= item.stats_tt.y + lib.ui.row.h * 0.9; |
| 595 | if (trace2) { |
| 596 | text = this.statisticsShow ? |
| 597 | (item.statistics !== undefined ? `${this.statistics[this.statisticsShow]}: ${item.statistics}` : '') : |
| 598 | (item.count ? `${['', '首', '项'][this.nodeCounts]}:${item.count}` : ''); |
| 599 | } else if (trace1) { |
| 600 | text = (!lib.panel.colMarker ? item.name : item.name.replace(Regex.LibMarkerColor, '')) + (!this.countsRight || this.statisticsShow ? item.count : ''); |
| 601 | text = text.replace(/&/g, '&&'); |
| 602 | } |
| 603 | if (text != libTooltip.Text) this.deactivateTooltip(); |
| 604 | if (!trace1 && !trace2 || !item.tt && !item.stats_tt) { |
| 605 | this.deactivateTooltip(); |
| 606 | return; |
| 607 | } |
| 608 | break; |
| 609 | } |
| 610 | case lib.panel.imgView: { |
| 611 | let trace1 = false; |
| 612 | let trace2 = false; |
| 613 | let trace3 = false; |
| 614 | if (!libImg.labels.hide) { |
| 615 | if (!item.tt) { |
| 616 | this.deactivateTooltip(); |
| 617 | return; |
| 618 | } |
| 619 | trace1 = x >= item.tt.x && x <= item.tt.x + item.tt.w && y >= item.tt.y1 && y <= item.tt.y1 + libImg.text.h; |
| 620 | trace2 = item.tt.y2 == -1 ? false : x >= item.tt.x && x <= item.tt.x + item.tt.w && y >= item.tt.y2 && y <= item.tt.y2 + libImg.text.h; |
| 621 | trace3 = item.tt.y3 == -1 ? false : x >= item.tt.x && x <= item.tt.x + item.tt.w && y >= item.tt.y3 && y <= item.tt.y3 + libImg.text.h; |
| 622 | text = trace1 || trace2 || trace3 ? item.tt.text : ''; |
| 623 | if (lib.panel.colMarker) text = text.replace(Regex.LibMarkerColor, ''); |
| 624 | text = text.replace(/&/g, '&&'); |
| 625 | if (text != libTooltip.Text) this.deactivateTooltip(); |
| 626 | if (!trace1 && !trace2 && !trace3 || !item.tt[1] && !item.tt[2] && !item.tt[3]) { |
| 627 | this.deactivateTooltip(); |
| 628 | return; |
| 629 | } |
| 630 | } else { |
| 631 | text = lib.panel.lines == 2 ? !libSet.albumArtFlipLabels ? `${item.grp}\n${item.lot}` : `${item.lot}\n${item.grp}` : item.grp; |
| 632 | if (lib.panel.colMarker) text = text.replace(Regex.LibMarkerColor, ''); |
| 633 | text = text.replace(/&/g, '&&'); |
| 634 | if (text != libTooltip.Text) this.deactivateTooltip(); |
| 635 | } |
| 636 | break; |
| 637 | } |
| 638 | } |
| 639 | this.activateTooltip(text); |
| 640 | lib.timer.tooltipLib(); |
| 641 | } |
| 642 | |
| 643 | checkTooltip(item, x, y, txt_w, w) { |
| 644 | item.tt = { |
| 645 | needed: txt_w > w, |
| 646 | x, |
| 647 | y, |
| 648 | w |
| 649 | }; |
| 650 | item.stats_tt = { |
| 651 | needed: !this.tooltipStatistics || !this.statisticsShow || item.root ? false : [false, true, false, true, true, true, true, true, true, true, true, true][this.statisticsShow] && item.statistics !== undefined, |
| 652 | x, |
| 653 | y: y + lib.ui.row.h * 0.1, |
| 654 | w |
| 655 | }; |
| 656 | } |
| 657 | |
| 658 | checkTooltipFont(type) { |
| 659 | switch (type) { |
| 660 | case 'btn': { |
| 661 | const newTooltipFont = this.butTooltipFont(); |
| 662 | if ($Lib.equal(this.cur, newTooltipFont)) return; |
| 663 | this.cur = newTooltipFont; |
| 664 | break; |
| 665 | } |
| 666 | case 'tree': { |
| 667 | const newTooltipFont = this.treeTooltipFont(); |
| 668 | if ($Lib.equal(this.cur, newTooltipFont)) return; |
| 669 | this.cur = newTooltipFont; |
| 670 | break; |
| 671 | } |
| 672 | } |
| 673 | libTooltip.SetFont(this.cur[0], this.cur[1], this.cur[2]); |
| 674 | } |
| 675 | |
| 676 | clearSelected() { |
| 677 | this.tree.forEach(v => v.sel = false); |
| 678 | } |
| 679 | |
| 680 | clearTree() { |
| 681 | if (lib.panel.imgView && this.tree.length) libImg.trimCache(this.tree[0].key); |
| 682 | this.tree = []; |
| 683 | } |
| 684 | |
| 685 | clearChild(br) { |
| 686 | br.child = []; |
| 687 | this.buildTree(lib.lib.root, 0, true, true); |
| 688 | } |
| 689 | |
| 690 | clickedOn(x, y, item) { |
| 691 | if (lib.panel.imgView) return 'text'; |
| 692 | if (this.inlineRoot && item.ix == 0) return this.check_ix(item, x, y, false) ? 'text' : 'none'; |
| 693 | const level = !this.inlineRoot ? item.level : Math.max(item.level - 1, 0); |
| 694 | return x < lib.ui.x + Math.round(this.treeIndent * level) + lib.ui.icon.w + lib.ui.sz.margin ? 'node' : this.check_ix(item, x, y, false) ? 'text' : 'none'; |
| 695 | } |
| 696 | |
| 697 | collapseAll() { |
| 698 | let ic = this.get_ix(lib.ui.x, lib.ui.y + lib.panel.tree.y + lib.ui.row.h / 2, true, false); |
| 699 | if (ic >= this.tree.length || ic < 0) return; |
| 700 | let j = this.tree[ic].level; |
| 701 | if (this.rootNode) j -= 1; |
| 702 | if (this.tree[ic].level != 0) { |
| 703 | const par = this.tree[ic].par; |
| 704 | const pr_pr = []; |
| 705 | for (let m = 1; m < j + 1; m++) { |
| 706 | pr_pr[m] = m == 1 ? par : this.tree[pr_pr[m - 1]].par; |
| 707 | ic = pr_pr[m]; |
| 708 | } |
| 709 | } |
| 710 | const nm = this.tree[ic].srt[0].toUpperCase(); |
| 711 | this.tree.forEach(v => { |
| 712 | if (!v.root) v.child = []; |
| 713 | }); |
| 714 | this.buildTree(lib.lib.root, 0); |
| 715 | let scr_pos = false; |
| 716 | this.tree.some((v, i) => { |
| 717 | if (v.srt[0].toUpperCase() == nm) { |
| 718 | lib.sbar.checkScroll(i * lib.ui.row.h); |
| 719 | return scr_pos = true; |
| 720 | } |
| 721 | }); |
| 722 | if (!scr_pos) { |
| 723 | lib.sbar.reset(); |
| 724 | lib.panel.treePaint(); |
| 725 | } |
| 726 | lib.lib.treeState(false, libSet.rememberTree); |
| 727 | } |
| 728 | |
| 729 | condense(child) { |
| 730 | child.forEach(v => { |
| 731 | if (typeof v.item[0] !== 'number') return; |
| 732 | v.item = this.createRanges(v.item); |
| 733 | }); |
| 734 | } |
| 735 | |
| 736 | copy(item) { |
| 737 | return item.map(v => v); |
| 738 | } |
| 739 | |
| 740 | createImages() { |
| 741 | if (!lib.ui.w || !lib.ui.h) return; |
| 742 | if (!this.nodeStyle) { |
| 743 | const sz = lib.ui.sz.node; |
| 744 | const ln_w = Math.max(Math.floor(sz / 9), 1); |
| 745 | let plus = true; |
| 746 | let hot = false; |
| 747 | let sy_w = ln_w; |
| 748 | const x = 0; |
| 749 | const y = 0; |
| 750 | if (((sz - ln_w * 3) / 2) % 1 != 0) sy_w = ln_w > 1 ? ln_w - 1 : ln_w + 1; |
| 751 | for (let j = 0; j < 4; j++) { |
| 752 | this.nd[j] = $Lib.gr(sz, sz, true, g => { |
| 753 | hot = j > 1; |
| 754 | plus = !j || j == 2; |
| 755 | if (grSet.libraryDesign !== 'reborn') { |
| 756 | g.FillSolidRect(x, y, sz, sz, RGB(145, 145, 145)); |
| 757 | if (!hot) FillGradRect(g, x + ln_w, y + ln_w, sz - ln_w * 2, sz - ln_w * 2, 91, plus ? lib.ui.col.icon_e[0] : lib.ui.col.icon_c[0], plus ? lib.ui.col.icon_e[1] : lib.ui.col.icon_c[1], 1.0); |
| 758 | else FillGradRect(g, x + ln_w, y + ln_w, sz - ln_w * 2, sz - ln_w * 2, 91, lib.ui.col.icon_h[0], lib.ui.col.icon_h[1], 1.0); |
| 759 | // const x_o = [x, x + sz - ln_w, x, x + sz - ln_w]; |
| 760 | // const y_o = [y, y, y + sz - ln_w, y + sz - ln_w]; |
| 761 | for (let i = 0; i < 4; i++) { // g.FillSolidRect(x_o[i], y_o[i], ln_w, ln_w, RGB(186, 187, 188)); |
| 762 | if (grSet.libraryDesign === 'traditional') g.FillSolidRect(x, y, sz, sz, lib.ui.col.iconPlusBg); |
| 763 | } |
| 764 | } else if (libSet.nodeStyle === 0) { |
| 765 | g.DrawRect(x, y, sz - 1, sz - 1, 1, lib.ui.col.iconPlusBg); |
| 766 | } |
| 767 | if (plus) g.FillSolidRect(Math.floor(x + (sz - sy_w) / 2), y + ln_w + Math.min(ln_w, sy_w), sy_w, sz - ln_w * 2 - Math.min(ln_w, sy_w) * 2, !hot ? lib.ui.col.iconPlus : lib.ui.col.iconPlus_h); |
| 768 | g.FillSolidRect(x + ln_w + Math.min(ln_w, sy_w), Math.floor(y + (sz - sy_w) / 2), sz - ln_w * 2 - Math.min(ln_w, sy_w) * 2, sy_w, !hot ? (plus ? lib.ui.col.iconMinus_e : lib.ui.col.iconMinus_c) : lib.ui.col.iconMinus_h); |
| 769 | }); |
| 770 | } |
| 771 | } else { |
| 772 | let lightCol = lib.ui.isLightCol(lib.ui.col.icon_h); |
| 773 | $Lib.gr(1, 1, false, g => { |
| 774 | const h = this.nodeStyle != 7 ? g.CalcTextHeight('String', lib.ui.icon.font) / 15 : g.CalcTextHeight('String', lib.ui.font.main) / 20; |
| 775 | this.sy_sz = Math.floor(Math.max(8 * libSet.zoomNode / 100 * h, 5)); |
| 776 | }); |
| 777 | |
| 778 | const sz = Math.max(Math.round(this.sy_sz * 1.666667), 1); |
| 779 | this.triangle.highlight = $Lib.gr(sz, sz, true, g => { |
| 780 | g.SetSmoothingMode(4); |
| 781 | g.FillPolygon(lib.ui.col.icon_h, 1, [sz, 0, sz, sz, 0, sz]); |
| 782 | g.SetSmoothingMode(0); |
| 783 | }); |
| 784 | lightCol = lib.ui.isLightCol(lib.ui.col.icon_e); |
| 785 | this.triangle.expand = $Lib.gr(sz, sz, true, g => { |
| 786 | g.SetSmoothingMode(4); |
| 787 | g.FillPolygon(lib.ui.col.icon_e & (lightCol ? 0xC0ffffff : 0xBAffffff), 1, [sz, 0, sz, sz, 0, sz]); |
| 788 | g.SetSmoothingMode(0); |
| 789 | }); |
| 790 | this.triangle.select = $Lib.gr(sz, sz, true, g => { |
| 791 | g.SetSmoothingMode(4); |
| 792 | g.FillPolygon(lib.ui.col.textSel & (lightCol ? 0xC0ffffff : 0xBAffffff), 1, [sz, 0, sz, sz, 0, sz]); |
| 793 | g.SetSmoothingMode(0); |
| 794 | }); |
| 795 | } |
| 796 | } |
| 797 | |
| 798 | createRanges(arr) { |
| 799 | const ret = []; |
| 800 | let start; |
| 801 | let end; |
| 802 | for (let i = 0; i < arr.length; i++) { |
| 803 | start = end = arr[i]; |
| 804 | while (arr[i + 1] == end + 1) { |
| 805 | end++; |
| 806 | i++; |
| 807 | } |
| 808 | ret.push(start == end ? { |
| 809 | start, |
| 810 | end: start, |
| 811 | count: 1 |
| 812 | } : { |
| 813 | start, |
| 814 | end, |
| 815 | count: end - start + 1 |
| 816 | }); |
| 817 | } |
| 818 | return ret; |
| 819 | } |
| 820 | |
| 821 | cusCol(gr, text, item, item_x, item_y, w, h, type, np, font, ellipsisSpace, cus) { |
| 822 | if (!text) return; |
| 823 | let col = []; |
| 824 | let col_x = []; |
| 825 | let col_w = []; |
| 826 | let w_arr = []; |
| 827 | let x = 0; |
| 828 | if (item[cus] && item[cus].id == this.id && !this.highlight.nowPlayingIndicator) { |
| 829 | col = item[cus].col; |
| 830 | col_x = item[cus].col_x; |
| 831 | col_w = item[cus].col_w; |
| 832 | text = item[cus].txt; |
| 833 | w_arr = item[cus].txt_w; |
| 834 | } else { |
| 835 | text = text.split('@!#'); |
| 836 | text.forEach((v, i) => { |
| 837 | if (i % 2 == 0) w_arr[i] = gr.CalcTextWidth(text[i], font); |
| 838 | }); |
| 839 | text.forEach((v, i) => { |
| 840 | if (i % 2 == 0) { |
| 841 | const cur_w = x + w_arr[i]; |
| 842 | const next_text = !!text[i + 2]; |
| 843 | let ellipsis_corr = 0; |
| 844 | const roomForEllipsis = !(next_text && cur_w < w && w - cur_w < ellipsisSpace); |
| 845 | if (!roomForEllipsis) { |
| 846 | text[i + 2] = ''; |
| 847 | ellipsis_corr = ellipsisSpace; |
| 848 | } |
| 849 | col[i] = i > 0 ? (text[i - 1]).split('`') : (!lib.panel.imgView || !libImg.labels.overlayDark ? lib.ui.col.txtArr : [RGB(240, 240, 240), lib.ui.col.text_h, lib.ui.col.text]); |
| 850 | col_x[i] = x; |
| 851 | col_w[i] = w - x - ellipsis_corr > ellipsis_corr ? w - x - ellipsis_corr : w - x; |
| 852 | x += w_arr[i]; |
| 853 | } |
| 854 | }); |
| 855 | item[cus] = { |
| 856 | id: this.id, |
| 857 | txt: text, |
| 858 | col, |
| 859 | col_x, |
| 860 | col_w, |
| 861 | txt_w: w_arr |
| 862 | }; |
| 863 | } |
| 864 | text.forEach((v, i) => { |
| 865 | if (i % 2 == 0 && text[i]) { |
| 866 | gr.GdiDrawText(text[i], font, !np ? col[i][type] : lib.ui.col.nowp, item_x + col_x[i], item_y, col_w[i], h, lib.panel.lc); |
| 867 | } |
| 868 | }); |
| 869 | } |
| 870 | |
| 871 | deactivateTooltip() { |
| 872 | if (!libTooltip.Text && !grSet.showStyledTooltips || lib.but.trace) return; |
| 873 | libTooltip.Text = ''; |
| 874 | lib.but.tooltipLib.delay = false; |
| 875 | libTooltip.Deactivate(); |
| 876 | } |
| 877 | |
| 878 | dragDrop(x, y) { |
| 879 | x -= lib.ui.x; y -= lib.ui.y; |
| 880 | if (!this.lbtnDn) return; |
| 881 | const drag_diff = !libSet.touchControl ? Math.sqrt((Math.pow(this.last_pressed_coord.x - x, 2) + Math.pow(this.last_pressed_coord.y - y, 2))) : Math.abs(x - this.last_pressed_coord.x); |
| 882 | if (drag_diff > 7) { |
| 883 | if (libSet.touchControl) { |
| 884 | const ix = this.get_ix(x, y, true, false); |
| 885 | const item = this.tree[ix]; |
| 886 | if (lib.ui.id.dragDrop != ix || ix >= this.tree.length || ix < 0) return; |
| 887 | if (!item.sel && !lib.vk.k('ctrl')) this.setTreeSel(ix, item.sel); |
| 888 | } |
| 889 | this.last_pressed_coord = { |
| 890 | x: undefined, |
| 891 | y: undefined |
| 892 | }; |
| 893 | |
| 894 | const handleList = this.getHandleList('newItems'); |
| 895 | this.sortIfNeeded(handleList); |
| 896 | |
| 897 | if (grm.ui.displayLibrarySplit()) { // * Drag and drop action from Library to Playlist in split layout |
| 898 | grm.ui.librarySplitDragDrop(handleList); |
| 899 | } else { |
| 900 | fb.DoDragDrop(0, handleList, handleList.Count ? 1 | 4 : 0); |
| 901 | } |
| 902 | |
| 903 | this.lbtnDn = false; |
| 904 | } |
| 905 | } |
| 906 | |
| 907 | draw(gr) { // * Heavily modified |
| 908 | if (lib.lib.empty) return gr.DrawString(lib.lib.empty, lib.ui.font.main, lib.ui.col.text, lib.ui.x, grm.ui.wh * 0.5 - lib.ui.y - lib.panel.search.h * 0.5, lib.panel.tree.w, lib.ui.row.h * 3, Stringformat.Align_Center); |
| 909 | if (!this.tree.length || !lib.panel.draw) return gr.GdiDrawText(this.libItems && !lib.panel.search.txt && !libSet.filterBy && libSet.libSource ? '加载中...\n\n' : lib.lib.none, lib.ui.font.main, lib.ui.col.text, lib.ui.x + lib.ui.sz.margin, lib.ui.y + lib.panel.search.h, lib.panel.tree.w, lib.ui.row.h * 3); |
| 910 | if (lib.panel.imgView) return; |
| 911 | const b = $Lib.clamp(Math.round(lib.sbar.delta / lib.ui.row.h + 0.4), 0, this.tree.length - 1); |
| 912 | // const bar_x = this.nodeStyle && this.nodeStyle < 5 ? 0 : lib.ui.sz.pad; |
| 913 | const f = Math.min(b + lib.panel.rows, this.tree.length); |
| 914 | const nm = []; |
| 915 | const nowp_c = []; |
| 916 | const updatedNowpBg = pl.col.header_nowplaying_bg !== null; // * Wait until nowplaying bg has a new color to prevent flashing |
| 917 | const colNowPlaying = grSet.libraryBgImg ? RGBtoRGBA(lib.ui.col.nowPlayingBg, grSet.libraryBgRowOpacity) : lib.ui.col.nowPlayingBg; |
| 918 | const colRowStripes = grSet.libraryBgImg ? RGBtoRGBA(lib.ui.col.rowStripes, grSet.libraryBgRowOpacity) : lib.ui.col.rowStripes; |
| 919 | const row = []; |
| 920 | const y1 = Math.round(lib.panel.search.h - lib.sbar.delta + lib.panel.node_y) + Math.floor(lib.ui.sz.node / 2); |
| 921 | let i = 0; |
| 922 | let item_x = 0; |
| 923 | let item_y = 0; |
| 924 | let sel_x = 0; |
| 925 | let sel_w = 0; |
| 926 | let level = 0; |
| 927 | this.checkNode(gr); |
| 928 | if (!lib.ui.style.squareNode) gr.SetTextRenderingHint(5); |
| 929 | this.rows = 0; |
| 930 | if (lib.ui.style.squareNode && lib.ui.col.line) { |
| 931 | level = !this.inlineRoot ? this.tree[b].level : Math.max(this.tree[b].level - 1, 0); |
| 932 | for (let j = 0; j <= level; j++) row[j] = b; |
| 933 | } |
| 934 | for (i = b; i < f; i++) { |
| 935 | const item = this.tree[i]; |
| 936 | this.getItemCount(item); |
| 937 | nm[i] = item.name + (i || this.rootNode != 3 || this.nodeCounts == 1 && (this.countsRight || this.statisticsShow) ? (!this.countsRight || this.statisticsShow ? item.count : '') : ''); |
| 938 | const counts = !this.statisticsShow ? item.count : item.statistics; |
| 939 | if (this.highlight.nowPlayingShow && !item.root && this.inRange(this.nowp, item.item)) nowp_c.push(i); |
| 940 | item.np = nowp_c.includes(i) || lib.panel.textDiffHighlight && this.m.i == i ? `${Unicode.BeamedEighthNotes} ` : ''; |
| 941 | if (item.np && item.id != this.id) this.row.note_w = gr.CalcTextWidth(item.np, lib.ui.font.main); |
| 942 | if (item.id != this.id || this.highlight.nowPlayingIndicator) { |
| 943 | let itemName = !lib.panel.colMarker ? nm[i] : nm[i].replace(Regex.LibMarkerColor, ''); |
| 944 | if (item.np && this.highlight.nowPlayingIndicator && item.track) itemName = item.np + itemName; |
| 945 | item.name_w = gr.CalcTextWidth(itemName, lib.ui.font.main, true); |
| 946 | item.count_w = this.nodeCounts && this.countsRight || this.statisticsShow ? |
| 947 | gr.CalcTextWidth(counts || '000', lib.ui.font.small) + (counts ? lib.ui.row.h * 0.2 : 0) : 0; |
| 948 | if (!this.fullLineSelection) { |
| 949 | item.w = item.name_w; |
| 950 | item.id = this.id; |
| 951 | } |
| 952 | } |
| 953 | |
| 954 | level = !this.inlineRoot ? item.level : Math.max(item.level - 1, 0); |
| 955 | if (this.highlight.nowPlaying && !item.root && this.inRange(this.nowp, item.item)) nowp_c.push(i); |
| 956 | item_y = Math.round(lib.ui.y + lib.ui.row.h * i + lib.panel.search.h - lib.sbar.delta); |
| 957 | if (item_y < lib.panel.filter.y) { |
| 958 | this.rows++; |
| 959 | if (this.rowStripes) { |
| 960 | const width = lib.sbar.w && libSet.sbarShow !== 0 ? lib.ui.w - SCALE(42) : lib.ui.w + 1; |
| 961 | if (i % 2 == 0) gr.FillSolidRect(lib.ui.x, item_y + 1, width, lib.ui.row.h - 2, colRowStripes /*ui.col.bg1*/); |
| 962 | else gr.FillSolidRect(lib.ui.x, item_y, width, lib.ui.row.h, lib.ui.col.bg2); |
| 963 | } |
| 964 | if ((item.sel || this.highlight.nowPlaying) && (lib.ui.col.bgSel != 0 || grCol.primary != 0)) { |
| 965 | const icon_w = !this.inlineRoot || i ? lib.ui.icon.w : 0; |
| 966 | item_x = Math.round(lib.ui.x + this.treeIndent * level + lib.ui.sz.margin) + icon_w; |
| 967 | sel_x = Math.round(item_x - lib.ui.sz.sel); |
| 968 | if (this.inlineRoot && !i) sel_x = Math.max(sel_x - lib.ui.sz.sel, 0); |
| 969 | sel_w = Math.min(item.name_w + lib.ui.sz.sel * 2, lib.ui.x + lib.panel.tree.w - sel_x - item.count_w - 1); |
| 970 | if (this.fullLineSelection) { |
| 971 | sel_x = lib.ui.x; |
| 972 | sel_w = lib.sbar.w && libSet.sbarShow !== 0 ? lib.ui.w - SCALE(42) : lib.ui.w + 1; |
| 973 | } |
| 974 | if (!nowp_c.includes(i)) { |
| 975 | if (this.fullLineSelection && this.sbarShow === 1 && lib.ui.sbar.type === 2 && (this.highlight.row || !this.fullLineSelection)) { |
| 976 | gr.FillSolidRect(sel_x, item_y, sel_w + lib.ui.l.w, lib.ui.row.h, lib.ui.col.bgSel); |
| 977 | gr.FillSolidRect(sel_x, item_y, sel_w + lib.ui.l.w, lib.ui.l.w, lib.ui.col.bgSelframe); |
| 978 | gr.FillSolidRect(sel_x, item_y + lib.ui.row.h, sel_w + lib.ui.l.w, lib.ui.l.w, lib.ui.col.bgSelframe); |
| 979 | } |
| 980 | } |
| 981 | // * Now playing bg selection |
| 982 | else if (this.highlight.nowPlaying && updatedNowpBg) { |
| 983 | gr.FillSolidRect(grSet.libraryDesign === 'traditional' ? item_x - SCALE(2) : lib.ui.x, item_y, grSet.libraryDesign === 'traditional' && this.fullLineSelection ? sel_w - item_x - lib.ui.sz.margin - lib.ui.sz.node + lib.ui.l.w : grSet.libraryDesign === 'traditional' && !this.fullLineSelection ? sel_w + sel_x - item_x + SCALE(2) : !this.fullLineSelection ? sel_w + lib.ui.sz.margin + sel_x - lib.ui.x - lib.ui.sz.sideMarker : sel_w, lib.ui.row.h, colNowPlaying); |
| 984 | |
| 985 | if (grSet.libraryDesign !== 'traditional') { |
| 986 | gr.FillSolidRect(lib.ui.x, item_y, lib.ui.sz.sideMarker, lib.ui.row.h, lib.ui.col.sideMarker); |
| 987 | } |
| 988 | } |
| 989 | // * Marker selection with now playing active |
| 990 | if (item.sel && this.highlight.nowPlaying) { |
| 991 | if (grSet.libraryDesign !== 'traditional') { |
| 992 | if (!this.inRange(this.nowp, item.item) || item.root) gr.DrawRect(this.fullLineSelection ? sel_x : lib.ui.x, item_y, this.fullLineSelection ? sel_w - 1 : sel_w + lib.ui.sz.margin + sel_x - lib.ui.x - lib.ui.sz.sideMarker, lib.ui.row.h, 1, lib.ui.col.selectionFrame); |
| 993 | gr.FillSolidRect(lib.ui.x, this.highlight.nowPlaying && !item.root && this.inRange(this.nowp, item.item) ? item_y + 1 : item_y, lib.ui.sz.sideMarker, this.highlight.nowPlaying && !item.root && this.inRange(this.nowp, item.item) ? lib.ui.row.h - 1 : lib.ui.row.h + 1, lib.ui.col.sideMarker); |
| 994 | } |
| 995 | else if (grSet.libraryDesign === 'traditional') { |
| 996 | gr.FillSolidRect(item_x - SCALE(2), item_y, grSet.libraryDesign === 'traditional' && this.fullLineSelection ? sel_w - item_x - lib.ui.sz.margin - lib.ui.sz.node + lib.ui.l.w : sel_w, lib.ui.row.h, colNowPlaying); |
| 997 | } |
| 998 | } |
| 999 | // * Marker selection with now playing deactivated |
| 1000 | if (item.sel && !this.highlight.nowPlaying && updatedNowpBg) { |
| 1001 | gr.FillSolidRect(grSet.libraryDesign === 'traditional' && this.fullLineSelection ? item_x - SCALE(2) : sel_x, item_y, grSet.libraryDesign === 'traditional' && this.fullLineSelection ? sel_w - item_x - lib.ui.sz.margin - lib.ui.sz.node + lib.ui.l.w : sel_w, lib.ui.row.h, colNowPlaying); |
| 1002 | |
| 1003 | if (grSet.libraryDesign !== 'traditional') { |
| 1004 | gr.FillSolidRect(lib.ui.x, item_y, lib.ui.w, lib.ui.row.h, colNowPlaying); |
| 1005 | gr.FillSolidRect(lib.ui.x, item_y, lib.ui.sz.sideMarker, lib.ui.row.h, lib.ui.col.sideMarker); |
| 1006 | } |
| 1007 | } |
| 1008 | } |
| 1009 | } |
| 1010 | } |
| 1011 | for (i = b; i < f; i++) { |
| 1012 | const item = this.tree[i]; |
| 1013 | const level = !this.inlineRoot ? item.level : Math.max(item.level - 1, 0); |
| 1014 | item_y = Math.round(lib.ui.y + lib.ui.row.h * i + lib.panel.search.h - lib.sbar.delta); |
| 1015 | if (item_y < lib.panel.filter.y) { |
| 1016 | item_x = Math.round(lib.ui.x + this.treeIndent * level + lib.ui.sz.margin); |
| 1017 | if (this.inlineRoot && !item.level) item_x = lib.ui.x + lib.ui.sz.marginSearch; |
| 1018 | if ((this.fullLineSelection && this.row.i == i || this.m.i == i)) { |
| 1019 | sel_x = Math.round(item_x - lib.ui.sz.sel); |
| 1020 | if (!this.inlineRoot || item.level) sel_x += lib.ui.icon.w; |
| 1021 | sel_w = Math.min(item.name_w + lib.ui.sz.sel * 2, lib.ui.x + lib.panel.tree.w - sel_x - item.count_w - 1); |
| 1022 | if (this.fullLineSelection) { |
| 1023 | sel_x = lib.ui.x; |
| 1024 | sel_w = lib.sbar.w ? lib.ui.w - SCALE(42) : lib.ui.w + 1; |
| 1025 | } |
| 1026 | if (this.highlight.row == 3 && (this.fullLineSelection && this.sbarShow == 1 && lib.ui.sbar.type == 2)) { |
| 1027 | gr.DrawLine(sel_x, item_y, sel_w, item_y, lib.ui.l.w, lib.ui.col.frame); |
| 1028 | gr.DrawLine(sel_x, item_y + lib.ui.row.h, sel_w, item_y + lib.ui.row.h, lib.ui.l.w, lib.ui.col.frame); |
| 1029 | } |
| 1030 | } |
| 1031 | |
| 1032 | if (lib.ui.style.squareNode && lib.ui.col.line) { |
| 1033 | if (item.top) row[level] = i; |
| 1034 | const ff = lib.sbar.rows_drawn == this.rows || i < lib.sbar.rows_drawn ? f : f - 1; |
| 1035 | if (item.bot || i === ff - 1) { |
| 1036 | for (let depth = (i === ff - 1 ? 0 : level); depth <= level; depth++) { |
| 1037 | if (row[depth] !== undefined && (this.inlineRoot || !item.root)) { |
| 1038 | const start = row[depth]; |
| 1039 | let end = i + (item.bot && depth === level ? 0.5 : 1); |
| 1040 | if (item_y >= lib.panel.filter.y) end -= 1; |
| 1041 | const l_x = Math.round(lib.ui.x + this.treeIndent * depth + lib.ui.sz.margin) + Math.floor(lib.ui.sz.node / 2) - lib.ui.l.wf; |
| 1042 | let l_y = Math.round(lib.ui.y + lib.ui.row.h * start + lib.panel.search.h - lib.sbar.delta); |
| 1043 | let l_h = Math.ceil(lib.ui.row.h * (end - start)) + lib.ui.l.wc; |
| 1044 | if (!start) { |
| 1045 | l_y += lib.ui.row.h / 2; |
| 1046 | l_h -= lib.ui.row.h / 2; |
| 1047 | } |
| 1048 | if (i <= this.row.lineMax[depth] && (!this.inlineRoot || item.level) && grSet.libraryDesign === 'traditional') gr.FillSolidRect(l_x, l_y, lib.ui.l.w, l_h, lib.ui.col.line); |
| 1049 | } |
| 1050 | } |
| 1051 | if (item.bot) row[level] = undefined; |
| 1052 | } |
| 1053 | } |
| 1054 | } |
| 1055 | } |
| 1056 | |
| 1057 | for (i = b; i < f; i++) { |
| 1058 | const item = this.tree[i]; |
| 1059 | const level = !this.inlineRoot ? item.level : Math.max(item.level - 1, 0); |
| 1060 | item_y = Math.round(lib.ui.y + lib.ui.row.h * i + lib.panel.search.h - lib.sbar.delta); |
| 1061 | if (item_y < lib.panel.filter.y) { |
| 1062 | item_x = Math.round(lib.ui.x + this.treeIndent * level + lib.ui.sz.margin); |
| 1063 | if (this.inlineRoot && !item.level) item_x = lib.ui.x + lib.ui.sz.marginSearch; |
| 1064 | if (lib.ui.style.squareNode) { |
| 1065 | if (!item.track && (!this.inlineRoot || item.level)) { |
| 1066 | const y2 = lib.ui.y + lib.ui.row.h * i + y1 - lib.ui.l.wf; |
| 1067 | if (lib.ui.col.line) if (grSet.libraryDesign !== 'reborn') gr.FillSolidRect(item_x + lib.ui.sz.node, y2, lib.ui.l.s1, lib.ui.l.w, lib.ui.col.line); |
| 1068 | this.drawNode(gr, item.child.length < 1 ? this.m.br != i ? 0 : 2 : this.m.br != i ? 1 : 3, item_x, item_y + lib.panel.node_y); |
| 1069 | } else if (lib.ui.col.line && (!this.inlineRoot || item.level)) { |
| 1070 | if (grSet.libraryDesign !== 'reborn') { |
| 1071 | const y2 = Math.round(lib.ui.y + lib.panel.search.h - lib.sbar.delta) + Math.ceil(lib.ui.row.h * (i + 0.5)) - lib.ui.l.wf; |
| 1072 | gr.FillSolidRect(item_x + lib.ui.l.s2, y2, lib.ui.l.s3, lib.ui.l.w, lib.ui.col.line); |
| 1073 | } |
| 1074 | } |
| 1075 | } else if (!item.track && (!this.inlineRoot || item.level)) this.drawNode(gr, item, item_x, item_y, item.child.length < 1, this.m.br == i, item.sel); |
| 1076 | if (!this.inlineRoot || item.level) item_x += lib.ui.icon.w + (!this.fullLineSelection ? lib.ui.l.wf : 0); |
| 1077 | const w = lib.ui.x + lib.panel.tree.w - item_x - lib.ui.sz.sel - item.count_w; |
| 1078 | this.checkTooltip(item, item_x, item_y, item.name_w, w); |
| 1079 | if (this.fullLineSelection && item.id != this.id) { |
| 1080 | item.w = lib.ui.x + lib.panel.tree.w - item_x - HD_4K(25, 45); |
| 1081 | item.id = this.id; |
| 1082 | } |
| 1083 | if (item.np && this.highlight.nowPlayingSidemarker) { |
| 1084 | gr.FillSolidRect(lib.ui.l.w, item_y, lib.ui.sz.sideMarker, lib.ui.row.h, lib.ui.col.nowp); |
| 1085 | } |
| 1086 | if (item.np && this.highlight.nowPlayingIndicator && item.track) nm[i] = item.np + nm[i]; |
| 1087 | const np = item.np && this.highlight.nowPlaying; |
| 1088 | // const txt_co = np && !item.sel ? lib.ui.col.nowp : item.sel && this.fullLineSelection ? (this.highlight.row ? lib.ui.col.textSel : lib.ui.col.text) : this.m.i == i && this.highlight.text ? lib.ui.col.text_h : lib.ui.col.counts || lib.ui.col.count; |
| 1089 | const type = item.sel ? (this.highlight.row || !this.fullLineSelection ? 2 : 0) : this.m.i == i && this.highlight.text ? 1 : 0; |
| 1090 | const txt_c = // np && !item.sel ? lib.ui.col.nowp : lib.ui.col.txtArr[type]; |
| 1091 | this.highlight.nowPlaying && !item.root && this.inRange(this.nowp, item.item) && updatedNowpBg ? lib.ui.col.text_nowp : |
| 1092 | item.sel ? lib.ui.col.textSel : |
| 1093 | this.m.i === i ? lib.ui.col.text_h : lib.ui.col.text; |
| 1094 | |
| 1095 | !lib.panel.colMarker ? gr.GdiDrawText(nm[i], lib.ui.font.main, txt_c, item_x, item_y, w, lib.ui.row.h, lib.panel.lc) : this.cusCol(gr, nm[i], item, item_x, item_y, w, lib.ui.row.h, type, np, lib.ui.font.main, lib.ui.font.mainEllipsisSpace, 'text'); |
| 1096 | if (this.countsRight || this.statisticsShow) { |
| 1097 | const scrollbar = libSet.sbarShow !== 0 && lib.sbar.w === SCALE(12) && lib.sbar.scrollable_lines > 0; |
| 1098 | const x = lib.panel.tree.w - item_x + (grSet.libraryLayout === 'split' ? 0 : lib.ui.x) - (scrollbar ? SCALE(24) : 0); |
| 1099 | gr.GdiDrawText(!this.statisticsShow ? item.count : item.statistics, !item.root || !this.label ? lib.ui.font.small : lib.ui.font.label, txt_c, item_x, item_y, x, lib.ui.row.h, lib.panel.rc); |
| 1100 | } |
| 1101 | } |
| 1102 | } |
| 1103 | } |
| 1104 | |
| 1105 | drawNode(gr, item, x, y, parent, hover, sel) { |
| 1106 | const selCol = sel && this.fullLineSelection && this.highlight.row; |
| 1107 | const y2 = Math.round(y); |
| 1108 | const ix = this.get_ix(x, y, true, false); |
| 1109 | if (ix >= this.tree.length || ix < 0) return; |
| 1110 | const itemtr = this.tree[ix]; |
| 1111 | const nowp = this.highlight.nowPlaying && !itemtr.root && this.inRange(this.nowp, itemtr.item); |
| 1112 | const icon_c = nowp ? lib.ui.col.text_nowp : hover ? lib.ui.col.iconPlus_h : selCol ? lib.ui.col.iconPlus_sel : lib.ui.col.iconPlus; |
| 1113 | |
| 1114 | switch (this.nodeStyle) { |
| 1115 | case 0: // * Squares - Traditional design |
| 1116 | if (!this.highlight.node && item > 1) item -= 2; |
| 1117 | x = Math.round(x); |
| 1118 | y = Math.round(y); |
| 1119 | if (this.nd[item]) gr.DrawImage(this.nd[item], x, y, this.nd[item].Width, this.nd[item].Height, 0, 0, this.nd[item].Width, this.nd[item].Height); |
| 1120 | break; |
| 1121 | case 1: |
| 1122 | case 2: // * Angles/Arrows - Modern design |
| 1123 | if (!this.highlight.row && this.fullLineSelection) x += lib.ui.l.w; |
| 1124 | if (parent) { |
| 1125 | if (hover) { |
| 1126 | gr.DrawString(lib.ui.icon.expand, lib.ui.icon.font, icon_c, x, y2, lib.ui.x + lib.panel.tree.w - x, lib.ui.row.h, lib.panel.s_lc); |
| 1127 | gr.DrawString(lib.ui.icon.expand, lib.ui.icon.font, icon_c, x + 1, y2, lib.ui.x + lib.panel.tree.w - x, lib.ui.row.h, lib.panel.s_lc); |
| 1128 | } else { |
| 1129 | gr.DrawString(lib.ui.icon.expand, lib.ui.icon.font, icon_c, x, y2, lib.ui.x + lib.panel.tree.w - x, lib.ui.row.h, lib.panel.s_lc); |
| 1130 | if (this.nodeStyle === 1) gr.DrawString(lib.ui.icon.expand, lib.ui.icon.font, icon_c, x + 1, y2, lib.ui.x + lib.panel.tree.w - x, lib.ui.row.h, lib.panel.s_lc); |
| 1131 | } |
| 1132 | } else { |
| 1133 | gr.DrawString(lib.ui.icon.collapse, lib.ui.icon.font, icon_c, x - lib.ui.icon.offset, y2, lib.ui.x + lib.panel.tree.w - x, lib.ui.row.h, lib.panel.s_lc); |
| 1134 | gr.DrawString(lib.ui.icon.collapse, lib.ui.icon.font, icon_c, x - lib.ui.icon.offset, y2 + 1, lib.ui.x + lib.panel.tree.w - x, lib.ui.row.h, lib.panel.s_lc); |
| 1135 | } |
| 1136 | break; |
| 1137 | case 3: case 4: { // * Triangle - Ultra-modern design |
| 1138 | if (!this.highlight.row && this.fullLineSelection) x += lib.ui.l.w; |
| 1139 | // const y3 = Math.round(y + (lib.ui.row.h - this.sy_sz) / 2 - 2); |
| 1140 | gr.SetSmoothingMode(4); |
| 1141 | if (parent) { |
| 1142 | if (hover) { |
| 1143 | gr.DrawString(lib.ui.icon.expand2, lib.ui.icon.font, icon_c, x, y2, lib.ui.x + lib.panel.tree.w - x, lib.ui.row.h, lib.panel.s_lc); |
| 1144 | } else { |
| 1145 | gr.DrawString(lib.ui.icon.expand, lib.ui.icon.font, icon_c, x, y2, lib.ui.x + lib.panel.tree.w - x, lib.ui.row.h, lib.panel.s_lc); |
| 1146 | if (this.nodeStyle === 3) gr.DrawString(lib.ui.icon.expand, lib.ui.icon.font, icon_c, x + 1, y2, lib.ui.x + lib.panel.tree.w - x, lib.ui.row.h, lib.panel.s_lc); |
| 1147 | } |
| 1148 | } else { |
| 1149 | gr.DrawString(lib.ui.icon.expand2, lib.ui.icon.font, icon_c, x, y2, lib.ui.x + lib.panel.tree.w - x, lib.ui.row.h, lib.panel.s_lc); |
| 1150 | } |
| 1151 | gr.SetSmoothingMode(0); |
| 1152 | break; |
| 1153 | } |
| 1154 | case 5: // * Custom node - Georgia-ReBORN design ( Clean +|- ) |
| 1155 | if (parent) { // Plus |
| 1156 | if (hover) { |
| 1157 | gr.DrawString(lib.ui.icon.expand, lib.ui.icon.font, icon_c, x, y2 - HD_4K(1, -1), lib.ui.x + lib.panel.tree.w - x, lib.ui.row.h, lib.panel.s_lc); |
| 1158 | gr.DrawString(lib.ui.icon.expand, lib.ui.icon.font, icon_c, x, y2 - HD_4K(1, -1), lib.ui.x + lib.panel.tree.w - x + 2, lib.ui.row.h + 2, lib.panel.s_lc); |
| 1159 | } else gr.DrawString(lib.ui.icon.expand, lib.ui.icon.font, icon_c, x, y2 - (DetectWine() ? HD_4K(0, -1) : HD_4K(1, -1)), lib.ui.x + lib.panel.tree.w - x, lib.ui.row.h, lib.panel.s_lc); |
| 1160 | } else { // Minus |
| 1161 | gr.DrawString(lib.ui.icon.collapse, lib.ui.icon.font, icon_c, x - lib.ui.icon.offset, y2 - (DetectWine() ? HD_4K(1, -1) : HD_4K(1, 0)), lib.ui.x + lib.panel.tree.w - x, lib.ui.row.h, lib.panel.s_lc); |
| 1162 | gr.DrawString(lib.ui.icon.collapse, lib.ui.icon.font, icon_c, x - lib.ui.icon.offset, y2 - HD_4K(1, -1), lib.ui.x + lib.panel.tree.w - x, lib.ui.row.h + 2, lib.panel.s_lc); |
| 1163 | } |
| 1164 | break; |
| 1165 | case 7: |
| 1166 | if (item > 1) item -= 2; |
| 1167 | lib.ui.style.symb.SetPartAndStateID(2, !item ? 1 : 2); |
| 1168 | lib.ui.style.symb.DrawThemeBackground(gr, x, y, lib.ui.sz.node, lib.ui.sz.node); |
| 1169 | break; |
| 1170 | default: |
| 1171 | if (parent) { |
| 1172 | if (hover) { |
| 1173 | gr.DrawString(lib.ui.icon.expand, lib.ui.icon.font, selCol ? lib.ui.col.textSel : this.highlight.node ? lib.ui.col.icon_h : lib.ui.col.icon_e, x, y + this.iconVerticalPad, lib.ui.x + lib.panel.tree.w - x, lib.ui.row.h, lib.panel.s_lc); |
| 1174 | } else gr.DrawString(lib.ui.icon.expand, lib.ui.icon.font, !selCol ? lib.ui.col.icon_c : lib.ui.col.textSel, x, y + this.iconVerticalPad, lib.ui.x + lib.panel.tree.w - x, lib.ui.row.h, lib.panel.s_lc); |
| 1175 | } else { |
| 1176 | if (hover) { |
| 1177 | gr.DrawString(lib.ui.icon.collapse, lib.ui.icon.font, selCol ? lib.ui.col.textSel : this.highlight.node ? lib.ui.col.icon_h : lib.ui.col.icon_c, x - lib.ui.icon.offset, y + this.iconVerticalPad, lib.ui.x + lib.panel.tree.w - x, lib.ui.row.h, lib.panel.s_lc); |
| 1178 | } else gr.DrawString(lib.ui.icon.collapse, lib.ui.icon.font, !selCol ? lib.ui.col.icon_e : lib.ui.col.textSel, x - lib.ui.icon.offset, y + this.iconVerticalPad, lib.ui.x + lib.panel.tree.w - x, lib.ui.row.h, lib.panel.s_lc); |
| 1179 | } |
| 1180 | break; |
| 1181 | } |
| 1182 | } |
| 1183 | |
| 1184 | expand(ie, nm) { |
| 1185 | let h = 0; |
| 1186 | let m = 0; |
| 1187 | if (ie) this.tree[ie].sel = true; |
| 1188 | if (!this.tree.some(v => v.sel)) return; |
| 1189 | if (libSet.autoCollapse) { |
| 1190 | const parent = []; |
| 1191 | const pr_pr = []; |
| 1192 | let par = 0; |
| 1193 | this.tree.forEach((v, j, arr) => { |
| 1194 | if (v.sel) { |
| 1195 | j = v.level; |
| 1196 | if (this.rootNode) j -= 1; |
| 1197 | if (v.level != 0) { |
| 1198 | par = v.par; |
| 1199 | for (m = 1; m < j + 1; m++) { |
| 1200 | pr_pr[m] = m == 1 ? par : arr[pr_pr[m - 1]].par; |
| 1201 | parent.push(pr_pr[m]); |
| 1202 | } |
| 1203 | } |
| 1204 | } |
| 1205 | }); |
| 1206 | this.tree.forEach((v, i) => { |
| 1207 | if (!parent.includes(i) && !v.sel && !v.root) v.child = []; |
| 1208 | }); |
| 1209 | this.buildTree(lib.lib.root, 0); |
| 1210 | } |
| 1211 | const start_l = this.tree.length; |
| 1212 | let nm_n = ''; |
| 1213 | let nodes = -1; |
| 1214 | m = this.tree.length; |
| 1215 | this.expandedTracks = 0; |
| 1216 | this.expandLmt = lib.men.treeExpandLimit; |
| 1217 | while (m--) { |
| 1218 | if (this.tree[m].sel) { |
| 1219 | this.expandNodes(this.tree[m], !(!this.rootNode || m)); |
| 1220 | nodes++; |
| 1221 | } |
| 1222 | } |
| 1223 | lib.sbar.setRows(this.tree.length); |
| 1224 | lib.panel.treePaint(); |
| 1225 | if (nm) { |
| 1226 | this.tree.some((v, i, arr) => { |
| 1227 | nm_n = (v.level ? arr[v.par].srt[0] : '') + v.srt[0]; |
| 1228 | nm_n = nm_n.toUpperCase(); |
| 1229 | if (nm_n == nm) { |
| 1230 | h = i; |
| 1231 | return true; |
| 1232 | } |
| 1233 | }); |
| 1234 | } else { |
| 1235 | this.tree.some((v, i) => { |
| 1236 | if (v.sel) { |
| 1237 | h = i; |
| 1238 | return true; |
| 1239 | } |
| 1240 | }); |
| 1241 | } |
| 1242 | const new_items = this.tree.length - start_l + nodes; |
| 1243 | const b = Math.round(lib.sbar.scroll / lib.ui.row.h + 0.4); |
| 1244 | const n = Math.max(h - b, this.rootNode ? 1 : 0); |
| 1245 | let scrollChk = false; |
| 1246 | if (n + 1 + new_items > this.rows) { |
| 1247 | scrollChk = true; |
| 1248 | if (new_items > this.rows - 2) lib.sbar.checkScroll(h * lib.ui.row.h); |
| 1249 | else lib.sbar.checkScroll(Math.min(h * lib.ui.row.h, (h + 1 - lib.sbar.rows_drawn + new_items) * lib.ui.row.h)); |
| 1250 | } |
| 1251 | if (lib.sbar.scroll > h * lib.ui.row.h) { |
| 1252 | scrollChk = true; |
| 1253 | lib.sbar.checkScroll(h * lib.ui.row.h); |
| 1254 | } |
| 1255 | if (!scrollChk) lib.sbar.scrollRound(); |
| 1256 | lib.lib.treeState(false, libSet.rememberTree); |
| 1257 | } |
| 1258 | |
| 1259 | expandCollapse(x, y, item, ix) { |
| 1260 | const expanded = item.child.length > 0 ? 1 : 0; |
| 1261 | switch (expanded) { |
| 1262 | case 0: { |
| 1263 | let n = 0; |
| 1264 | if (libSet.autoCollapse) { |
| 1265 | n = this.branchChange(item, false, true); |
| 1266 | lib.sbar.checkScroll(lib.sbar.scroll - n * lib.ui.row.h, 'step'); |
| 1267 | } |
| 1268 | const row = this.getRowNumber(y); |
| 1269 | this.branch(item, !!item.root, true); |
| 1270 | if (!ix) lib.panel.setHeight(true); |
| 1271 | n = 2; |
| 1272 | if (item.child.length == 1 && libSet.treeAutoExpandSingle) { |
| 1273 | this.branch(item.child[0], false, true); |
| 1274 | n += item.child[0].child.length; |
| 1275 | } |
| 1276 | if (libSet.autoCollapse) ix = item.ix; |
| 1277 | if (row + n + item.child.length > this.rows) { |
| 1278 | if (item.child.length > (this.rows - n)) lib.sbar.checkScroll(ix * lib.ui.row.h); |
| 1279 | else lib.sbar.checkScroll(Math.min(ix * lib.ui.row.h, (ix + n - lib.sbar.rows_drawn + item.child.length) * lib.ui.row.h)); |
| 1280 | } |
| 1281 | break; |
| 1282 | } |
| 1283 | case 1: { |
| 1284 | this.clearChild(item); |
| 1285 | if (!ix && this.tree.length == 1) lib.panel.setHeight(false); |
| 1286 | const b = $Lib.clamp(Math.round(lib.sbar.delta / lib.ui.row.h + 0.4), 0, this.tree.length - 1); |
| 1287 | const f = Math.min(b + lib.panel.rows, this.tree.length); |
| 1288 | if (f - b < lib.panel.rows) lib.sbar.checkScroll((this.tree.length - lib.panel.rows) * lib.ui.row.h); |
| 1289 | break; |
| 1290 | } |
| 1291 | } |
| 1292 | if (lib.sbar.scroll > ix * lib.ui.row.h) lib.sbar.checkScroll(ix * lib.ui.row.h); |
| 1293 | } |
| 1294 | |
| 1295 | expandNodes(obj, am) { |
| 1296 | this.branch(obj, !!am, true, true); |
| 1297 | if (obj.child) { |
| 1298 | obj.child.some(v => { |
| 1299 | if (v.track) this.expandedTracks++; |
| 1300 | if (this.expandedTracks >= this.expandLmt) return true; |
| 1301 | if (!v.track) this.expandNodes(v); |
| 1302 | }); |
| 1303 | } |
| 1304 | } |
| 1305 | |
| 1306 | fixMarkers(n) { |
| 1307 | while (n.includes('@!##!#')) { // $colour |
| 1308 | n = n.replace('@!##!#', '<!>'); |
| 1309 | n = n.replace('@!#', '~#~'); |
| 1310 | } |
| 1311 | n = n.replace(Regex.LibMarkerExcl, '@!##!#').replace(Regex.LibMarkerTildeHash, '#!#@!#'); |
| 1312 | |
| 1313 | while (n.includes('#@##!#')) { // $nodisplay |
| 1314 | n = n.replace('#@##!#', '<!>'); |
| 1315 | n = n.replace('#@#', '~#~'); |
| 1316 | } |
| 1317 | n = n.replace(Regex.LibMarkerExcl, '#@##!#').replace(Regex.LibMarkerTildeHash, '#!##@#'); |
| 1318 | return n; |
| 1319 | } |
| 1320 | |
| 1321 | focusShow(i) { |
| 1322 | this.setTreeSel(i); |
| 1323 | lib.panel.treePaint(); |
| 1324 | this.showItem(i); |
| 1325 | } |
| 1326 | |
| 1327 | formatBytes(bytes, decimals = 1) { |
| 1328 | if (!+bytes) return '0 字节' |
| 1329 | |
| 1330 | const k = 1024 |
| 1331 | const dm = decimals < 0 ? 0 : decimals |
| 1332 | const sizes = ['字节', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] |
| 1333 | |
| 1334 | const i = Math.floor(Math.log(bytes) / Math.log(k)) |
| 1335 | |
| 1336 | return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}` |
| 1337 | } |
| 1338 | |
| 1339 | getAllCombinations(n) { |
| 1340 | n = this.fixMarkers(n); |
| 1341 | return n.includes('^@^') && n.includes(lib.panel.softSplitter) ? this.imgView(n) : this.getCombos(n); |
| 1342 | } |
| 1343 | |
| 1344 | getCombos(n) { |
| 1345 | const combinations = []; |
| 1346 | const divisors = []; |
| 1347 | const arraysToCombine = []; |
| 1348 | n = n.replace(RegExp(`(#!#|)${lib.panel.softSplitter}(#!#|)`, 'g'), '@@').split('#!#'); |
| 1349 | const ln = n.length; |
| 1350 | let i = 0; |
| 1351 | for (i = 0; i < ln; i++) { |
| 1352 | n[i] = n[i].split('@@'); |
| 1353 | if (n[i] != '') arraysToCombine.push(n[i]); |
| 1354 | } |
| 1355 | const arraysToCombineLength = arraysToCombine.length; |
| 1356 | for (i = arraysToCombineLength - 1; i >= 0; i--) divisors[i] = divisors[i + 1] ? divisors[i + 1] * arraysToCombine[i + 1].length : 1; |
| 1357 | const getPermutation = (n, arraysToCombine) => { |
| 1358 | const result = []; |
| 1359 | let cur_array; |
| 1360 | for (let j = 0; j < arraysToCombineLength; j++) { |
| 1361 | cur_array = arraysToCombine[j]; |
| 1362 | result.push(cur_array[Math.floor(n / divisors[j]) % cur_array.length]); |
| 1363 | } |
| 1364 | return result; |
| 1365 | }; |
| 1366 | let numPerms = arraysToCombine[0].length; |
| 1367 | for (i = 1; i < arraysToCombineLength; i++) numPerms *= arraysToCombine[i].length; |
| 1368 | for (i = 0; i < numPerms; i++) combinations.push(getPermutation(i, arraysToCombine)); |
| 1369 | return this.removeDuplicateArr(combinations); |
| 1370 | } |
| 1371 | |
| 1372 | getChildCount(arr, ix) { |
| 1373 | arr.forEach(v => { |
| 1374 | if (v.child && ix > v.ix) { |
| 1375 | this.childCount += v.child.length; |
| 1376 | if (!v.track) this.getChildCount(v.child, ix); |
| 1377 | } |
| 1378 | }); |
| 1379 | } |
| 1380 | |
| 1381 | getItemCount(v) { |
| 1382 | const prop = !lib.panel.imgView ? 'statistics' : '_statistics'; |
| 1383 | if (this.statisticsShow && v[prop] == null) v[prop] = v.root && this.label && !lib.panel.imgView ? this.label : this.calcStatistics(v); |
| 1384 | if (v.count === '' && this.nodeCounts) { |
| 1385 | if (!lib.panel.imgView) { |
| 1386 | if (v.root && this.label) { |
| 1387 | if (!this.statisticsShow) v.count = this.label; |
| 1388 | } else { |
| 1389 | const type = lib.panel.search.txt ? 'search' : libSet.filterBy ? 'filter' : 'standard'; |
| 1390 | const key = this.getKey(v); |
| 1391 | v.count = !v.track || !this.showTracks ? (v.name ? ' ' : '') + (this.nodeCounts == 1 ? `(${this.trackCount(v.item)})` : this.nodeCounts == 2 ? `(${this.branchCount(v, !!v.root, true, false, key, type)})` : '') : ''; |
| 1392 | if (!this.showTracks && v.count == `${v.name ? ' ' : ''}(0)`) v.count = ''; |
| 1393 | if (this.countsRight && !this.statisticsShow) v.count = v.count.replace(Regex.PunctParen, ''); |
| 1394 | } |
| 1395 | } else { |
| 1396 | const getTracks = [true, true, true, true, false, false, true, false, false, false, false][libSet.itemShowStatistics]; |
| 1397 | if (getTracks) { |
| 1398 | v.count = this.trackCount(v.item); |
| 1399 | v.count += v.count > 1 ? ' 首' : ' 首'; |
| 1400 | } |
| 1401 | const getItemCount = !v.root && libSet.itemOverlayType != 1 && libSet.albumArtLabelType == 2 && !libSet.itemShowStatistics && (lib.pop.nodeCounts == 1 || lib.pop.nodeCounts == 2); |
| 1402 | if (getItemCount) { |
| 1403 | const count = v.count.replace(Regex.NumNonDigits, ''); |
| 1404 | if (lib.panel.lines == 1 || libSet.albumArtFlipLabels) v.grp += ` (${count})`; |
| 1405 | else v.lot += ` (${count})`; |
| 1406 | } |
| 1407 | } |
| 1408 | } |
| 1409 | } |
| 1410 | |
| 1411 | getHandleList(n) { |
| 1412 | if (n == 'newItems') this.getTreeSel(); |
| 1413 | const handleList = new FbMetadbHandleList(); |
| 1414 | this.sel_items.some(v => { |
| 1415 | if (v >= lib.panel.list.Count) return true; |
| 1416 | handleList.Add(lib.panel.list[v]); |
| 1417 | }); |
| 1418 | return handleList; |
| 1419 | } |
| 1420 | |
| 1421 | get_ix(x, y, simple, type) { |
| 1422 | let ix; |
| 1423 | y -= lib.ui.y - 1; // - 1 = workaround to adjust and fix background color selection ( text_nowp in drawNode() ) to draw correct colored nodes in tree when option "Nowplaying in highlight" is active |
| 1424 | x -= lib.ui.x; |
| 1425 | if (lib.panel.imgView) { |
| 1426 | if (y > libImg.panel.y && y < libImg.panel.y + libImg.panel.h && x > libImg.panel.x && x < libImg.panel.x + libImg.panel.w) { |
| 1427 | const row_ix = libImg.style.vertical ? Math.ceil((y + lib.sbar.delta - libImg.panel.y) / libImg.row.h) - 1 : 0; |
| 1428 | const column_ix = libImg.style.vertical ? (!libImg.labels.right && !libSet.albumArtFlowMode ? Math.ceil((x - libImg.panel.x) / libImg.columnWidth) - 1 : 0) : Math.ceil((x + lib.sbar.delta - libImg.panel.x) / libImg.columnWidth) - 1; |
| 1429 | ix = (row_ix * libImg.columns) + column_ix; |
| 1430 | return ix > this.tree.length - 1 ? -1 : ix; |
| 1431 | } |
| 1432 | return -1; |
| 1433 | } |
| 1434 | ix = y > lib.panel.tree.y && y < lib.panel.tree.y + this.rows * lib.ui.row.h ? Math.round((y + lib.sbar.delta - lib.panel.search.h - lib.ui.row.h * 0.5) / lib.ui.row.h) : -1; |
| 1435 | if (simple) return ix; |
| 1436 | return this.tree.length > ix && ix >= 0 && x < lib.panel.tree.w && y > lib.panel.tree.y && y < lib.panel.tree.y + this.rows * lib.ui.row.h && this.check_ix(this.tree[ix], x + lib.ui.x, y + lib.ui.y, type) ? ix : -1; |
| 1437 | } |
| 1438 | |
| 1439 | getKey(v) { |
| 1440 | const level = v.level; |
| 1441 | const o = { |
| 1442 | a: v.nm || '', |
| 1443 | b: level != 0 ? this.tree[v.par].nm : '', |
| 1444 | c: level > 1 ? this.tree[this.tree[v.par].par].nm : '', |
| 1445 | d: level > 2 ? this.tree[this.tree[this.tree[v.par].par].par].nm : '' |
| 1446 | } |
| 1447 | return level + (level > 2 ? o.d : '') + (level > 1 ? o.c : '') + (level > 0 ? o.b : '') + o.a; |
| 1448 | } |
| 1449 | |
| 1450 | getNowplaying(handle, stop) { |
| 1451 | if (stop) { |
| 1452 | lib.panel.treePaint(); |
| 1453 | return this.nowp = -1; |
| 1454 | } |
| 1455 | if (!handle && fb.IsPlaying) handle = fb.GetNowPlaying(); |
| 1456 | if (!handle) return this.nowp = -1; |
| 1457 | this.nowp = lib.panel.list.Find(handle); |
| 1458 | lib.panel.treePaint(); |
| 1459 | } |
| 1460 | |
| 1461 | getNumbers(arr) { // test [0, '0', "0", "0.5", 10, '10', "", '', '-', null, true, false, 'Oh'] |
| 1462 | return arr.filter(v => Number(v)); // gives ["0.5", 10, "10", true] |
| 1463 | //return arr.filter(v => parseFloat(v) == v); // gives [0, "0", "0", "0.5", 10, "10"] |
| 1464 | //return arr.filter(v => Number(v) && parseFloat(v) == v); // gives ["0.5", 10, "10"] |
| 1465 | } |
| 1466 | |
| 1467 | getRowNumber(y) { |
| 1468 | return Math.round((y - lib.panel.tree.y - lib.ui.row.h * 0.5) / lib.ui.row.h); |
| 1469 | } |
| 1470 | |
| 1471 | getTreeSel() { |
| 1472 | lib.panel.treePaint(); |
| 1473 | this.sel_items = []; |
| 1474 | this.tree.forEach(v => { |
| 1475 | if (v.sel) this.addItems(this.sel_items, v.item); |
| 1476 | }); |
| 1477 | this.uniq(this.sel_items); |
| 1478 | } |
| 1479 | |
| 1480 | imgView(n) { |
| 1481 | let a; let b; const c = []; |
| 1482 | n = n.split('^@^'); |
| 1483 | if (n[0]) a = this.getCombos(n[0]); |
| 1484 | if (n[1]) b = this.getCombos(n[1]); |
| 1485 | a.forEach(v => b.forEach(w => c.push([`${v.join('')}^@^${w.join('')}`]))); |
| 1486 | return this.removeDuplicateArr(c); |
| 1487 | } |
| 1488 | |
| 1489 | isDate(n) { |
| 1490 | return isNaN(n) && !isNaN(Date.parse(n)); |
| 1491 | } |
| 1492 | |
| 1493 | inRange(num, item) { |
| 1494 | return item.some(v => { |
| 1495 | const end = v.end; |
| 1496 | const start = v.start; |
| 1497 | return num >= Math.min(start, end) && num <= Math.max(start, end); |
| 1498 | }); |
| 1499 | } |
| 1500 | |
| 1501 | lbtn_dblclk(x, y) { |
| 1502 | if (this.autoPlay.click > 2 && libSet.libSource) return; |
| 1503 | if (lib.vk.k('alt')) { |
| 1504 | this.mbtnDblClickOrAltDblClick(x, y, '', 'alt'); |
| 1505 | return; |
| 1506 | } |
| 1507 | this.dbl_clicked = true; |
| 1508 | if (y < lib.panel.search.h) return; |
| 1509 | const ix = this.get_ix(x, y, true, false); |
| 1510 | if (ix >= this.tree.length || ix < 0) return; |
| 1511 | const item = this.tree[ix]; |
| 1512 | switch (this.clicked_on) { |
| 1513 | case 'node': |
| 1514 | this.expandCollapse(x, y, item, ix); |
| 1515 | break; |
| 1516 | case 'text': |
| 1517 | if (!this.check_ix(item, x, y, false)) return; |
| 1518 | if (this.dblClickAction == 3) { |
| 1519 | const handleList = new FbMetadbHandleList(); |
| 1520 | this.range(item.item).forEach(v => { |
| 1521 | if (v < lib.panel.list.Count) handleList.Add(lib.panel.list[v]); |
| 1522 | }); |
| 1523 | if (handleList.Count) plman.FlushPlaybackQueue(); |
| 1524 | for (let i = 0; i < handleList.Count; i++) { |
| 1525 | plman.AddItemToPlaybackQueue(handleList[i]); |
| 1526 | } |
| 1527 | fb.Play(); |
| 1528 | return; |
| 1529 | } |
| 1530 | if (!libSet.libSource) { |
| 1531 | plman.ExecutePlaylistDefaultAction($Lib.pl_active, this.range(item.item)[0]); |
| 1532 | //plman.ExecutePlaylistDefaultAction($.pl_active, plman.GetPlaylistFocusItemIndex($.pl_active));//Asion 修正来源选择列表模式下搜索内容播放不正常 |
| 1533 | |
| 1534 | /*const plsIdx = libSet.fixedPlaylist ? plman.FindPlaylist(libSet.fixedPlaylistName) : $.pl_active; |
| 1535 | if (plsIdx !== -1) { |
| 1536 | const idx = lib.panel.search.txt.length |
| 1537 | ? plman.GetPlaylistItems(plsIdx).Find(lib.panel.list[this.range(item.item)[0]]) |
| 1538 | : this.range(item.item)[0]; |
| 1539 | if (idx !== -1) { plman.ExecutePlaylistDefaultAction(plsIdx, idx); } |
| 1540 | } */ |
| 1541 | return; |
| 1542 | } |
| 1543 | if (!this.dblClickAction && !this.autoFill.mouse && !this.autoPlay.click) return this.send(item, x, y); |
| 1544 | if (this.dblClickAction === 2 && !item.track && !lib.panel.imgView) { |
| 1545 | this.expandCollapse(x, y, item, ix); |
| 1546 | lib.lib.treeState(false, libSet.rememberTree); |
| 1547 | } |
| 1548 | if (!this.dblClickAction || this.autoPlay.click === 2) return; |
| 1549 | if (this.dblClickAction !== 2 && this.dblClickAction !== 3 || this.dblClickAction === 2 && item.track || this.dblClickAction === 2 && lib.panel.imgView) { |
| 1550 | if (!this.autoFill.mouse) this.send(item, x, y); |
| 1551 | let pl_stnd_idx = plman.FindOrCreatePlaylist(libSet.libPlaylist.replace(Regex.TFLibViewName, lib.panel.viewName), false); |
| 1552 | if (libSet.sendToCur) pl_stnd_idx = plman.ActivePlaylist; |
| 1553 | else plman.ActivePlaylist = pl_stnd_idx; |
| 1554 | plman.ActivePlaylist = pl_stnd_idx; |
| 1555 | const c = (plman.PlaybackOrder === 3 || plman.PlaybackOrder === 4) ? Math.ceil(plman.PlaylistItemCount(pl_stnd_idx) * Math.random() - 1) : 0; |
| 1556 | plman.ExecutePlaylistDefaultAction(pl_stnd_idx, c); |
| 1557 | } |
| 1558 | if (libSet.dblClickAction === 3) { |
| 1559 | if (plman.PlaylistItemCount(plman.ActivePlaylist) <= 0) { |
| 1560 | lib.panel.pos = 0; |
| 1561 | this.setTreeSel(this.tree[lib.panel.pos].ix); |
| 1562 | this.setPlaylist(lib.panel.pos, this.tree[lib.panel.pos]); |
| 1563 | this.load(lib.panel.pos, true, true, true, false, false); |
| 1564 | } |
| 1565 | plman.ExecutePlaylistDefaultAction($Lib.pl_active, this.sel_items[0]); |
| 1566 | this.load(this.sel_items, true, false, true, false, false); |
| 1567 | if (!item.root) { |
| 1568 | setTimeout(() => { fb.RunMainMenuCommand('编辑/撤消'); }, 100); |
| 1569 | } |
| 1570 | } |
| 1571 | break; |
| 1572 | } |
| 1573 | } |
| 1574 | |
| 1575 | lbtn_dn(x, y) { |
| 1576 | this.lbtnDn = false; |
| 1577 | this.dbl_clicked = false; |
| 1578 | if (y < lib.panel.search.h) return; |
| 1579 | const ix = this.get_ix(x, y, true, false); |
| 1580 | if (ix >= this.tree.length || ix < 0) return; |
| 1581 | this.deactivateTooltip(); |
| 1582 | if (libSet.touchControl) { |
| 1583 | lib.ui.id.dragDrop = lib.ui.id.touch_dn = ix; |
| 1584 | } |
| 1585 | const item = this.tree[ix]; |
| 1586 | this.clicked_on = this.clickedOn(x, y, item); |
| 1587 | switch (this.clicked_on) { |
| 1588 | case 'node': |
| 1589 | lib.panel.pos = ix; |
| 1590 | this.expandCollapse(x, y, item, ix); |
| 1591 | this.checkRow(x, y); |
| 1592 | break; |
| 1593 | case 'text': |
| 1594 | this.last_pressed_coord.x = x - lib.ui.x; |
| 1595 | this.last_pressed_coord.y = y - lib.ui.y; |
| 1596 | this.lbtnDn = true; |
| 1597 | lib.panel.pos = ix; |
| 1598 | if (libSet.touchControl) break; |
| 1599 | if (lib.vk.k('alt')) { |
| 1600 | this.alt_dbl_clicked = false; |
| 1601 | if (libSet.altClickAction == 2) { |
| 1602 | return; |
| 1603 | } |
| 1604 | if (this.autoFill.mouse) return; |
| 1605 | } |
| 1606 | if (!item.sel && !lib.vk.k('ctrl')) this.setTreeSel(ix, item.sel); |
| 1607 | break; |
| 1608 | } |
| 1609 | lib.lib.treeState(false, libSet.rememberTree); |
| 1610 | } |
| 1611 | |
| 1612 | lbtn_up(x, y) { |
| 1613 | if (lib.lib.empty && libSet.libSource == 1 && !libSet.fixedPlaylist && y > lib.panel.search.h) fb.RunMainMenuCommand('媒体库/配置'); |
| 1614 | this.last_pressed_coord = { |
| 1615 | x: undefined, |
| 1616 | y: undefined |
| 1617 | }; |
| 1618 | this.lbtnDn = false; |
| 1619 | if (y < lib.panel.search.h || x < lib.ui.x || this.dbl_clicked || lib.but.Dn) return; |
| 1620 | const ix = this.get_ix(x, y, true, false); |
| 1621 | lib.panel.pos = ix; |
| 1622 | if (ix >= this.tree.length || ix < 0) return; |
| 1623 | if (libSet.touchControl && (this.autoFill.mouse || this.autoPlay.click) && lib.ui.id.touch_dn != ix) return; |
| 1624 | const item = this.tree[ix]; |
| 1625 | if (this.clicked_on != 'text') return; |
| 1626 | if (!libSet.libSource) return this.setPlaylistSelection(ix, item); |
| 1627 | if (lib.vk.k('alt')) { |
| 1628 | if (grSet.addTracksPlaylistSwitch) { |
| 1629 | grm.button.btn.library.enabled = false; |
| 1630 | grm.button.btn.library.changeButtonState(ButtonState.Default); |
| 1631 | grm.ui.displayLibrary = false; |
| 1632 | grm.ui.displayPlaylist = true; |
| 1633 | if (!grSet.playlistAutoScrollNowPlaying) grm.ui.setPlaylistSize(); |
| 1634 | setTimeout(() => { |
| 1635 | if (pl.playlist.is_scrollbar_available) { |
| 1636 | pl.playlist.scrollbar.scroll_to_end(); |
| 1637 | } |
| 1638 | }, 500); |
| 1639 | window.Repaint(); |
| 1640 | } |
| 1641 | this.mbtnUpOrAltClickUp(x, y, '', 'alt'); // { |
| 1642 | return; |
| 1643 | } |
| 1644 | if (!lib.vk.k('ctrl')) { |
| 1645 | this.clearSelected(); |
| 1646 | if (!item.sel) this.setTreeSel(ix, item.sel); |
| 1647 | } else this.setTreeSel(ix, item.sel); |
| 1648 | if (this.autoFill.mouse || this.autoPlay.click) { |
| 1649 | window.Repaint(true); |
| 1650 | this.send(item, x, y); |
| 1651 | } else { |
| 1652 | lib.panel.treePaint(); |
| 1653 | } |
| 1654 | this.track(this.autoFill.mouse || this.autoPlay.click); |
| 1655 | lib.lib.treeState(false, libSet.rememberTree); |
| 1656 | } |
| 1657 | |
| 1658 | leave() { |
| 1659 | this.deactivateTooltip(); |
| 1660 | lib.panel.m.x = -1; |
| 1661 | lib.panel.m.y = -1; |
| 1662 | if (lib.men.r_up) return; |
| 1663 | this.m.br = -1; |
| 1664 | this.m.cur_br = 0; |
| 1665 | this.m.i = -1; |
| 1666 | this.cur_ix = 0; |
| 1667 | this.row.i = -1; |
| 1668 | this.row.cur = 0; |
| 1669 | lib.panel.treePaint(); |
| 1670 | } |
| 1671 | |
| 1672 | leftKeyCheckScroll() { |
| 1673 | const row = (lib.panel.pos * lib.ui.row.h - lib.sbar.scroll) / lib.ui.row.h; |
| 1674 | if (lib.sbar.scroll > lib.panel.pos * lib.ui.row.h) lib.sbar.checkScroll(lib.panel.pos * lib.ui.row.h); |
| 1675 | else if (row - lib.sbar.rows_drawn > 0) { |
| 1676 | lib.sbar.checkScroll((lib.panel.pos + 3 - lib.sbar.rows_drawn) * lib.ui.row.h); |
| 1677 | } |
| 1678 | else lib.sbar.scrollRound(); |
| 1679 | lib.lib.treeState(false, libSet.rememberTree); |
| 1680 | } |
| 1681 | |
| 1682 | load(list, isArray, add, autoPlay, def_pl, insert) { |
| 1683 | let np_item = -1; |
| 1684 | let pid = -1; |
| 1685 | const pl_stnd = libSet.libPlaylist.replace(Regex.TFLibViewName, lib.panel.viewName); |
| 1686 | let pl_stnd_idx = plman.FindOrCreatePlaylist(pl_stnd, true); |
| 1687 | |
| 1688 | if (!def_pl && plman.ActivePlaylist != -1) pl_stnd_idx = plman.ActivePlaylist; |
| 1689 | else if (libSet.activateOnChange) plman.ActivePlaylist = pl_stnd_idx; |
| 1690 | |
| 1691 | if (autoPlay == 4 && plman.PlaylistItemCount(pl_stnd_idx) || autoPlay == 3 && fb.IsPlaying) { |
| 1692 | autoPlay = false; |
| 1693 | add = true; |
| 1694 | } |
| 1695 | const items = isArray ? this.getHandleList() : list.Clone(); |
| 1696 | |
| 1697 | this.sortIfNeeded(items); |
| 1698 | this.selList = items.Clone(); |
| 1699 | this.selection_holder.SetSelection(this.selList); |
| 1700 | const plnIsValid = pl_stnd_idx != -1 && pl_stnd_idx < plman.PlaylistCount; |
| 1701 | const pllockRemoveOrAdd = plnIsValid ? plman.GetPlaylistLockedActions(pl_stnd_idx).includes('RemoveItems') || plman.GetPlaylistLockedActions(pl_stnd_idx).includes('ReplaceItems') || plman.GetPlaylistLockedActions(pl_stnd_idx).includes('AddItems') : false; |
| 1702 | if (!add && pllockRemoveOrAdd) return; |
| 1703 | if (fb.IsPlaying && !add) { |
| 1704 | if (libSet.actionMode == 1) { |
| 1705 | const pl_playing = `${libSet.libPlaylist} (正在播放)`; |
| 1706 | const pl_playing_idx = plman.FindOrCreatePlaylist(pl_playing, false); |
| 1707 | if (plman.PlayingPlaylist == pl_stnd_idx) { |
| 1708 | plman.RenamePlaylist(pl_stnd_idx, pl_playing); |
| 1709 | plman.RenamePlaylist(pl_playing_idx, pl_stnd); |
| 1710 | plman.SetPlaylistSelection(pl_playing_idx, $Lib.range(0, plman.PlaylistItemCount(pl_playing_idx) - 1), true); |
| 1711 | plman.RemovePlaylistSelection(pl_playing_idx, false); |
| 1712 | plman.InsertPlaylistItems(pl_playing_idx, 0, items, false); |
| 1713 | plman.MovePlaylist(pl_playing_idx, pl_stnd_idx); |
| 1714 | plman.MovePlaylist(pl_stnd_idx + 1, pl_playing_idx); |
| 1715 | } else { |
| 1716 | plman.SetPlaylistSelection(pl_stnd_idx, $Lib.range(0, plman.PlaylistItemCount(pl_stnd_idx) - 1), true); |
| 1717 | plman.RemovePlaylistSelection(pl_stnd_idx, false); |
| 1718 | plman.InsertPlaylistItems(pl_stnd_idx, 0, items, false); |
| 1719 | } |
| 1720 | plman.ActivePlaylist = pl_stnd_idx; |
| 1721 | } else if (fb.GetNowPlaying()) { |
| 1722 | np_item = items.Find(fb.GetNowPlaying()); |
| 1723 | let pl_chk = true; |
| 1724 | let np; |
| 1725 | if (np_item != -1) { |
| 1726 | np = plman.GetPlayingItemLocation(); |
| 1727 | if (np.IsValid) { |
| 1728 | if (np.PlaylistIndex != pl_stnd_idx) pl_chk = false; |
| 1729 | else pid = np.PlaylistItemIndex; |
| 1730 | } |
| 1731 | if (pl_chk && pid == -1 && items.Count < 5000) { |
| 1732 | if (lib.ui.dui) plman.SetActivePlaylistContext(); |
| 1733 | const start = Date.now(); |
| 1734 | for (let i = 0; i < 20; i++) { |
| 1735 | if (Date.now() - start > 300) break; |
| 1736 | fb.RunMainMenuCommand('编辑/撤消'); |
| 1737 | np = plman.GetPlayingItemLocation(); |
| 1738 | if (np.IsValid) { |
| 1739 | pid = np.PlaylistItemIndex; |
| 1740 | if (pid != -1) break; |
| 1741 | } |
| 1742 | } |
| 1743 | } |
| 1744 | } |
| 1745 | if (pid != -1) { |
| 1746 | plman.ClearPlaylistSelection(pl_stnd_idx); |
| 1747 | plman.SetPlaylistSelectionSingle(pl_stnd_idx, pid, true); |
| 1748 | plman.RemovePlaylistSelection(pl_stnd_idx, true); |
| 1749 | const it = items.Clone(); |
| 1750 | items.RemoveRange(np_item, items.Count); |
| 1751 | it.RemoveRange(0, np_item + 1); |
| 1752 | if (plman.PlaylistItemCount(pl_stnd_idx) < 5000) plman.UndoBackup(pl_stnd_idx); |
| 1753 | plman.InsertPlaylistItems(pl_stnd_idx, 0, items); |
| 1754 | plman.InsertPlaylistItems(pl_stnd_idx, plman.PlaylistItemCount(pl_stnd_idx), it); |
| 1755 | } else { |
| 1756 | if (plman.PlaylistItemCount(pl_stnd_idx) < 5000) plman.UndoBackup(pl_stnd_idx); |
| 1757 | plman.ClearPlaylist(pl_stnd_idx); |
| 1758 | plman.InsertPlaylistItems(pl_stnd_idx, 0, items); |
| 1759 | } |
| 1760 | } |
| 1761 | } else if (!add) { |
| 1762 | if (plman.PlaylistItemCount(pl_stnd_idx) < 5000) plman.UndoBackup(pl_stnd_idx); |
| 1763 | plman.ClearPlaylist(pl_stnd_idx); |
| 1764 | plman.InsertPlaylistItems(pl_stnd_idx, 0, items); |
| 1765 | plman.SetPlaylistFocusItem(pl_stnd_idx, 0); |
| 1766 | } else { |
| 1767 | if (plman.PlaylistItemCount(pl_stnd_idx) < 5000) plman.UndoBackup(pl_stnd_idx); |
| 1768 | plman.InsertPlaylistItems(pl_stnd_idx, !insert ? plman.PlaylistItemCount(pl_stnd_idx) : plman.GetPlaylistFocusItemIndex(pl_stnd_idx), items, true); |
| 1769 | const f_ix = !insert || plman.GetPlaylistFocusItemIndex(pl_stnd_idx) == -1 ? plman.PlaylistItemCount(pl_stnd_idx) - items.Count : plman.GetPlaylistFocusItemIndex(pl_stnd_idx) - items.Count; |
| 1770 | plman.SetPlaylistFocusItem(pl_stnd_idx, f_ix); |
| 1771 | plman.EnsurePlaylistItemVisible(pl_stnd_idx, f_ix); |
| 1772 | } |
| 1773 | if (autoPlay) { |
| 1774 | const c = (plman.PlaybackOrder == 3 || plman.PlaybackOrder == 4) ? Math.ceil(plman.PlaylistItemCount(pl_stnd_idx) * Math.random() - 1) : 0; |
| 1775 | plman.ExecutePlaylistDefaultAction(pl_stnd_idx, c); |
| 1776 | } |
| 1777 | } |
| 1778 | |
| 1779 | mbtn_dn() { |
| 1780 | this.mbtn_dbl_clicked = false; |
| 1781 | } |
| 1782 | |
| 1783 | mbtnDblClickOrAltDblClick(x, y, mask, type) { |
| 1784 | this[`${type}_dbl_clicked`] = true; |
| 1785 | if (type == 'mbtn' && (libSet.actionMode == 2 || libSet.mbtnClickAction == 2) || type == 'alt' && libSet.altClickAction == 2) { |
| 1786 | const ix = this.get_ix(x, y, true, false); |
| 1787 | if (ix < this.tree.length && ix >= 0) { |
| 1788 | const handleList = new FbMetadbHandleList(); |
| 1789 | this.range(this.tree[ix].item).forEach(v => { |
| 1790 | if (v < lib.panel.list.Count) handleList.Add(lib.panel.list[v]); |
| 1791 | }); |
| 1792 | const queueHandles = plman.GetPlaybackQueueHandles(); |
| 1793 | let remove = []; |
| 1794 | for (let i = 0; i < handleList.Count; i++) { |
| 1795 | for (let k = 0; k < queueHandles.Count; k++) { |
| 1796 | if (handleList[i].Compare(queueHandles[k])) { |
| 1797 | remove.push(k); |
| 1798 | } |
| 1799 | } |
| 1800 | } |
| 1801 | remove = [...new Set(remove)]; |
| 1802 | plman.RemoveItemsFromPlaybackQueue(remove); |
| 1803 | } |
| 1804 | } |
| 1805 | } |
| 1806 | |
| 1807 | mbtnUpOrAltClickUp(x, y, mask, type) { |
| 1808 | if (this[`${type}_dbl_clicked`]) return; |
| 1809 | if (type == 'mbtn' && (libSet.actionMode == 2 || libSet.mbtnClickAction == 2) || type == 'alt' && libSet.altClickAction == 2) { |
| 1810 | setTimeout(() => { // timeout: wait & see if double click, but adds a little lag to single click: timeout can be commented out |
| 1811 | if (this[`${type}_dbl_clicked`]) return; |
| 1812 | const ix = this.get_ix(x, y, true, false); |
| 1813 | if (ix < this.tree.length && ix >= 0) { |
| 1814 | const handleList = new FbMetadbHandleList(); |
| 1815 | this.range(this.tree[ix].item).forEach(v => { |
| 1816 | if (v < lib.panel.list.Count) handleList.Add(lib.panel.list[v]); |
| 1817 | }); |
| 1818 | const add = new FbMetadbHandleList(); |
| 1819 | handleList.Convert().forEach(h => { |
| 1820 | let found = false; |
| 1821 | plman.GetPlaybackQueueHandles().Convert().forEach(q => { |
| 1822 | if (h.Compare(q)) found = true; |
| 1823 | }); |
| 1824 | if (!found) add.Add(h); |
| 1825 | }); |
| 1826 | const ap = plman.ActivePlaylist; |
| 1827 | const plItems = plman.GetPlaylistItems(ap); |
| 1828 | add.Convert().forEach(v => { |
| 1829 | const ix = plItems.Find(v); |
| 1830 | if (ix != -1) { |
| 1831 | plman.AddPlaylistItemToPlaybackQueue(ap, ix); |
| 1832 | } else plman.AddItemToPlaybackQueue(v); |
| 1833 | }); |
| 1834 | } |
| 1835 | }, 180); |
| 1836 | } else { |
| 1837 | if (!libSet.libSource) return; |
| 1838 | this.add(x, y, !libSet[`${type}ClickAction`]); |
| 1839 | } |
| 1840 | } |
| 1841 | |
| 1842 | merge(m, mergeBrCount) { |
| 1843 | if (!libSet.libSource && !lib.panel.multiProcess) return; |
| 1844 | const seen = {}; |
| 1845 | for (let i = 0; i < m.length; i++) { |
| 1846 | const v = m[i].srt[0].toUpperCase(); |
| 1847 | if (seen[v] === undefined) seen[v] = i; |
| 1848 | else { |
| 1849 | if (!mergeBrCount) m[i].item.forEach(w => m[seen[v]].item.push(w)); |
| 1850 | m.splice(i, 1); |
| 1851 | i--; |
| 1852 | } |
| 1853 | } |
| 1854 | } |
| 1855 | |
| 1856 | move(x, y) { |
| 1857 | if (lib.but.Dn) return; |
| 1858 | const ix = this.get_ix(x, y, false, false); |
| 1859 | this.row.i = this.checkRow(x, y); |
| 1860 | this.m.i = -1; |
| 1861 | if (ix != -1) { |
| 1862 | this.m.i = ix; |
| 1863 | this.check_tooltip(ix, x, y); |
| 1864 | } else if (this.countsRight || this.statisticsShow) { |
| 1865 | this.check_tooltip(this.row.i, x, y); |
| 1866 | } else this.deactivateTooltip(); |
| 1867 | if (!libSet.mousePointerOnly) { |
| 1868 | if (this.highlight.node || lib.panel.imgView) { |
| 1869 | if (ix != -1 || this.inlineRoot && !this.m.br) this.hand = true; |
| 1870 | } else if (this.m.br != -1 && !(this.inlineRoot && !this.m.br)) this.hand = true; |
| 1871 | } |
| 1872 | SetCursor(this.hand ? 'Hand' : !lib.but.Dn && y > lib.ui.y && y < lib.ui.y + lib.panel.search.h && libSet.searchShow && x > lib.ui.x + lib.but.q.h + lib.but.margin && x < lib.panel.search.x + lib.panel.search.w ? 'IBeam' : 'Arrow'); |
| 1873 | const same = this.m.i == this.cur_ix && this.m.br == this.m.cur_br && this.row.i == this.row.cur; |
| 1874 | if (same && !lib.sbar.touch.dn) return; |
| 1875 | if (!lib.sbar.draw_timer && !same) lib.panel.treePaint(); |
| 1876 | this.cur_ix = this.m.i; |
| 1877 | this.m.cur_br = this.m.br; |
| 1878 | this.row.cur = this.row.i; |
| 1879 | } |
| 1880 | |
| 1881 | notifySelection(list) { |
| 1882 | if (list === undefined) list = this.getHandleList('newItems'); |
| 1883 | window.NotifyOthers(window.Name, list); |
| 1884 | if (list.Count) return true; |
| 1885 | } |
| 1886 | |
| 1887 | nowPlayingShow() { |
| 1888 | if (this.nowp != -1) { |
| 1889 | let np_i = -1; |
| 1890 | for (let i = 0; i < this.tree.length; i++) { |
| 1891 | const v = this.tree[i]; |
| 1892 | if (this.inRange(this.nowp, v.item)) { |
| 1893 | np_i = i; |
| 1894 | if (!v.root) { |
| 1895 | if (lib.panel.imgView) i = this.tree.length; |
| 1896 | else if (!v.track) this.branch(this.tree[np_i]); |
| 1897 | } |
| 1898 | } |
| 1899 | } |
| 1900 | if (!lib.panel.imgView && !this.tree[np_i].root) { |
| 1901 | this.clearSelected(); |
| 1902 | if (!this.highlight.nowPlaying) this.tree[np_i].sel = true; |
| 1903 | } |
| 1904 | if (np_i != -1) this.showItem(np_i, 'np'); |
| 1905 | } |
| 1906 | } |
| 1907 | |
| 1908 | numSort(a, b) { |
| 1909 | return a - b; |
| 1910 | } |
| 1911 | |
| 1912 | on_char(code) { |
| 1913 | if (lib.panel.search.active) return; |
| 1914 | switch (code) { |
| 1915 | case lib.vk.copy: { |
| 1916 | const handleList = this.getHandleList('newItems'); |
| 1917 | fb.CopyHandleListToClipboard(handleList); |
| 1918 | break; |
| 1919 | } |
| 1920 | case lib.vk.selAll: |
| 1921 | this.tree.forEach(v => { |
| 1922 | if (!v.root) v.sel = true; |
| 1923 | }); |
| 1924 | this.getTreeSel(); |
| 1925 | if (!this.sel_items.length) return; |
| 1926 | this.setPlaylist(); |
| 1927 | break; |
| 1928 | case lib.vk.eFocusSearch: |
| 1929 | case lib.vk.lFocusSearch: |
| 1930 | if (libSet.searchShow) lib.search.focus(); |
| 1931 | break; |
| 1932 | case lib.vk.insert: |
| 1933 | this.getTreeSel(); |
| 1934 | if (!this.sel_items.length) return; |
| 1935 | this.load(this.sel_items, true, true, false, false, true); |
| 1936 | break; |
| 1937 | } |
| 1938 | } |
| 1939 | |
| 1940 | on_focus(p_is_focused) { |
| 1941 | this.is_focused = p_is_focused; |
| 1942 | if (p_is_focused && this.selList && this.selList.Count) this.selection_holder.SetSelection(this.selList); |
| 1943 | } |
| 1944 | |
| 1945 | setPlaylist(ix, item) { |
| 1946 | if (libSet.libSource) { |
| 1947 | if (this.autoFill.key) this.load(this.sel_items, true, false, false, !libSet.sendToCur, false); |
| 1948 | this.track(true); |
| 1949 | } else if (this.autoFill.key) this.setPlaylistSelection(ix, item); |
| 1950 | } |
| 1951 | |
| 1952 | on_key_down(vkey) { |
| 1953 | if (vkey == lib.vk.collapseAll && !lib.panel.imgView) this.collapseAll(); |
| 1954 | if (vkey == lib.vk.expand && !lib.panel.imgView) { |
| 1955 | const isSel = this.tree.some(v => v.sel); |
| 1956 | this.expand(); |
| 1957 | if (isSel) { |
| 1958 | lib.panel.setHeight(true); |
| 1959 | this.checkAutoHeight(); |
| 1960 | } |
| 1961 | } |
| 1962 | if (lib.panel.search.active) return; |
| 1963 | if (lib.vk.k('enter')) { |
| 1964 | if (!this.sel_items.length) return; |
| 1965 | if (!libSet.libSource) { |
| 1966 | if (this.autoPlay.send) plman.ExecutePlaylistDefaultAction($Lib.pl_active, this.sel_items[0]); |
| 1967 | return; |
| 1968 | } |
| 1969 | switch (true) { |
| 1970 | case lib.vk.k('shift'): |
| 1971 | return this.load(this.sel_items, true, true, false, false, false); |
| 1972 | case lib.vk.k('ctrl'): |
| 1973 | return this.sendToNewPlaylist(); |
| 1974 | default: |
| 1975 | return this.load(this.sel_items, true, false, this.autoPlay.send, false, false); |
| 1976 | } |
| 1977 | } |
| 1978 | let item = -1; |
| 1979 | switch (vkey) { |
| 1980 | case lib.vk.left: |
| 1981 | if (lib.panel.imgView) lib.panel.pos -= 1; |
| 1982 | lib.panel.pos = $Lib.clamp(lib.panel.pos, 0, this.tree.length - 1); |
| 1983 | this.row.i = -1; |
| 1984 | this.m.i = -1; |
| 1985 | if (lib.panel.imgView) { |
| 1986 | item = this.tree[lib.panel.pos]; |
| 1987 | this.m.i = lib.panel.pos; |
| 1988 | this.showItem(item.ix, 'left'); |
| 1989 | this.setPlaylist(lib.panel.pos, item); |
| 1990 | break; |
| 1991 | } |
| 1992 | // !imgView |
| 1993 | if ((this.tree[lib.panel.pos].level == (this.rootNode ? 1 : 0)) && this.tree[lib.panel.pos].child.length < 1) { |
| 1994 | item = this.tree[lib.panel.pos]; |
| 1995 | this.m.i = lib.panel.pos = item.ix; |
| 1996 | this.leftKeyCheckScroll(); |
| 1997 | break; |
| 1998 | } |
| 1999 | if (this.tree[lib.panel.pos].child.length > 0) { |
| 2000 | item = this.tree[lib.panel.pos]; |
| 2001 | this.clearChild(item); |
| 2002 | this.setTreeSel(item.ix); |
| 2003 | this.m.i = lib.panel.pos = item.ix; |
| 2004 | } else { |
| 2005 | item = this.tree[this.tree[lib.panel.pos].par]; |
| 2006 | if (item) this.clearChild(item); |
| 2007 | this.setTreeSel(item.ix); |
| 2008 | this.m.i = lib.panel.pos = item.ix; |
| 2009 | } |
| 2010 | lib.panel.treePaint(); |
| 2011 | this.setPlaylist(lib.panel.pos, item); |
| 2012 | lib.sbar.setRows(this.tree.length); |
| 2013 | this.leftKeyCheckScroll(); |
| 2014 | break; |
| 2015 | case lib.vk.right: { |
| 2016 | if (lib.panel.imgView) lib.panel.pos += 1; |
| 2017 | lib.panel.pos = $Lib.clamp(lib.panel.pos, 0, this.tree.length - 1); |
| 2018 | this.row.i = -1; |
| 2019 | this.m.i = -1; |
| 2020 | item = this.tree[lib.panel.pos]; |
| 2021 | if (lib.panel.imgView) { |
| 2022 | this.m.i = lib.panel.pos; |
| 2023 | this.showItem(item.ix, 'right'); |
| 2024 | this.setPlaylist(lib.panel.pos, item); |
| 2025 | break; |
| 2026 | } |
| 2027 | // !imgView |
| 2028 | if (item.child.length) { |
| 2029 | lib.panel.pos++; |
| 2030 | lib.panel.pos = $Lib.clamp(lib.panel.pos, !this.rootNode ? 0 : 1, this.tree.length - 1); |
| 2031 | this.upDnKeyCheckScroll(vkey); |
| 2032 | break; |
| 2033 | } |
| 2034 | if (libSet.autoCollapse) this.branchChange(item, false, true); |
| 2035 | this.branch(item, !!item.root, true); |
| 2036 | let n = 2; |
| 2037 | if (item.child.length == 1 && libSet.treeAutoExpandSingle) { |
| 2038 | this.branch(item.child[0], false, true); |
| 2039 | n += item.child[0].child.length; |
| 2040 | } |
| 2041 | this.setTreeSel(item.ix); |
| 2042 | lib.panel.treePaint(); |
| 2043 | this.m.i = lib.panel.pos = item.ix; |
| 2044 | this.setPlaylist(lib.panel.pos, item); |
| 2045 | lib.sbar.setRows(this.tree.length); |
| 2046 | const row = (lib.panel.pos * lib.ui.row.h - lib.sbar.scroll) / lib.ui.row.h; |
| 2047 | if (row + n + item.child.length > lib.sbar.rows_drawn || row < 0) { |
| 2048 | if (item.child.length > (lib.sbar.rows_drawn - n) || row < 0) lib.sbar.checkScroll(lib.panel.pos * lib.ui.row.h); |
| 2049 | else lib.sbar.checkScroll(Math.min(lib.panel.pos * lib.ui.row.h, (lib.panel.pos + n - lib.sbar.rows_drawn + item.child.length) * lib.ui.row.h)); |
| 2050 | } else lib.sbar.scrollRound(); |
| 2051 | lib.lib.treeState(false, libSet.rememberTree); |
| 2052 | break; |
| 2053 | } |
| 2054 | case lib.vk.pgUp: |
| 2055 | if (this.tree.length == 0) break; |
| 2056 | if (lib.panel.imgView) { |
| 2057 | lib.panel.pos = lib.panel.pos - libImg.columns * (lib.panel.rows - 1); |
| 2058 | lib.panel.pos = $Lib.clamp(lib.panel.pos, 0, this.tree.length - 1); |
| 2059 | } else lib.panel.pos = Math.max(Math.round(lib.sbar.scroll / lib.ui.row.h + 0.4) - Math.floor(lib.panel.rows) + 1, !this.rootNode ? 0 : 1); |
| 2060 | lib.sbar.pageThrottle(1); |
| 2061 | this.setTreeSel(this.tree[lib.panel.pos].ix); |
| 2062 | lib.panel.treePaint(); |
| 2063 | this.setPlaylist(lib.panel.pos, this.tree[lib.panel.pos]); |
| 2064 | lib.lib.treeState(false, libSet.rememberTree); |
| 2065 | break; |
| 2066 | |
| 2067 | case lib.vk.pgDn: |
| 2068 | if (this.tree.length == 0) break; |
| 2069 | if (lib.panel.imgView) { |
| 2070 | lib.panel.pos = lib.panel.pos + libImg.columns * (lib.panel.rows - 1); |
| 2071 | lib.panel.pos = $Lib.clamp(lib.panel.pos, 0, this.tree.length - 1); |
| 2072 | } else lib.panel.pos = Math.min(Math.round(lib.sbar.scroll / lib.ui.row.h + 0.4) + Math.floor(lib.panel.rows) * 2 - 2, this.tree.length - 1); |
| 2073 | lib.sbar.pageThrottle(-1); |
| 2074 | this.setTreeSel(this.tree[lib.panel.pos].ix); |
| 2075 | lib.panel.treePaint(); |
| 2076 | this.setPlaylist(lib.panel.pos, this.tree[lib.panel.pos]); |
| 2077 | lib.lib.treeState(false, libSet.rememberTree); |
| 2078 | break; |
| 2079 | case lib.vk.home: |
| 2080 | if (this.tree.length == 0) break; |
| 2081 | lib.panel.pos = !this.rootNode ? 0 : 1; |
| 2082 | lib.sbar.checkScroll(0, 'full'); |
| 2083 | this.setTreeSel(this.tree[lib.panel.pos].ix); |
| 2084 | lib.panel.treePaint(); |
| 2085 | this.setPlaylist(lib.panel.pos, this.tree[lib.panel.pos]); |
| 2086 | lib.lib.treeState(false, libSet.rememberTree); |
| 2087 | break; |
| 2088 | case lib.vk.end: |
| 2089 | if (this.tree.length == 0) break; |
| 2090 | lib.panel.pos = this.tree.length - 1; |
| 2091 | lib.sbar.scrollToEnd(); |
| 2092 | this.setTreeSel(this.tree[lib.panel.pos].ix); |
| 2093 | lib.panel.treePaint(); |
| 2094 | this.setPlaylist(lib.panel.pos, this.tree[lib.panel.pos]); |
| 2095 | lib.lib.treeState(false, libSet.rememberTree); |
| 2096 | break; |
| 2097 | case lib.vk.dn: |
| 2098 | case lib.vk.up: |
| 2099 | if (this.tree.length == 0) break; |
| 2100 | if ((lib.panel.pos == 0 && vkey == lib.vk.up) || (lib.panel.pos == this.tree.length - 1 && vkey == lib.vk.dn)) { |
| 2101 | this.setTreeSel(-1); |
| 2102 | break; |
| 2103 | } |
| 2104 | this.row.i = -1; |
| 2105 | this.m.i = -1; |
| 2106 | if (!lib.panel.imgView) { |
| 2107 | if (vkey == lib.vk.dn) lib.panel.pos++; |
| 2108 | if (vkey == lib.vk.up) lib.panel.pos--; |
| 2109 | } else { |
| 2110 | if (vkey == lib.vk.dn) lib.panel.pos += libImg.columns; |
| 2111 | if (vkey == lib.vk.up) lib.panel.pos -= libImg.columns; |
| 2112 | } |
| 2113 | lib.panel.pos = $Lib.clamp(lib.panel.pos, !this.rootNode ? 0 : 1, this.tree.length - 1); |
| 2114 | if (lib.panel.imgView) { |
| 2115 | item = this.tree[lib.panel.pos]; |
| 2116 | this.m.i = lib.panel.pos; |
| 2117 | this.showItem(item.ix, vkey == lib.vk.up ? 'up' : 'down'); |
| 2118 | this.setPlaylist(lib.panel.pos, item); |
| 2119 | return; |
| 2120 | } |
| 2121 | this.upDnKeyCheckScroll(vkey); |
| 2122 | break; |
| 2123 | } |
| 2124 | } |
| 2125 | |
| 2126 | on_main_menu(index) { |
| 2127 | if (index == this.getMainMenuIndex.add) { |
| 2128 | this.getTreeSel(); |
| 2129 | if (!this.sel_items.length) return; |
| 2130 | this.load(this.sel_items, true, true, false, false, false); |
| 2131 | } |
| 2132 | if (index == this.getMainMenuIndex.collapseAll) this.collapseAll(); |
| 2133 | if (index == this.getMainMenuIndex.insert) { |
| 2134 | this.getTreeSel(); |
| 2135 | if (!this.sel_items.length) return; |
| 2136 | this.load(this.sel_items, true, true, false, false, true); |
| 2137 | } |
| 2138 | if (index == this.getMainMenuIndex.new) { |
| 2139 | this.getTreeSel(); |
| 2140 | if (!this.sel_items.length) return; |
| 2141 | this.sendToNewPlaylist(); |
| 2142 | } |
| 2143 | if (index == this.getMainMenuIndex.searchClear && libSet.searchShow) lib.search.clear(); |
| 2144 | if (index == this.getMainMenuIndex.searchFocus && this.is_focused && libSet.searchShow) lib.search.focus(); |
| 2145 | } |
| 2146 | |
| 2147 | range(item) { |
| 2148 | const items = []; |
| 2149 | item.forEach(v => { |
| 2150 | for (let i = v.start; i <= v.end; i++) items.push(i); |
| 2151 | }); |
| 2152 | return items; |
| 2153 | } |
| 2154 | |
| 2155 | removeDuplicateArr(arr) { |
| 2156 | const t = {}; |
| 2157 | return arr.filter(v => !(t[v] = v in t)); |
| 2158 | } |
| 2159 | |
| 2160 | send(item, x, y) { |
| 2161 | if (!this.check_ix(item, x, y, false)) return; |
| 2162 | if (lib.vk.k('ctrl') || lib.vk.k('shift')) this.load(this.sel_items, true, false, false, !libSet.sendToCur, false); |
| 2163 | else this.load(this.sel_items, true, false, this.autoPlay.click, !libSet.sendToCur, false); |
| 2164 | } |
| 2165 | |
| 2166 | sendToNewPlaylist() { |
| 2167 | const names = this.tree.filter(v => v.sel).map(v => v.name); |
| 2168 | plman.ActivePlaylist = plman.CreatePlaylist(plman.PlaylistCount, [...new Set(names)].join('; ')); |
| 2169 | this.load(this.sel_items, true, false, this.autoPlay.send, false, false); |
| 2170 | } |
| 2171 | |
| 2172 | setActions() { |
| 2173 | this.autoPlay = { |
| 2174 | click: libSet.clickAction < 2 ? false : libSet.clickAction, |
| 2175 | send: libSet.autoPlay |
| 2176 | }; |
| 2177 | this.autoFill = { |
| 2178 | mouse: libSet.actionMode == 2 ? false : libSet.clickAction == 1 || libSet.actionMode == 1, |
| 2179 | key: libSet.keyAction |
| 2180 | }; |
| 2181 | this.dblClickAction = libSet.actionMode == 1 ? 1 : libSet.actionMode == 2 ? 3 : libSet.dblClickAction; |
| 2182 | } |
| 2183 | |
| 2184 | setPlaylistSelection(ix, item) { |
| 2185 | this.clearSelected(); |
| 2186 | if (!item.sel) this.setTreeSel(ix, item.sel); |
| 2187 | lib.panel.treePaint(); |
| 2188 | plman.ClearPlaylistSelection($Lib.pl_active); |
| 2189 | let items = []; |
| 2190 | if (lib.panel.search.txt || libSet.filterBy || lib.panel.multiProcess) { |
| 2191 | const hl = this.getHandleList(); |
| 2192 | hl.Convert().forEach(h => { |
| 2193 | const i = lib.lib.full_list.Find(h); |
| 2194 | if (i != -1) items.push(i); |
| 2195 | }); |
| 2196 | } else { |
| 2197 | items = this.range(item.item); |
| 2198 | } |
| 2199 | plman.SetPlaylistSelection($Lib.pl_active, items, true); |
| 2200 | this.setFocus = true; |
| 2201 | plman.SetPlaylistFocusItem($Lib.pl_active, items[0]); |
| 2202 | this.track(false); |
| 2203 | lib.lib.treeState(false, libSet.rememberTree); |
| 2204 | } |
| 2205 | |
| 2206 | setPos(pos) { |
| 2207 | this.m.i = this.row.i = lib.panel.pos = pos; |
| 2208 | } |
| 2209 | |
| 2210 | setTreeSel(idx, state) { |
| 2211 | const sel_type = idx == -1 ? 0 : lib.vk.k('shift') && this.last_sel > -1 && libSet.libSource ? 1 : lib.vk.k('ctrl') ? 2 : !state ? 3 : 0; |
| 2212 | switch (sel_type) { |
| 2213 | case 0: |
| 2214 | this.clearSelected(); |
| 2215 | this.sel_items = []; |
| 2216 | break; |
| 2217 | case 1: { |
| 2218 | const direction = (idx > this.last_sel) ? 1 : -1; |
| 2219 | if (!lib.vk.k('ctrl')) this.clearSelected(); |
| 2220 | for (let i = this.last_sel; ; i += direction) { |
| 2221 | this.tree[i].sel = true; |
| 2222 | if (i == idx) break; |
| 2223 | } |
| 2224 | this.getTreeSel(); |
| 2225 | lib.panel.treePaint(); |
| 2226 | break; |
| 2227 | } |
| 2228 | case 2: |
| 2229 | this.tree[idx].sel = !this.tree[idx].sel; |
| 2230 | this.getTreeSel(); |
| 2231 | this.last_sel = idx; |
| 2232 | break; |
| 2233 | case 3: |
| 2234 | this.sel_items = []; |
| 2235 | this.clearSelected(); |
| 2236 | this.tree[idx].sel = true; |
| 2237 | this.addItems(this.sel_items, this.tree[idx].item); |
| 2238 | this.uniq(this.sel_items); |
| 2239 | this.last_sel = idx; |
| 2240 | break; |
| 2241 | } |
| 2242 | } |
| 2243 | |
| 2244 | setValues() { |
| 2245 | this.countsRight = libSet.countsRight; |
| 2246 | this.fullLineSelection = libSet.fullLineSelection; |
| 2247 | this.highlight = { |
| 2248 | node: libSet.highLightNode, |
| 2249 | nowPlaying: libSet.highLightNowplaying, |
| 2250 | nowPlayingIndicator: libSet.nowPlayingIndicator, |
| 2251 | nowPlayingSidemarker: libSet.nowPlayingSidemarker, |
| 2252 | nowPlayingShow: libSet.highLightNowplaying || libSet.nowPlayingIndicator || libSet.nowPlayingSidemarker, |
| 2253 | row: libSet.highLightRow, |
| 2254 | text: libSet.highLightText |
| 2255 | }; |
| 2256 | this.iconVerticalPad = libSet.iconVerticalPad; |
| 2257 | this.nodeCounts = libSet.nodeCounts; |
| 2258 | this.nodeStyle = libSet.nodeStyle; |
| 2259 | this.rootNode = libSet.rootNode; |
| 2260 | this.rowStripes = libSet.rowStripes; |
| 2261 | this.sbarShow = libSet.sbarShow; |
| 2262 | this.showTracks = !libSet.facetView ? libSet.showTracks : false; |
| 2263 | this.statistics = ['', '比特率', '持续时间', '合计大小', '等级', '热门', '日期', '播放队列', '播放次数', '首次播放', '最近播放', '添加时间']; |
| 2264 | this.statisticsShow = libSet.itemShowStatistics; |
| 2265 | this.label = !libSet.labelStatistics ? '' : this.statisticsShow ? this.statistics[this.statisticsShow] : ''; |
| 2266 | this.tooltipStatistics = libSet.tooltipStatistics; |
| 2267 | this.treeIndent = libSet.treeIndent; |
| 2268 | this.imgGetItemCount = libSet.itemOverlayType != 1 && libSet.albumArtLabelType == 2 && !this.statisticsShow && (this.nodeCounts == 1 || this.nodeCounts == 2); |
| 2269 | } |
| 2270 | |
| 2271 | showItem(i, type) { |
| 2272 | if (!lib.panel.imgView) { |
| 2273 | lib.sbar.checkScroll(i * lib.ui.row.h - Math.round(lib.sbar.rows_drawn / 2 - 1) * lib.ui.row.h, 'full'); |
| 2274 | return; |
| 2275 | } |
| 2276 | this.m.i = -1; |
| 2277 | const b = $Lib.clamp(Math.round(lib.sbar.delta / lib.sbar.row.h), 0, lib.panel.rows - 1); |
| 2278 | const f = Math.min(b + Math.floor(libImg.panel.h / lib.sbar.row.h), lib.panel.rows); |
| 2279 | this.setTreeSel(i); |
| 2280 | lib.panel.treePaint(); |
| 2281 | const row1 = Math.floor(i / (libImg.style.vertical ? libImg.columns : 1)); |
| 2282 | if (row1 <= b || row1 >= f) { |
| 2283 | switch (type) { |
| 2284 | case 'np': |
| 2285 | case 'focus': { |
| 2286 | const delta = (libImg.style.vertical ? libImg.panel.h : libImg.panel.w) / 2 > lib.sbar.row.h ? Math.floor((libImg.style.vertical ? libImg.panel.h : libImg.panel.w) / 2) : 0; |
| 2287 | const deltaRows = Math.floor(delta / lib.sbar.row.h) * lib.sbar.row.h; |
| 2288 | lib.sbar.checkScroll((libImg.style.vertical ? row1 : i) * lib.sbar.row.h - deltaRows, 'full'); |
| 2289 | break; |
| 2290 | } |
| 2291 | case 'up': |
| 2292 | case 'down': |
| 2293 | case 'left': |
| 2294 | case 'right': { |
| 2295 | const row2 = (row1 * lib.sbar.row.h - lib.sbar.scroll) / lib.sbar.row.h; |
| 2296 | if (lib.sbar.rows_drawn - row2 < 1 || row2 < 0) lib.sbar.checkScroll((row1 + 1) * lib.sbar.row.h - lib.sbar.rows_drawn * lib.sbar.row.h); |
| 2297 | else if (row2 < 1 & (type == 'up' || type == 'left')) lib.sbar.checkScroll(row1 * lib.sbar.row.h); |
| 2298 | } |
| 2299 | } |
| 2300 | } |
| 2301 | lib.lib.treeState(false, libSet.rememberTree); |
| 2302 | } |
| 2303 | |
| 2304 | |
| 2305 | sort(data) { |
| 2306 | // 修复:ppt 和 panel 改为从 this 获取(作用域问题) |
| 2307 | if (!this.ppt?.libSource && !this.panel?.multiProcess) return; |
| 2308 | |
| 2309 | this.specialCharSort(data); |
| 2310 | |
| 2311 | // 一次排序完成:类型排序 + 原有字段排序 + 字符串自然排序 |
| 2312 | data.sort((a, b) => { |
| 2313 | // ============= 1. 优先按【字符类型】排序(核心规则) ============= |
| 2314 | const nameA = (a.srt[0] || '').trim(); |
| 2315 | const nameB = (b.srt[0] || '').trim(); |
| 2316 | |
| 2317 | // 字符类型判断:符号(0) → 数字(1) → 英文(2) → 中文(3) → 其他(4) |
| 2318 | const getCharType = (str) => { |
| 2319 | if (!str) return 4; // 空字符串排最后 |
| 2320 | const char = str.charAt(0); // 取第一个字符判断类型 |
| 2321 | const code = char.charCodeAt(0); |
| 2322 | |
| 2323 | // 中文字符 |
| 2324 | if (code >= 0x4e00 && code <= 0x9fff) return 3; |
| 2325 | // 数字 0-9 |
| 2326 | if (code >= 48 && code <= 57) return 1; |
| 2327 | // 英文字母 A-Z a-z |
| 2328 | if ((code >= 65 && code <= 90) || (code >= 97 && code <= 122)) return 2; |
| 2329 | // 符号(非中文、非数字、非字母) |
| 2330 | return 0; |
| 2331 | }; |
| 2332 | |
| 2333 | const typeA = getCharType(nameA); |
| 2334 | const typeB = getCharType(nameB); |
| 2335 | |
| 2336 | // 类型不同 → 按类型排序 |
| 2337 | if (typeA !== typeB) return typeA - typeB; |
| 2338 | |
| 2339 | // ============= 2. 类型相同 → 按原有 collator 字段排序 ============= |
| 2340 | const collatorCompare = this.collator.compare(a.srt[2], b.srt[2]); |
| 2341 | if (collatorCompare !== 0) return collatorCompare; |
| 2342 | |
| 2343 | // ============= 3. 再按 srt[3] 布尔值排序 ============= |
| 2344 | const flagCompare = (a.srt[3] && !b.srt[3]) ? 1 : (!a.srt[3] && b.srt[3]) ? -1 : 0; |
| 2345 | if (flagCompare !== 0) return flagCompare; |
| 2346 | |
| 2347 | // ============= 4. 最后按字符串本身自然排序 ============= |
| 2348 | return nameA.localeCompare(nameB, undefined, { numeric: true, sensitivity: 'base' }); |
| 2349 | }); |
| 2350 | } |
| 2351 | |
| 2352 | |
| 2353 | sortIfNeeded(items) { |
| 2354 | if (lib.panel.multiProcess && !libSet.customSort.length) items.OrderByFormat(lib.panel.playlistSort, 1); |
| 2355 | else if (libSet.customSort.length) items.OrderByFormat(this.customSort, 1); |
| 2356 | } |
| 2357 | |
| 2358 | sortViewByFolder(data) { |
| 2359 | // Use Intl.Collator with numeric: true to sort file/folder names (srt[0]) naturally, |
| 2360 | // matching Windows Explorer's alphanumeric order (e.g., "2.01" < "2.15", "1985a" < "1986 -"). |
| 2361 | // This fixes incorrect track and folder sorting in View by Folder Structure. |
| 2362 | const naturalCollator = new Intl.Collator(undefined, { numeric: true }); |
| 2363 | |
| 2364 | data.sort((a, b) => { |
| 2365 | const nameCompare = naturalCollator.compare(a.srt[0], b.srt[0]); |
| 2366 | if (nameCompare !== 0) return nameCompare; |
| 2367 | // Fallback to metadata sorting (srt[2], srt[3]) for identical file/folder names. |
| 2368 | return this.collator.compare(a.srt[2], b.srt[2]) || (a.srt[3] && !b.srt[3] ? 1 : 0); |
| 2369 | }); |
| 2370 | } |
| 2371 | |
| 2372 | specialCharHas(name) { |
| 2373 | return RegExp(this.specialChar).test(name); |
| 2374 | } |
| 2375 | |
| 2376 | specialCharIsLeading(name) { |
| 2377 | return RegExp(`^${this.specialChar}`).test(name); |
| 2378 | } |
| 2379 | |
| 2380 | specialCharPad(name) { |
| 2381 | return name.replace(RegExp(`${this.specialChar}+`, 'g'), v => (`${v} `).slice(0, 5)); |
| 2382 | } |
| 2383 | |
| 2384 | specialCharSort(data) { |
| 2385 | const removed = []; |
| 2386 | const filtered = data.filter((v, i) => { |
| 2387 | const f = this.specialCharHas(v.srt[0]); |
| 2388 | if (f) { |
| 2389 | v.srt[1] = this.specialCharPad(v.srt[0]); |
| 2390 | v.srt[2] = this.specialCharStrip(v.srt[0]); |
| 2391 | v.srt[3] = this.specialCharIsLeading(v.srt[0]); |
| 2392 | removed.push(i); |
| 2393 | } |
| 2394 | return f; |
| 2395 | }); |
| 2396 | removed.reverse(); |
| 2397 | removed.forEach(v => data.splice(v, 1)); |
| 2398 | const sorted = filtered.sort((a, b) => this.collator.compare(a.srt[1], b.srt[1])); |
| 2399 | sorted.forEach(v => data.push(v)); |
| 2400 | } |
| 2401 | |
| 2402 | specialCharStrip(name) { |
| 2403 | let [str1, ...str2] = name.split(' '); |
| 2404 | str2 = str2.join(' '); |
| 2405 | if (this.isDate(str1)) return `${str1} ${str2.replace(RegExp(this.specialChar, 'g'), '')}`; |
| 2406 | return name.replace(RegExp(this.specialChar, 'g'), ''); |
| 2407 | } |
| 2408 | |
| 2409 | track(plLoaded) { |
| 2410 | const list = this.getHandleList(); |
| 2411 | this.notifySelection(list); |
| 2412 | if (!plLoaded) this.selection_holder.SetSelection(list); |
| 2413 | } |
| 2414 | |
| 2415 | trackCount(item) { |
| 2416 | return item.reduce((a, b) => a + b.count, 0); |
| 2417 | } |
| 2418 | |
| 2419 | treeTooltipFont() { |
| 2420 | const libraryFontSize = SCALE((RES._4K ? grSet.libraryFontSize_layout - 0 : grSet.libraryFontSize_layout) || 14); |
| 2421 | return !lib.panel.imgView ? [lib.ui.font.main.Name, /* ui.font.main.Size */ libraryFontSize + 3, lib.ui.font.main.Style] : [lib.ui.font.group.Name, /* ui.font.group.Size */ libraryFontSize + 3, lib.ui.font.group.Style]; |
| 2422 | } |
| 2423 | |
| 2424 | uniq(arr) { |
| 2425 | this.sel_items = [...new Set(arr)].sort(this.numSort); |
| 2426 | } |
| 2427 | |
| 2428 | upDnKeyCheckScroll(vkey) { |
| 2429 | const row = (lib.panel.pos * lib.ui.row.h - lib.sbar.scroll) / lib.ui.row.h; |
| 2430 | if (lib.sbar.rows_drawn - row < 3 || row < 0) lib.sbar.checkScroll((lib.panel.pos + 3) * lib.ui.row.h - lib.sbar.rows_drawn * lib.ui.row.h); |
| 2431 | else if (row < 2 && vkey == lib.vk.up) lib.sbar.checkScroll((lib.panel.pos - 1) * lib.ui.row.h); |
| 2432 | this.m.i = lib.panel.pos; |
| 2433 | this.setTreeSel(lib.panel.pos); |
| 2434 | lib.panel.treePaint(); |
| 2435 | this.setPlaylist(lib.panel.pos, this.tree[lib.panel.pos]); |
| 2436 | lib.lib.treeState(false, libSet.rememberTree); |
| 2437 | } |
| 2438 | } |
| 2439 |