/// Grid shape and per-cell channel assignments. /// /// Cell numbering is 1..N in row-major order. A cell may be unassigned /// (channel = null), in which case it renders as empty. class GridConfig { GridConfig({ required this.rows, required this.cols, List? cellChannels, }) : cellChannels = cellChannels ?? List.filled(rows * cols, null); int rows; int cols; /// Length = rows * cols. Element is the proto channel index (1..16) or null. List cellChannels; int get cellCount => rows * cols; /// 1-based cell number → channel, or null if unassigned. int? channelForCell(int cellNumber) { if (cellNumber < 1 || cellNumber > cellCount) return null; return cellChannels[cellNumber - 1]; } /// Set the channel assigned to a 1-based cell. If [channel] is already /// assigned to a different cell, that cell is cleared (auto-swap). void assign(int cellNumber, int? channel) { if (cellNumber < 1 || cellNumber > cellCount) return; if (channel != null) { for (var i = 0; i < cellChannels.length; i++) { if (cellChannels[i] == channel) cellChannels[i] = null; } } cellChannels[cellNumber - 1] = channel; } /// Resize the grid. Channel assignments to cells beyond the new range /// are dropped. void resize(int newRows, int newCols) { final newCells = List.filled(newRows * newCols, null); final keep = newCells.length < cellChannels.length ? newCells.length : cellChannels.length; for (var i = 0; i < keep; i++) { newCells[i] = cellChannels[i]; } rows = newRows; cols = newCols; cellChannels = newCells; } /// Auto-assign: fill cells with the lowest-numbered enabled channels. void autoAssign(List enabledChannels) { cellChannels = List.filled(cellCount, null); final n = enabledChannels.length < cellCount ? enabledChannels.length : cellCount; for (var i = 0; i < n; i++) { cellChannels[i] = enabledChannels[i]; } } Map toJson() => { 'rows': rows, 'cols': cols, 'cellChannels': cellChannels, }; static GridConfig fromJson(Map j) => GridConfig( rows: j['rows'] as int, cols: j['cols'] as int, cellChannels: (j['cellChannels'] as List) .map((e) => e as int?) .toList(), ); }