278 lines
9.0 KiB
C++
278 lines
9.0 KiB
C++
#pragma once
|
|
|
|
#include "IDisplayDriver.h"
|
|
#include "ScreenState.h"
|
|
#include <cmath>
|
|
|
|
/// 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;
|
|
};
|