diff --git a/app/src/chart/component/edit/Waveform.ts b/app/src/chart/component/edit/Waveform.ts index cc1e5b2b..81e9d1a0 100644 --- a/app/src/chart/component/edit/Waveform.ts +++ b/app/src/chart/component/edit/Waveform.ts @@ -1,10 +1,12 @@ import { + Color, FXAAFilter, ParticleContainer, RenderTexture, Sprite, Texture, } from "pixi.js" +import { colorFallback } from "../../../util/Color" import { EventHandler } from "../../../util/EventHandler" import { clamp } from "../../../util/Math" import { Options } from "../../../util/Options" @@ -51,6 +53,9 @@ export class Waveform extends Sprite implements ChartRendererComponent { private drawDirty = true private blockCache = new Map() + private colorCache = new Color("black") + private filteredColorCache = new Color("black") + constructor(renderer: ChartRenderer) { super() this.renderer = renderer @@ -95,8 +100,6 @@ export class Waveform extends Sprite implements ChartRendererComponent { () => this.renderer.chartManager.app.renderer.screen.height, () => this.resizeWaveform() ) - this.trackVariable(() => Options.chart.waveform.opacity) - this.trackVariable(() => Options.chart.waveform.filteredOpacity) this.trackVariable(() => Options.chart.waveform.filteredColor) this.trackVariable(() => Options.chart.waveform.color) this.trackVariable(() => Options.chart.waveform.speedChanges) @@ -165,6 +168,16 @@ export class Waveform extends Sprite implements ChartRendererComponent { if (!Options.chart.waveform.enabled) return if (this.drawDirty || this.variableChanged()) { + if (this.colorCache.toHexa() != Options.chart.waveform.color) { + this.colorCache = colorFallback(Options.chart.waveform.color) + } + if ( + this.filteredColorCache.toHexa() != Options.chart.waveform.filteredColor + ) { + this.filteredColorCache = colorFallback( + Options.chart.waveform.filteredColor + ) + } this.drawDirty = false this.renderData() this.renderer.chartManager.app.renderer.render(this.lineContainer, { @@ -471,8 +484,8 @@ export class Waveform extends Sprite implements ChartRendererComponent { 16 * Options.chart.zoom line.y = y - line.tint = Options.chart.waveform.color - line.alpha = Options.chart.waveform.opacity + line.tint = this.colorCache + line.alpha = this.colorCache.alpha line.x = this.waveformTex.width / 2 + 288 * (channel + 0.5 - this.rawData.length / 2) * Options.chart.zoom @@ -482,8 +495,8 @@ export class Waveform extends Sprite implements ChartRendererComponent { this.getSample(this.filteredRawData[channel], second, "filter") * 16 * Options.chart.zoom - filteredLine.tint = Options.chart.waveform.filteredColor - filteredLine.alpha = Options.chart.waveform.filteredOpacity + filteredLine.tint = this.filteredColorCache + filteredLine.alpha = this.filteredColorCache.alpha filteredLine.y = y filteredLine.x = this.waveformTex.width / 2 + diff --git a/app/src/data/UserOptionsWindowData.ts b/app/src/data/UserOptionsWindowData.ts index cb74be10..64909242 100644 --- a/app/src/data/UserOptionsWindowData.ts +++ b/app/src/data/UserOptionsWindowData.ts @@ -1,3 +1,4 @@ +import { Color } from "pixi.js" import { App } from "../App" import { TimingWindowCollection } from "../chart/play/TimingWindowCollection" @@ -94,7 +95,7 @@ interface UserOptionCheckboxInput { interface UserOptionColorInput { type: "color" - onChange?: (app: App, value: number) => void + onChange?: (app: App, value: Color) => void } type UserOptionInput = @@ -416,17 +417,6 @@ export const USER_OPTIONS_WINDOW_DATA: UserOption[] = [ type: "color", }, }, - { - type: "item", - label: "Opacity", - id: "chart.waveform.opacity", - input: { - type: "slider", - min: 0, - max: 1, - step: 0.01, - }, - }, ], }, { @@ -448,17 +438,6 @@ export const USER_OPTIONS_WINDOW_DATA: UserOption[] = [ type: "color", }, }, - { - type: "item", - label: "Filtered opacity", - id: "chart.waveform.filteredOpacity", - input: { - type: "slider", - min: 0, - max: 1, - step: 0.01, - }, - }, ], }, { diff --git a/app/src/gui/element/ColorPicker.ts b/app/src/gui/element/ColorPicker.ts index 5428c47a..13e6ae37 100644 --- a/app/src/gui/element/ColorPicker.ts +++ b/app/src/gui/element/ColorPicker.ts @@ -1,26 +1,27 @@ import { Color } from "pixi.js" -import { colorToHsla, colorToHsva } from "../../util/Color" +import { colorFallback, colorToHsla, colorToHsva } from "../../util/Color" import { clamp } from "../../util/Math" class TransparentPreview extends HTMLDivElement { colorElement!: HTMLDivElement - static create() { - const bg = document.createElement("div") as TransparentPreview - bg.classList.add("color-picker-transparent") - Object.setPrototypeOf(bg, TransparentPreview.prototype) - const color = document.createElement("div") - color.style.width = "100%" - color.style.height = "100%" - bg.colorElement = color - bg.appendChild(color) - return bg - } set color(value: Color) { this.colorElement.style.background = value.toHexa() } } +function createTransparent() { + const bg = document.createElement("div") as TransparentPreview + bg.classList.add("color-picker-transparent") + Object.setPrototypeOf(bg, TransparentPreview.prototype) + const color = document.createElement("div") + color.style.width = "100%" + color.style.height = "100%" + bg.colorElement = color + bg.appendChild(color) + return bg +} + interface ColorFormatInputOptions { setValue: (color: Color) => string isValid: (value: string) => string | null @@ -158,10 +159,10 @@ export class ColorPicker extends TransparentPreview { onColorChange?: (color: Color) => void - static createPicker(options: ColorPickerOptions): ColorPicker { - const picker = TransparentPreview.create() as ColorPicker + static create(options: ColorPickerOptions): ColorPicker { + const picker = createTransparent() as ColorPicker Object.setPrototypeOf(picker, ColorPicker.prototype) - picker.value = new Color(options.value) + picker.value = colorFallback(options.value) picker.classList.add("color-picker") picker.formats = [] @@ -266,7 +267,10 @@ export class ColorPicker extends TransparentPreview { const [hueSlider, hueThumb] = this.createSlider({ ondrag: () => (this.hueDragging = true), offdrag: () => (this.hueDragging = false), - change: v => (this.hue = v), + change: v => { + this.hue = v + this.onColorChange?.(this._value) + }, }) this.hueThumb = hueThumb hueSlider.style.background = @@ -275,7 +279,10 @@ export class ColorPicker extends TransparentPreview { const [alphaSlider, alphaThumb] = this.createSlider({ ondrag: () => (this.alphaDragging = true), offdrag: () => (this.alphaDragging = false), - change: v => (this.alpha = v), + change: v => { + this.alpha = v + this.onColorChange?.(this._value) + }, }) this.alphaThumb = alphaThumb alphaSlider.classList.add("color-picker-transparent") @@ -431,8 +438,8 @@ export class ColorPicker extends TransparentPreview { const previewContainer = document.createElement("div") previewContainer.classList.add("color-picker-preview") - const previewNew = TransparentPreview.create() - const previewOld = TransparentPreview.create() + const previewNew = createTransparent() + const previewOld = createTransparent() previewOld.color = this._value previewNew.color = this._value previewContainer.replaceChildren(previewNew, previewOld) @@ -477,7 +484,7 @@ export class ColorPicker extends TransparentPreview { updatePopup() { if (!this.popup) return this.matrix!.style.backgroundColor = `hsl(${this._hue * 360} 100% 50%)` - this.matrixDot!.style.backgroundColor = this._value.toRgbaString() + this.matrixDot!.style.backgroundColor = this._value.toHex() if (!this.matrixDragging) { this.matrixDot!.style.left = this._sat * 200 + "px" this.matrixDot!.style.top = (1 - this._val) * 200 + "px" diff --git a/app/src/gui/widget/NPSGraphWidget.ts b/app/src/gui/widget/NPSGraphWidget.ts index b4543f94..89ab32db 100644 --- a/app/src/gui/widget/NPSGraphWidget.ts +++ b/app/src/gui/widget/NPSGraphWidget.ts @@ -1,5 +1,5 @@ import { BitmapText, FederatedPointerEvent, Graphics, Texture } from "pixi.js" -import { assignTint } from "../../util/Color" +import { assignTint, colorFallback } from "../../util/Color" import { EventHandler } from "../../util/EventHandler" import { clamp, lerp, unlerp } from "../../util/Math" import { Options } from "../../util/Options" @@ -227,14 +227,8 @@ 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) - .padStart(6, "0")}` - const color2 = `#${Options.chart.npsGraph.color2 - .toString(16) - .padStart(6, "0")}` - grd.addColorStop(0, color1) - grd.addColorStop(0.8, color2) + grd.addColorStop(0, colorFallback(Options.chart.npsGraph.color1).toHexa()) + grd.addColorStop(0.8, colorFallback(Options.chart.npsGraph.color2).toHexa()) ctx.fillStyle = grd ctx.fillRect(0, 0, quality, quality) diff --git a/app/src/gui/window/ThemeWindow.ts b/app/src/gui/window/ThemeWindow.ts index 8b73908e..497da0f4 100644 --- a/app/src/gui/window/ThemeWindow.ts +++ b/app/src/gui/window/ThemeWindow.ts @@ -111,7 +111,7 @@ export class ThemeWindow extends Window { const colorLabel = document.createElement("div") colorLabel.classList.add("theme-color-detail") - const colorPicker = ColorPicker.createPicker({ + const colorPicker = ColorPicker.create({ value: "white", width: 30, height: 30, diff --git a/app/src/gui/window/UserOptionsWindow.ts b/app/src/gui/window/UserOptionsWindow.ts index 3d98d77a..0331c42c 100644 --- a/app/src/gui/window/UserOptionsWindow.ts +++ b/app/src/gui/window/UserOptionsWindow.ts @@ -10,6 +10,7 @@ import { clamp, roundDigit } from "../../util/Math" import { Options } from "../../util/Options" import { parseString } from "../../util/Util" import { Icons } from "../Icons" +import { ColorPicker } from "../element/ColorPicker" import { Dropdown } from "../element/Dropdown" import { NumberSpinner } from "../element/NumberSpinner" import { Window } from "./Window" @@ -337,22 +338,19 @@ export class UserOptionsWindow extends Window { } case "color": { const callback = option.input.onChange - const colorInput = document.createElement("input") - colorInput.type = "color" - colorInput.value = "#" + optionValue.toString(16).padStart(6, "0") + const colorInput = ColorPicker.create({ + value: optionValue, + }) // 'change' event is fired when the user closes the color picker - colorInput.oninput = () => { - Options.applyOption([ - option.id, - parseInt(colorInput.value.slice(1), 16), - ]) + colorInput.onColorChange = c => { + Options.applyOption([option.id, c.toHexa()]) EventHandler.emit("userOptionUpdated", option.id) revert.style.display = Options.getDefaultOption(option.id) === Options.getOption(option.id) ? "none" : "block" - callback?.(this.app, parseInt(colorInput.value.slice(1), 16)) + callback?.(this.app, c) } input = colorInput } diff --git a/app/src/util/Color.ts b/app/src/util/Color.ts index 85c1d8f9..8d751744 100644 --- a/app/src/util/Color.ts +++ b/app/src/util/Color.ts @@ -149,3 +149,14 @@ export function colorToHsva(color: Color) { return [h, s, v, a] } + +export function colorFallback( + colorString: ColorSource, + fallback?: ColorSource +) { + try { + return new Color(colorString) + } catch (e) { + return new Color(fallback ?? "black") + } +} diff --git a/app/src/util/Options.ts b/app/src/util/Options.ts index 2c4effe5..4d3e482a 100644 --- a/app/src/util/Options.ts +++ b/app/src/util/Options.ts @@ -45,11 +45,9 @@ export class DefaultOptions { waveform: { enabled: true, antialiasing: true, - color: 0x606172, - opacity: 0.5, + color: "#60617288", allowFilter: true, - filteredColor: 0x1e523e, - filteredOpacity: 0.5, + filteredColor: "#1e523e88", lineHeight: 1, speedChanges: true, }, @@ -58,8 +56,8 @@ export class DefaultOptions { }, npsGraph: { enabled: false, - color1: 0x4aa7bc, - color2: 0x423c7a, + color1: "#4aa7bcff", + color2: "#423c7aff", }, timingEventOrder: { left: [