From 4bdee941d68331f61f0dd22f6c690a306f3fccd8 Mon Sep 17 00:00:00 2001 From: Michael Votaw Date: Fri, 7 Jun 2024 17:24:56 -0500 Subject: [PATCH 01/10] Throwing together a really quick implementation of nps graph --- app/src/gui/widget/NPSGraphWidget.ts | 276 +++++++++++++++++++++++++++ app/src/gui/widget/WidgetManager.ts | 3 +- 2 files changed, 278 insertions(+), 1 deletion(-) create mode 100644 app/src/gui/widget/NPSGraphWidget.ts diff --git a/app/src/gui/widget/NPSGraphWidget.ts b/app/src/gui/widget/NPSGraphWidget.ts new file mode 100644 index 00000000..7ed00753 --- /dev/null +++ b/app/src/gui/widget/NPSGraphWidget.ts @@ -0,0 +1,276 @@ +import { + FederatedPointerEvent, + ParticleContainer, + RenderTexture, + Sprite, + Texture, + Graphics, +} from "pixi.js" +import { EditMode } from "../../chart/ChartManager" +import { QUANT_COLORS } from "../../chart/component/edit/SnapContainer" +import { Chart } from "../../chart/sm/Chart" +import { isHoldNote } from "../../chart/sm/NoteTypes" +import { BetterRoundedRect } from "../../util/BetterRoundedRect" +import { EventHandler } from "../../util/EventHandler" +import { Flags } from "../../util/Flags" +import { clamp, lerp, unlerp } from "../../util/Math" +import { Options } from "../../util/Options" +import { destroyChildIf, getDivision } from "../../util/Util" +import { Widget } from "./Widget" +import { WidgetManager } from "./WidgetManager" + +export class NPSGraphWidget extends Widget { + barContainer = new ParticleContainer( + 1500, + { position: true, scale: true, tint: true }, + 16384, + true + ) + backing: BetterRoundedRect = new BetterRoundedRect() + bars: Sprite + barTexture: RenderTexture + overlay: Sprite = new Sprite(Texture.WHITE) + graph: Graphics + + private lastHeight = 0 + private lastCMod + private mouseDown = false + private queued = false + + constructor(manager: WidgetManager) { + super(manager) + this.addChild(this.backing) + this.visible = false + this.backing.tint = 0 + this.backing.alpha = 0.3 + this.barTexture = RenderTexture.create({ + resolution: this.manager.app.renderer.resolution, + }) + this.bars = new Sprite(this.barTexture) + this.bars.anchor.set(0.5) + this.addChild(this.bars) + this.overlay.anchor.x = 0.5 + this.overlay.anchor.y = 0 + this.overlay.alpha = 0.3 + this.lastCMod = Options.chart.CMod + this.addChild(this.overlay) + this.x = this.manager.app.renderer.screen.width / 2 - 20 + this.graph = new Graphics() + this.addChild(this.graph) + EventHandler.on("chartLoaded", () => { + this.queued = false + this.populate() + }) + EventHandler.on("chartModifiedAfter", () => { + if (!this.queued) this.populate() + this.queued = true + }) + const interval = setInterval(() => { + if (this.queued) { + this.queued = false + this.populate() + } + }, 3000) + + this.on("destroyed", () => clearInterval(interval)) + this.populate() + + this.eventMode = "static" + this.on("mousedown", event => { + this.mouseDown = true + this.handleMouse(event) + }) + this.on("mousemove", event => { + if (this.mouseDown) this.handleMouse(event) + }) + window.onmouseup = () => { + this.mouseDown = false + } + } + + private handleMouse(event: FederatedPointerEvent) { + if (this.manager.chartManager.getMode() == EditMode.Play) return + if (!this.getChart()) return + let t = + (this.bars.toLocal(event.global).y + this.bars.height / 2) / + this.bars.height + t = clamp(t, 0, 1) + const lastNote = this.getChart().getNotedata().at(-1) + if (!lastNote) return + const lastBeat = lastNote.beat + (isHoldNote(lastNote) ? lastNote.hold : 0) + const lastSecond = this.getChart().getSecondsFromBeat(lastBeat) + if (Options.chart.CMod) { + this.manager.chartManager.setTime( + lerp(-this.getChart().timingData.getOffset(), lastSecond, t) + ) + } else { + this.manager.chartManager.setBeat(lastBeat * t) + } + } + + update() { + this.scale.y = Options.chart.reverse ? -1 : 1 + const height = this.manager.app.renderer.screen.height - 40 + this.backing.height = height + 10 + this.backing.position.y = -this.backing.height / 2 + this.backing.position.x = -this.backing.width / 2 + this.bars.height = height + this.x = this.manager.app.renderer.screen.width / 2 - 60 + const chart = this.getChart() + const chartView = this.manager.chartManager.chartView! + if (!chart || !chartView || !Flags.layout) { + this.visible = false + return + } + this.visible = true + const lastNote = chart.getNotedata().at(-1) + if (!lastNote) { + this.overlay.height = 0 + return + } + const lastBeat = lastNote.beat + (isHoldNote(lastNote) ? lastNote.hold : 0) + const lastSecond = chart.getSecondsFromBeat(lastBeat) + const start = Options.chart.CMod + ? chartView.getSecondFromYPos( + -this.manager.app.renderer.screen.height / 2 + ) + : chartView.getBeatFromYPos( + -this.manager.app.renderer.screen.height / 2, + true + ) + const end = Options.chart.CMod + ? chartView.getSecondFromYPos(this.manager.app.renderer.screen.height / 2) + : chartView.getBeatFromYPos( + this.manager.app.renderer.screen.height / 2, + true + ) + let t_startY = unlerp(0, lastBeat, start) + let t_endY = unlerp(0, lastBeat, end) + if (Options.chart.CMod) { + t_startY = unlerp(-chart.timingData.getOffset(), lastSecond, start) + t_endY = unlerp(-chart.timingData.getOffset(), lastSecond, end) + } + t_startY = clamp(t_startY, 0, 1) + t_endY = clamp(t_endY, 0, 1) + if (t_startY > t_endY) [t_startY, t_endY] = [t_endY, t_startY] + const startY = (t_startY - 0.5) * (this.backing.height - 10) + const endY = (t_endY - 0.5) * (this.backing.height - 10) + this.overlay.y = startY + this.overlay.height = endY - startY + this.overlay.height = Math.max(2, this.overlay.height) + if ( + this.manager.app.renderer.screen.height != this.lastHeight || + this.lastCMod != Options.chart.CMod + ) { + this.lastCMod = Options.chart.CMod + this.lastHeight = this.manager.app.renderer.screen.height + this.populate() + } + } + + populate() { + const chart = this.getChart() + if (!chart) { + destroyChildIf(this.barContainer.children, () => true) + + this.manager.app.renderer.render(this.barContainer, { + renderTexture: this.barTexture, + }) + return + } + const childIndex = 0 + const numCols = chart.gameType.numCols + const lastNote = chart.getNotedata().at(-1) + const firstNote = chart.getNotedata().at(0) + + const height = this.manager.app.renderer.screen.height - 40 + this.backing.height = height + this.backing.width = numCols * 6 + 8 + this.overlay.width = numCols * 6 + 8 + this.pivot.x = this.backing.width / 2 + + this.barTexture.resize(numCols * 6, height) + + if (!lastNote || !firstNote) { + destroyChildIf(this.barContainer.children, () => true) + this.manager.app.renderer.render(this.barContainer, { + renderTexture: this.barTexture, + }) + return + } + const lastBeat = lastNote.beat + (isHoldNote(lastNote) ? lastNote.hold : 0) + const lastSecond = chart.getSecondsFromBeat(lastBeat) + const songOffset = chart.timingData.getOffset() + + const maxNps = chart.getMaxNPS() + const npsGraph = chart.getNPSGraph() + + this.graph.clear() + // this.graph.lineStyle(2, 0xffffff, 1) + this.graph.beginFill(0x00ff00, 1) + + this.graph.pivot.x = this.backing.width / 2 + this.graph.pivot.y = height / 2 + + const lastMeasure = npsGraph.length + + const startY = this.getY(lastBeat, songOffset, lastSecond, 0, height) + this.graph.moveTo(0, startY) + + for (let measureIndex = 0; measureIndex < lastMeasure; measureIndex++) { + const nps = npsGraph[measureIndex] || 0 + const beat = chart.timingData.getBeatFromMeasure(measureIndex) + const endOfMeasureBeat = Math.min( + lastBeat, + chart.timingData.getBeatFromMeasure(measureIndex + 1) + ) + const x = unlerp(0, maxNps, nps) * numCols * 6 + const y = this.getY(lastBeat, songOffset, lastSecond, beat, height) + const endOfMeasureY = this.getY( + lastBeat, + songOffset, + lastSecond, + endOfMeasureBeat, + height + ) + console.log( + `measure: ${measureIndex}, nps: ${nps.toFixed(3)}, x: ${x}, y: ${y}` + ) + this.graph.lineTo(x, y) + this.graph.lineTo(x, endOfMeasureY) + } + + const lastnps = npsGraph.at(-1)! + const lastX = unlerp(0, maxNps, lastnps) * numCols * 6 + const lastY = this.getY(lastBeat, songOffset, lastSecond, lastBeat, height) + this.graph.lineTo(lastX, lastY) + this.graph.lineTo(0, lastY) + this.graph.endFill() + destroyChildIf( + this.barContainer.children, + (_, index) => index >= childIndex + ) + + this.manager.app.renderer.render(this.barContainer, { + renderTexture: this.barTexture, + }) + } + + private getY( + lastBeat: number, + songOffset: number, + lastSecond: number, + beat: number, + height: number + ): number { + let t = unlerp(0, lastBeat, beat) + if (Options.chart.CMod) { + const second = this.getChart().timingData.getSecondsFromBeat(beat) + t = unlerp(songOffset, lastSecond, second) + } + return t * height + } + private getChart(): Chart { + return this.manager.chartManager.loadedChart! + } +} diff --git a/app/src/gui/widget/WidgetManager.ts b/app/src/gui/widget/WidgetManager.ts index b60efcdd..1df2a486 100644 --- a/app/src/gui/widget/WidgetManager.ts +++ b/app/src/gui/widget/WidgetManager.ts @@ -6,7 +6,7 @@ import { NoteLayoutWidget } from "./NoteLayoutWidget" import { PlayInfoWidget } from "./PlayInfoWidget" import { StatusWidget } from "./StatusWidget" import { Widget } from "./Widget" - +import { NPSGraphWidget } from "./NPSGraphWidget" export class WidgetManager extends Container { app: App chartManager: ChartManager @@ -20,6 +20,7 @@ export class WidgetManager extends Container { this.addChild(new PlayInfoWidget(this)) this.addChild(new StatusWidget(this)) this.addChild(new DebugWidget(this)) + this.addChild(new NPSGraphWidget(this)) this.zIndex = 2 } From 0f344861ce7c9050b7cfa2f63f623578bd59827a Mon Sep 17 00:00:00 2001 From: Michael Votaw Date: Tue, 11 Jun 2024 08:46:12 -0500 Subject: [PATCH 02/10] Added gradient colors, options to enable/disable nps graph and set gradient colors --- app/src/data/UserOptionsWindowData.ts | 35 +++++++++ app/src/gui/widget/NPSGraphWidget.ts | 95 +++++++++++++++++++------ app/src/gui/window/UserOptionsWindow.ts | 19 ++++- app/src/util/Options.ts | 5 ++ 4 files changed, 130 insertions(+), 24 deletions(-) diff --git a/app/src/data/UserOptionsWindowData.ts b/app/src/data/UserOptionsWindowData.ts index 04398071..d0120c0c 100644 --- a/app/src/data/UserOptionsWindowData.ts +++ b/app/src/data/UserOptionsWindowData.ts @@ -413,6 +413,41 @@ export const USER_OPTIONS_WINDOW_DATA: UserOption[] = [ }, ], }, + { + type: "subgroup", + label: "NPS Graph", + children: [ + { + type: "item", + label: "Show NPS Graph", + id: "chart.npsGraph.enabled", + input: { + type: "checkbox", + }, + }, + { + type: "subgroup", + children: [ + { + type: "item", + label: "Start Color", + id: "chart.npsGraph.color1", + input: { + type: "color", + }, + }, + { + type: "item", + label: "End Color", + id: "chart.npsGraph.color2", + input: { + type: "color", + }, + }, + ], + }, + ], + }, ], }, { diff --git a/app/src/gui/widget/NPSGraphWidget.ts b/app/src/gui/widget/NPSGraphWidget.ts index 7ed00753..5548b1fc 100644 --- a/app/src/gui/widget/NPSGraphWidget.ts +++ b/app/src/gui/widget/NPSGraphWidget.ts @@ -30,15 +30,19 @@ export class NPSGraphWidget extends Widget { bars: Sprite barTexture: RenderTexture overlay: Sprite = new Sprite(Texture.WHITE) - graph: Graphics + npsGraph: Graphics private lastHeight = 0 private lastCMod private mouseDown = false private queued = false + private graphGradient: Texture | null = null + + private graphWidth: number = 40 constructor(manager: WidgetManager) { super(manager) + this.graphGradient = this.makeGradient() this.addChild(this.backing) this.visible = false this.backing.tint = 0 @@ -49,14 +53,21 @@ export class NPSGraphWidget extends Widget { this.bars = new Sprite(this.barTexture) this.bars.anchor.set(0.5) this.addChild(this.bars) + + this.npsGraph = new Graphics() + this.addChild(this.npsGraph) + this.overlay.anchor.x = 0.5 this.overlay.anchor.y = 0 this.overlay.alpha = 0.3 this.lastCMod = Options.chart.CMod this.addChild(this.overlay) this.x = this.manager.app.renderer.screen.width / 2 - 20 - this.graph = new Graphics() - this.addChild(this.graph) + + this.setupEventHandlers() + } + + private setupEventHandlers() { EventHandler.on("chartLoaded", () => { this.queued = false this.populate() @@ -72,6 +83,15 @@ export class NPSGraphWidget extends Widget { } }, 3000) + EventHandler.on("userOptionUpdated", optionId => { + if ( + optionId == "chart.npsGraph.color1" || + optionId == "chart.npsGraph.color2" + ) { + this.graphGradient = this.makeGradient() + this.populate() + } + }) this.on("destroyed", () => clearInterval(interval)) this.populate() @@ -109,6 +129,10 @@ export class NPSGraphWidget extends Widget { } update() { + if (!Options.chart.npsGraph.enabled) { + this.visible = false + return + } this.scale.y = Options.chart.reverse ? -1 : 1 const height = this.manager.app.renderer.screen.height - 40 this.backing.height = height + 10 @@ -179,17 +203,16 @@ export class NPSGraphWidget extends Widget { return } const childIndex = 0 - const numCols = chart.gameType.numCols const lastNote = chart.getNotedata().at(-1) const firstNote = chart.getNotedata().at(0) const height = this.manager.app.renderer.screen.height - 40 this.backing.height = height - this.backing.width = numCols * 6 + 8 - this.overlay.width = numCols * 6 + 8 + this.backing.width = this.graphWidth + 8 + this.overlay.width = this.graphWidth + 8 this.pivot.x = this.backing.width / 2 - this.barTexture.resize(numCols * 6, height) + this.barTexture.resize(this.graphWidth, height) if (!lastNote || !firstNote) { destroyChildIf(this.barContainer.children, () => true) @@ -205,17 +228,21 @@ export class NPSGraphWidget extends Widget { const maxNps = chart.getMaxNPS() const npsGraph = chart.getNPSGraph() - this.graph.clear() + this.npsGraph.clear() // this.graph.lineStyle(2, 0xffffff, 1) - this.graph.beginFill(0x00ff00, 1) + if (this.graphGradient) { + this.npsGraph.beginTextureFill({ texture: this.graphGradient }) + } else { + this.npsGraph.beginFill(0x000000, 1) + } - this.graph.pivot.x = this.backing.width / 2 - this.graph.pivot.y = height / 2 + this.npsGraph.pivot.x = this.backing.width / 2 + this.npsGraph.pivot.y = height / 2 const lastMeasure = npsGraph.length const startY = this.getY(lastBeat, songOffset, lastSecond, 0, height) - this.graph.moveTo(0, startY) + this.npsGraph.moveTo(0, startY) for (let measureIndex = 0; measureIndex < lastMeasure; measureIndex++) { const nps = npsGraph[measureIndex] || 0 @@ -224,7 +251,7 @@ export class NPSGraphWidget extends Widget { lastBeat, chart.timingData.getBeatFromMeasure(measureIndex + 1) ) - const x = unlerp(0, maxNps, nps) * numCols * 6 + const x = unlerp(0, maxNps, nps) * this.graphWidth const y = this.getY(lastBeat, songOffset, lastSecond, beat, height) const endOfMeasureY = this.getY( lastBeat, @@ -233,19 +260,17 @@ export class NPSGraphWidget extends Widget { endOfMeasureBeat, height ) - console.log( - `measure: ${measureIndex}, nps: ${nps.toFixed(3)}, x: ${x}, y: ${y}` - ) - this.graph.lineTo(x, y) - this.graph.lineTo(x, endOfMeasureY) + + this.npsGraph.lineTo(x, y) + this.npsGraph.lineTo(x, endOfMeasureY) } const lastnps = npsGraph.at(-1)! - const lastX = unlerp(0, maxNps, lastnps) * numCols * 6 + const lastX = unlerp(0, maxNps, lastnps) * this.graphWidth const lastY = this.getY(lastBeat, songOffset, lastSecond, lastBeat, height) - this.graph.lineTo(lastX, lastY) - this.graph.lineTo(0, lastY) - this.graph.endFill() + this.npsGraph.lineTo(lastX, lastY) + this.npsGraph.lineTo(0, lastY) + this.npsGraph.endFill() destroyChildIf( this.barContainer.children, (_, index) => index >= childIndex @@ -273,4 +298,30 @@ export class NPSGraphWidget extends Widget { private getChart(): Chart { return this.manager.chartManager.loadedChart! } + + private makeGradient() { + const quality = this.graphWidth + const canvas = document.createElement("canvas") + + canvas.width = quality + canvas.height = quality + + const ctx = canvas.getContext("2d") + + if (ctx == null) { + return null + } + // use canvas2d API to create gradient + const grd = ctx.createLinearGradient(0, 0, quality, 0) + + const color1 = `#${Options.chart.npsGraph.color1.toString(16)}` + const color2 = `#${Options.chart.npsGraph.color2.toString(16)}` + grd.addColorStop(0, color1) + grd.addColorStop(0.8, color2) + + ctx.fillStyle = grd + ctx.fillRect(0, 0, quality, quality) + + return Texture.from(canvas) + } } diff --git a/app/src/gui/window/UserOptionsWindow.ts b/app/src/gui/window/UserOptionsWindow.ts index 17ac5fa4..2fbcab92 100644 --- a/app/src/gui/window/UserOptionsWindow.ts +++ b/app/src/gui/window/UserOptionsWindow.ts @@ -12,6 +12,7 @@ import { Icons } from "../Icons" import { Dropdown } from "../element/Dropdown" import { NumberSpinner } from "../element/NumberSpinner" import { Window } from "./Window" +import { EventHandler } from "../../util/EventHandler" export class UserOptionsWindow extends Window { app: App @@ -125,6 +126,7 @@ export class UserOptionsWindow extends Window { revert.style.width = "12px" revert.addEventListener("click", () => { Options.applyOption([option.id, Options.getDefaultOption(option.id)]) + EventHandler.emit("userOptionUpdated", option.id) // Reload the option item.replaceWith(this.makeOption(option)) }) @@ -149,6 +151,7 @@ export class UserOptionsWindow extends Window { checkbox.onblur = null checkbox.onchange = () => { Options.applyOption([option.id, checkbox.checked]) + EventHandler.emit("userOptionUpdated", option.id) revert.style.display = Options.getDefaultOption(option.id) === Options.getOption(option.id) @@ -174,6 +177,7 @@ export class UserOptionsWindow extends Window { ) dropdown.onChange(value => { Options.applyOption([option.id, deserializer(value)]) + EventHandler.emit("userOptionUpdated", option.id) revert.style.display = Options.getDefaultOption(option.id) === Options.getOption(option.id) @@ -188,6 +192,7 @@ export class UserOptionsWindow extends Window { const dropdown = Dropdown.create(option.input.items, optionValue) dropdown.onChange(value => { Options.applyOption([option.id, value]) + EventHandler.emit("userOptionUpdated", option.id) revert.style.display = Options.getDefaultOption(option.id) === Options.getOption(option.id) @@ -219,6 +224,7 @@ export class UserOptionsWindow extends Window { return } Options.applyOption([option.id, deserializer(value)]) + EventHandler.emit("userOptionUpdated", option.id) revert.style.display = Options.getDefaultOption(option.id) === Options.getOption(option.id) @@ -264,9 +270,12 @@ export class UserOptionsWindow extends Window { value = clamp(value, hardMin, hardMax) numberInput.value = roundDigit(value, 3).toString() numberInput.blur() - if (numberInput.value == "") + if (numberInput.value == "") { numberInput.value = serializer(value).toString() - else Options.applyOption([option.id, deserializer(value)]) + } else { + Options.applyOption([option.id, deserializer(value)]) + EventHandler.emit("userOptionUpdated", option.id) + } slider.value = value.toString() revert.style.display = Options.getDefaultOption(option.id) === @@ -301,6 +310,7 @@ export class UserOptionsWindow extends Window { textInput.value = optionValue.toString() textInput.onblur = () => { Options.applyOption([option.id, textInput.value]) + EventHandler.emit("userOptionUpdated", option.id) revert.style.display = Options.getDefaultOption(option.id) === Options.getOption(option.id) @@ -318,11 +328,16 @@ export class UserOptionsWindow extends Window { const colorInput = document.createElement("input") colorInput.type = "color" colorInput.value = "#" + optionValue.toString(16) + // 'change' event is fired when the user closes the color picker + colorInput.onchange = () => { + colorInput.blur() + } colorInput.onblur = () => { Options.applyOption([ option.id, parseInt(colorInput.value.slice(1), 16), ]) + EventHandler.emit("userOptionUpdated", option.id) revert.style.display = Options.getDefaultOption(option.id) === Options.getOption(option.id) diff --git a/app/src/util/Options.ts b/app/src/util/Options.ts index e450ded9..c3792c3f 100644 --- a/app/src/util/Options.ts +++ b/app/src/util/Options.ts @@ -44,6 +44,11 @@ export class DefaultOptions { lineHeight: 1, speedChanges: true, }, + npsGraph: { + enabled: false, + color1: 0x4aa7bc, + color2: 0x423c7a, + }, timingEventOrder: { left: [ "LABELS", From bcd125d73969bfc7f7b19d65b29a4a0caf0fe9cb Mon Sep 17 00:00:00 2001 From: Michael Votaw Date: Tue, 11 Jun 2024 09:02:20 -0500 Subject: [PATCH 03/10] Removed unused bar-related variables --- app/src/gui/widget/NPSGraphWidget.ts | 51 ++-------------------------- 1 file changed, 2 insertions(+), 49 deletions(-) diff --git a/app/src/gui/widget/NPSGraphWidget.ts b/app/src/gui/widget/NPSGraphWidget.ts index 5548b1fc..24ff8411 100644 --- a/app/src/gui/widget/NPSGraphWidget.ts +++ b/app/src/gui/widget/NPSGraphWidget.ts @@ -1,13 +1,5 @@ -import { - FederatedPointerEvent, - ParticleContainer, - RenderTexture, - Sprite, - Texture, - Graphics, -} from "pixi.js" +import { FederatedPointerEvent, Sprite, Texture, Graphics } from "pixi.js" import { EditMode } from "../../chart/ChartManager" -import { QUANT_COLORS } from "../../chart/component/edit/SnapContainer" import { Chart } from "../../chart/sm/Chart" import { isHoldNote } from "../../chart/sm/NoteTypes" import { BetterRoundedRect } from "../../util/BetterRoundedRect" @@ -15,20 +7,11 @@ import { EventHandler } from "../../util/EventHandler" import { Flags } from "../../util/Flags" import { clamp, lerp, unlerp } from "../../util/Math" import { Options } from "../../util/Options" -import { destroyChildIf, getDivision } from "../../util/Util" import { Widget } from "./Widget" import { WidgetManager } from "./WidgetManager" export class NPSGraphWidget extends Widget { - barContainer = new ParticleContainer( - 1500, - { position: true, scale: true, tint: true }, - 16384, - true - ) backing: BetterRoundedRect = new BetterRoundedRect() - bars: Sprite - barTexture: RenderTexture overlay: Sprite = new Sprite(Texture.WHITE) npsGraph: Graphics @@ -47,12 +30,6 @@ export class NPSGraphWidget extends Widget { this.visible = false this.backing.tint = 0 this.backing.alpha = 0.3 - this.barTexture = RenderTexture.create({ - resolution: this.manager.app.renderer.resolution, - }) - this.bars = new Sprite(this.barTexture) - this.bars.anchor.set(0.5) - this.addChild(this.bars) this.npsGraph = new Graphics() this.addChild(this.npsGraph) @@ -111,9 +88,7 @@ export class NPSGraphWidget extends Widget { private handleMouse(event: FederatedPointerEvent) { if (this.manager.chartManager.getMode() == EditMode.Play) return if (!this.getChart()) return - let t = - (this.bars.toLocal(event.global).y + this.bars.height / 2) / - this.bars.height + let t = this.npsGraph.toLocal(event.global).y / this.npsGraph.height t = clamp(t, 0, 1) const lastNote = this.getChart().getNotedata().at(-1) if (!lastNote) return @@ -138,7 +113,6 @@ export class NPSGraphWidget extends Widget { this.backing.height = height + 10 this.backing.position.y = -this.backing.height / 2 this.backing.position.x = -this.backing.width / 2 - this.bars.height = height this.x = this.manager.app.renderer.screen.width / 2 - 60 const chart = this.getChart() const chartView = this.manager.chartManager.chartView! @@ -195,14 +169,8 @@ export class NPSGraphWidget extends Widget { populate() { const chart = this.getChart() if (!chart) { - destroyChildIf(this.barContainer.children, () => true) - - this.manager.app.renderer.render(this.barContainer, { - renderTexture: this.barTexture, - }) return } - const childIndex = 0 const lastNote = chart.getNotedata().at(-1) const firstNote = chart.getNotedata().at(0) @@ -212,13 +180,7 @@ export class NPSGraphWidget extends Widget { this.overlay.width = this.graphWidth + 8 this.pivot.x = this.backing.width / 2 - this.barTexture.resize(this.graphWidth, height) - if (!lastNote || !firstNote) { - destroyChildIf(this.barContainer.children, () => true) - this.manager.app.renderer.render(this.barContainer, { - renderTexture: this.barTexture, - }) return } const lastBeat = lastNote.beat + (isHoldNote(lastNote) ? lastNote.hold : 0) @@ -229,7 +191,6 @@ export class NPSGraphWidget extends Widget { const npsGraph = chart.getNPSGraph() this.npsGraph.clear() - // this.graph.lineStyle(2, 0xffffff, 1) if (this.graphGradient) { this.npsGraph.beginTextureFill({ texture: this.graphGradient }) } else { @@ -271,14 +232,6 @@ export class NPSGraphWidget extends Widget { this.npsGraph.lineTo(lastX, lastY) this.npsGraph.lineTo(0, lastY) this.npsGraph.endFill() - destroyChildIf( - this.barContainer.children, - (_, index) => index >= childIndex - ) - - this.manager.app.renderer.render(this.barContainer, { - renderTexture: this.barTexture, - }) } private getY( From 7224e082257cb195740c47140d66448fcb38835c Mon Sep 17 00:00:00 2001 From: Michael Votaw Date: Wed, 19 Jun 2024 11:18:13 -0500 Subject: [PATCH 04/10] Added a nps display that follows the mouse cursor when hovering over nps graph --- app/src/gui/widget/NPSGraphWidget.ts | 74 +++++++++++++++++++++++++++- 1 file changed, 72 insertions(+), 2 deletions(-) diff --git a/app/src/gui/widget/NPSGraphWidget.ts b/app/src/gui/widget/NPSGraphWidget.ts index 24ff8411..f5ceb49c 100644 --- a/app/src/gui/widget/NPSGraphWidget.ts +++ b/app/src/gui/widget/NPSGraphWidget.ts @@ -1,4 +1,10 @@ -import { FederatedPointerEvent, Sprite, Texture, Graphics } from "pixi.js" +import { + FederatedPointerEvent, + Sprite, + Texture, + Graphics, + BitmapText, +} from "pixi.js" import { EditMode } from "../../chart/ChartManager" import { Chart } from "../../chart/sm/Chart" import { isHoldNote } from "../../chart/sm/NoteTypes" @@ -22,6 +28,10 @@ export class NPSGraphWidget extends Widget { private graphGradient: Texture | null = null private graphWidth: number = 40 + private npsText: BitmapText = new BitmapText("npsText", { + fontName: "Main", + fontSize: 12, + }) constructor(manager: WidgetManager) { super(manager) @@ -41,6 +51,10 @@ export class NPSGraphWidget extends Widget { this.addChild(this.overlay) this.x = this.manager.app.renderer.screen.width / 2 - 20 + this.npsText.visible = false + this.npsText.anchor.x = 1 + this.addChild(this.npsText) + this.setupEventHandlers() } @@ -78,7 +92,11 @@ export class NPSGraphWidget extends Widget { this.handleMouse(event) }) this.on("mousemove", event => { - if (this.mouseDown) this.handleMouse(event) + this.handleMouse(event) + }) + this.on("mouseleave", () => { + this.mouseDown = false + this.clearNpsDisplay() }) window.onmouseup = () => { this.mouseDown = false @@ -86,6 +104,13 @@ export class NPSGraphWidget extends Widget { } private handleMouse(event: FederatedPointerEvent) { + if (this.mouseDown) { + this.handleUpdateChartPosition(event) + } + this.updateNpsDisplay(event) + } + + private handleUpdateChartPosition(event: FederatedPointerEvent) { if (this.manager.chartManager.getMode() == EditMode.Play) return if (!this.getChart()) return let t = this.npsGraph.toLocal(event.global).y / this.npsGraph.height @@ -103,6 +128,51 @@ export class NPSGraphWidget extends Widget { } } + private updateNpsDisplay(event: FederatedPointerEvent) { + const chart = this.getChart() + if (!chart) { + this.npsText.visible = false + return + } + const lastNote = chart.getNotedata().at(-1) + if (!lastNote) { + this.npsText.visible = false + return + } + const npsGraph = chart.getNPSGraph() + const lastBeat = lastNote.beat + (isHoldNote(lastNote) ? lastNote.hold : 0) + const lastSecond = chart.getSecondsFromBeat(lastBeat) + + let t = this.npsGraph.toLocal(event.global).y / this.npsGraph.height + t = clamp(t, 0, 1) + + const beat = lerp(0, lastBeat, t) + const npsIndex = Math.floor(chart.timingData.getMeasure(beat)) + + // npsGraph is a sparse array, so make sure we have a valid number, + // otherwise the nps for that measure is 0 + const nps = npsGraph[npsIndex] ?? 0 + + const ypos = + this.getY( + lastBeat, + chart.timingData.getOffset(), + lastSecond, + beat, + this.npsGraph.height + ) - + this.npsGraph.height / 2 + + this.npsText.text = nps.toFixed(1) + " nps" + this.npsText.position.y = ypos - 6 + this.npsText.position.x = -this.backing.width / 2 - 10 + this.npsText.visible = true + } + + private clearNpsDisplay() { + this.npsText.visible = false + } + update() { if (!Options.chart.npsGraph.enabled) { this.visible = false From 69783a19c13a736ab4ea3a6db02090f2da721175 Mon Sep 17 00:00:00 2001 From: tillvit Date: Tue, 25 Jun 2024 14:07:43 +0800 Subject: [PATCH 05/10] Fix y calculation --- app/src/chart/sm/Chart.ts | 29 +++++ app/src/gui/widget/NPSGraphWidget.ts | 157 ++++++++++++++------------- app/src/gui/widget/WidgetManager.ts | 3 +- 3 files changed, 110 insertions(+), 79 deletions(-) diff --git a/app/src/chart/sm/Chart.ts b/app/src/chart/sm/Chart.ts index 404d8896..804c3021 100644 --- a/app/src/chart/sm/Chart.ts +++ b/app/src/chart/sm/Chart.ts @@ -32,6 +32,9 @@ export class Chart { private _notedataStats!: Record private _npsGraph!: number[] + private _lastBeat = 0 + private _lastSecond = 0 + constructor(sm: Simfile, data?: string | { [key: string]: string }) { this.timingData = sm.timingData.createChartTimingData(this) this.timingData.reloadCache() @@ -134,6 +137,14 @@ export class Chart { return max } + getLastBeat() { + return this._lastBeat + } + + getLastSecond() { + return this._lastSecond + } + getSecondsFromBeat( beat: number, option?: "noclamp" | "before" | "after" | "" @@ -157,6 +168,23 @@ export class Chart { return this.timingData.isBeatFaked(beat) } + private recalculateLastNote() { + let lastBeat = 0 + let lastSecond = 0 + this.notedata.forEach(note => { + const endBeat = note.beat + (isHoldNote(note) ? note.hold : 0) + const endSecond = this.timingData.getSecondsFromBeat(endBeat) + if (endBeat > lastBeat) { + lastBeat = endBeat + } + if (endSecond > lastSecond) { + lastSecond = endSecond + } + }) + this._lastBeat = lastBeat + this._lastSecond = lastSecond + } + private getNoteIndex(note: PartialNotedataEntry): number { if (this.notedata.includes(note as NotedataEntry)) { return this.notedata.indexOf(note as NotedataEntry) @@ -268,6 +296,7 @@ export class Chart { this.notedata, this.timingData ) + this.recalculateLastNote() } getMusicPath(): string { diff --git a/app/src/gui/widget/NPSGraphWidget.ts b/app/src/gui/widget/NPSGraphWidget.ts index f5ceb49c..af7d986b 100644 --- a/app/src/gui/widget/NPSGraphWidget.ts +++ b/app/src/gui/widget/NPSGraphWidget.ts @@ -1,13 +1,12 @@ import { + BitmapText, FederatedPointerEvent, + Graphics, Sprite, Texture, - Graphics, - BitmapText, } from "pixi.js" import { EditMode } from "../../chart/ChartManager" import { Chart } from "../../chart/sm/Chart" -import { isHoldNote } from "../../chart/sm/NoteTypes" import { BetterRoundedRect } from "../../util/BetterRoundedRect" import { EventHandler } from "../../util/EventHandler" import { Flags } from "../../util/Flags" @@ -53,6 +52,7 @@ export class NPSGraphWidget extends Widget { this.npsText.visible = false this.npsText.anchor.x = 1 + this.npsText.anchor.y = 0.5 this.addChild(this.npsText) this.setupEventHandlers() @@ -115,16 +115,16 @@ export class NPSGraphWidget extends Widget { if (!this.getChart()) return let t = this.npsGraph.toLocal(event.global).y / this.npsGraph.height t = clamp(t, 0, 1) - const lastNote = this.getChart().getNotedata().at(-1) - if (!lastNote) return - const lastBeat = lastNote.beat + (isHoldNote(lastNote) ? lastNote.hold : 0) - const lastSecond = this.getChart().getSecondsFromBeat(lastBeat) if (Options.chart.CMod) { this.manager.chartManager.setTime( - lerp(-this.getChart().timingData.getOffset(), lastSecond, t) + lerp( + -this.getChart().timingData.getOffset(), + this.getChart().getLastSecond(), + t + ) ) } else { - this.manager.chartManager.setBeat(lastBeat * t) + this.manager.chartManager.setBeat(this.getChart().getLastBeat() * t) } } @@ -140,31 +140,28 @@ export class NPSGraphWidget extends Widget { return } const npsGraph = chart.getNPSGraph() - const lastBeat = lastNote.beat + (isHoldNote(lastNote) ? lastNote.hold : 0) - const lastSecond = chart.getSecondsFromBeat(lastBeat) let t = this.npsGraph.toLocal(event.global).y / this.npsGraph.height t = clamp(t, 0, 1) - const beat = lerp(0, lastBeat, t) + let beat = lerp(0, chart.getLastBeat(), t) + if (Options.chart.CMod) { + const second = lerp( + -chart.timingData.getOffset(), + chart.getLastSecond(), + t + ) + beat = chart.timingData.getBeatFromSeconds(second) + } + const npsIndex = Math.floor(chart.timingData.getMeasure(beat)) // npsGraph is a sparse array, so make sure we have a valid number, // otherwise the nps for that measure is 0 const nps = npsGraph[npsIndex] ?? 0 - const ypos = - this.getY( - lastBeat, - chart.timingData.getOffset(), - lastSecond, - beat, - this.npsGraph.height - ) - - this.npsGraph.height / 2 - this.npsText.text = nps.toFixed(1) + " nps" - this.npsText.position.y = ypos - 6 + this.npsText.position.y = this.getYFromBeat(beat) - this.npsGraph.height / 2 this.npsText.position.x = -this.backing.width / 2 - 10 this.npsText.visible = true } @@ -178,12 +175,7 @@ export class NPSGraphWidget extends Widget { this.visible = false return } - this.scale.y = Options.chart.reverse ? -1 : 1 - const height = this.manager.app.renderer.screen.height - 40 - this.backing.height = height + 10 - this.backing.position.y = -this.backing.height / 2 - this.backing.position.x = -this.backing.width / 2 - this.x = this.manager.app.renderer.screen.width / 2 - 60 + const chart = this.getChart() const chartView = this.manager.chartManager.chartView! if (!chart || !chartView || !Flags.layout) { @@ -191,39 +183,47 @@ export class NPSGraphWidget extends Widget { return } this.visible = true - const lastNote = chart.getNotedata().at(-1) - if (!lastNote) { + + this.scale.y = Options.chart.reverse ? -1 : 1 + const height = this.manager.app.renderer.screen.height - 40 + this.backing.height = height + 10 + this.backing.position.y = -this.backing.height / 2 + this.backing.position.x = -this.backing.width / 2 + + this.x = this.manager.app.renderer.screen.width / 2 - 60 + + if (chart.getNotedata().length == 0) { this.overlay.height = 0 return } - const lastBeat = lastNote.beat + (isHoldNote(lastNote) ? lastNote.hold : 0) - const lastSecond = chart.getSecondsFromBeat(lastBeat) - const start = Options.chart.CMod - ? chartView.getSecondFromYPos( + + let startY, endY: number + if (Options.chart.CMod) { + startY = this.getYFromSecond( + chartView.getSecondFromYPos( -this.manager.app.renderer.screen.height / 2 ) - : chartView.getBeatFromYPos( + ) + endY = this.getYFromSecond( + chartView.getSecondFromYPos(this.manager.app.renderer.screen.height / 2) + ) + } else { + startY = this.getYFromBeat( + chartView.getBeatFromYPos( -this.manager.app.renderer.screen.height / 2, true ) - const end = Options.chart.CMod - ? chartView.getSecondFromYPos(this.manager.app.renderer.screen.height / 2) - : chartView.getBeatFromYPos( + ) + endY = this.getYFromBeat( + chartView.getBeatFromYPos( this.manager.app.renderer.screen.height / 2, true ) - let t_startY = unlerp(0, lastBeat, start) - let t_endY = unlerp(0, lastBeat, end) - if (Options.chart.CMod) { - t_startY = unlerp(-chart.timingData.getOffset(), lastSecond, start) - t_endY = unlerp(-chart.timingData.getOffset(), lastSecond, end) + ) } - t_startY = clamp(t_startY, 0, 1) - t_endY = clamp(t_endY, 0, 1) - if (t_startY > t_endY) [t_startY, t_endY] = [t_endY, t_startY] - const startY = (t_startY - 0.5) * (this.backing.height - 10) - const endY = (t_endY - 0.5) * (this.backing.height - 10) - this.overlay.y = startY + + if (startY > endY) [startY, endY] = [endY, startY] + this.overlay.y = startY - this.npsGraph.height / 2 this.overlay.height = endY - startY this.overlay.height = Math.max(2, this.overlay.height) if ( @@ -241,24 +241,19 @@ export class NPSGraphWidget extends Widget { if (!chart) { return } - const lastNote = chart.getNotedata().at(-1) - const firstNote = chart.getNotedata().at(0) const height = this.manager.app.renderer.screen.height - 40 - this.backing.height = height this.backing.width = this.graphWidth + 8 this.overlay.width = this.graphWidth + 8 this.pivot.x = this.backing.width / 2 - if (!lastNote || !firstNote) { + if (chart.getNotedata().length == 0) { return } - const lastBeat = lastNote.beat + (isHoldNote(lastNote) ? lastNote.hold : 0) - const lastSecond = chart.getSecondsFromBeat(lastBeat) - const songOffset = chart.timingData.getOffset() const maxNps = chart.getMaxNPS() const npsGraph = chart.getNPSGraph() + const lastBeat = chart.getLastBeat() this.npsGraph.clear() if (this.graphGradient) { @@ -272,7 +267,7 @@ export class NPSGraphWidget extends Widget { const lastMeasure = npsGraph.length - const startY = this.getY(lastBeat, songOffset, lastSecond, 0, height) + const startY = this.getYFromBeat(0) this.npsGraph.moveTo(0, startY) for (let measureIndex = 0; measureIndex < lastMeasure; measureIndex++) { @@ -283,14 +278,8 @@ export class NPSGraphWidget extends Widget { chart.timingData.getBeatFromMeasure(measureIndex + 1) ) const x = unlerp(0, maxNps, nps) * this.graphWidth - const y = this.getY(lastBeat, songOffset, lastSecond, beat, height) - const endOfMeasureY = this.getY( - lastBeat, - songOffset, - lastSecond, - endOfMeasureBeat, - height - ) + const y = this.getYFromBeat(beat) + const endOfMeasureY = this.getYFromBeat(endOfMeasureBeat) this.npsGraph.lineTo(x, y) this.npsGraph.lineTo(x, endOfMeasureY) @@ -298,26 +287,38 @@ export class NPSGraphWidget extends Widget { const lastnps = npsGraph.at(-1)! const lastX = unlerp(0, maxNps, lastnps) * this.graphWidth - const lastY = this.getY(lastBeat, songOffset, lastSecond, lastBeat, height) + const lastY = this.getYFromBeat(lastBeat) this.npsGraph.lineTo(lastX, lastY) this.npsGraph.lineTo(0, lastY) this.npsGraph.endFill() } - private getY( - lastBeat: number, - songOffset: number, - lastSecond: number, - beat: number, - height: number - ): number { - let t = unlerp(0, lastBeat, beat) + private getYFromBeat(beat: number): number { if (Options.chart.CMod) { - const second = this.getChart().timingData.getSecondsFromBeat(beat) - t = unlerp(songOffset, lastSecond, second) + return this.getYFromSecond( + this.getChart().timingData.getSecondsFromBeat(beat) + ) + } + let t = unlerp(0, this.getChart().getLastBeat(), beat) + t = clamp(t, 0, 1) + return t * (this.backing.height - 10) + } + + private getYFromSecond(second: number): number { + if (!Options.chart.CMod) { + return this.getYFromBeat( + this.getChart().timingData.getBeatFromSeconds(second) + ) } - return t * height + let t = unlerp( + -this.getChart().timingData.getOffset(), + this.getChart().getLastSecond(), + second + ) + t = clamp(t, 0, 1) + return t * (this.backing.height - 10) } + private getChart(): Chart { return this.manager.chartManager.loadedChart! } diff --git a/app/src/gui/widget/WidgetManager.ts b/app/src/gui/widget/WidgetManager.ts index 68bb0d87..655a8dc5 100644 --- a/app/src/gui/widget/WidgetManager.ts +++ b/app/src/gui/widget/WidgetManager.ts @@ -2,12 +2,13 @@ import { Container } from "pixi.js" import { App } from "../../App" import { ChartManager } from "../../chart/ChartManager" import { DebugWidget } from "./DebugWidget" +import { NPSGraphWidget } from "./NPSGraphWidget" import { NoteLayoutWidget } from "./NoteLayoutWidget" import { PlayInfoWidget } from "./PlayInfoWidget" import { PlaybackOptionsWidget } from "./PlaybackOptionsWidget" import { StatusWidget } from "./StatusWidget" import { Widget } from "./Widget" -import { NPSGraphWidget } from "./NPSGraphWidget" + export class WidgetManager extends Container { app: App chartManager: ChartManager From 0656e5776670b5a7d34c9cb2af9bbf6dd97c960c Mon Sep 17 00:00:00 2001 From: Michael Votaw Date: Fri, 12 Jul 2024 18:08:01 -0500 Subject: [PATCH 06/10] Created BaseTimelineWidget as a base class for NPSGraphWidget and NoteLayoutWidget --- app/src/gui/widget/BaseTimelineWidget.ts | 226 +++++++++++++++++++++++ app/src/gui/widget/NPSGraphWidget.ts | 201 ++++---------------- app/src/gui/widget/NoteLayoutWidget.ts | 200 +------------------- 3 files changed, 272 insertions(+), 355 deletions(-) create mode 100644 app/src/gui/widget/BaseTimelineWidget.ts diff --git a/app/src/gui/widget/BaseTimelineWidget.ts b/app/src/gui/widget/BaseTimelineWidget.ts new file mode 100644 index 00000000..0350b917 --- /dev/null +++ b/app/src/gui/widget/BaseTimelineWidget.ts @@ -0,0 +1,226 @@ +import { Container, FederatedPointerEvent, Sprite, Texture } from "pixi.js" +import { EditMode } from "../../chart/ChartManager" +import { Chart } from "../../chart/sm/Chart" +import { BetterRoundedRect } from "../../util/BetterRoundedRect" +import { EventHandler } from "../../util/EventHandler" +import { Flags } from "../../util/Flags" +import { clamp, lerp, maxArr, minArr, unlerp } from "../../util/Math" +import { Options } from "../../util/Options" +import { getNoteEnd } from "../../util/Util" +import { Widget } from "./Widget" +import { WidgetManager } from "./WidgetManager" + +export class BaseTimelineWidget extends Widget { + backing: BetterRoundedRect = new BetterRoundedRect() + overlay: Sprite = new Sprite(Texture.WHITE) + selectionOverlay: Sprite = new Sprite(Texture.WHITE) + container: Container = new Container() + + protected lastHeight = 0 + protected lastCMod + protected mouseDown = false + protected queued = false + + protected verticalMargin = 40 + protected backingVerticalPadding = 10 + protected backingWidth = 32 + xOffset = 20 + + constructor( + manager: WidgetManager, + xOffset: number = 20, + backingWidth: number = 32 + ) { + super(manager) + this.backingWidth = backingWidth + this.xOffset = xOffset + + this.addChild(this.backing) + this.addChild(this.container) + this.visible = false + + this.backing.tint = 0 + this.backing.alpha = 0.3 + + this.overlay.anchor.x = 0.5 + this.overlay.anchor.y = 0 + this.overlay.alpha = 0.3 + + this.lastCMod = Options.chart.CMod + this.addChild(this.overlay) + + this.x = this.manager.app.renderer.screen.width / 2 - this.xOffset + + EventHandler.on("chartLoaded", () => { + this.queued = false + this.populate() + }) + EventHandler.on("chartModifiedAfter", () => { + if (!this.queued) this.populate() + this.queued = true + }) + const interval = setInterval(() => { + if (this.queued) { + this.queued = false + this.populate() + } + }, 3000) + + this.on("destroyed", () => clearInterval(interval)) + + this.eventMode = "static" + this.on("mousedown", event => { + this.mouseDown = true + this.handleMouse(event) + console.log("mousedown!") + }) + this.on("mousemove", event => { + if (this.mouseDown) this.handleMouse(event) + }) + + this.on("mouseup", () => { + this.mouseDown = false + }) + + this.on("mouseleave", () => { + this.mouseDown = false + }) + } + + protected handleMouse(event: FederatedPointerEvent) { + if (this.manager.chartManager.getMode() == EditMode.Play) return + if (!this.getChart()) return + let t = + (this.container.toLocal(event.global).y + this.container.height / 2) / + this.container.height + t = clamp(t, 0, 1) + const lastNote = this.getChart().getNotedata().at(-1) + if (!lastNote) return + if (Options.chart.CMod) { + this.manager.chartManager.setTime( + lerp( + -this.getChart().timingData.getOffset(), + this.getChart().getLastSecond(), + t + ) + ) + } else { + this.manager.chartManager.setBeat(this.getChart().getLastBeat() * t) + } + } + + update() { + this.scale.y = Options.chart.reverse ? -1 : 1 + const width = this.manager.app.renderer.screen.height - this.verticalMargin + this.backing.height = width + this.backingVerticalPadding + this.backing.position.y = -this.backing.height / 2 + this.backing.position.x = -this.backing.width / 2 + this.x = this.manager.app.renderer.screen.width / 2 - this.xOffset + const chart = this.getChart() + const chartView = this.manager.chartManager.chartView! + if (!chart || !chartView || !Flags.layout) { + this.visible = false + return + } + this.visible = true + const lastNote = chart.getNotedata().at(-1) + if (!lastNote) { + this.overlay.height = 0 + return + } + + const overlayStart = Options.chart.CMod + ? chartView.getSecondFromYPos( + -this.manager.app.renderer.screen.height / 2 + ) + : chartView.getBeatFromYPos( + -this.manager.app.renderer.screen.height / 2, + true + ) + const overlayEnd = Options.chart.CMod + ? chartView.getSecondFromYPos(this.manager.app.renderer.screen.height / 2) + : chartView.getBeatFromYPos( + this.manager.app.renderer.screen.height / 2, + true + ) + const overlayRange = this.getYFromRange(chart, overlayStart, overlayEnd) + this.overlay.y = overlayRange.startY + this.overlay.height = overlayRange.endY - overlayRange.startY + this.overlay.height = Math.max(2, this.overlay.height) + + const selection = this.manager.chartManager.selection.notes + if (selection.length < 1) { + this.selectionOverlay.visible = false + } else { + this.selectionOverlay.visible = true + let selectionStart, selectionEnd + if (Options.chart.CMod) { + selectionStart = minArr(selection.map(note => note.second)) + selectionEnd = maxArr( + selection.map(note => chart.getSecondsFromBeat(getNoteEnd(note))) + ) + } else { + selectionStart = minArr(selection.map(note => note.beat)) + selectionEnd = maxArr(selection.map(note => getNoteEnd(note))) + } + const selectionRange = this.getYFromRange( + chart, + selectionStart, + selectionEnd + ) + this.selectionOverlay.y = selectionRange.startY + this.selectionOverlay.height = selectionRange.endY - selectionRange.startY + this.selectionOverlay.height = Math.max(2, this.selectionOverlay.height) + } + + if ( + this.manager.app.renderer.screen.height != this.lastHeight || + this.lastCMod != Options.chart.CMod + ) { + this.lastCMod = Options.chart.CMod + this.lastHeight = this.manager.app.renderer.screen.height + this.updateDimensions() + this.populate() + } + } + + private getYFromRange(chart: Chart, start: number, end: number) { + const lastBeat = chart.getLastBeat() + const lastSecond = chart.getLastSecond() + let t_startY = unlerp(0, lastBeat, start) + let t_endY = unlerp(0, lastBeat, end) + if (Options.chart.CMod) { + t_startY = unlerp(-chart.timingData.getOffset(), lastSecond, start) + t_endY = unlerp(-chart.timingData.getOffset(), lastSecond, end) + } + t_startY = clamp(t_startY, 0, 1) + t_endY = clamp(t_endY, 0, 1) + if (t_startY > t_endY) [t_startY, t_endY] = [t_endY, t_startY] + const startY = (t_startY - 0.5) * this.container.height + const endY = (t_endY - 0.5) * this.container.height + return { + startY, + endY, + } + } + + updateDimensions() { + const chart = this.getChart() + if (!chart) { + return + } + + const height = this.manager.app.renderer.screen.height - this.verticalMargin + this.backing.height = height + this.backingVerticalPadding + this.backing.width = this.backingWidth + this.overlay.width = this.backingWidth + this.selectionOverlay.width = this.backingWidth + this.pivot.x = this.backing.width / 2 + } + + populate() {} + + protected getChart(): Chart { + return this.manager.chartManager.loadedChart! + } +} diff --git a/app/src/gui/widget/NPSGraphWidget.ts b/app/src/gui/widget/NPSGraphWidget.ts index af7d986b..0baf084e 100644 --- a/app/src/gui/widget/NPSGraphWidget.ts +++ b/app/src/gui/widget/NPSGraphWidget.ts @@ -1,79 +1,34 @@ -import { - BitmapText, - FederatedPointerEvent, - Graphics, - Sprite, - Texture, -} from "pixi.js" -import { EditMode } from "../../chart/ChartManager" -import { Chart } from "../../chart/sm/Chart" -import { BetterRoundedRect } from "../../util/BetterRoundedRect" +import { BitmapText, FederatedPointerEvent, Graphics, Texture } from "pixi.js" import { EventHandler } from "../../util/EventHandler" -import { Flags } from "../../util/Flags" import { clamp, lerp, unlerp } from "../../util/Math" import { Options } from "../../util/Options" -import { Widget } from "./Widget" +import { BaseTimelineWidget } from "./BaseTimelineWidget" import { WidgetManager } from "./WidgetManager" -export class NPSGraphWidget extends Widget { - backing: BetterRoundedRect = new BetterRoundedRect() - overlay: Sprite = new Sprite(Texture.WHITE) +export class NPSGraphWidget extends BaseTimelineWidget { npsGraph: Graphics - - private lastHeight = 0 - private lastCMod - private mouseDown = false - private queued = false private graphGradient: Texture | null = null - private graphWidth: number = 40 - private npsText: BitmapText = new BitmapText("npsText", { + private npsText: BitmapText = new BitmapText("", { fontName: "Main", fontSize: 12, }) constructor(manager: WidgetManager) { - super(manager) + const graphWidth = 40 + super(manager, 60, graphWidth) + this.graphWidth = graphWidth + this.graphGradient = this.makeGradient() - this.addChild(this.backing) - this.visible = false - this.backing.tint = 0 - this.backing.alpha = 0.3 this.npsGraph = new Graphics() - this.addChild(this.npsGraph) - - this.overlay.anchor.x = 0.5 - this.overlay.anchor.y = 0 - this.overlay.alpha = 0.3 - this.lastCMod = Options.chart.CMod - this.addChild(this.overlay) - this.x = this.manager.app.renderer.screen.width / 2 - 20 + this.container.addChild(this.npsGraph) this.npsText.visible = false this.npsText.anchor.x = 1 this.npsText.anchor.y = 0.5 this.addChild(this.npsText) - this.setupEventHandlers() - } - - private setupEventHandlers() { - EventHandler.on("chartLoaded", () => { - this.queued = false - this.populate() - }) - EventHandler.on("chartModifiedAfter", () => { - if (!this.queued) this.populate() - this.queued = true - }) - const interval = setInterval(() => { - if (this.queued) { - this.queued = false - this.populate() - } - }, 3000) - EventHandler.on("userOptionUpdated", optionId => { if ( optionId == "chart.npsGraph.color1" || @@ -83,49 +38,20 @@ export class NPSGraphWidget extends Widget { this.populate() } }) - this.on("destroyed", () => clearInterval(interval)) - this.populate() - this.eventMode = "static" - this.on("mousedown", event => { - this.mouseDown = true - this.handleMouse(event) - }) - this.on("mousemove", event => { - this.handleMouse(event) - }) this.on("mouseleave", () => { - this.mouseDown = false - this.clearNpsDisplay() + this.hideNpsDisplay() }) - window.onmouseup = () => { - this.mouseDown = false - } - } - private handleMouse(event: FederatedPointerEvent) { - if (this.mouseDown) { - this.handleUpdateChartPosition(event) - } - this.updateNpsDisplay(event) - } + this.on("mouseenter", () => { + this.showNpsDisplay() + }) - private handleUpdateChartPosition(event: FederatedPointerEvent) { - if (this.manager.chartManager.getMode() == EditMode.Play) return - if (!this.getChart()) return - let t = this.npsGraph.toLocal(event.global).y / this.npsGraph.height - t = clamp(t, 0, 1) - if (Options.chart.CMod) { - this.manager.chartManager.setTime( - lerp( - -this.getChart().timingData.getOffset(), - this.getChart().getLastSecond(), - t - ) - ) - } else { - this.manager.chartManager.setBeat(this.getChart().getLastBeat() * t) - } + this.on("mousemove", event => { + this.updateNpsDisplay(event) + }) + + this.populate() } private updateNpsDisplay(event: FederatedPointerEvent) { @@ -139,7 +65,7 @@ export class NPSGraphWidget extends Widget { this.npsText.visible = false return } - const npsGraph = chart.getNPSGraph() + const npsGraphData = chart.getNPSGraph() let t = this.npsGraph.toLocal(event.global).y / this.npsGraph.height t = clamp(t, 0, 1) @@ -158,7 +84,7 @@ export class NPSGraphWidget extends Widget { // npsGraph is a sparse array, so make sure we have a valid number, // otherwise the nps for that measure is 0 - const nps = npsGraph[npsIndex] ?? 0 + const nps = npsGraphData[npsIndex] ?? 0 this.npsText.text = nps.toFixed(1) + " nps" this.npsText.position.y = this.getYFromBeat(beat) - this.npsGraph.height / 2 @@ -166,74 +92,30 @@ export class NPSGraphWidget extends Widget { this.npsText.visible = true } - private clearNpsDisplay() { + private hideNpsDisplay() { this.npsText.visible = false } - update() { - if (!Options.chart.npsGraph.enabled) { - this.visible = false - return - } - + private showNpsDisplay() { const chart = this.getChart() - const chartView = this.manager.chartManager.chartView! - if (!chart || !chartView || !Flags.layout) { - this.visible = false + if (!chart) { + this.npsText.visible = false return } - this.visible = true - - this.scale.y = Options.chart.reverse ? -1 : 1 - const height = this.manager.app.renderer.screen.height - 40 - this.backing.height = height + 10 - this.backing.position.y = -this.backing.height / 2 - this.backing.position.x = -this.backing.width / 2 - - this.x = this.manager.app.renderer.screen.width / 2 - 60 - - if (chart.getNotedata().length == 0) { - this.overlay.height = 0 + const lastNote = chart.getNotedata().at(-1) + if (!lastNote) { + this.npsText.visible = false return } + this.npsText.visible = true + } - let startY, endY: number - if (Options.chart.CMod) { - startY = this.getYFromSecond( - chartView.getSecondFromYPos( - -this.manager.app.renderer.screen.height / 2 - ) - ) - endY = this.getYFromSecond( - chartView.getSecondFromYPos(this.manager.app.renderer.screen.height / 2) - ) - } else { - startY = this.getYFromBeat( - chartView.getBeatFromYPos( - -this.manager.app.renderer.screen.height / 2, - true - ) - ) - endY = this.getYFromBeat( - chartView.getBeatFromYPos( - this.manager.app.renderer.screen.height / 2, - true - ) - ) - } - - if (startY > endY) [startY, endY] = [endY, startY] - this.overlay.y = startY - this.npsGraph.height / 2 - this.overlay.height = endY - startY - this.overlay.height = Math.max(2, this.overlay.height) - if ( - this.manager.app.renderer.screen.height != this.lastHeight || - this.lastCMod != Options.chart.CMod - ) { - this.lastCMod = Options.chart.CMod - this.lastHeight = this.manager.app.renderer.screen.height - this.populate() + update() { + if (!Options.chart.npsGraph.enabled) { + this.visible = false + return } + super.update() } populate() { @@ -243,16 +125,13 @@ export class NPSGraphWidget extends Widget { } const height = this.manager.app.renderer.screen.height - 40 - this.backing.width = this.graphWidth + 8 - this.overlay.width = this.graphWidth + 8 - this.pivot.x = this.backing.width / 2 if (chart.getNotedata().length == 0) { return } const maxNps = chart.getMaxNPS() - const npsGraph = chart.getNPSGraph() + const npsGraphData = chart.getNPSGraph() const lastBeat = chart.getLastBeat() this.npsGraph.clear() @@ -265,13 +144,13 @@ export class NPSGraphWidget extends Widget { this.npsGraph.pivot.x = this.backing.width / 2 this.npsGraph.pivot.y = height / 2 - const lastMeasure = npsGraph.length + const lastMeasure = npsGraphData.length const startY = this.getYFromBeat(0) this.npsGraph.moveTo(0, startY) for (let measureIndex = 0; measureIndex < lastMeasure; measureIndex++) { - const nps = npsGraph[measureIndex] || 0 + const nps = npsGraphData[measureIndex] ?? 0 const beat = chart.timingData.getBeatFromMeasure(measureIndex) const endOfMeasureBeat = Math.min( lastBeat, @@ -285,7 +164,7 @@ export class NPSGraphWidget extends Widget { this.npsGraph.lineTo(x, endOfMeasureY) } - const lastnps = npsGraph.at(-1)! + const lastnps = npsGraphData.at(-1) ?? 0 const lastX = unlerp(0, maxNps, lastnps) * this.graphWidth const lastY = this.getYFromBeat(lastBeat) this.npsGraph.lineTo(lastX, lastY) @@ -319,10 +198,6 @@ export class NPSGraphWidget extends Widget { return t * (this.backing.height - 10) } - private getChart(): Chart { - return this.manager.chartManager.loadedChart! - } - private makeGradient() { const quality = this.graphWidth const canvas = document.createElement("canvas") diff --git a/app/src/gui/widget/NoteLayoutWidget.ts b/app/src/gui/widget/NoteLayoutWidget.ts index 928e9604..c5aa7b9d 100644 --- a/app/src/gui/widget/NoteLayoutWidget.ts +++ b/app/src/gui/widget/NoteLayoutWidget.ts @@ -1,44 +1,27 @@ -import { - FederatedPointerEvent, - ParticleContainer, - RenderTexture, - Sprite, - Texture, -} from "pixi.js" -import { EditMode } from "../../chart/ChartManager" +import { ParticleContainer, RenderTexture, Sprite, Texture } from "pixi.js" import { QUANT_COLORS } from "../../chart/component/edit/SnapContainer" -import { Chart } from "../../chart/sm/Chart" import { isHoldNote } from "../../chart/sm/NoteTypes" -import { BetterRoundedRect } from "../../util/BetterRoundedRect" -import { EventHandler } from "../../util/EventHandler" -import { Flags } from "../../util/Flags" -import { clamp, lerp, maxArr, minArr, unlerp } from "../../util/Math" +import { unlerp } from "../../util/Math" import { Options } from "../../util/Options" -import { destroyChildIf, getDivision, getNoteEnd } from "../../util/Util" -import { Widget } from "./Widget" +import { destroyChildIf, getDivision } from "../../util/Util" +import { BaseTimelineWidget } from "./BaseTimelineWidget" import { WidgetManager } from "./WidgetManager" -export class NoteLayoutWidget extends Widget { +export class NoteLayoutWidget extends BaseTimelineWidget { barContainer = new ParticleContainer( 1500, { position: true, scale: true, tint: true }, 16384, true ) - backing: BetterRoundedRect = new BetterRoundedRect() + bars: Sprite barTexture: RenderTexture - overlay: Sprite = new Sprite(Texture.WHITE) - selectionOverlay: Sprite = new Sprite(Texture.WHITE) - - private lastHeight = 0 - private lastCMod - private mouseDown = false - private queued = false constructor(manager: WidgetManager) { super(manager) this.addChild(this.backing) + this.addChild(this.container) this.visible = false this.backing.tint = 0 @@ -50,165 +33,8 @@ export class NoteLayoutWidget extends Widget { this.bars = new Sprite(this.barTexture) this.bars.anchor.set(0.5) - this.addChild(this.bars) - - this.overlay.anchor.x = 0.5 - this.overlay.anchor.y = 0 - this.overlay.alpha = 0.3 - - this.selectionOverlay.anchor.x = 0.5 - this.selectionOverlay.anchor.y = 0 - this.selectionOverlay.alpha = 0.3 - this.selectionOverlay.tint = 0x4e6fa3 - - this.lastCMod = Options.chart.CMod - this.addChild(this.overlay) - this.addChild(this.selectionOverlay) - this.x = this.manager.app.renderer.screen.width / 2 - 20 - EventHandler.on("chartLoaded", () => { - this.queued = false - this.populate() - }) - EventHandler.on("chartModifiedAfter", () => { - if (!this.queued) this.populate() - this.queued = true - }) - const interval = setInterval(() => { - if (this.queued) { - this.queued = false - this.populate() - } - }, 3000) - - this.on("destroyed", () => clearInterval(interval)) + this.container.addChild(this.bars) this.populate() - - this.eventMode = "static" - this.on("mousedown", event => { - this.mouseDown = true - this.handleMouse(event) - }) - this.on("mousemove", event => { - if (this.mouseDown) this.handleMouse(event) - }) - window.onmouseup = () => { - this.mouseDown = false - } - } - - private handleMouse(event: FederatedPointerEvent) { - if (this.manager.chartManager.getMode() == EditMode.Play) return - if (!this.getChart()) return - let t = - (this.bars.toLocal(event.global).y + this.bars.height / 2) / - this.bars.height - t = clamp(t, 0, 1) - const lastNote = this.getChart().getNotedata().at(-1) - if (!lastNote) return - const lastBeat = lastNote.beat + (isHoldNote(lastNote) ? lastNote.hold : 0) - const lastSecond = this.getChart().getSecondsFromBeat(lastBeat) - if (Options.chart.CMod) { - this.manager.chartManager.setTime( - lerp(-this.getChart().timingData.getOffset(), lastSecond, t) - ) - } else { - this.manager.chartManager.setBeat(lastBeat * t) - } - } - - update() { - this.scale.y = Options.chart.reverse ? -1 : 1 - const height = this.manager.app.renderer.screen.height - 40 - this.backing.height = height + 10 - this.backing.position.y = -this.backing.height / 2 - this.backing.position.x = -this.backing.width / 2 - this.bars.height = height - this.x = this.manager.app.renderer.screen.width / 2 - 20 - const chart = this.getChart() - const chartView = this.manager.chartManager.chartView! - if (!chart || !chartView || !Flags.layout) { - this.visible = false - return - } - this.visible = true - const lastNote = chart.getNotedata().at(-1) - if (!lastNote) { - this.overlay.height = 0 - return - } - - const overlayStart = Options.chart.CMod - ? chartView.getSecondFromYPos( - -this.manager.app.renderer.screen.height / 2 - ) - : chartView.getBeatFromYPos( - -this.manager.app.renderer.screen.height / 2, - true - ) - const overlayEnd = Options.chart.CMod - ? chartView.getSecondFromYPos(this.manager.app.renderer.screen.height / 2) - : chartView.getBeatFromYPos( - this.manager.app.renderer.screen.height / 2, - true - ) - const overlayRange = this.getYFromRange(chart, overlayStart, overlayEnd) - this.overlay.y = overlayRange.startY - this.overlay.height = overlayRange.endY - overlayRange.startY - this.overlay.height = Math.max(2, this.overlay.height) - - const selection = this.manager.chartManager.selection.notes - if (selection.length < 1) { - this.selectionOverlay.visible = false - } else { - this.selectionOverlay.visible = true - let selectionStart, selectionEnd - if (Options.chart.CMod) { - selectionStart = minArr(selection.map(note => note.second)) - selectionEnd = maxArr( - selection.map(note => chart.getSecondsFromBeat(getNoteEnd(note))) - ) - } else { - selectionStart = minArr(selection.map(note => note.beat)) - selectionEnd = maxArr(selection.map(note => getNoteEnd(note))) - } - const selectionRange = this.getYFromRange( - chart, - selectionStart, - selectionEnd - ) - this.selectionOverlay.y = selectionRange.startY - this.selectionOverlay.height = selectionRange.endY - selectionRange.startY - this.selectionOverlay.height = Math.max(2, this.selectionOverlay.height) - } - - if ( - this.manager.app.renderer.screen.height != this.lastHeight || - this.lastCMod != Options.chart.CMod - ) { - this.lastCMod = Options.chart.CMod - this.lastHeight = this.manager.app.renderer.screen.height - this.populate() - } - } - - private getYFromRange(chart: Chart, start: number, end: number) { - const lastBeat = getNoteEnd(chart.getNotedata().at(-1)!) - const lastSecond = chart.getSecondsFromBeat(lastBeat) - let t_startY = unlerp(0, lastBeat, start) - let t_endY = unlerp(0, lastBeat, end) - if (Options.chart.CMod) { - t_startY = unlerp(-chart.timingData.getOffset(), lastSecond, start) - t_endY = unlerp(-chart.timingData.getOffset(), lastSecond, end) - } - t_startY = clamp(t_startY, 0, 1) - t_endY = clamp(t_endY, 0, 1) - if (t_startY > t_endY) [t_startY, t_endY] = [t_endY, t_startY] - const startY = (t_startY - 0.5) * (this.backing.height - 10) - const endY = (t_endY - 0.5) * (this.backing.height - 10) - return { - startY, - endY, - } } populate() { @@ -226,12 +52,6 @@ export class NoteLayoutWidget extends Widget { const lastNote = chart.getNotedata().at(-1) const height = this.manager.app.renderer.screen.height - 40 - this.backing.height = height - this.backing.width = numCols * 6 + 8 - this.overlay.width = numCols * 6 + 8 - this.selectionOverlay.width = numCols * 6 + 8 - this.pivot.x = this.backing.width / 2 - this.barTexture.resize(numCols * 6, height) if (!lastNote) { @@ -296,8 +116,4 @@ export class NoteLayoutWidget extends Widget { renderTexture: this.barTexture, }) } - - private getChart(): Chart { - return this.manager.chartManager.loadedChart! - } } From d1f6f4de6845002c7381b6fa4ecaae855e16b530 Mon Sep 17 00:00:00 2001 From: tillvit Date: Sat, 13 Jul 2024 11:57:34 -0700 Subject: [PATCH 07/10] Fix colors when red is below 16 --- app/src/gui/widget/NPSGraphWidget.ts | 8 ++++++-- app/src/gui/window/UserOptionsWindow.ts | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/src/gui/widget/NPSGraphWidget.ts b/app/src/gui/widget/NPSGraphWidget.ts index 0baf084e..f41b308f 100644 --- a/app/src/gui/widget/NPSGraphWidget.ts +++ b/app/src/gui/widget/NPSGraphWidget.ts @@ -213,8 +213,12 @@ export class NPSGraphWidget extends BaseTimelineWidget { // use canvas2d API to create gradient const grd = ctx.createLinearGradient(0, 0, quality, 0) - const color1 = `#${Options.chart.npsGraph.color1.toString(16)}` - const color2 = `#${Options.chart.npsGraph.color2.toString(16)}` + const color1 = `#${Options.chart.npsGraph.color1 + .toString(16) + .padStart(6, "0")}` + const color2 = `#${Options.chart.npsGraph.color2 + .toString(16) + .padStart(6, "0")}` grd.addColorStop(0, color1) grd.addColorStop(0.8, color2) diff --git a/app/src/gui/window/UserOptionsWindow.ts b/app/src/gui/window/UserOptionsWindow.ts index 2fbcab92..e6c64252 100644 --- a/app/src/gui/window/UserOptionsWindow.ts +++ b/app/src/gui/window/UserOptionsWindow.ts @@ -5,6 +5,7 @@ import { UserOption, UserOptionGroup, } from "../../data/UserOptionsWindowData" +import { EventHandler } from "../../util/EventHandler" import { clamp, roundDigit } from "../../util/Math" import { Options } from "../../util/Options" import { parseString } from "../../util/Util" @@ -12,7 +13,6 @@ import { Icons } from "../Icons" import { Dropdown } from "../element/Dropdown" import { NumberSpinner } from "../element/NumberSpinner" import { Window } from "./Window" -import { EventHandler } from "../../util/EventHandler" export class UserOptionsWindow extends Window { app: App @@ -327,7 +327,7 @@ export class UserOptionsWindow extends Window { case "color": { const colorInput = document.createElement("input") colorInput.type = "color" - colorInput.value = "#" + optionValue.toString(16) + colorInput.value = "#" + optionValue.toString(16).padStart(6, "0") // 'change' event is fired when the user closes the color picker colorInput.onchange = () => { colorInput.blur() From 3ccbe8c60dfe5c86f9f69e3add428d1cccded03c Mon Sep 17 00:00:00 2001 From: tillvit Date: Sat, 13 Jul 2024 12:04:40 -0700 Subject: [PATCH 08/10] Make notelayout togglable, move npsgraph with different col nums --- app/src/data/UserOptionsWindowData.ts | 14 ++++++++++++++ app/src/gui/widget/NPSGraphWidget.ts | 9 +++++++++ app/src/gui/widget/NoteLayoutWidget.ts | 12 ++++++++++++ app/src/util/Options.ts | 3 +++ 4 files changed, 38 insertions(+) diff --git a/app/src/data/UserOptionsWindowData.ts b/app/src/data/UserOptionsWindowData.ts index d0120c0c..dcaec9ac 100644 --- a/app/src/data/UserOptionsWindowData.ts +++ b/app/src/data/UserOptionsWindowData.ts @@ -413,6 +413,20 @@ export const USER_OPTIONS_WINDOW_DATA: UserOption[] = [ }, ], }, + { + type: "subgroup", + label: "Note Layout", + children: [ + { + type: "item", + label: "Show Note Layout", + id: "chart.noteLayout.enabled", + input: { + type: "checkbox", + }, + }, + ], + }, { type: "subgroup", label: "NPS Graph", diff --git a/app/src/gui/widget/NPSGraphWidget.ts b/app/src/gui/widget/NPSGraphWidget.ts index f41b308f..affe31b8 100644 --- a/app/src/gui/widget/NPSGraphWidget.ts +++ b/app/src/gui/widget/NPSGraphWidget.ts @@ -115,6 +115,15 @@ export class NPSGraphWidget extends BaseTimelineWidget { this.visible = false return } + this.visible = true + const layoutWidget = this.manager.getChildByName("note-layout") as + | NPSGraphWidget + | undefined + if (layoutWidget !== undefined && layoutWidget.visible) { + this.xOffset = layoutWidget.backingWidth + 28 + } else { + this.xOffset = 20 + } super.update() } diff --git a/app/src/gui/widget/NoteLayoutWidget.ts b/app/src/gui/widget/NoteLayoutWidget.ts index c5aa7b9d..05a975a8 100644 --- a/app/src/gui/widget/NoteLayoutWidget.ts +++ b/app/src/gui/widget/NoteLayoutWidget.ts @@ -23,6 +23,7 @@ export class NoteLayoutWidget extends BaseTimelineWidget { this.addChild(this.backing) this.addChild(this.container) this.visible = false + this.name = "note-layout" this.backing.tint = 0 this.backing.alpha = 0.3 @@ -37,6 +38,15 @@ export class NoteLayoutWidget extends BaseTimelineWidget { this.populate() } + update() { + if (!Options.chart.noteLayout.enabled) { + this.visible = false + return + } + this.visible = true + super.update() + } + populate() { const chart = this.getChart() if (!chart) { @@ -53,6 +63,8 @@ export class NoteLayoutWidget extends BaseTimelineWidget { const height = this.manager.app.renderer.screen.height - 40 this.barTexture.resize(numCols * 6, height) + this.backingWidth = numCols * 6 + 8 + this.updateDimensions() if (!lastNote) { destroyChildIf(this.barContainer.children, () => true) diff --git a/app/src/util/Options.ts b/app/src/util/Options.ts index 415e8c53..60f0a99c 100644 --- a/app/src/util/Options.ts +++ b/app/src/util/Options.ts @@ -45,6 +45,9 @@ export class DefaultOptions { lineHeight: 1, speedChanges: true, }, + noteLayout: { + enabled: true, + }, npsGraph: { enabled: false, color1: 0x4aa7bc, From 4c0b7ccbbf04ff81c19a8aed9844dbf353e296fb Mon Sep 17 00:00:00 2001 From: tillvit Date: Sat, 13 Jul 2024 12:07:24 -0700 Subject: [PATCH 09/10] Fix note layout quants not working with time sigs --- app/src/gui/widget/BaseTimelineWidget.ts | 1 - app/src/gui/widget/NoteLayoutWidget.ts | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/src/gui/widget/BaseTimelineWidget.ts b/app/src/gui/widget/BaseTimelineWidget.ts index 0350b917..e32ea0a6 100644 --- a/app/src/gui/widget/BaseTimelineWidget.ts +++ b/app/src/gui/widget/BaseTimelineWidget.ts @@ -72,7 +72,6 @@ export class BaseTimelineWidget extends Widget { this.on("mousedown", event => { this.mouseDown = true this.handleMouse(event) - console.log("mousedown!") }) this.on("mousemove", event => { if (this.mouseDown) this.handleMouse(event) diff --git a/app/src/gui/widget/NoteLayoutWidget.ts b/app/src/gui/widget/NoteLayoutWidget.ts index 05a975a8..573b82dc 100644 --- a/app/src/gui/widget/NoteLayoutWidget.ts +++ b/app/src/gui/widget/NoteLayoutWidget.ts @@ -3,7 +3,7 @@ import { QUANT_COLORS } from "../../chart/component/edit/SnapContainer" import { isHoldNote } from "../../chart/sm/NoteTypes" import { unlerp } from "../../util/Math" import { Options } from "../../util/Options" -import { destroyChildIf, getDivision } from "../../util/Util" +import { destroyChildIf } from "../../util/Util" import { BaseTimelineWidget } from "./BaseTimelineWidget" import { WidgetManager } from "./WidgetManager" @@ -91,7 +91,7 @@ export class NoteLayoutWidget extends BaseTimelineWidget { let t = unlerp(0, lastBeat, note.beat) if (Options.chart.CMod) t = unlerp(songOffset, lastSecond, note.second) obj.y = t * height - obj.tint = QUANT_COLORS[getDivision(note.beat)] + obj.tint = QUANT_COLORS[note.quant] if (note.type == "Mine") obj.tint = 0x808080 childIndex++ if (isHoldNote(note)) { From d052197da1e16ff756e2ceec94ce90eaa92e7705 Mon Sep 17 00:00:00 2001 From: tillvit Date: Sat, 13 Jul 2024 12:08:53 -0700 Subject: [PATCH 10/10] Fix nps text flipping when in reverse --- app/src/gui/widget/NPSGraphWidget.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/gui/widget/NPSGraphWidget.ts b/app/src/gui/widget/NPSGraphWidget.ts index affe31b8..a95c8e1b 100644 --- a/app/src/gui/widget/NPSGraphWidget.ts +++ b/app/src/gui/widget/NPSGraphWidget.ts @@ -124,6 +124,9 @@ export class NPSGraphWidget extends BaseTimelineWidget { } else { this.xOffset = 20 } + + this.npsText.scale.y = Options.chart.reverse ? -1 : 1 + super.update() }