From 1b3584f7f658d3df61ae2c5711280236490dc55d Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Tue, 14 May 2024 16:23:17 -0300 Subject: add/remove part of group to/from selection --- ui.js | 100 ++++++++++++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 79 insertions(+), 21 deletions(-) (limited to 'ui.js') diff --git a/ui.js b/ui.js index f006a70..3e0098f 100644 --- a/ui.js +++ b/ui.js @@ -45,6 +45,7 @@ import { parentSymbol, reverseOrderOfGroups, searchForItem, + selectTracks, shuffleOrderOfGroups, } from './playlist-utils.js' @@ -317,6 +318,15 @@ export default class AppElement extends FocusElement { {value: 'normal', label: 'In order'} ], this.showContextMenu) + this.selectGrouplikeItemsControl = new InlineListPickerElement('Which?', [ + {value: 'all', label: 'all tracks'}, + {value: '1', label: 'one track, randomly'}, + {value: '4', label: 'four tracks, randomly'}, + {value: '25%', label: '25% of the tracks, randomly'}, + {value: '50%', label: '50% of the tracks, randomly'}, + {value: '75%', label: '75% of the tracks, randomly'}, + ], this.showContextMenu) + this.menubar.buildItems([ {text: 'mtui', menuItems: [ {label: 'mtui (perpetual development)'}, @@ -914,32 +924,79 @@ export default class AppElement extends FocusElement { this.emitMarkChanged() } - markItem(item) { - if (isGroup(item)) { - for (const child of item.items) { - this.markItem(child) + selectTracksForMarking(item, forUnmarking = false, useGroupSelectionControl = false) { + let mode = 'all' + let modeValue = 0 + + if (useGroupSelectionControl) { + const value = this.selectGrouplikeItemsControl.curValue + if (value === 'all') { + // Do nothing, this is the default + } else if (value.endsWith('%')) { + mode = 'random' + modeValue = value + } else if (parseInt(value).toString() === value) { + mode = 'random' + modeValue = value } + } + + const range = { + items: + (flattenGrouplike(item) + .items + .filter(isTrack) + .filter(track => + this.markGrouplike.items.includes(track) === forUnmarking)) + } + + return selectTracks(range, mode, modeValue).items + } + + markItem(item, useGroupSelectionControl = false) { + const { items: mark } = this.markGrouplike + + let add + if (isGroup(item)) { + add = this.selectTracksForMarking(item, false, useGroupSelectionControl) + } else if (mark.includes(item)) { + add = [] } else { - const { items } = this.markGrouplike - if (!items.includes(item)) { - items.push(item) - this.emitMarkChanged() - } + add = [item] + } + + if (!add.length) { + return } + + for (const track of add) { + mark.push(track) + } + + this.emitMarkChanged() } - unmarkItem(item) { + unmarkItem(item, useGroupSelectionControl) { + const { items: mark } = this.markGrouplike + + let remove if (isGroup(item)) { - for (const child of item.items) { - this.unmarkItem(child) - } + remove = this.selectTracksForMarking(item, true, useGroupSelectionControl) + } else if (mark.includes(item)) { + remove = [item] } else { - const { items } = this.markGrouplike - if (items.includes(item)) { - items.splice(items.indexOf(item), 1) - this.emitMarkChanged() - } + remove = [] } + + if (!remove.length) { + return + } + + for (const track of remove) { + mark.splice(mark.indexOf(track), 1) + } + + this.emitMarkChanged() } getMarkStatus(item) { @@ -1442,9 +1499,10 @@ export default class AppElement extends FocusElement { ...(item === this.markGrouplike ? [{label: 'Deselect all', action: () => this.unmarkAll()}] : [ - this.getMarkStatus(item) !== 'unmarked' && {label: 'Remove from selection', action: () => this.unmarkItem(item)}, - this.getMarkStatus(item) !== 'marked' && {label: 'Add to selection', action: () => this.markItem(item)} - ]) + isGroup(item) && {element: this.selectGrouplikeItemsControl}, + this.getMarkStatus(item) !== 'unmarked' && {label: 'Remove from selection', action: () => this.unmarkItem(item, true)}, + this.getMarkStatus(item) !== 'marked' && {label: 'Add to selection', action: () => this.markItem(item, true)}, + ]) ] } } -- cgit 1.3.0-6-gf8a5 From a9cb64751d35334dce978ba2165893eacb4d29bb Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Tue, 14 May 2024 16:35:00 -0300 Subject: remember which main context menu page was open --- ui.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) (limited to 'ui.js') diff --git a/ui.js b/ui.js index 3e0098f..b786838 100644 --- a/ui.js +++ b/ui.js @@ -199,6 +199,8 @@ export default class AppElement extends FocusElement { this.timestampDictionary = new WeakMap() + this.itemMenuPage = 0 + // We add this is a child later (so that it's on top of every element). this.menuLayer = new DisplayElement() this.menuLayer.clickThrough = true @@ -1515,10 +1517,15 @@ export default class AppElement extends FocusElement { // TODO: Implement this! :P // const isMarked = false - this.showContextMenu({ + const menu = this.showContextMenu({ x: el.absLeft, y: el.absTop + 1, - pages + pages, + pageNum: this.itemMenuPage + }) + + menu.on('page changed', pageNum => { + this.itemMenuPage = pageNum }) } @@ -4837,6 +4844,7 @@ class ContextMenu extends FocusElement { } this.close(false) this.show({x, y, pages, pageNum}) + this.emit('page changed', pageNum) } } @@ -4848,6 +4856,7 @@ class ContextMenu extends FocusElement { } this.close(false) this.show({x, y, pages, pageNum}) + this.emit('page changed', pageNum) } } @@ -4859,7 +4868,7 @@ class ContextMenu extends FocusElement { if (pages.length === 0) { return } - itemsArg = pages[pageNum] + itemsArg = pages[Math.min(pages.length - 1, pageNum)] } let items = (typeof itemsArg === 'function') ? itemsArg() : itemsArg -- cgit 1.3.0-6-gf8a5 From 3111926f8826b323825bde6efe4ffdb3d10488cd Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Tue, 14 May 2024 16:42:32 -0300 Subject: save current context menu page number as a named id --- ui.js | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) (limited to 'ui.js') diff --git a/ui.js b/ui.js index b786838..20d7381 100644 --- a/ui.js +++ b/ui.js @@ -199,7 +199,7 @@ export default class AppElement extends FocusElement { this.timestampDictionary = new WeakMap() - this.itemMenuPage = 0 + this.itemMenuPage = 'cursor' // We add this is a child later (so that it's on top of every element). this.menuLayer = new DisplayElement() @@ -1429,6 +1429,9 @@ export default class AppElement extends FocusElement { }) } + // TODO: Implement this! :P + // const isMarked = false + // const hasNotesFile = !!getCorrespondingFileForItem(item, '.txt') const timestampsItem = this.hasTimestampsFile(item) && (this.timestampsExpanded(item, listing) ? {label: 'Collapse saved timestamps', action: () => this.collapseTimestamps(item, listing)} @@ -1509,23 +1512,33 @@ export default class AppElement extends FocusElement { } } - const pages = [ - this.markGrouplike.items.length && generatePageForItem(this.markGrouplike), - el.item && generatePageForItem(el.item) + const pageInfo = [ + this.markGrouplike.items.length && { + id: 'mark', + page: generatePageForItem(this.markGrouplike), + }, + + el.item && { + id: 'cursor', + page: generatePageForItem(el.item), + } ].filter(Boolean) - // TODO: Implement this! :P - // const isMarked = false + const pages = pageInfo.map(({ page }) => page) + + let pageNum = pageInfo.findIndex(({ id }) => id === this.itemMenuPage) + if (pageNum === -1) pageNum = pageInfo.findIndex(({ id }) => id === 'cursor') + if (pageNum === -1) pageNum = 0 const menu = this.showContextMenu({ x: el.absLeft, y: el.absTop + 1, pages, - pageNum: this.itemMenuPage + pageNum, }) menu.on('page changed', pageNum => { - this.itemMenuPage = pageNum + this.itemMenuPage = pageInfo[pageNum].id }) } -- cgit 1.3.0-6-gf8a5 From 180df0db0904ac8a0fd4070d0f2f320358d04a88 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Tue, 14 May 2024 17:33:20 -0300 Subject: fix context menu not detecting scroll bar appropriately --- ui.js | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) (limited to 'ui.js') diff --git a/ui.js b/ui.js index 20d7381..3d66ce8 100644 --- a/ui.js +++ b/ui.js @@ -5086,16 +5086,30 @@ class ContextMenu extends FocusElement { width += 2 // Space for the pane border height += 2 // Space for the pane border - if (this.form.scrollBarShown) width++ + + // Hilarity time: at THIS point, we can't identify if the form has its + // scroll bar visible, because we haven't constrained the height of the + // form. We're going to apply all the dimensions leading to the form twice, + // adapting the second time to be one wider if the form shows its scroll + // bar the first time. + this.w = width this.h = height - this.fitToParent() this.pane.fillParent() this.form.fillParent() this.form.fixLayout() + // We only need to update our own (and the descendants') width this time, + // because height won't have changed from the earlier measurement. + if (this.form.scrollBarShown) width++ + this.w = width + + this.pane.fillParent() + this.form.fillParent() + this.form.fixLayout() + // After everything else, do a second pass to apply the decided width // to every element, so that they expand to all be the same width. // In order to change the width of a button (which is what these elements -- cgit 1.3.0-6-gf8a5 From 778a6961cc1cca9d6649d3ff38206f6f5e9e929a Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Tue, 14 May 2024 17:42:59 -0300 Subject: prepare to view selected items page after starting selection This was the intended behavior from the start! We just forgot to code this part, oops. --- ui.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'ui.js') diff --git a/ui.js b/ui.js index 3d66ce8..b784688 100644 --- a/ui.js +++ b/ui.js @@ -971,6 +971,13 @@ export default class AppElement extends FocusElement { return } + // If this is the first addition (starting from empty), switch the + // remembered context menu page so that the next context menu will show + // the marked items automatically. + if (!mark.length) { + this.itemMenuPage = 'mark' + } + for (const track of add) { mark.push(track) } @@ -998,6 +1005,15 @@ export default class AppElement extends FocusElement { mark.splice(mark.indexOf(track), 1) } + // If this is the last removal (going to empty), switch the remembered + // context menu page so that the next context menu will show the usual + // controls for the item under the cursor. This isn't exactly necessary + // since various fallbacks will handle this value pointing to a page that + // doesn't exist anymore, but it's nice for consistency. + if (!mark.length) { + this.itemMenuPage = 'cursor' + } + this.emitMarkChanged() } -- cgit 1.3.0-6-gf8a5