diff --git a/src/systems/dashboard/dashboardManager.js b/src/systems/dashboard/dashboardManager.js index 0a6144f..dcf1d5f 100644 --- a/src/systems/dashboard/dashboardManager.js +++ b/src/systems/dashboard/dashboardManager.js @@ -101,48 +101,36 @@ export class DashboardManager { // Create container structure this.createContainerStructure(); + // Initialize Widget Registry (use provided registry or create new one) + this.registry = this.config.registry || new WidgetRegistry(); + // Initialize Grid Engine (columns calculated dynamically) this.gridEngine = new GridEngine({ rowHeight: this.config.rowHeight, gap: this.config.gap, container: this.gridContainer, + registry: this.registry, // Pass registry for maxAutoSize lookups onColumnsChange: (newCols, oldCols) => { console.log('[DashboardManager] Grid columns changed:', oldCols, '→', newCols); - // Fix widget dimensions when column count changes - // This prevents widgets from shrinking when grid switches between 2/3/4 columns + // Auto-reflow current tab to optimize for new column count const currentTab = this.tabManager.getTab(this.currentTabId); - if (currentTab) { - currentTab.widgets.forEach(widget => { - // If widget was full-width in old grid, make it full-width in new grid - if (widget.w === oldCols) { - console.log(`[DashboardManager] Adjusting full-width widget ${widget.id}: w=${widget.w} → ${newCols}`); - widget.w = newCols; - } - // If widget is wider than new grid, clamp it - else if (widget.w > newCols) { - console.log(`[DashboardManager] Clamping oversized widget ${widget.id}: w=${widget.w} → ${newCols}`); - widget.w = newCols; - } - // If widget x position is out of bounds, reset to 0 - if (widget.x >= newCols) { - console.log(`[DashboardManager] Resetting out-of-bounds widget ${widget.id}: x=${widget.x} → 0`); - widget.x = 0; - } - }); + if (currentTab && currentTab.widgets && currentTab.widgets.length > 0) { + console.log(`[DashboardManager] Auto-reflowing ${currentTab.widgets.length} widgets for ${newCols} columns`); + + // Run auto-layout to reflow and expand widgets for new grid + // This prevents overlap and optimizes space usage + this.gridEngine.autoLayout(currentTab.widgets, { preserveOrder: true }); // Save changes this.triggerAutoSave(); } - // Re-render all widgets with adjusted dimensions + // Re-render all widgets with new layout this.renderAllWidgets(); } }); - // Initialize Widget Registry (use provided registry or create new one) - this.registry = this.config.registry || new WidgetRegistry(); - // Initialize Tab Manager with dashboard data structure // Create default tab if no tabs exist if (this.dashboard.tabs.length === 0) { diff --git a/src/systems/dashboard/defaultLayout.js b/src/systems/dashboard/defaultLayout.js index adfcddd..a398d79 100644 --- a/src/systems/dashboard/defaultLayout.js +++ b/src/systems/dashboard/defaultLayout.js @@ -32,14 +32,14 @@ export function generateDefaultDashboard() { }, tabs: [ + // Tab 1: Status (User widgets only - compact and focused) { id: 'tab-status', name: 'Status', icon: '📊', order: 0, widgets: [ - // === USER CLUSTER (Top) === - // Row 0: User Info (avatar, name, level) - AT TOP + // Row 0: User Info (avatar, name, level) { id: 'widget-userinfo', type: 'userInfo', @@ -61,17 +61,17 @@ export function generateDefaultDashboard() { statBarGradient: true } }, - // Row 3: User Mood (left column) + // Row 3: User Mood { id: 'widget-usermood', type: 'userMood', x: 0, y: 3, - w: 1, + w: 2, h: 1, config: {} }, - // Row 4-5: User Attributes (full width, needs 2 columns for 3x2 grid) + // Row 4-5: User Attributes { id: 'widget-userattributes', type: 'userAttributes', @@ -80,15 +80,22 @@ export function generateDefaultDashboard() { w: 2, h: 2, config: {} - }, - - // === SCENE CLUSTER (Middle) === - // Row 6-7: Calendar (left) + Weather (right) + } + ] + }, + // Tab 2: Scene (Scene info widgets + characters) + { + id: 'tab-scene', + name: 'Scene', + icon: '🌍', + order: 1, + widgets: [ + // Row 0-1: Calendar (left) + Weather (right) { id: 'widget-calendar', type: 'calendar', x: 0, - y: 6, + y: 0, w: 1, h: 2, config: {} @@ -97,19 +104,19 @@ export function generateDefaultDashboard() { id: 'widget-weather', type: 'weather', x: 1, - y: 6, + y: 0, w: 1, h: 2, config: { compact: false } }, - // Row 8-9: Temperature (left) + Clock (right) + // Row 2-3: Temperature (left) + Clock (right) { id: 'widget-temperature', type: 'temperature', x: 0, - y: 8, + y: 2, w: 1, h: 2, config: { @@ -120,31 +127,29 @@ export function generateDefaultDashboard() { id: 'widget-clock', type: 'clock', x: 1, - y: 8, + y: 2, w: 1, h: 2, config: { format: 'digital' } }, - // Row 10-11: Location (full width) + // Row 4-5: Location (full width) { id: 'widget-location', type: 'location', x: 0, - y: 10, + y: 4, w: 2, h: 2, config: {} }, - - // === SOCIAL CLUSTER (Bottom) === - // Row 12-14: Present Characters (full width) + // Row 6-8: Present Characters (full width, will expand with auto-layout) { id: 'widget-presentchars', type: 'presentCharacters', x: 0, - y: 12, + y: 6, w: 2, h: 3, config: { @@ -154,11 +159,12 @@ export function generateDefaultDashboard() { } ] }, + // Tab 3: Inventory (Full tab for inventory system) { id: 'tab-inventory', name: 'Inventory', icon: '🎒', - order: 1, + order: 2, widgets: [ { id: 'widget-inventory', diff --git a/src/systems/dashboard/gridEngine.js b/src/systems/dashboard/gridEngine.js index 0b2beb1..e43bbcc 100644 --- a/src/systems/dashboard/gridEngine.js +++ b/src/systems/dashboard/gridEngine.js @@ -26,6 +26,9 @@ export class GridEngine { this.snapToGrid = config.snapToGrid !== false; this.container = config.container || null; + // Widget registry for accessing widget definitions (e.g., maxAutoSize) + this.registry = config.registry || null; + // Container width will be set dynamically this.containerWidth = 0; @@ -562,7 +565,92 @@ export class GridEngine { } }); - console.log(`[GridEngine] Auto-layout complete (compacted ${compactedCount} widgets)`); + console.log(`[GridEngine] Compaction complete (${compactedCount} widgets moved up)`); + + // Expansion pass: Try to expand widgets to fill available space + console.log('[GridEngine] Expanding widgets to fill available space...'); + let expandedCount = 0; + + // Sort widgets by position (top-to-bottom, left-to-right) for orderly expansion + const sortedForExpand = [...sorted].sort((a, b) => { + if (a.y !== b.y) return a.y - b.y; // Top to bottom + return a.x - b.x; // Left to right + }); + + // Helper to get widget max size from registry + const getWidgetMaxSize = (widget) => { + // Try to get widget definition from registry + if (this.registry && widget.type) { + const definition = this.registry.get(widget.type); + if (definition && definition.maxAutoSize) { + return definition.maxAutoSize; + } + } + // Default max size if not specified (flexible expansion) + return { w: this.columns, h: 10 }; + }; + + sortedForExpand.forEach(widget => { + const maxSize = getWidgetMaxSize(widget); + const originalW = widget.w; + const originalH = widget.h; + + // Try expanding height first (fills vertical gaps) + let expandedH = false; + for (let tryH = originalH + 1; tryH <= Math.min(maxSize.h, originalH + 3); tryH++) { + // Clear current position + for (let row = widget.y; row < widget.y + widget.h; row++) { + for (let col = widget.x; col < widget.x + widget.w; col++) { + occupied.delete(`${col},${row}`); + } + } + + // Check if expanded height is free + if (isFree(widget.x, widget.y, widget.w, tryH)) { + widget.h = tryH; + markOccupied(widget, widget.x, widget.y, widget.w, tryH); + expandedH = true; + expandedCount++; + console.log(`[GridEngine] Expanded ${widget.id} height: ${originalH} → ${tryH}`); + break; + } else { + // Re-mark original and try next size + markOccupied(widget, widget.x, widget.y, widget.w, widget.h); + } + } + + // Try expanding width (fills horizontal gaps) + let expandedW = false; + for (let tryW = originalW + 1; tryW <= Math.min(maxSize.w, this.columns); tryW++) { + // Clear current position + for (let row = widget.y; row < widget.y + widget.h; row++) { + for (let col = widget.x; col < widget.x + widget.w; col++) { + occupied.delete(`${col},${row}`); + } + } + + // Check if expanded width is free + if (isFree(widget.x, widget.y, tryW, widget.h)) { + widget.w = tryW; + markOccupied(widget, widget.x, widget.y, tryW, widget.h); + expandedW = true; + expandedCount++; + console.log(`[GridEngine] Expanded ${widget.id} width: ${originalW} → ${tryW}`); + break; + } else { + // Re-mark original and try next size + markOccupied(widget, widget.x, widget.y, widget.w, widget.h); + } + } + + if (!expandedH && !expandedW) { + // Widget couldn't expand - ensure it's still marked in grid + markOccupied(widget, widget.x, widget.y, widget.w, widget.h); + } + }); + + console.log(`[GridEngine] Expansion complete (${expandedCount} expansions made)`); + console.log(`[GridEngine] Auto-layout complete`); return widgets; } } diff --git a/src/systems/dashboard/widgets/infoBoxWidgets.js b/src/systems/dashboard/widgets/infoBoxWidgets.js index f4accce..f1f7068 100644 --- a/src/systems/dashboard/widgets/infoBoxWidgets.js +++ b/src/systems/dashboard/widgets/infoBoxWidgets.js @@ -194,6 +194,7 @@ export function registerCalendarWidget(registry, dependencies) { category: 'scene', minSize: { w: 1, h: 2 }, defaultSize: { w: 1, h: 2 }, + maxAutoSize: { w: 2, h: 3 }, // Max size for auto-arrange expansion requiresSchema: false, render(container, config = {}) { @@ -281,6 +282,7 @@ export function registerWeatherWidget(registry, dependencies) { description: 'Weather emoji and forecast', minSize: { w: 1, h: 2 }, defaultSize: { w: 1, h: 2 }, + maxAutoSize: { w: 2, h: 3 }, // Max size for auto-arrange expansion requiresSchema: false, render(container, config = {}) { @@ -314,6 +316,7 @@ export function registerTemperatureWidget(registry, dependencies) { description: 'Temperature display with thermometer', minSize: { w: 1, h: 2 }, defaultSize: { w: 1, h: 2 }, + maxAutoSize: { w: 2, h: 3 }, // Max size for auto-arrange expansion requiresSchema: false, render(container, config = {}) { @@ -354,6 +357,7 @@ export function registerClockWidget(registry, dependencies) { description: 'Analog clock with time display', minSize: { w: 1, h: 2 }, defaultSize: { w: 1, h: 2 }, + maxAutoSize: { w: 2, h: 3 }, // Max size for auto-arrange expansion requiresSchema: false, render(container, config = {}) { @@ -403,6 +407,7 @@ export function registerLocationWidget(registry, dependencies) { description: 'Map with location display', minSize: { w: 1, h: 2 }, defaultSize: { w: 2, h: 2 }, + maxAutoSize: { w: 3, h: 3 }, // Max size for auto-arrange expansion requiresSchema: false, render(container, config = {}) { diff --git a/src/systems/dashboard/widgets/inventoryWidget.js b/src/systems/dashboard/widgets/inventoryWidget.js index 1b66acc..dd8b869 100644 --- a/src/systems/dashboard/widgets/inventoryWidget.js +++ b/src/systems/dashboard/widgets/inventoryWidget.js @@ -65,6 +65,7 @@ export function registerInventoryWidget(registry, dependencies) { category: 'inventory', minSize: { w: 2, h: 4 }, defaultSize: { w: 2, h: 6 }, + maxAutoSize: { w: 3, h: 8 }, // Max size for auto-arrange expansion (full tab) requiresSchema: false, render(container, config = {}) { diff --git a/src/systems/dashboard/widgets/presentCharactersWidget.js b/src/systems/dashboard/widgets/presentCharactersWidget.js index 95bf2c8..15cc417 100644 --- a/src/systems/dashboard/widgets/presentCharactersWidget.js +++ b/src/systems/dashboard/widgets/presentCharactersWidget.js @@ -238,6 +238,7 @@ export function registerPresentCharactersWidget(registry, dependencies) { category: 'social', minSize: { w: 2, h: 2 }, defaultSize: { w: 2, h: 3 }, + maxAutoSize: { w: 3, h: 6 }, // Max size for auto-arrange expansion (can expand significantly) requiresSchema: false, render(container, config = {}) { diff --git a/style.css b/style.css index 0ae6f8e..7230226 100644 --- a/style.css +++ b/style.css @@ -1152,7 +1152,10 @@ body:has(.rpg-panel.rpg-position-left) #sheld { .rpg-dashboard-grid { position: relative; width: 100%; - min-height: 200px; + flex: 1; /* Fill available space in dashboard container */ + overflow-y: auto; /* Allow scrolling within grid if needed */ + overflow-x: hidden; + min-height: 0; /* Allow flex to shrink below natural size */ } /* Hide resize handles by default */