Feature: Scrolling song name in standby mode
This commit is contained in:
parent
31debfe151
commit
767460e08c
@ -130,6 +130,18 @@ export class ProjectionScreen extends SceneFeature {
|
||||
this.isVisualizerActive = false;
|
||||
this.screens = [];
|
||||
this.colorBuffer = new Float32Array(16 * 3);
|
||||
|
||||
this.standbyState = {
|
||||
active: false,
|
||||
scrollX: 0,
|
||||
text: '',
|
||||
header: '',
|
||||
subtext: ''
|
||||
};
|
||||
this.standbyCanvas = null;
|
||||
this.standbyCtx = null;
|
||||
this.standbyTexture = null;
|
||||
|
||||
sceneFeatureManager.register(this);
|
||||
}
|
||||
|
||||
@ -232,6 +244,10 @@ export class ProjectionScreen extends SceneFeature {
|
||||
|
||||
update(deltaTime) {
|
||||
updateScreenEffect();
|
||||
|
||||
if (this.standbyState.active) {
|
||||
this.renderStandbyFrame();
|
||||
}
|
||||
|
||||
// Wobble Logic
|
||||
const time = state.clock.getElapsedTime();
|
||||
@ -337,68 +353,129 @@ export class ProjectionScreen extends SceneFeature {
|
||||
this.isVisualizerActive = false;
|
||||
turnTvScreenOff();
|
||||
}
|
||||
|
||||
activateStandby() {
|
||||
this.isVisualizerActive = false;
|
||||
|
||||
if (!this.standbyCanvas) {
|
||||
this.standbyCanvas = document.createElement('canvas');
|
||||
this.standbyCanvas.width = 1024;
|
||||
this.standbyCanvas.height = 576;
|
||||
this.standbyCtx = this.standbyCanvas.getContext('2d');
|
||||
this.standbyTexture = new THREE.CanvasTexture(this.standbyCanvas);
|
||||
this.standbyTexture.minFilter = THREE.LinearFilter;
|
||||
this.standbyTexture.magFilter = THREE.LinearFilter;
|
||||
}
|
||||
|
||||
const songTitle = (state.music && state.music.songTitle) ? state.music.songTitle.toUpperCase() : "";
|
||||
|
||||
this.standbyState = {
|
||||
active: true,
|
||||
scrollX: 0,
|
||||
text: songTitle,
|
||||
header: "PLEASE STAND BY",
|
||||
subtext: "WAITING FOR PARTY START"
|
||||
};
|
||||
|
||||
const material = new THREE.MeshBasicMaterial({
|
||||
map: this.standbyTexture,
|
||||
side: THREE.DoubleSide
|
||||
});
|
||||
|
||||
this.applyMaterialToAll(material);
|
||||
this.setAllVisible(true);
|
||||
state.screenLight.intensity = 0.5;
|
||||
|
||||
// Measure text to set initial scroll
|
||||
this.standbyCtx.font = 'bold 80px monospace';
|
||||
const textWidth = this.standbyCtx.measureText(this.standbyState.text).width;
|
||||
if (textWidth > this.standbyCanvas.width * 0.9) {
|
||||
this.standbyState.scrollX = this.standbyCanvas.width;
|
||||
}
|
||||
|
||||
this.renderStandbyFrame();
|
||||
}
|
||||
|
||||
renderStandbyFrame() {
|
||||
if (!this.standbyState.active || !this.standbyCtx) return;
|
||||
|
||||
const ctx = this.standbyCtx;
|
||||
const width = this.standbyCanvas.width;
|
||||
const height = this.standbyCanvas.height;
|
||||
|
||||
// Draw Background
|
||||
const colors = ['#ffffff', '#ffff00', '#00ffff', '#00ff00', '#ff00ff', '#ff0000', '#0000ff'];
|
||||
const barWidth = width / colors.length;
|
||||
colors.forEach((color, i) => {
|
||||
ctx.fillStyle = color;
|
||||
ctx.fillRect(i * barWidth, 0, barWidth, height);
|
||||
});
|
||||
ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';
|
||||
ctx.fillRect(0, 0, width, height);
|
||||
|
||||
// Header
|
||||
ctx.fillStyle = '#ffffff';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.font = 'bold 40px monospace';
|
||||
ctx.fillText(this.standbyState.header, width / 2, height * 0.2);
|
||||
|
||||
// Song Title
|
||||
const text = this.standbyState.text;
|
||||
ctx.font = 'bold 80px monospace';
|
||||
const textMetrics = ctx.measureText(text);
|
||||
const textWidth = textMetrics.width;
|
||||
const centerY = height * 0.5;
|
||||
|
||||
if (textWidth > width * 0.9) {
|
||||
// Scroll
|
||||
this.standbyState.scrollX -= 3; // Speed
|
||||
if (this.standbyState.scrollX < -textWidth) {
|
||||
this.standbyState.scrollX = width;
|
||||
}
|
||||
ctx.textAlign = 'left';
|
||||
ctx.fillText(text, this.standbyState.scrollX, centerY);
|
||||
} else {
|
||||
// Center
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText(text, width / 2, centerY);
|
||||
}
|
||||
|
||||
// Subtext
|
||||
ctx.font = '30px monospace';
|
||||
ctx.fillStyle = '#cccccc';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText(this.standbyState.subtext, width / 2, height * 0.8);
|
||||
|
||||
if (this.standbyTexture) this.standbyTexture.needsUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Exported Control Functions ---
|
||||
|
||||
export function showStandbyScreen() {
|
||||
if (projectionScreenInstance) projectionScreenInstance.isVisualizerActive = false;
|
||||
|
||||
let texture;
|
||||
if (projectionScreenInstance) {
|
||||
projectionScreenInstance.isVisualizerActive = false;
|
||||
projectionScreenInstance.standbyState.active = false;
|
||||
}
|
||||
|
||||
if (state.posterImage) {
|
||||
texture = new THREE.TextureLoader().load(state.posterImage);
|
||||
} else {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = 1024;
|
||||
canvas.height = 576;
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
// Draw Color Bars
|
||||
const colors = ['#ffffff', '#ffff00', '#00ffff', '#00ff00', '#ff00ff', '#ff0000', '#0000ff'];
|
||||
const barWidth = canvas.width / colors.length;
|
||||
colors.forEach((color, i) => {
|
||||
ctx.fillStyle = color;
|
||||
ctx.fillRect(i * barWidth, 0, barWidth, canvas.height);
|
||||
const texture = new THREE.TextureLoader().load(state.posterImage);
|
||||
const material = new THREE.MeshBasicMaterial({
|
||||
map: texture,
|
||||
side: THREE.DoubleSide
|
||||
});
|
||||
|
||||
// Semi-transparent overlay
|
||||
ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
// Text settings
|
||||
ctx.fillStyle = '#ffffff';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
|
||||
// Song Title
|
||||
let text = "PLEASE STAND BY";
|
||||
if (state.music && state.music.songTitle) {
|
||||
text = state.music.songTitle.toUpperCase();
|
||||
if (projectionScreenInstance) {
|
||||
projectionScreenInstance.applyMaterialToAll(material);
|
||||
projectionScreenInstance.setAllVisible(true);
|
||||
}
|
||||
|
||||
ctx.font = 'bold 60px monospace';
|
||||
if (ctx.measureText(text).width > canvas.width * 0.9) {
|
||||
ctx.font = 'bold 40px monospace';
|
||||
}
|
||||
ctx.fillText(text, canvas.width / 2, canvas.height / 2 - 20);
|
||||
|
||||
// Subtext
|
||||
ctx.font = '30px monospace';
|
||||
ctx.fillStyle = '#cccccc';
|
||||
ctx.fillText("WAITING FOR PARTY START", canvas.width / 2, canvas.height / 2 + 50);
|
||||
|
||||
texture = new THREE.CanvasTexture(canvas);
|
||||
state.screenLight.intensity = 0.5;
|
||||
return;
|
||||
}
|
||||
|
||||
const material = new THREE.MeshBasicMaterial({
|
||||
map: texture,
|
||||
side: THREE.DoubleSide
|
||||
});
|
||||
if (projectionScreenInstance) projectionScreenInstance.applyMaterialToAll(material);
|
||||
if (projectionScreenInstance) projectionScreenInstance.setAllVisible(true);
|
||||
|
||||
state.screenLight.intensity = 0.5;
|
||||
if (projectionScreenInstance) {
|
||||
projectionScreenInstance.activateStandby();
|
||||
}
|
||||
}
|
||||
|
||||
export function turnTvScreenOn() {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user