Zuletzt aktiv 22 hours ago

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

Änderung 62675a546b74d9f44ed73e4c52157da0bda08a96

lib-images.js Originalformat
1'use strict';
2
3class 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 */
1439const libImg = new LibImages();
1440
lib-populate.js Originalformat
1'use strict';
2
3class 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
2305sort(data) {
2306 // 修复:ppt 和 panel 改为从 this 获取(作用域问题)
2307 if (!this.ppt?.libSource && !this.panel?.multiProcess) return;
2308
2309 this.specialCharSort(data);
2310
2311 // 一次排序完成:类型排序 + 原有字段排序 + 字符串自然排序
2312 data.sort((a, b) => {
2313 // ============= 1. 优先按【字符类型】排序(核心规则) =============
2314 const nameA = (a.srt[0] || '').trim();
2315 const nameB = (b.srt[0] || '').trim();
2316
2317 // 字符类型判断:符号(0) → 数字(1) → 英文(2) → 中文(3) → 其他(4)
2318 const getCharType = (str) => {
2319 if (!str) return 4; // 空字符串排最后
2320 const char = str.charAt(0); // 取第一个字符判断类型
2321 const code = char.charCodeAt(0);
2322
2323 // 中文字符
2324 if (code >= 0x4e00 && code <= 0x9fff) return 3;
2325 // 数字 0-9
2326 if (code >= 48 && code <= 57) return 1;
2327 // 英文字母 A-Z a-z
2328 if ((code >= 65 && code <= 90) || (code >= 97 && code <= 122)) return 2;
2329 // 符号(非中文、非数字、非字母)
2330 return 0;
2331 };
2332
2333 const typeA = getCharType(nameA);
2334 const typeB = getCharType(nameB);
2335
2336 // 类型不同 → 按类型排序
2337 if (typeA !== typeB) return typeA - typeB;
2338
2339 // ============= 2. 类型相同 → 按原有 collator 字段排序 =============
2340 const collatorCompare = this.collator.compare(a.srt[2], b.srt[2]);
2341 if (collatorCompare !== 0) return collatorCompare;
2342
2343 // ============= 3. 再按 srt[3] 布尔值排序 =============
2344 const flagCompare = (a.srt[3] && !b.srt[3]) ? 1 : (!a.srt[3] && b.srt[3]) ? -1 : 0;
2345 if (flagCompare !== 0) return flagCompare;
2346
2347 // ============= 4. 最后按字符串本身自然排序 =============
2348 return nameA.localeCompare(nameB, undefined, { numeric: true, sensitivity: 'base' });
2349 });
2350 }
2351
2352
2353 sortIfNeeded(items) {
2354 if (lib.panel.multiProcess && !libSet.customSort.length) items.OrderByFormat(lib.panel.playlistSort, 1);
2355 else if (libSet.customSort.length) items.OrderByFormat(this.customSort, 1);
2356 }
2357
2358 sortViewByFolder(data) {
2359 // Use Intl.Collator with numeric: true to sort file/folder names (srt[0]) naturally,
2360 // matching Windows Explorer's alphanumeric order (e.g., "2.01" < "2.15", "1985a" < "1986 -").
2361 // This fixes incorrect track and folder sorting in View by Folder Structure.
2362 const naturalCollator = new Intl.Collator(undefined, { numeric: true });
2363
2364 data.sort((a, b) => {
2365 const nameCompare = naturalCollator.compare(a.srt[0], b.srt[0]);
2366 if (nameCompare !== 0) return nameCompare;
2367 // Fallback to metadata sorting (srt[2], srt[3]) for identical file/folder names.
2368 return this.collator.compare(a.srt[2], b.srt[2]) || (a.srt[3] && !b.srt[3] ? 1 : 0);
2369 });
2370 }
2371
2372 specialCharHas(name) {
2373 return RegExp(this.specialChar).test(name);
2374 }
2375
2376 specialCharIsLeading(name) {
2377 return RegExp(`^${this.specialChar}`).test(name);
2378 }
2379
2380 specialCharPad(name) {
2381 return name.replace(RegExp(`${this.specialChar}+`, 'g'), v => (`${v} `).slice(0, 5));
2382 }
2383
2384 specialCharSort(data) {
2385 const removed = [];
2386 const filtered = data.filter((v, i) => {
2387 const f = this.specialCharHas(v.srt[0]);
2388 if (f) {
2389 v.srt[1] = this.specialCharPad(v.srt[0]);
2390 v.srt[2] = this.specialCharStrip(v.srt[0]);
2391 v.srt[3] = this.specialCharIsLeading(v.srt[0]);
2392 removed.push(i);
2393 }
2394 return f;
2395 });
2396 removed.reverse();
2397 removed.forEach(v => data.splice(v, 1));
2398 const sorted = filtered.sort((a, b) => this.collator.compare(a.srt[1], b.srt[1]));
2399 sorted.forEach(v => data.push(v));
2400 }
2401
2402 specialCharStrip(name) {
2403 let [str1, ...str2] = name.split(' ');
2404 str2 = str2.join(' ');
2405 if (this.isDate(str1)) return `${str1} ${str2.replace(RegExp(this.specialChar, 'g'), '')}`;
2406 return name.replace(RegExp(this.specialChar, 'g'), '');
2407 }
2408
2409 track(plLoaded) {
2410 const list = this.getHandleList();
2411 this.notifySelection(list);
2412 if (!plLoaded) this.selection_holder.SetSelection(list);
2413 }
2414
2415 trackCount(item) {
2416 return item.reduce((a, b) => a + b.count, 0);
2417 }
2418
2419 treeTooltipFont() {
2420 const libraryFontSize = SCALE((RES._4K ? grSet.libraryFontSize_layout - 0 : grSet.libraryFontSize_layout) || 14);
2421 return !lib.panel.imgView ? [lib.ui.font.main.Name, /* ui.font.main.Size */ libraryFontSize + 3, lib.ui.font.main.Style] : [lib.ui.font.group.Name, /* ui.font.group.Size */ libraryFontSize + 3, lib.ui.font.group.Style];
2422 }
2423
2424 uniq(arr) {
2425 this.sel_items = [...new Set(arr)].sort(this.numSort);
2426 }
2427
2428 upDnKeyCheckScroll(vkey) {
2429 const row = (lib.panel.pos * lib.ui.row.h - lib.sbar.scroll) / lib.ui.row.h;
2430 if (lib.sbar.rows_drawn - row < 3 || row < 0) lib.sbar.checkScroll((lib.panel.pos + 3) * lib.ui.row.h - lib.sbar.rows_drawn * lib.ui.row.h);
2431 else if (row < 2 && vkey == lib.vk.up) lib.sbar.checkScroll((lib.panel.pos - 1) * lib.ui.row.h);
2432 this.m.i = lib.panel.pos;
2433 this.setTreeSel(lib.panel.pos);
2434 lib.panel.treePaint();
2435 this.setPlaylist(lib.panel.pos, this.tree[lib.panel.pos]);
2436 lib.lib.treeState(false, libSet.rememberTree);
2437 }
2438}
2439