#pragma once #include "IDisplayDriver.h" #include "ScreenState.h" #include /// Layout helper for dashboard tiles - computes positions based on constraints class TileLayoutHelper { public: /// Calculate tile layouts for the given display dimensions /// Returns array of TileLayout (must have capacity for DASHBOARD_TILE_COUNT) /// Returns the number of columns and rows used static int calculateLayouts( int displayW, int displayH, int headerH, int margin, TileLayout* outLayouts, int* outCols, int* outRows ) { int contentH = displayH - headerH; int tileCount = DASHBOARD_TILE_COUNT; // Determine base grid based on display aspect ratio int cols, rows; calculateGrid(tileCount, displayW, contentH, &cols, &rows); // Calculate base cell sizes int cellW = displayW / cols; int cellH = contentH / rows; // Simple first-fit: place tiles in order, respecting min sizes // For more complex layouts, tiles could specify preferred positions for(int i = 0; i < tileCount; i++) { const DashboardTile& tile = DASHBOARD_TILES[i]; int tileCols = tile.constraint.minCols; int tileRows = tile.constraint.minRows; // Find next available position int col = 0, row = 0; findNextPosition(outLayouts, i, cols, rows, &col, &row); // Ensure tile fits within grid if(col + tileCols > cols) tileCols = cols - col; if(row + tileRows > rows) tileRows = rows - row; // Calculate pixel position int x = col * cellW + margin; int y = headerH + row * cellH + margin; int w = tileCols * cellW - 2 * margin; int h = tileRows * cellH - 2 * margin; outLayouts[i] = {x, y, w, h, col, row, tileCols, tileRows}; } *outCols = cols; *outRows = rows; return tileCount; } private: /// Calculate optimal grid dimensions based on display and tile constraints static void calculateGrid(int tileCount, int displayW, int contentH, int* outCols, int* outRows) { // Calculate aspect ratio to determine preferred layout float aspectRatio = (float)displayW / contentH; // Start with simple square-ish grid int cols = (int)std::sqrt(tileCount * aspectRatio); if(cols < 1) cols = 1; if(cols > tileCount) cols = tileCount; int rows = (tileCount + cols - 1) / cols; // For wide displays (landscape), prefer more columns if(aspectRatio > 1.5f && tileCount <= 6) { cols = tileCount; rows = 1; } // For tall displays (portrait), prefer more rows else if(aspectRatio < 0.8f && tileCount <= 6) { rows = tileCount; cols = 1; } *outCols = cols; *outRows = rows; } /// Find next available grid position static void findNextPosition(const TileLayout* layouts, int count, int gridCols, int gridRows, int* outCol, int* outRow) { // Simple: find first empty cell // Could be enhanced to pack tightly based on tile sizes for(int r = 0; r < gridRows; r++) { for(int c = 0; c < gridCols; c++) { bool occupied = false; for(int i = 0; i < count; i++) { if(layouts[i].col <= c && c < layouts[i].col + layouts[i].cols && layouts[i].row <= r && r < layouts[i].row + layouts[i].rows) { occupied = true; break; } } if(!occupied) { *outCol = c; *outRow = r; return; } } } // Fallback: just use count position *outCol = count % gridCols; *outRow = count / gridCols; } }; class DisplayManager { public: DisplayManager(IDisplayDriver* drv) : _drv(drv) { } void begin() { if(_drv) _drv->begin(); } void setBacklight(bool on) { if(_drv) _drv->setBacklight(on); } void render(const ScreenState& st) { if(!_drv) return; _drv->render(st); } TouchEvent readTouch() { return _drv ? _drv->readTouch() : TouchEvent {}; } HoldState updateHold(unsigned long ms) { return _drv ? _drv->updateHold(ms) : HoldState {}; } void updateHint(int x, int y, bool active) { if(_drv) _drv->updateHint(x, y, active); } /// Show touch feedback - highlights the tile at given coordinates /// Returns true if a valid tile is being touched bool showTouchFeedback(int x, int y) { if(!_drv || _gridCols <= 0) return false; // Transform touch coordinates _drv->transformTouch(&x, &y); int headerH = 30; if(y < headerH) return false; // Calculate which cell int cellW = _drv->width() / _gridCols; int cellH = (_drv->height() - headerH) / _gridRows; int col = x / cellW; int row = (y - headerH) / cellH; if(col < 0 || col >= _gridCols || row < 0 || row >= _gridRows) return false; // Find which tile is at this position for(int i = 0; i < _tileCount; i++) { const TileLayout& lay = _layouts[i]; if(lay.col <= col && col < lay.col + lay.cols && lay.row <= row && lay.row + lay.rows > row) { // Found the tile - draw highlight via driver _drv->updateHint(lay.x, lay.y, true); // active=true means show feedback return true; } } return false; } /// Clear touch feedback void clearTouchFeedback() { if(_drv) _drv->updateHint(0, 0, false); // active=false means clear } /// Check if current position is still in same tile as touch-down bool isSameTile(int downX, int downY, int currentX, int currentY) const { if(!_drv || _gridCols <= 0 || downX < 0) return false; int dx = downX, dy = downY; int cx = currentX, cy = currentY; _drv->transformTouch(&dx, &dy); _drv->transformTouch(&cx, &cy); int headerH = 30; int cellW = _drv->width() / _gridCols; int cellH = (_drv->height() - headerH) / _gridRows; int downCol = dx / cellW; int downRow = (dy - headerH) / cellH; int curCol = cx / cellW; int curRow = (cy - headerH) / cellH; return downCol == curCol && downRow == curRow; } int width() { return _drv ? _drv->width() : 0; } int height() { return _drv ? _drv->height() : 0; } /// Handle dashboard touch - returns action for tapped tile, or NONE TileAction handleDashboardTouch(int x, int y) const { if(!_drv || _gridCols <= 0) return TileAction::NONE; // Transform touch coordinates (handles rotated touch panels) _drv->transformTouch(&x, &y); int dispW = _drv->width(); int dispH = _drv->height(); int headerH = 30; // Check if in header area if(y < headerH) return TileAction::NONE; // Calculate which tile was touched using grid int cellW = dispW / _gridCols; int cellH = (dispH - headerH) / _gridRows; int col = x / cellW; int row = (y - headerH) / cellH; // Bounds check if(col < 0 || col >= _gridCols || row < 0 || row >= _gridRows) { return TileAction::NONE; } // Find which tile occupies this cell for(int i = 0; i < _tileCount; i++) { const TileLayout& layout = _layouts[i]; if(layout.col <= col && col < layout.col + layout.cols && layout.row <= row && row < layout.row + layout.rows) { return DASHBOARD_TILES[i].action; } } return TileAction::NONE; } /// Calculate and store layouts for dashboard tiles /// Called by drivers who want to use the layout helper int calculateDashboardLayouts(int headerH = 30, int margin = 8) { if(!_drv) return 0; _tileCount = TileLayoutHelper::calculateLayouts( _drv->width(), _drv->height(), headerH, margin, _layouts, &_gridCols, &_gridRows ); return _tileCount; } /// Get calculated layout for a specific tile const TileLayout* getTileLayouts() const { return _layouts; } int getGridCols() const { return _gridCols; } int getGridRows() const { return _gridRows; } private: IDisplayDriver* _drv; TileLayout _layouts[DASHBOARD_TILE_COUNT]; int _tileCount = 0; int _gridCols = 0; int _gridRows = 0; };