feat: added region color customization feature
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import {
|
||||
DEFAULT_AUDIO_REGION_COLOR,
|
||||
DEFAULT_MIDI_REGION_COLOR,
|
||||
} from '../constants/regionColorPalette';
|
||||
import { buildRegionSurfaceColors, resolveRegionColor } from './regionColor';
|
||||
|
||||
describe('regionColor helpers', () => {
|
||||
it('prefers the region color over the track color', () => {
|
||||
expect(resolveRegionColor('#123456', '#654321', false)).toBe('#123456');
|
||||
});
|
||||
|
||||
it('falls back to the track color when the region color is missing', () => {
|
||||
expect(resolveRegionColor(undefined, '#654321', false)).toBe('#654321');
|
||||
});
|
||||
|
||||
it('falls back to the default type color when no overrides are present', () => {
|
||||
expect(resolveRegionColor(undefined, undefined, false)).toBe(DEFAULT_MIDI_REGION_COLOR);
|
||||
expect(resolveRegionColor(undefined, undefined, true)).toBe(DEFAULT_AUDIO_REGION_COLOR);
|
||||
});
|
||||
|
||||
it('builds distinct surface colors for region chrome', () => {
|
||||
const colors = buildRegionSurfaceColors('#4CBEC2');
|
||||
|
||||
expect(colors.borderColor).not.toBe('#4CBEC2');
|
||||
expect(colors.headerColor).not.toBe('#4CBEC2');
|
||||
expect(colors.contentColor).not.toBe('#4CBEC2');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,83 @@
|
||||
import {
|
||||
DEFAULT_AUDIO_REGION_COLOR,
|
||||
DEFAULT_MIDI_REGION_COLOR,
|
||||
} from '../constants/regionColorPalette';
|
||||
|
||||
function clampChannel(value: number): number {
|
||||
return Math.max(0, Math.min(255, Math.round(value)));
|
||||
}
|
||||
|
||||
function normalizeHex(color: string): string {
|
||||
const trimmed = color.trim();
|
||||
const hex = trimmed.startsWith('#') ? trimmed.slice(1) : trimmed;
|
||||
if (hex.length === 3) {
|
||||
return `#${hex.split('').map(channel => channel + channel).join('').toUpperCase()}`;
|
||||
}
|
||||
return `#${hex.toUpperCase()}`;
|
||||
}
|
||||
|
||||
function parseHexColor(color: string): [number, number, number] | null {
|
||||
const normalized = normalizeHex(color);
|
||||
if (!/^#[0-9A-F]{6}$/.test(normalized)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
parseInt(normalized.slice(1, 3), 16),
|
||||
parseInt(normalized.slice(3, 5), 16),
|
||||
parseInt(normalized.slice(5, 7), 16),
|
||||
];
|
||||
}
|
||||
|
||||
function rgbToHex(red: number, green: number, blue: number): string {
|
||||
return `#${[red, green, blue].map(channel => clampChannel(channel).toString(16).padStart(2, '0')).join('').toUpperCase()}`;
|
||||
}
|
||||
|
||||
function mixColor(color: string, target: [number, number, number], amount: number): string {
|
||||
const rgb = parseHexColor(color);
|
||||
if (!rgb) {
|
||||
return color;
|
||||
}
|
||||
|
||||
return rgbToHex(
|
||||
rgb[0] + (target[0] - rgb[0]) * amount,
|
||||
rgb[1] + (target[1] - rgb[1]) * amount,
|
||||
rgb[2] + (target[2] - rgb[2]) * amount,
|
||||
);
|
||||
}
|
||||
|
||||
export function normalizeRegionColor(color: string | undefined): string | undefined {
|
||||
if (!color) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const normalized = normalizeHex(color);
|
||||
return /^#[0-9A-F]{6}$/.test(normalized) ? normalized : undefined;
|
||||
}
|
||||
|
||||
export function resolveRegionColor(
|
||||
regionColor: string | undefined,
|
||||
trackColor: string | undefined,
|
||||
isAudioRegion: boolean,
|
||||
): string {
|
||||
return normalizeRegionColor(regionColor)
|
||||
?? normalizeRegionColor(trackColor)
|
||||
?? (isAudioRegion ? DEFAULT_AUDIO_REGION_COLOR : DEFAULT_MIDI_REGION_COLOR);
|
||||
}
|
||||
|
||||
export function darkenRegionColor(color: string, amount: number): string {
|
||||
return mixColor(color, [0, 0, 0], amount);
|
||||
}
|
||||
|
||||
export function lightenRegionColor(color: string, amount: number): string {
|
||||
return mixColor(color, [255, 255, 255], amount);
|
||||
}
|
||||
|
||||
export function buildRegionSurfaceColors(color: string) {
|
||||
return {
|
||||
borderColor: darkenRegionColor(color, 0.35),
|
||||
headerColor: darkenRegionColor(color, 0.2),
|
||||
headerHoverColor: darkenRegionColor(color, 0.1),
|
||||
contentColor: lightenRegionColor(color, 0.06),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user