Redesign UI: dark theme, collapsible object dropdowns, steppers, improved canvas rendering

This commit is contained in:
2026-10-17 20:33:43 -04:00
parent 478b037e6a
commit e314623c10
5 changed files with 899 additions and 309 deletions
+82 -18
View File
@@ -34,7 +34,27 @@ export function RoomCanvas({ state, onStateChange, mode, selectedId, onSelect }:
ctx.save();
ctx.scale(zoom, zoom);
ctx.strokeStyle = '#666';
ctx.fillStyle = '#12151b';
ctx.fillRect(0, 0, state.room.width, state.room.height);
// subtle grid
ctx.strokeStyle = 'rgba(255,255,255,0.035)';
ctx.lineWidth = 1;
const step = 25;
for (let x = 0; x <= state.room.width; x += step) {
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, state.room.height);
ctx.stroke();
}
for (let y = 0; y <= state.room.height; y += step) {
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(state.room.width, y);
ctx.stroke();
}
ctx.strokeStyle = '#2c3342';
ctx.lineWidth = 2;
ctx.strokeRect(0, 0, state.room.width, state.room.height);
@@ -42,30 +62,58 @@ export function RoomCanvas({ state, onStateChange, mode, selectedId, onSelect }:
const e = state.entrance;
const w = e.width ?? 20;
const h = e.height ?? 20;
ctx.fillStyle = '#4caf50';
const selected = selectedId === '__entrance__';
ctx.fillStyle = '#3fbf7f';
ctx.fillRect(e.x, e.y, w, h);
ctx.strokeStyle = selectedId === '__entrance__' ? '#ff5722' : '#333';
ctx.lineWidth = selectedId === '__entrance__' ? 3 : 1;
ctx.strokeStyle = selected ? '#4f8cff' : 'rgba(0, 0, 0, 0.4)';
ctx.lineWidth = selected ? 2.5 : 1;
ctx.strokeRect(e.x, e.y, w, h);
ctx.fillStyle = '#000';
ctx.font = '10px sans-serif';
ctx.fillText('ENTRANCE', e.x + 2, e.y + 12);
ctx.fillStyle = 'rgba(0, 0, 0, 0.8)';
ctx.font = '600 10px Inter, sans-serif';
ctx.textBaseline = 'middle';
if (w > 50) ctx.fillText('ENTRANCE', e.x + 6, e.y + h / 2);
// resize handle (SE)
ctx.fillStyle = selectedId === '__entrance__' ? '#ff5722' : '#666';
ctx.fillStyle = selected ? '#4f8cff' : 'rgba(0, 0, 0, 0.5)';
ctx.fillRect(e.x + w - 4, e.y + h - 4, 8, 8);
}
for (const f of state.furniture) {
ctx.fillStyle = f.color || '#9aa0a6';
const b = { x: f.shape.x, y: f.shape.y, w: f.shape.width, h: f.shape.height };
ctx.globalAlpha = f.lockedForOptimization && mode === 'optimize' ? 0.5 : 0.9;
ctx.fillRect(b.x, b.y, b.w, b.h);
ctx.strokeStyle = selectedId === f.id ? '#ff5722' : '#333';
ctx.lineWidth = selectedId === f.id ? 3 : 1;
ctx.strokeRect(b.x, b.y, b.w, b.h);
ctx.fillStyle = '#000';
ctx.font = '10px sans-serif';
ctx.fillText(f.name.substring(0, 12), b.x + 2, b.y + 12);
const r = Math.min(8, b.w / 4, b.h / 4);
const selected = selectedId === f.id;
ctx.globalAlpha = f.lockedForOptimization && mode === 'optimize' ? 0.55 : 1;
ctx.beginPath();
roundRect(ctx, b.x, b.y, b.w, b.h, r);
ctx.fillStyle = f.color || '#9aa0a6';
ctx.fill();
if (selected) {
ctx.shadowColor = '#4f8cff';
ctx.shadowBlur = 14;
ctx.strokeStyle = '#4f8cff';
ctx.lineWidth = 2.5;
ctx.stroke();
ctx.shadowBlur = 0;
} else {
ctx.strokeStyle = 'rgba(0, 0, 0, 0.35)';
ctx.lineWidth = 1;
ctx.stroke();
}
ctx.fillStyle = 'rgba(0, 0, 0, 0.72)';
ctx.font = '600 11px Inter, sans-serif';
ctx.textBaseline = 'middle';
const label = b.w > 44 ? f.name.substring(0, 14) : '';
if (label) ctx.fillText(label, b.x + 7, b.y + b.h / 2);
if (f.lockedForOptimization) {
ctx.font = '10px Inter, sans-serif';
ctx.fillStyle = 'rgba(0, 0, 0, 0.55)';
ctx.fillText('🔒', b.x + b.w - 15, b.y + 9);
}
ctx.globalAlpha = 1;
}
@@ -156,7 +204,7 @@ export function RoomCanvas({ state, onStateChange, mode, selectedId, onSelect }:
ref={canvasRef}
width={Math.max(state.room.width, 400)}
height={Math.max(state.room.height, 300)}
style={{ border: '1px solid #ccc', maxWidth: '100%', height: 'auto' }}
style={{ maxWidth: '100%', height: 'auto' }}
onMouseDown={handleMouseDown}
onMouseMove={handleMouseMove}
onMouseUp={handleMouseUp}
@@ -167,3 +215,19 @@ export function RoomCanvas({ state, onStateChange, mode, selectedId, onSelect }:
function pointInRect(p: Point, r: { x: number; y: number; w: number; h: number }): boolean {
return p.x >= r.x && p.x <= r.x + r.w && p.y >= r.y && p.y <= r.y + r.h;
}
function roundRect(
ctx: CanvasRenderingContext2D,
x: number,
y: number,
w: number,
h: number,
r: number,
): void {
ctx.moveTo(x + r, y);
ctx.arcTo(x + w, y, x + w, y + h, r);
ctx.arcTo(x + w, y + h, x, y + h, r);
ctx.arcTo(x, y + h, x, y, r);
ctx.arcTo(x, y, x + w, y, r);
ctx.closePath();
}