commit 9e364976cc2580514a47c3b3bce474db46def332
parent e13c9a77f1ce2d04002a00f6e533cf709cabc3a6
Author: Michael Camilleri <[email protected]>
Date: Fri, 15 May 2026 17:45:42 +0900
Use selection fill for remote Cursor Track
Diffstat:
2 files changed, 9 insertions(+), 77 deletions(-)
diff --git a/Crossmate/Views/CellView.swift b/Crossmate/Views/CellView.swift
@@ -9,7 +9,6 @@ struct CellView: View, Equatable {
var isRelatedToFocus: Bool = false
let specialKind: Puzzle.Special?
var remoteWordTint: Color? = nil
- var remoteTrackBorder: CellBorder? = nil
var authorTint: Color? = nil
@Environment(PlayerPreferences.self) private var preferences
@@ -24,7 +23,6 @@ struct CellView: View, Equatable {
&& lhs.isRelatedToFocus == rhs.isRelatedToFocus
&& lhs.specialKind == rhs.specialKind
&& lhs.remoteWordTint == rhs.remoteWordTint
- && lhs.remoteTrackBorder == rhs.remoteTrackBorder
&& lhs.authorTint == rhs.authorTint
}
@@ -57,10 +55,6 @@ struct CellView: View, Equatable {
Rectangle()
.strokeBorder(playerColor.highlightFill, lineWidth: 3)
}
- if let remoteTrackBorder {
- CellBorderShape(edges: remoteTrackBorder.edges)
- .stroke(remoteTrackBorder.color, lineWidth: 2)
- }
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
@@ -122,45 +116,6 @@ struct CellView: View, Equatable {
}
}
-struct CellBorder: Equatable {
- let color: Color
- let edges: CellBorderEdges
-}
-
-struct CellBorderEdges: OptionSet, Equatable {
- let rawValue: Int
-
- static let top = CellBorderEdges(rawValue: 1 << 0)
- static let trailing = CellBorderEdges(rawValue: 1 << 1)
- static let bottom = CellBorderEdges(rawValue: 1 << 2)
- static let leading = CellBorderEdges(rawValue: 1 << 3)
-}
-
-private struct CellBorderShape: Shape {
- let edges: CellBorderEdges
-
- func path(in rect: CGRect) -> Path {
- var path = Path()
- if edges.contains(.top) {
- path.move(to: CGPoint(x: rect.minX, y: rect.minY))
- path.addLine(to: CGPoint(x: rect.maxX, y: rect.minY))
- }
- if edges.contains(.trailing) {
- path.move(to: CGPoint(x: rect.maxX, y: rect.minY))
- path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
- }
- if edges.contains(.bottom) {
- path.move(to: CGPoint(x: rect.maxX, y: rect.maxY))
- path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY))
- }
- if edges.contains(.leading) {
- path.move(to: CGPoint(x: rect.minX, y: rect.maxY))
- path.addLine(to: CGPoint(x: rect.minX, y: rect.minY))
- }
- return path
- }
-}
-
/// Small dot pinned to the top-leading corner of the cell, sized as a
/// fraction of the shorter cell dimension. Marks cells that start a word.
private struct CornerDot: Shape {
diff --git a/Crossmate/Views/GridView.swift b/Crossmate/Views/GridView.swift
@@ -10,7 +10,7 @@ struct GridView: View {
var body: some View {
let width = session.puzzle.width
let height = session.puzzle.height
- let (borderByCell, tintByCell) = showsSharedAnnotations ? remoteOverlays() : ([:], [:])
+ let tintByCell: [GridPosition: Color] = showsSharedAnnotations ? remoteTrackTints() : [:]
let authorTintByID: [String: Color] = showsSharedAnnotations
? Dictionary(
uniqueKeysWithValues: roster.entries.map { ($0.authorID, $0.color.tint) }
@@ -41,7 +41,6 @@ struct GridView: View {
isRelatedToFocus: relatedCells.contains(pos),
specialKind: session.puzzle.specialKind,
remoteWordTint: tintByCell[pos],
- remoteTrackBorder: borderByCell[pos],
authorTint: square.entry.isEmpty
? nil
: square.letterAuthorID.flatMap { authorTintByID[$0] }
@@ -56,44 +55,22 @@ struct GridView: View {
}
/// Builds remote word-tint overlays from each peer's persisted cursor
- /// track. The exact cursor reticle is intentionally local-only; CloudKit
- /// presence records carry the selected answer slot, not the focused
- /// square.
- private func remoteOverlays() -> (
- border: [GridPosition: CellBorder],
- tint: [GridPosition: Color]
- ) {
- var border: [GridPosition: (Date, CellBorder)] = [:]
+ /// track. Every cell in the peer's selected answer is filled with their
+ /// selection colour so the track reads as a coloured run; the exact
+ /// focused square is intentionally local-only.
+ private func remoteTrackTints() -> [GridPosition: Color] {
var tint: [GridPosition: (Date, Color)] = [:]
for (_, sel) in roster.remoteSelections {
- let cells = session.puzzle.wordCells(
+ for cell in session.puzzle.wordCells(
atRow: sel.row, col: sel.col, direction: sel.direction
- )
- for (index, cell) in cells.enumerated() {
+ ) {
let pos = GridPosition(row: cell.row, col: cell.col)
if tint[pos].map({ $0.0 < sel.updatedAt }) ?? true {
- tint[pos] = (sel.updatedAt, sel.color.highlightFill)
- }
- var edges: CellBorderEdges
- switch sel.direction {
- case .across:
- edges = [.top, .bottom]
- if index == cells.startIndex { edges.insert(.leading) }
- if index == cells.index(before: cells.endIndex) { edges.insert(.trailing) }
- case .down:
- edges = [.leading, .trailing]
- if index == cells.startIndex { edges.insert(.top) }
- if index == cells.index(before: cells.endIndex) { edges.insert(.bottom) }
- }
- if border[pos].map({ $0.0 < sel.updatedAt }) ?? true {
- border[pos] = (
- sel.updatedAt,
- CellBorder(color: sel.color.selectionFill, edges: edges)
- )
+ tint[pos] = (sel.updatedAt, sel.color.selectionFill)
}
}
}
- return (border.mapValues { $0.1 }, tint.mapValues { $0.1 })
+ return tint.mapValues { $0.1 }
}
}