mirror of
https://github.com/yattee/yattee.git
synced 2026-02-20 01:39:46 +00:00
151 lines
5.3 KiB
Swift
151 lines
5.3 KiB
Swift
//
|
|
// TapZoneCalculator.swift
|
|
// Yattee
|
|
//
|
|
// Calculator for determining tap zone hit-testing and frames.
|
|
//
|
|
|
|
#if os(iOS)
|
|
import CoreGraphics
|
|
import Foundation
|
|
|
|
/// Utility for calculating tap zone positions and hit-testing.
|
|
enum TapZoneCalculator {
|
|
/// Safe margin from screen edges to avoid system gesture conflicts.
|
|
static let safeMargin: CGFloat = 25
|
|
|
|
/// Determines which zone was tapped based on the layout and tap point.
|
|
/// - Parameters:
|
|
/// - point: The tap location in the bounds coordinate system.
|
|
/// - bounds: The total gesture-recognizable area bounds.
|
|
/// - layout: The current tap zone layout.
|
|
/// - Returns: The zone position that was tapped, or nil if outside safe margins.
|
|
static func zone(
|
|
for point: CGPoint,
|
|
in bounds: CGRect,
|
|
layout: TapZoneLayout
|
|
) -> TapZonePosition? {
|
|
// Check safe margins
|
|
let safeArea = bounds.insetBy(dx: safeMargin, dy: safeMargin)
|
|
guard safeArea.contains(point) else { return nil }
|
|
|
|
// Normalize point to 0-1 range within safe area
|
|
let normalizedX = (point.x - safeArea.minX) / safeArea.width
|
|
let normalizedY = (point.y - safeArea.minY) / safeArea.height
|
|
|
|
switch layout {
|
|
case .single:
|
|
return .full
|
|
|
|
case .horizontalSplit:
|
|
return normalizedX < 0.5 ? .left : .right
|
|
|
|
case .verticalSplit:
|
|
return normalizedY < 0.5 ? .top : .bottom
|
|
|
|
case .threeColumns:
|
|
if normalizedX < 1.0 / 3.0 {
|
|
return .leftThird
|
|
} else if normalizedX < 2.0 / 3.0 {
|
|
return .center
|
|
} else {
|
|
return .rightThird
|
|
}
|
|
|
|
case .quadrants:
|
|
let isTop = normalizedY < 0.5
|
|
let isLeft = normalizedX < 0.5
|
|
|
|
if isTop {
|
|
return isLeft ? .topLeft : .topRight
|
|
} else {
|
|
return isLeft ? .bottomLeft : .bottomRight
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Returns the frame for a specific zone position within bounds.
|
|
/// - Parameters:
|
|
/// - position: The zone position.
|
|
/// - bounds: The total gesture-recognizable area bounds.
|
|
/// - layout: The current tap zone layout.
|
|
/// - Returns: The frame for the zone, or nil if position doesn't match layout.
|
|
static func frame(
|
|
for position: TapZonePosition,
|
|
in bounds: CGRect,
|
|
layout: TapZoneLayout
|
|
) -> CGRect? {
|
|
// Use safe area for calculations
|
|
let safeArea = bounds.insetBy(dx: safeMargin, dy: safeMargin)
|
|
|
|
switch layout {
|
|
case .single:
|
|
guard position == .full else { return nil }
|
|
return safeArea
|
|
|
|
case .horizontalSplit:
|
|
let halfWidth = safeArea.width / 2
|
|
switch position {
|
|
case .left:
|
|
return CGRect(x: safeArea.minX, y: safeArea.minY,
|
|
width: halfWidth, height: safeArea.height)
|
|
case .right:
|
|
return CGRect(x: safeArea.minX + halfWidth, y: safeArea.minY,
|
|
width: halfWidth, height: safeArea.height)
|
|
default:
|
|
return nil
|
|
}
|
|
|
|
case .verticalSplit:
|
|
let halfHeight = safeArea.height / 2
|
|
switch position {
|
|
case .top:
|
|
return CGRect(x: safeArea.minX, y: safeArea.minY,
|
|
width: safeArea.width, height: halfHeight)
|
|
case .bottom:
|
|
return CGRect(x: safeArea.minX, y: safeArea.minY + halfHeight,
|
|
width: safeArea.width, height: halfHeight)
|
|
default:
|
|
return nil
|
|
}
|
|
|
|
case .threeColumns:
|
|
let thirdWidth = safeArea.width / 3
|
|
switch position {
|
|
case .leftThird:
|
|
return CGRect(x: safeArea.minX, y: safeArea.minY,
|
|
width: thirdWidth, height: safeArea.height)
|
|
case .center:
|
|
return CGRect(x: safeArea.minX + thirdWidth, y: safeArea.minY,
|
|
width: thirdWidth, height: safeArea.height)
|
|
case .rightThird:
|
|
return CGRect(x: safeArea.minX + thirdWidth * 2, y: safeArea.minY,
|
|
width: thirdWidth, height: safeArea.height)
|
|
default:
|
|
return nil
|
|
}
|
|
|
|
case .quadrants:
|
|
let halfWidth = safeArea.width / 2
|
|
let halfHeight = safeArea.height / 2
|
|
switch position {
|
|
case .topLeft:
|
|
return CGRect(x: safeArea.minX, y: safeArea.minY,
|
|
width: halfWidth, height: halfHeight)
|
|
case .topRight:
|
|
return CGRect(x: safeArea.minX + halfWidth, y: safeArea.minY,
|
|
width: halfWidth, height: halfHeight)
|
|
case .bottomLeft:
|
|
return CGRect(x: safeArea.minX, y: safeArea.minY + halfHeight,
|
|
width: halfWidth, height: halfHeight)
|
|
case .bottomRight:
|
|
return CGRect(x: safeArea.minX + halfWidth, y: safeArea.minY + halfHeight,
|
|
width: halfWidth, height: halfHeight)
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|