GridThumbnailView.swift (1845B)
1 import SwiftUI 2 3 /// A miniature, non-interactive rendering of a crossword grid for use in 4 /// game list rows. Drawn in a single `Canvas` pass: the whole thumbnail is 5 /// filled black, then non-block cells are drawn on top, so the gaps between 6 /// cells form the grid lines for free. 7 struct GridThumbnailView: View { 8 let width: Int 9 let height: Int 10 let cells: [GameThumbnailCell] 11 12 private let size: CGFloat = 60 13 private let spacing: CGFloat = 0.5 14 15 var body: some View { 16 Canvas(rendersAsynchronously: false) { ctx, canvasSize in 17 ctx.fill( 18 Path(CGRect(origin: .zero, size: canvasSize)), 19 with: .color(.black) 20 ) 21 22 let cols = CGFloat(width) 23 let rows = CGFloat(height) 24 let cellW = (canvasSize.width - spacing * (cols + 1)) / cols 25 let cellH = (canvasSize.height - spacing * (rows + 1)) / rows 26 let cell = min(cellW, cellH) 27 28 let gridW = cell * cols + spacing * (cols + 1) 29 let gridH = cell * rows + spacing * (rows + 1) 30 let originX = (canvasSize.width - gridW) / 2 31 let originY = (canvasSize.height - gridH) / 2 32 33 for index in cells.indices { 34 let cellValue = cells[index] 35 guard cellValue != .block else { continue } 36 let r = index / width 37 let c = index % width 38 let x = originX + spacing + CGFloat(c) * (cell + spacing) 39 let y = originY + spacing + CGFloat(r) * (cell + spacing) 40 ctx.fill( 41 Path(CGRect(x: x, y: y, width: cell, height: cell)), 42 with: .color(cellValue == .filled ? Color(.systemGray3) : .white) 43 ) 44 } 45 } 46 .frame(width: size, height: size) 47 } 48 }