Swift 6.4 migration and exhancements #1
@@ -19,3 +19,32 @@ extension Graphics {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InlineArray needs macOS 26 on the host, so these conveniences are gated
|
||||||
|
// there while the tuple API keeps working on older systems. The device and
|
||||||
|
// Linux have no such restriction. Both representations are 16 contiguous
|
||||||
|
// bytes, so converting between them is a reinterpretation, not a copy.
|
||||||
|
@available(macOS 26, *)
|
||||||
|
extension Graphics.Pattern {
|
||||||
|
/// Creates a pattern from 8 rows of image data and 8 rows of mask.
|
||||||
|
public init(bytes: [16 of UInt8]) {
|
||||||
|
self.init(bytes: unsafeBitCast(bytes, to: Bytes.self))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates an opaque pattern from 8 rows of image data.
|
||||||
|
public init(rows: [8 of UInt8]) {
|
||||||
|
self.init(bytes: [16 of UInt8] { $0 < 8 ? rows[$0] : 0xff })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The pattern's bytes as an inline array: 8 rows of image data
|
||||||
|
/// followed by 8 rows of mask.
|
||||||
|
public var inlineBytes: [16 of UInt8] {
|
||||||
|
get { unsafeBitCast(bytes, to: [16 of UInt8].self) }
|
||||||
|
set { bytes = unsafeBitCast(newValue, to: Bytes.self) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Graphics.Pattern {
|
||||||
|
typealias Bytes = (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
|
||||||
|
UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8)
|
||||||
|
}
|
||||||
|
|||||||
@@ -244,6 +244,17 @@ public final class Sprite {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets an 8×8 stencil pattern (8 rows of image data).
|
||||||
|
@available(macOS 26, *)
|
||||||
|
public func setStencilPattern(_ rows: [8 of UInt8]) {
|
||||||
|
// The C side copies the pattern, so passing the array's storage
|
||||||
|
// directly is safe.
|
||||||
|
rows.span.withUnsafeBufferPointer { buffer in
|
||||||
|
spriteAPI.pointee.setStencilPattern.unsafelyUnwrapped(
|
||||||
|
pointer, UnsafeMutablePointer(mutating: buffer.baseAddress))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func clearStencil() {
|
public func clearStencil() {
|
||||||
retainedStencil = nil
|
retainedStencil = nil
|
||||||
spriteAPI.pointee.clearStencil.unsafelyUnwrapped(pointer)
|
spriteAPI.pointee.clearStencil.unsafelyUnwrapped(pointer)
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ enum Mock {
|
|||||||
nonisolated(unsafe) static var buttonState: (current: UInt32, pushed: UInt32, released: UInt32) = (0, 0, 0)
|
nonisolated(unsafe) static var buttonState: (current: UInt32, pushed: UInt32, released: UInt32) = (0, 0, 0)
|
||||||
/// The 16 bytes behind the last pattern `LCDColor` seen by a stub.
|
/// The 16 bytes behind the last pattern `LCDColor` seen by a stub.
|
||||||
nonisolated(unsafe) static var patternBytes: [UInt8] = []
|
nonisolated(unsafe) static var patternBytes: [UInt8] = []
|
||||||
|
/// The 8 rows last handed to `setStencilPattern`.
|
||||||
|
nonisolated(unsafe) static var stencilRows: [UInt8] = []
|
||||||
/// Caps the bytes a file read returns (0 = end of file, negative =
|
/// Caps the bytes a file read returns (0 = end of file, negative =
|
||||||
/// error); `nil` fills the whole request.
|
/// error); `nil` fills the whole request.
|
||||||
nonisolated(unsafe) static var fileReadLimit: Int32?
|
nonisolated(unsafe) static var fileReadLimit: Int32?
|
||||||
@@ -90,6 +92,7 @@ enum Mock {
|
|||||||
events = []
|
events = []
|
||||||
buttonState = (0, 0, 0)
|
buttonState = (0, 0, 0)
|
||||||
patternBytes = []
|
patternBytes = []
|
||||||
|
stencilRows = []
|
||||||
fileReadLimit = nil
|
fileReadLimit = nil
|
||||||
tilesPointer = nil
|
tilesPointer = nil
|
||||||
tilesCount = 0
|
tilesCount = 0
|
||||||
@@ -246,6 +249,9 @@ enum Mock {
|
|||||||
Mock.record("newSprite")
|
Mock.record("newSprite")
|
||||||
return Mock.fakePointer()
|
return Mock.fakePointer()
|
||||||
}
|
}
|
||||||
|
spriteAPI.pointee.setStencilPattern = { _, pattern in
|
||||||
|
Mock.stencilRows = Array(UnsafeBufferPointer(start: pattern, count: 8))
|
||||||
|
}
|
||||||
spriteAPI.pointee.freeSprite = { _ in
|
spriteAPI.pointee.freeSprite = { _ in
|
||||||
Mock.record("freeSprite")
|
Mock.record("freeSprite")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,6 +64,24 @@ struct WrapperTests {
|
|||||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff])
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test func inlineArrayPatternMatchesTheTupleOne() throws {
|
||||||
|
guard #available(macOS 26, *) else { return }
|
||||||
|
// An array literal picks the InlineArray overload; a tuple literal
|
||||||
|
// still picks the tuple one.
|
||||||
|
let inline = Graphics.Pattern(rows: [1, 2, 3, 4, 5, 6, 7, 8])
|
||||||
|
let tuple = Graphics.Pattern(rows: (1, 2, 3, 4, 5, 6, 7, 8))
|
||||||
|
#expect(withUnsafeBytes(of: inline.bytes, Array.init)
|
||||||
|
== withUnsafeBytes(of: tuple.bytes, Array.init))
|
||||||
|
|
||||||
|
Graphics.fillRect(x: 0, y: 0, width: 8, height: 8, color: .pattern(inline))
|
||||||
|
#expect(Mock.patternBytes == [1, 2, 3, 4, 5, 6, 7, 8,
|
||||||
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff])
|
||||||
|
|
||||||
|
var pattern = inline
|
||||||
|
pattern.inlineBytes[8] = 0x0f
|
||||||
|
#expect(pattern.bytes.8 == 0x0f)
|
||||||
|
}
|
||||||
|
|
||||||
@Test func drawTextSendsUTF8BytesAndLength() {
|
@Test func drawTextSendsUTF8BytesAndLength() {
|
||||||
let width = Graphics.drawText("Hëllo", x: 4, y: 6)
|
let width = Graphics.drawText("Hëllo", x: 4, y: 6)
|
||||||
#expect(Mock.events == ["drawText(Hëllo,enc:\(kUTF8Encoding.rawValue),4,6)"])
|
#expect(Mock.events == ["drawText(Hëllo,enc:\(kUTF8Encoding.rawValue),4,6)"])
|
||||||
@@ -121,6 +139,17 @@ struct WrapperTests {
|
|||||||
#expect(updated == [ObjectIdentifier(sprite)])
|
#expect(updated == [ObjectIdentifier(sprite)])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test func spriteStencilPatternOverloadsSendTheSameRows() {
|
||||||
|
let sprite = Sprite()
|
||||||
|
sprite.setStencilPattern((1, 2, 3, 4, 5, 6, 7, 8))
|
||||||
|
#expect(Mock.stencilRows == [1, 2, 3, 4, 5, 6, 7, 8])
|
||||||
|
|
||||||
|
guard #available(macOS 26, *) else { return }
|
||||||
|
Mock.stencilRows = []
|
||||||
|
sprite.setStencilPattern([1, 2, 3, 4, 5, 6, 7, 8])
|
||||||
|
#expect(Mock.stencilRows == [1, 2, 3, 4, 5, 6, 7, 8])
|
||||||
|
}
|
||||||
|
|
||||||
@Test func spriteIsFreedOnDeinitAndNotWhileReferenced() {
|
@Test func spriteIsFreedOnDeinitAndNotWhileReferenced() {
|
||||||
var sprite: Sprite? = Sprite()
|
var sprite: Sprite? = Sprite()
|
||||||
_ = sprite
|
_ = sprite
|
||||||
|
|||||||
Reference in New Issue
Block a user