Ultima attività 22 hours ago

\georgia-reborn\scripts\Library\scripts lib-images.js 曲库封面下三行居中

wojack's Avatar wojack ha revisionato questo gist 22 hours ago. Vai alla revisione

2 files changed, 3877 insertions

lib-images.js(file creato)

@@ -0,0 +1,1439 @@
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();

lib-populate.js(file creato)

@@ -0,0 +1,2438 @@
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 + }
Più nuovi Più vecchi