crossmate

A collaborative crossword app for iOS
Log | Files | Refs | LICENSE

GridThumbnailView.swift (2201B)


      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     var size: CGFloat = 60
     12 
     13     private let spacing: CGFloat = 0.5
     14 
     15     /// Fill for cells with a letter entered. The thumbnail depicts the grid,
     16     /// which is black-and-white in every appearance, so this stays a fixed
     17     /// gray (systemGray3's light-mode value) rather than a dynamic colour —
     18     /// the dark-mode variant is nearly indistinguishable from the black
     19     /// blocks.
     20     private let filledColor = Color(white: 0.78)
     21 
     22     var body: some View {
     23         Canvas(rendersAsynchronously: false) { ctx, canvasSize in
     24             ctx.fill(
     25                 Path(CGRect(origin: .zero, size: canvasSize)),
     26                 with: .color(.black)
     27             )
     28 
     29             let cols = CGFloat(width)
     30             let rows = CGFloat(height)
     31             let cellW = (canvasSize.width  - spacing * (cols + 1)) / cols
     32             let cellH = (canvasSize.height - spacing * (rows + 1)) / rows
     33             let cell  = min(cellW, cellH)
     34 
     35             let gridW = cell * cols + spacing * (cols + 1)
     36             let gridH = cell * rows + spacing * (rows + 1)
     37             let originX = (canvasSize.width  - gridW) / 2
     38             let originY = (canvasSize.height - gridH) / 2
     39 
     40             for index in cells.indices {
     41                 let cellValue = cells[index]
     42                 guard cellValue != .block else { continue }
     43                 let r = index / width
     44                 let c = index % width
     45                 let x = originX + spacing + CGFloat(c) * (cell + spacing)
     46                 let y = originY + spacing + CGFloat(r) * (cell + spacing)
     47                 ctx.fill(
     48                     Path(CGRect(x: x, y: y, width: cell, height: cell)),
     49                     with: .color(cellValue == .filled ? filledColor : .white)
     50                 )
     51             }
     52         }
     53         .frame(width: size, height: size)
     54     }
     55 }