Redesign UI: dark theme, collapsible object dropdowns, steppers, improved canvas rendering
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user