Restructured the source code in the Playdate bindings target.
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
internal import CPlaydate
|
||||
|
||||
extension Graphics {
|
||||
/// An image that can be drawn to the screen or used as a drawing target.
|
||||
/// Wraps `LCDBitmap`.
|
||||
public final class Bitmap {
|
||||
let pointer: OpaquePointer
|
||||
/// Whether this wrapper owns the underlying `LCDBitmap` and frees it
|
||||
/// on deinit. Bitmaps vended by tables or the system are not owned;
|
||||
/// keep their owner alive while using them.
|
||||
let isOwned: Bool
|
||||
|
||||
init(pointer: OpaquePointer, isOwned: Bool) {
|
||||
self.pointer = pointer
|
||||
self.isOwned = isOwned
|
||||
}
|
||||
|
||||
/// Allocates a new bitmap filled with `backgroundColor`.
|
||||
public convenience init(width: Int, height: Int, backgroundColor: Color = .clear) {
|
||||
let pointer = backgroundColor.withLCDColor {
|
||||
gfx.pointee.newBitmap.unsafelyUnwrapped(Int32(width), Int32(height), $0)
|
||||
}
|
||||
self.init(pointer: pointer.unsafelyUnwrapped, isOwned: true)
|
||||
}
|
||||
|
||||
/// Loads a bitmap from a file in the game's pdx or Data directory.
|
||||
public convenience init(path: String) throws(PlaydateError) {
|
||||
var error: UnsafePointer<CChar>?
|
||||
let pointer = path.withPlaydateCString { gfx.pointee.loadBitmap.unsafelyUnwrapped($0, &error) }
|
||||
guard let pointer else { throw PlaydateError(cString: error) }
|
||||
self.init(pointer: pointer, isOwned: true)
|
||||
}
|
||||
|
||||
deinit {
|
||||
if isOwned {
|
||||
gfx.pointee.freeBitmap.unsafelyUnwrapped(pointer)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Properties
|
||||
|
||||
public var data: Data {
|
||||
var width: Int32 = 0, height: Int32 = 0, rowBytes: Int32 = 0
|
||||
var mask: UnsafeMutablePointer<UInt8>?
|
||||
var data: UnsafeMutablePointer<UInt8>?
|
||||
gfx.pointee.getBitmapData.unsafelyUnwrapped(pointer, &width, &height, &rowBytes, &mask, &data)
|
||||
return Data(width: Int(width), height: Int(height), rowBytes: Int(rowBytes),
|
||||
mask: mask, data: data)
|
||||
}
|
||||
|
||||
/// Cached dimensions, so `width`/`height` don't pay a full
|
||||
/// `getBitmapData` round-trip per access. Only `load(path:)` can
|
||||
/// change a bitmap's size, which resets the cache.
|
||||
private var cachedSize: (width: Int, height: Int)?
|
||||
|
||||
private var size: (width: Int, height: Int) {
|
||||
if let cachedSize { return cachedSize }
|
||||
let data = self.data
|
||||
let size = (data.width, data.height)
|
||||
cachedSize = size
|
||||
return size
|
||||
}
|
||||
|
||||
public var width: Int { size.width }
|
||||
public var height: Int { size.height }
|
||||
|
||||
/// The color of the pixel at (x, y).
|
||||
public func pixel(x: Int, y: Int) -> SolidColor {
|
||||
SolidColor(gfx.pointee.getBitmapPixel.unsafelyUnwrapped(pointer, Int32(x), Int32(y)))
|
||||
}
|
||||
|
||||
// MARK: Operations
|
||||
|
||||
/// Replaces the bitmap's contents with the image at `path`.
|
||||
public func load(path: String) throws(PlaydateError) {
|
||||
var error: UnsafePointer<CChar>?
|
||||
path.withPlaydateCString { gfx.pointee.loadIntoBitmap.unsafelyUnwrapped($0, pointer, &error) }
|
||||
cachedSize = nil
|
||||
if let error { throw PlaydateError(cString: error) }
|
||||
}
|
||||
|
||||
/// Fills the bitmap with `color`.
|
||||
public func clear(color: Color) {
|
||||
color.withLCDColor { gfx.pointee.clearBitmap.unsafelyUnwrapped(pointer, $0) }
|
||||
}
|
||||
|
||||
public func copy() -> Bitmap {
|
||||
Bitmap(pointer: gfx.pointee.copyBitmap.unsafelyUnwrapped(pointer).unsafelyUnwrapped, isOwned: true)
|
||||
}
|
||||
|
||||
/// Returns a new bitmap rotated by `degrees` (clockwise) and scaled.
|
||||
public func rotated(by degrees: Float, xScale: Float = 1, yScale: Float = 1) -> Bitmap? {
|
||||
var allocatedSize: Int32 = 0
|
||||
guard let rotated = gfx.pointee.rotatedBitmap.unsafelyUnwrapped(
|
||||
pointer, degrees, xScale, yScale, &allocatedSize) else { return nil }
|
||||
return Bitmap(pointer: rotated, isOwned: true)
|
||||
}
|
||||
|
||||
/// Sets a mask image. The mask must match the bitmap's dimensions.
|
||||
@discardableResult
|
||||
public func setMask(_ mask: Bitmap?) -> Bool {
|
||||
gfx.pointee.setBitmapMask.unsafelyUnwrapped(pointer, mask?.pointer) != 0
|
||||
}
|
||||
|
||||
/// The bitmap's mask, if any. The returned bitmap references storage
|
||||
/// owned by this bitmap.
|
||||
public var mask: Bitmap? {
|
||||
guard let mask = gfx.pointee.getBitmapMask.unsafelyUnwrapped(pointer) else { return nil }
|
||||
return Bitmap(pointer: mask, isOwned: false)
|
||||
}
|
||||
|
||||
/// Tests whether the opaque pixels of two bitmaps overlap within
|
||||
/// `rect`, given each bitmap's position and flip.
|
||||
public func checkMaskCollision(x: Int, y: Int, flip: BitmapFlip = .unflipped,
|
||||
other: Bitmap, otherX: Int, otherY: Int,
|
||||
otherFlip: BitmapFlip = .unflipped,
|
||||
in rect: Rect) -> Bool {
|
||||
gfx.pointee.checkMaskCollision.unsafelyUnwrapped(
|
||||
pointer, Int32(x), Int32(y), flip.cValue,
|
||||
other.pointer, Int32(otherX), Int32(otherY), otherFlip.cValue,
|
||||
rect.cValue) != 0
|
||||
}
|
||||
|
||||
// MARK: Drawing
|
||||
|
||||
/// Draws the bitmap with its upper-left corner at (x, y).
|
||||
public func draw(x: Int, y: Int, flip: BitmapFlip = .unflipped) {
|
||||
gfx.pointee.drawBitmap.unsafelyUnwrapped(pointer, Int32(x), Int32(y), flip.cValue)
|
||||
}
|
||||
|
||||
/// Draws the bitmap scaled by (xScale, yScale) with its upper-left
|
||||
/// corner at (x, y).
|
||||
public func drawScaled(x: Int, y: Int, xScale: Float, yScale: Float) {
|
||||
gfx.pointee.drawScaledBitmap.unsafelyUnwrapped(pointer, Int32(x), Int32(y), xScale, yScale)
|
||||
}
|
||||
|
||||
/// Draws the bitmap rotated by `degrees` around its anchor point,
|
||||
/// where (0.5, 0.5) is the center.
|
||||
public func drawRotated(x: Int, y: Int, degrees: Float,
|
||||
centerX: Float = 0.5, centerY: Float = 0.5,
|
||||
xScale: Float = 1, yScale: Float = 1) {
|
||||
gfx.pointee.drawRotatedBitmap.unsafelyUnwrapped(pointer, Int32(x), Int32(y), degrees,
|
||||
centerX, centerY, xScale, yScale)
|
||||
}
|
||||
|
||||
/// Tiles the bitmap over the given area.
|
||||
public func tile(x: Int, y: Int, width: Int, height: Int, flip: BitmapFlip = .unflipped) {
|
||||
gfx.pointee.tileBitmap.unsafelyUnwrapped(pointer, Int32(x), Int32(y),
|
||||
Int32(width), Int32(height), flip.cValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
internal import CPlaydate
|
||||
|
||||
extension Graphics {
|
||||
/// A collection of bitmaps loaded from an image table. Wraps `LCDBitmapTable`.
|
||||
public final class BitmapTable {
|
||||
let pointer: OpaquePointer
|
||||
|
||||
init(pointer: OpaquePointer) {
|
||||
self.pointer = pointer
|
||||
}
|
||||
|
||||
/// Allocates a table with room for `count` bitmaps of the given size.
|
||||
public convenience init(count: Int, width: Int, height: Int) {
|
||||
let pointer = gfx.pointee.newBitmapTable.unsafelyUnwrapped(Int32(count), Int32(width), Int32(height))
|
||||
self.init(pointer: pointer.unsafelyUnwrapped)
|
||||
}
|
||||
|
||||
/// Loads an image table from a file.
|
||||
public convenience init(path: String) throws(PlaydateError) {
|
||||
var error: UnsafePointer<CChar>?
|
||||
let pointer = path.withPlaydateCString { gfx.pointee.loadBitmapTable.unsafelyUnwrapped($0, &error) }
|
||||
guard let pointer else { throw PlaydateError(cString: error) }
|
||||
self.init(pointer: pointer)
|
||||
}
|
||||
|
||||
deinit {
|
||||
gfx.pointee.freeBitmapTable.unsafelyUnwrapped(pointer)
|
||||
}
|
||||
|
||||
/// Replaces the table's contents with the image table at `path`.
|
||||
public func load(path: String) throws(PlaydateError) {
|
||||
var error: UnsafePointer<CChar>?
|
||||
path.withPlaydateCString { gfx.pointee.loadIntoBitmapTable.unsafelyUnwrapped($0, pointer, &error) }
|
||||
if let error { throw PlaydateError(cString: error) }
|
||||
}
|
||||
|
||||
/// The bitmap at `index`, or `nil` if out of range. The bitmap
|
||||
/// references storage owned by the table; keep the table alive while
|
||||
/// using it.
|
||||
public func bitmap(at index: Int) -> Bitmap? {
|
||||
guard let bitmap = gfx.pointee.getTableBitmap.unsafelyUnwrapped(pointer, Int32(index)) else {
|
||||
return nil
|
||||
}
|
||||
return Bitmap(pointer: bitmap, isOwned: false)
|
||||
}
|
||||
|
||||
/// The number of bitmaps in the table and the number of cells per row
|
||||
/// of the source image.
|
||||
public var info: (count: Int, cellsWide: Int) {
|
||||
var count: Int32 = 0, width: Int32 = 0
|
||||
gfx.pointee.getBitmapTableInfo.unsafelyUnwrapped(pointer, &count, &width)
|
||||
return (Int(count), Int(width))
|
||||
}
|
||||
|
||||
public var count: Int { info.count }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
internal import CPlaydate
|
||||
|
||||
extension Graphics {
|
||||
/// A font loaded from a .pft file. Wraps `LCDFont`.
|
||||
public final class Font {
|
||||
let pointer: OpaquePointer
|
||||
/// Fonts created from in-memory data reference that data; it is kept
|
||||
/// alive here.
|
||||
private let retainedData: UnsafeRawPointer?
|
||||
|
||||
init(pointer: OpaquePointer, retainedData: UnsafeRawPointer? = nil) {
|
||||
self.pointer = pointer
|
||||
self.retainedData = retainedData
|
||||
}
|
||||
|
||||
/// Loads a font from a file.
|
||||
public convenience init(path: String) throws(PlaydateError) {
|
||||
var error: UnsafePointer<CChar>?
|
||||
let pointer = path.withPlaydateCString { gfx.pointee.loadFont.unsafelyUnwrapped($0, &error) }
|
||||
guard let pointer else { throw PlaydateError(cString: error) }
|
||||
self.init(pointer: pointer)
|
||||
}
|
||||
|
||||
/// Creates a font from the contents of a .pft file already in memory.
|
||||
/// The bytes are copied and retained for the font's lifetime.
|
||||
public convenience init?(data: UnsafeRawBufferPointer, wide: Bool = false) {
|
||||
let copy = UnsafeMutableRawPointer.allocate(byteCount: data.count, alignment: 4)
|
||||
copy.copyMemory(from: data.baseAddress.unsafelyUnwrapped, byteCount: data.count)
|
||||
let fontData = OpaquePointer(copy)
|
||||
guard let pointer = gfx.pointee.makeFontFromData.unsafelyUnwrapped(
|
||||
fontData, wide ? 1 : 0, Int32(data.count)) else {
|
||||
copy.deallocate()
|
||||
return nil
|
||||
}
|
||||
self.init(pointer: pointer, retainedData: UnsafeRawPointer(copy))
|
||||
}
|
||||
|
||||
deinit {
|
||||
// Per the C API docs, fonts are freed with the system allocator.
|
||||
System.systemFree(UnsafeMutableRawPointer(pointer))
|
||||
retainedData?.deallocate()
|
||||
}
|
||||
|
||||
/// The font's glyph height in pixels.
|
||||
public var height: Int {
|
||||
Int(gfx.pointee.getFontHeight.unsafelyUnwrapped(pointer))
|
||||
}
|
||||
|
||||
/// The width of `text` when drawn with this font.
|
||||
public func textWidth(_ text: String, tracking: Int = 0) -> Int {
|
||||
text.withPlaydateUTF8 { bytes, count in
|
||||
Int(gfx.pointee.getTextWidth.unsafelyUnwrapped(pointer, bytes, count,
|
||||
kUTF8Encoding, Int32(tracking)))
|
||||
}
|
||||
}
|
||||
|
||||
/// The height of `text` when wrapped to `maxWidth` with this font.
|
||||
public func textHeight(_ text: String, maxWidth: Int, wrap: TextWrappingMode = .word,
|
||||
tracking: Int = 0, extraLeading: Int = 0) -> Int {
|
||||
text.withPlaydateUTF8 { bytes, count in
|
||||
Int(gfx.pointee.getTextHeightForMaxWidth.unsafelyUnwrapped(
|
||||
pointer, bytes, count, Int32(maxWidth), kUTF8Encoding,
|
||||
wrap.cValue, Int32(tracking), Int32(extraLeading)))
|
||||
}
|
||||
}
|
||||
|
||||
/// The page containing glyph data for the character `codepoint`
|
||||
/// belongs to. The page references data owned by the font.
|
||||
public func page(for codepoint: UInt32) -> FontPage? {
|
||||
guard let page = gfx.pointee.getFontPage.unsafelyUnwrapped(pointer, codepoint) else { return nil }
|
||||
return FontPage(pointer: page, font: self)
|
||||
}
|
||||
|
||||
/// The glyph for `codepoint`, with its bitmap and advance.
|
||||
/// The bitmap references data owned by the font.
|
||||
public func glyph(for codepoint: UInt32) -> (glyph: Glyph, bitmap: Bitmap?, advance: Int)? {
|
||||
var bitmap: OpaquePointer?
|
||||
var advance: Int32 = 0
|
||||
guard let glyph = gfx.pointee.getFontGlyph.unsafelyUnwrapped(pointer, codepoint, &bitmap, &advance) else {
|
||||
return nil
|
||||
}
|
||||
return (Glyph(pointer: glyph, font: self),
|
||||
bitmap.map { Bitmap(pointer: $0, isOwned: false) },
|
||||
Int(advance))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
internal import CPlaydate
|
||||
|
||||
private var streamAPI: UnsafePointer<playdate_videostream> { Playdate.videoStreamAPI.unsafelyUnwrapped }
|
||||
|
||||
extension Graphics {
|
||||
/// Streams video (and audio) from a file or network connection.
|
||||
/// Wraps `LCDStreamPlayer`.
|
||||
public final class StreamPlayer {
|
||||
let pointer: OpaquePointer
|
||||
/// Retains the active source so it outlives the stream.
|
||||
private var retainedSource: AnyObject?
|
||||
|
||||
public init() {
|
||||
pointer = streamAPI.pointee.newPlayer.unsafelyUnwrapped().unsafelyUnwrapped
|
||||
}
|
||||
|
||||
deinit {
|
||||
streamAPI.pointee.freePlayer.unsafelyUnwrapped(pointer)
|
||||
}
|
||||
|
||||
/// Sets the sizes of the stream's video and audio buffers, in bytes.
|
||||
public func setBufferSize(video: Int, audio: Int) {
|
||||
streamAPI.pointee.setBufferSize.unsafelyUnwrapped(pointer, Int32(video), Int32(audio))
|
||||
}
|
||||
|
||||
/// Streams from an open file.
|
||||
public func setFile(_ file: File.Handle) {
|
||||
retainedSource = file
|
||||
streamAPI.pointee.setFile.unsafelyUnwrapped(pointer, file.pointer)
|
||||
}
|
||||
|
||||
/// Streams from an HTTP connection.
|
||||
public func setHTTPConnection(_ connection: Network.HTTPConnection) {
|
||||
retainedSource = connection
|
||||
streamAPI.pointee.setHTTPConnection.unsafelyUnwrapped(pointer, connection.pointer)
|
||||
}
|
||||
|
||||
/// Streams from a TCP connection.
|
||||
public func setTCPConnection(_ connection: Network.TCPConnection) {
|
||||
retainedSource = connection
|
||||
streamAPI.pointee.setTCPConnection.unsafelyUnwrapped(pointer, connection.pointer)
|
||||
}
|
||||
|
||||
/// The player used for the stream's audio track. Owned by the stream.
|
||||
/// The same wrapper is returned on every access, so callbacks
|
||||
/// registered on it stay valid for the stream's lifetime.
|
||||
public var filePlayer: Sound.FilePlayer? {
|
||||
guard let player = streamAPI.pointee.getFilePlayer.unsafelyUnwrapped(pointer) else { return nil }
|
||||
if let cached = cachedFilePlayer, cached.pointer == player {
|
||||
return cached
|
||||
}
|
||||
let wrapper = Sound.FilePlayer(pointer: player, isOwned: false)
|
||||
cachedFilePlayer = wrapper
|
||||
return wrapper
|
||||
}
|
||||
|
||||
private var cachedFilePlayer: Sound.FilePlayer?
|
||||
|
||||
/// The player used for the stream's video track. Owned by the stream.
|
||||
public var videoPlayer: VideoPlayer? {
|
||||
guard let player = streamAPI.pointee.getVideoPlayer.unsafelyUnwrapped(pointer) else { return nil }
|
||||
return VideoPlayer(pointer: player, isOwned: false)
|
||||
}
|
||||
|
||||
/// Advances the stream. Returns `true` if a frame was drawn.
|
||||
@discardableResult
|
||||
public func update() -> Bool {
|
||||
streamAPI.pointee.update.unsafelyUnwrapped(pointer)
|
||||
}
|
||||
|
||||
/// The number of video frames currently buffered.
|
||||
public var bufferedFrameCount: Int {
|
||||
Int(streamAPI.pointee.getBufferedFrameCount.unsafelyUnwrapped(pointer))
|
||||
}
|
||||
|
||||
/// The total number of bytes read from the source.
|
||||
public var bytesRead: UInt32 {
|
||||
streamAPI.pointee.getBytesRead.unsafelyUnwrapped(pointer)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
internal import CPlaydate
|
||||
|
||||
private var tilemapAPI: UnsafePointer<playdate_tilemap> { Playdate.tilemapAPI.unsafelyUnwrapped }
|
||||
|
||||
extension Graphics {
|
||||
/// A grid of tiles drawn from a bitmap table. Wraps `LCDTileMap`.
|
||||
public final class TileMap {
|
||||
let pointer: OpaquePointer
|
||||
/// The image table is retained so the tilemap's tiles stay valid.
|
||||
private var retainedImageTable: BitmapTable?
|
||||
|
||||
public init() {
|
||||
pointer = tilemapAPI.pointee.newTilemap.unsafelyUnwrapped().unsafelyUnwrapped
|
||||
}
|
||||
|
||||
deinit {
|
||||
tilemapAPI.pointee.freeTilemap.unsafelyUnwrapped(pointer)
|
||||
}
|
||||
|
||||
/// The bitmap table the tile indexes refer to.
|
||||
public var imageTable: BitmapTable? {
|
||||
get { retainedImageTable }
|
||||
set {
|
||||
retainedImageTable = newValue
|
||||
tilemapAPI.pointee.setImageTable.unsafelyUnwrapped(pointer, newValue?.pointer)
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the tilemap's size in tiles.
|
||||
public func setSize(tilesWide: Int, tilesHigh: Int) {
|
||||
tilemapAPI.pointee.setSize.unsafelyUnwrapped(pointer, Int32(tilesWide), Int32(tilesHigh))
|
||||
}
|
||||
|
||||
/// The tilemap's size in tiles.
|
||||
public var size: (tilesWide: Int, tilesHigh: Int) {
|
||||
var wide: Int32 = 0, high: Int32 = 0
|
||||
tilemapAPI.pointee.getSize.unsafelyUnwrapped(pointer, &wide, &high)
|
||||
return (Int(wide), Int(high))
|
||||
}
|
||||
|
||||
/// The tilemap's total size in pixels.
|
||||
public var pixelSize: (width: Int, height: Int) {
|
||||
var width: UInt32 = 0, height: UInt32 = 0
|
||||
tilemapAPI.pointee.getPixelSize.unsafelyUnwrapped(pointer, &width, &height)
|
||||
return (Int(width), Int(height))
|
||||
}
|
||||
|
||||
/// Fills the tilemap with `indexes`, `rowWidth` tiles per row. The
|
||||
/// tilemap is resized to fit.
|
||||
public func setTiles(_ indexes: [UInt16], rowWidth: Int) {
|
||||
var indexes = indexes
|
||||
indexes.withUnsafeMutableBufferPointer { buffer in
|
||||
tilemapAPI.pointee.setTiles.unsafelyUnwrapped(pointer, buffer.baseAddress,
|
||||
Int32(buffer.count), Int32(rowWidth))
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the tile index at position (x, y).
|
||||
public func setTile(x: Int, y: Int, index: UInt16) {
|
||||
tilemapAPI.pointee.setTileAtPosition.unsafelyUnwrapped(pointer, Int32(x), Int32(y), index)
|
||||
}
|
||||
|
||||
/// The tile index at position (x, y), or `nil` if out of bounds.
|
||||
public func tile(x: Int, y: Int) -> Int? {
|
||||
let index = tilemapAPI.pointee.getTileAtPosition.unsafelyUnwrapped(pointer, Int32(x), Int32(y))
|
||||
return index < 0 ? nil : Int(index)
|
||||
}
|
||||
|
||||
/// Draws the tilemap with its upper-left corner at (x, y).
|
||||
public func draw(x: Float, y: Float) {
|
||||
tilemapAPI.pointee.drawAtPoint.unsafelyUnwrapped(pointer, x, y)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
internal import CPlaydate
|
||||
|
||||
private var videoAPI: UnsafePointer<playdate_video> { Playdate.videoAPI.unsafelyUnwrapped }
|
||||
|
||||
extension Graphics {
|
||||
/// Plays .pdv video files. Wraps `LCDVideoPlayer`.
|
||||
public final class VideoPlayer {
|
||||
let pointer: OpaquePointer
|
||||
let isOwned: Bool
|
||||
/// Retains the render context bitmap while the player uses it.
|
||||
private var retainedContext: Bitmap?
|
||||
|
||||
init(pointer: OpaquePointer, isOwned: Bool) {
|
||||
self.pointer = pointer
|
||||
self.isOwned = isOwned
|
||||
}
|
||||
|
||||
/// Opens the .pdv file at `path`.
|
||||
public convenience init(path: String) throws(PlaydateError) {
|
||||
let pointer = path.withPlaydateCString { videoAPI.pointee.loadVideo.unsafelyUnwrapped($0) }
|
||||
guard let pointer else {
|
||||
throw PlaydateError(message: "unable to load video: \(path)")
|
||||
}
|
||||
self.init(pointer: pointer, isOwned: true)
|
||||
}
|
||||
|
||||
deinit {
|
||||
if isOwned {
|
||||
videoAPI.pointee.freePlayer.unsafelyUnwrapped(pointer)
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the bitmap the video renders into.
|
||||
public func setContext(_ context: Bitmap) throws(PlaydateError) {
|
||||
guard videoAPI.pointee.setContext.unsafelyUnwrapped(pointer, context.pointer) != 0 else {
|
||||
throw PlaydateError(message: error ?? "unable to set video context")
|
||||
}
|
||||
retainedContext = context
|
||||
}
|
||||
|
||||
/// The bitmap the video renders into.
|
||||
public var context: Bitmap? {
|
||||
guard let context = videoAPI.pointee.getContext.unsafelyUnwrapped(pointer) else { return nil }
|
||||
return Bitmap(pointer: context, isOwned: false)
|
||||
}
|
||||
|
||||
/// Renders directly into the display framebuffer.
|
||||
public func useScreenContext() {
|
||||
retainedContext = nil
|
||||
videoAPI.pointee.useScreenContext.unsafelyUnwrapped(pointer)
|
||||
}
|
||||
|
||||
/// Renders frame `frame` into the current context.
|
||||
public func renderFrame(_ frame: Int) throws(PlaydateError) {
|
||||
guard videoAPI.pointee.renderFrame.unsafelyUnwrapped(pointer, Int32(frame)) != 0 else {
|
||||
// Static message: the caller knows the frame it passed, and
|
||||
// interpolating it would pull integer formatting machinery
|
||||
// into the device binary.
|
||||
throw PlaydateError(message: error ?? "unable to render frame")
|
||||
}
|
||||
}
|
||||
|
||||
/// The most recent error message, if any.
|
||||
public var error: String? {
|
||||
String(playdateCString: videoAPI.pointee.getError.unsafelyUnwrapped(pointer))
|
||||
}
|
||||
|
||||
/// The video's dimensions, frame rate, frame count, and current frame.
|
||||
public var info: (width: Int, height: Int, frameRate: Float, frameCount: Int, currentFrame: Int) {
|
||||
var width: Int32 = 0, height: Int32 = 0, frameCount: Int32 = 0, currentFrame: Int32 = 0
|
||||
var frameRate: Float = 0
|
||||
videoAPI.pointee.getInfo.unsafelyUnwrapped(pointer, &width, &height, &frameRate,
|
||||
&frameCount, ¤tFrame)
|
||||
return (Int(width), Int(height), frameRate, Int(frameCount), Int(currentFrame))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
internal import CPlaydate
|
||||
|
||||
extension Graphics {
|
||||
/// Mirroring applied when drawing a bitmap.
|
||||
public enum BitmapFlip: UInt32, Sendable {
|
||||
case unflipped = 0
|
||||
case flippedX = 1
|
||||
case flippedY = 2
|
||||
case flippedXY = 3
|
||||
|
||||
init(_ flip: LCDBitmapFlip) { self = BitmapFlip(rawValue: UInt32(flip.rawValue)) ?? .unflipped }
|
||||
var cValue: LCDBitmapFlip { LCDBitmapFlip(LCDBitmapFlip.RawValue(rawValue)) }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
internal import CPlaydate
|
||||
|
||||
extension Graphics {
|
||||
/// A drawing color: solid or an 8×8 pattern.
|
||||
public enum Color: Sendable {
|
||||
case black
|
||||
case white
|
||||
case clear
|
||||
case xor
|
||||
case pattern(Pattern)
|
||||
|
||||
/// Materializes the `LCDColor` for the duration of `body`. Pattern
|
||||
/// colors pass a pointer to a temporary, so the value must not be
|
||||
/// stored beyond the call.
|
||||
func withLCDColor<Result>(_ body: (LCDColor) -> Result) -> Result {
|
||||
switch self {
|
||||
case .black: return body(LCDColor(kColorBlack.rawValue))
|
||||
case .white: return body(LCDColor(kColorWhite.rawValue))
|
||||
case .clear: return body(LCDColor(kColorClear.rawValue))
|
||||
case .xor: return body(LCDColor(kColorXOR.rawValue))
|
||||
case .pattern(let pattern):
|
||||
return withUnsafeBytes(of: pattern.bytes) { buffer in
|
||||
body(LCDColor(UInt(bitPattern: buffer.baseAddress)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
internal import CPlaydate
|
||||
|
||||
extension Graphics {
|
||||
/// How source pixels combine with the destination when drawing.
|
||||
public enum DrawMode: UInt32, Sendable {
|
||||
case copy = 0
|
||||
case whiteTransparent = 1
|
||||
case blackTransparent = 2
|
||||
case fillWhite = 3
|
||||
case fillBlack = 4
|
||||
case xor = 5
|
||||
case nxor = 6
|
||||
case inverted = 7
|
||||
|
||||
init(_ mode: LCDBitmapDrawMode) { self = DrawMode(rawValue: UInt32(mode.rawValue)) ?? .copy }
|
||||
var cValue: LCDBitmapDrawMode { LCDBitmapDrawMode(LCDBitmapDrawMode.RawValue(rawValue)) }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
internal import CPlaydate
|
||||
|
||||
extension Graphics {
|
||||
/// The end cap style used when drawing lines.
|
||||
public enum LineCapStyle: UInt32, Sendable {
|
||||
case butt = 0
|
||||
case square = 1
|
||||
case round = 2
|
||||
|
||||
var cValue: LCDLineCapStyle { LCDLineCapStyle(LCDLineCapStyle.RawValue(rawValue)) }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
internal import CPlaydate
|
||||
|
||||
extension Graphics {
|
||||
/// The winding rule used by `fillPolygon`.
|
||||
public enum PolygonFillRule: UInt32, Sendable {
|
||||
case nonZero = 0
|
||||
case evenOdd = 1
|
||||
|
||||
var cValue: LCDPolygonFillRule { LCDPolygonFillRule(LCDPolygonFillRule.RawValue(rawValue)) }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
internal import CPlaydate
|
||||
|
||||
extension Graphics {
|
||||
/// A solid color, for APIs that cannot take a pattern.
|
||||
public enum SolidColor: UInt32, Sendable {
|
||||
case black = 0
|
||||
case white = 1
|
||||
case clear = 2
|
||||
case xor = 3
|
||||
|
||||
init(_ color: LCDSolidColor) { self = SolidColor(rawValue: UInt32(color.rawValue)) ?? .clear }
|
||||
var cValue: LCDSolidColor { LCDSolidColor(LCDSolidColor.RawValue(rawValue)) }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
internal import CPlaydate
|
||||
|
||||
extension Graphics {
|
||||
/// The encoding of text passed to the text functions.
|
||||
public enum StringEncoding: UInt32, Sendable {
|
||||
case ascii = 0
|
||||
case utf8 = 1
|
||||
case utf16LittleEndian = 2
|
||||
|
||||
var cValue: PDStringEncoding { PDStringEncoding(PDStringEncoding.RawValue(rawValue)) }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
internal import CPlaydate
|
||||
|
||||
extension Graphics {
|
||||
/// Horizontal alignment for `drawText(in:)`.
|
||||
public enum TextAlignment: UInt32, Sendable {
|
||||
case left = 0
|
||||
case center = 1
|
||||
case right = 2
|
||||
|
||||
var cValue: PDTextAlignment { PDTextAlignment(PDTextAlignment.RawValue(rawValue)) }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
internal import CPlaydate
|
||||
|
||||
extension Graphics {
|
||||
/// How text wraps in `drawText(in:)`.
|
||||
public enum TextWrappingMode: UInt32, Sendable {
|
||||
case clip = 0
|
||||
case character = 1
|
||||
case word = 2
|
||||
|
||||
var cValue: PDTextWrappingMode { PDTextWrappingMode(PDTextWrappingMode.RawValue(rawValue)) }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,257 @@
|
||||
internal import CPlaydate
|
||||
|
||||
/// The graphics API: drawing, bitmaps, fonts, tilemaps, and video.
|
||||
public enum Graphics {}
|
||||
|
||||
var gfx: UnsafePointer<playdate_graphics> { Playdate.graphicsAPI.unsafelyUnwrapped }
|
||||
|
||||
extension Graphics {
|
||||
// MARK: - Screen constants
|
||||
|
||||
/// The width of the screen in pixels (`LCD_COLUMNS`).
|
||||
public static let columns = 400
|
||||
/// The height of the screen in pixels (`LCD_ROWS`).
|
||||
public static let rows = 240
|
||||
/// The stride of a framebuffer row in bytes (`LCD_ROWSIZE`).
|
||||
public static let rowSize = 52
|
||||
|
||||
// MARK: - Drawing state
|
||||
|
||||
/// Clears the entire display, filling it with `color`.
|
||||
public static func clear(color: Color = .white) {
|
||||
color.withLCDColor { gfx.pointee.clear.unsafelyUnwrapped($0) }
|
||||
}
|
||||
|
||||
/// Sets the background color shown when the display is offset or for
|
||||
/// clear pixels in drawn images.
|
||||
public static func setBackgroundColor(_ color: SolidColor) {
|
||||
gfx.pointee.setBackgroundColor.unsafelyUnwrapped(color.cValue)
|
||||
}
|
||||
|
||||
/// Sets the mode that determines how source pixels combine with the
|
||||
/// destination. Returns the previous mode.
|
||||
@discardableResult
|
||||
public static func setDrawMode(_ mode: DrawMode) -> DrawMode {
|
||||
DrawMode(gfx.pointee.setDrawMode.unsafelyUnwrapped(mode.cValue))
|
||||
}
|
||||
|
||||
/// Offsets all subsequent drawing by (dx, dy).
|
||||
public static func setDrawOffset(dx: Int, dy: Int) {
|
||||
gfx.pointee.setDrawOffset.unsafelyUnwrapped(Int32(dx), Int32(dy))
|
||||
}
|
||||
|
||||
/// Sets the clip rect in world coordinates (affected by the draw offset).
|
||||
public static func setClipRect(x: Int, y: Int, width: Int, height: Int) {
|
||||
gfx.pointee.setClipRect.unsafelyUnwrapped(Int32(x), Int32(y), Int32(width), Int32(height))
|
||||
}
|
||||
|
||||
/// Sets the clip rect in screen coordinates (unaffected by the draw offset).
|
||||
public static func setScreenClipRect(x: Int, y: Int, width: Int, height: Int) {
|
||||
gfx.pointee.setScreenClipRect.unsafelyUnwrapped(Int32(x), Int32(y), Int32(width), Int32(height))
|
||||
}
|
||||
|
||||
public static func clearClipRect() {
|
||||
gfx.pointee.clearClipRect.unsafelyUnwrapped()
|
||||
}
|
||||
|
||||
public static func setLineCapStyle(_ style: LineCapStyle) {
|
||||
gfx.pointee.setLineCapStyle.unsafelyUnwrapped(style.cValue)
|
||||
}
|
||||
|
||||
/// Sets the stencil applied to subsequent drawing. If `tile` is `true`
|
||||
/// the stencil image is tiled, and its width must be a multiple of 32.
|
||||
/// Pass `nil` to clear the stencil.
|
||||
public static func setStencil(_ image: Bitmap?, tile: Bool = false) {
|
||||
gfx.pointee.setStencilImage.unsafelyUnwrapped(image?.pointer, tile ? 1 : 0)
|
||||
}
|
||||
|
||||
/// Pushes a new drawing context targeting `target`, or the display if
|
||||
/// `target` is `nil`.
|
||||
public static func pushContext(_ target: Bitmap? = nil) {
|
||||
gfx.pointee.pushContext.unsafelyUnwrapped(target?.pointer)
|
||||
}
|
||||
|
||||
public static func popContext() {
|
||||
gfx.pointee.popContext.unsafelyUnwrapped()
|
||||
}
|
||||
|
||||
// MARK: - Shapes
|
||||
|
||||
public static func drawLine(x1: Int, y1: Int, x2: Int, y2: Int, width: Int, color: Color) {
|
||||
color.withLCDColor {
|
||||
gfx.pointee.drawLine.unsafelyUnwrapped(Int32(x1), Int32(y1), Int32(x2), Int32(y2), Int32(width), $0)
|
||||
}
|
||||
}
|
||||
|
||||
public static func fillTriangle(x1: Int, y1: Int, x2: Int, y2: Int, x3: Int, y3: Int, color: Color) {
|
||||
color.withLCDColor {
|
||||
gfx.pointee.fillTriangle.unsafelyUnwrapped(Int32(x1), Int32(y1), Int32(x2), Int32(y2),
|
||||
Int32(x3), Int32(y3), $0)
|
||||
}
|
||||
}
|
||||
|
||||
public static func drawRect(x: Int, y: Int, width: Int, height: Int, color: Color) {
|
||||
color.withLCDColor {
|
||||
gfx.pointee.drawRect.unsafelyUnwrapped(Int32(x), Int32(y), Int32(width), Int32(height), $0)
|
||||
}
|
||||
}
|
||||
|
||||
public static func fillRect(x: Int, y: Int, width: Int, height: Int, color: Color) {
|
||||
color.withLCDColor {
|
||||
gfx.pointee.fillRect.unsafelyUnwrapped(Int32(x), Int32(y), Int32(width), Int32(height), $0)
|
||||
}
|
||||
}
|
||||
|
||||
public static func drawRoundRect(x: Int, y: Int, width: Int, height: Int, radius: Int,
|
||||
lineWidth: Int, color: Color) {
|
||||
color.withLCDColor {
|
||||
gfx.pointee.drawRoundRect.unsafelyUnwrapped(Int32(x), Int32(y), Int32(width), Int32(height),
|
||||
Int32(radius), Int32(lineWidth), $0)
|
||||
}
|
||||
}
|
||||
|
||||
public static func fillRoundRect(x: Int, y: Int, width: Int, height: Int, radius: Int, color: Color) {
|
||||
color.withLCDColor {
|
||||
gfx.pointee.fillRoundRect.unsafelyUnwrapped(Int32(x), Int32(y), Int32(width), Int32(height),
|
||||
Int32(radius), $0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Draws an ellipse stroked inside the rect. If the angles differ, draws
|
||||
/// an arc from `startAngle` to `endAngle` (clockwise degrees, 0 at top).
|
||||
public static func drawEllipse(x: Int, y: Int, width: Int, height: Int, lineWidth: Int,
|
||||
startAngle: Float = 0, endAngle: Float = 0, color: Color) {
|
||||
color.withLCDColor {
|
||||
gfx.pointee.drawEllipse.unsafelyUnwrapped(Int32(x), Int32(y), Int32(width), Int32(height),
|
||||
Int32(lineWidth), startAngle, endAngle, $0)
|
||||
}
|
||||
}
|
||||
|
||||
public static func fillEllipse(x: Int, y: Int, width: Int, height: Int,
|
||||
startAngle: Float = 0, endAngle: Float = 0, color: Color) {
|
||||
color.withLCDColor {
|
||||
gfx.pointee.fillEllipse.unsafelyUnwrapped(Int32(x), Int32(y), Int32(width), Int32(height),
|
||||
startAngle, endAngle, $0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Fills the polygon described by the points, connecting the last point
|
||||
/// back to the first.
|
||||
public static func fillPolygon(points: [(x: Int, y: Int)], color: Color,
|
||||
fillRule: PolygonFillRule = .nonZero) {
|
||||
withUnsafeTemporaryAllocation(of: Int32.self, capacity: points.count * 2) { coordinates in
|
||||
var index = 0
|
||||
for point in points {
|
||||
coordinates[index] = Int32(point.x)
|
||||
coordinates[index + 1] = Int32(point.y)
|
||||
index += 2
|
||||
}
|
||||
color.withLCDColor { cColor in
|
||||
gfx.pointee.fillPolygon.unsafelyUnwrapped(Int32(points.count), coordinates.baseAddress,
|
||||
cColor, fillRule.cValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the pixel at (x, y) in the current drawing context.
|
||||
public static func setPixel(x: Int, y: Int, color: Color) {
|
||||
color.withLCDColor { gfx.pointee.setPixel.unsafelyUnwrapped(Int32(x), Int32(y), $0) }
|
||||
}
|
||||
|
||||
/// Reads an 8×8 pattern from the bitmap starting at (x, y).
|
||||
public static func colorToPattern(from bitmap: Bitmap, x: Int, y: Int) -> Pattern {
|
||||
var color: LCDColor = 0
|
||||
gfx.pointee.setColorToPattern.unsafelyUnwrapped(&color, bitmap.pointer, Int32(x), Int32(y))
|
||||
var pattern = Pattern(bytes: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0))
|
||||
if let source = UnsafeRawPointer(bitPattern: UInt(color)) {
|
||||
withUnsafeMutableBytes(of: &pattern.bytes) { destination in
|
||||
destination.copyMemory(from: UnsafeRawBufferPointer(start: source, count: 16))
|
||||
}
|
||||
}
|
||||
return pattern
|
||||
}
|
||||
|
||||
// MARK: - Text
|
||||
|
||||
/// Draws `text` at (x, y) using the current font. Returns the drawn width.
|
||||
@discardableResult
|
||||
public static func drawText(_ text: String, x: Int, y: Int) -> Int {
|
||||
text.withPlaydateUTF8 { bytes, count in
|
||||
Int(gfx.pointee.drawText.unsafelyUnwrapped(bytes, count,
|
||||
kUTF8Encoding, Int32(x), Int32(y)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Draws `text` wrapped and aligned inside the given rectangle.
|
||||
public static func drawText(_ text: String, x: Int, y: Int, width: Int, height: Int,
|
||||
wrap: TextWrappingMode = .word, align: TextAlignment = .left) {
|
||||
text.withPlaydateUTF8 { bytes, count in
|
||||
gfx.pointee.drawTextInRect.unsafelyUnwrapped(bytes, count, kUTF8Encoding,
|
||||
Int32(x), Int32(y), Int32(width), Int32(height),
|
||||
wrap.cValue, align.cValue)
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the font used by subsequent text drawing.
|
||||
public static func setFont(_ font: Font) {
|
||||
gfx.pointee.setFont.unsafelyUnwrapped(font.pointer)
|
||||
}
|
||||
|
||||
/// Extra space added between letters, in pixels.
|
||||
public static func setTextTracking(_ tracking: Int) {
|
||||
gfx.pointee.setTextTracking.unsafelyUnwrapped(Int32(tracking))
|
||||
}
|
||||
|
||||
public static var textTracking: Int {
|
||||
Int(gfx.pointee.getTextTracking.unsafelyUnwrapped())
|
||||
}
|
||||
|
||||
/// Adjusts the line height used when drawing multi-line text.
|
||||
public static func setTextLeading(_ lineHeightAdjustment: Int) {
|
||||
gfx.pointee.setTextLeading.unsafelyUnwrapped(Int32(lineHeightAdjustment))
|
||||
}
|
||||
|
||||
// MARK: - Framebuffer
|
||||
|
||||
/// The current working framebuffer. Rows are `rowSize` bytes.
|
||||
/// Call `markUpdatedRows(from:to:)` after writing directly.
|
||||
public static var frame: UnsafeMutablePointer<UInt8>? {
|
||||
gfx.pointee.getFrame.unsafelyUnwrapped()
|
||||
}
|
||||
|
||||
/// The framebuffer currently shown on the display. Rows are `rowSize` bytes.
|
||||
public static var displayFrame: UnsafeMutablePointer<UInt8>? {
|
||||
gfx.pointee.getDisplayFrame.unsafelyUnwrapped()
|
||||
}
|
||||
|
||||
/// A bitmap view of the display framebuffer. Simulator only; `nil` on device.
|
||||
public static var debugBitmap: Bitmap? {
|
||||
guard let getDebugBitmap = gfx.pointee.getDebugBitmap,
|
||||
let pointer = getDebugBitmap() else { return nil }
|
||||
return Bitmap(pointer: pointer, isOwned: false)
|
||||
}
|
||||
|
||||
/// A bitmap referencing the display framebuffer (not a copy).
|
||||
public static var displayBufferBitmap: Bitmap? {
|
||||
guard let pointer = gfx.pointee.getDisplayBufferBitmap.unsafelyUnwrapped() else { return nil }
|
||||
return Bitmap(pointer: pointer, isOwned: false)
|
||||
}
|
||||
|
||||
/// A copy of the working framebuffer as a new bitmap.
|
||||
public static func copyFrameBufferBitmap() -> Bitmap? {
|
||||
guard let pointer = gfx.pointee.copyFrameBufferBitmap.unsafelyUnwrapped() else { return nil }
|
||||
return Bitmap(pointer: pointer, isOwned: true)
|
||||
}
|
||||
|
||||
/// Tells the system which rows (inclusive) were changed by direct
|
||||
/// framebuffer writes and need redisplay.
|
||||
public static func markUpdatedRows(from start: Int, to end: Int) {
|
||||
gfx.pointee.markUpdatedRows.unsafelyUnwrapped(Int32(start), Int32(end))
|
||||
}
|
||||
|
||||
/// Manually flushes the framebuffer to the display. Only needed when
|
||||
/// drawing outside the normal update cycle.
|
||||
public static func display() {
|
||||
gfx.pointee.display.unsafelyUnwrapped()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
extension Graphics.Bitmap {
|
||||
/// The bitmap's dimensions, row stride, and raw pixel/mask storage.
|
||||
/// The pointers are owned by the bitmap.
|
||||
public struct Data {
|
||||
public let width: Int
|
||||
public let height: Int
|
||||
public let rowBytes: Int
|
||||
public let mask: UnsafeMutablePointer<UInt8>?
|
||||
public let data: UnsafeMutablePointer<UInt8>?
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
internal import CPlaydate
|
||||
|
||||
extension Graphics {
|
||||
/// A page of glyphs within a font. Wraps `LCDFontPage`.
|
||||
/// Keep the font alive while using its pages.
|
||||
public struct FontPage {
|
||||
let pointer: OpaquePointer
|
||||
let font: Font
|
||||
|
||||
/// The glyph for `codepoint` within this page, with its bitmap and advance.
|
||||
public func glyph(for codepoint: UInt32) -> (glyph: Glyph, bitmap: Bitmap?, advance: Int)? {
|
||||
var bitmap: OpaquePointer?
|
||||
var advance: Int32 = 0
|
||||
guard let glyph = gfx.pointee.getPageGlyph.unsafelyUnwrapped(pointer, codepoint, &bitmap, &advance) else {
|
||||
return nil
|
||||
}
|
||||
return (Glyph(pointer: glyph, font: font),
|
||||
bitmap.map { Bitmap(pointer: $0, isOwned: false) },
|
||||
Int(advance))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
internal import CPlaydate
|
||||
|
||||
extension Graphics {
|
||||
/// A single glyph within a font. Wraps `LCDFontGlyph`.
|
||||
/// Keep the font alive while using its glyphs.
|
||||
public struct Glyph {
|
||||
let pointer: OpaquePointer
|
||||
let font: Font
|
||||
|
||||
/// The kerning adjustment between this glyph and the next character.
|
||||
public func kerning(glyphCode: UInt32, nextCode: UInt32) -> Int {
|
||||
Int(gfx.pointee.getGlyphKerning.unsafelyUnwrapped(pointer, glyphCode, nextCode))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
internal import CPlaydate
|
||||
|
||||
extension Graphics {
|
||||
/// An integer rectangle mirroring `LCDRect`. `right` and `bottom` are
|
||||
/// not inclusive.
|
||||
public struct Rect: Sendable {
|
||||
public var left: Int
|
||||
public var right: Int
|
||||
public var top: Int
|
||||
public var bottom: Int
|
||||
|
||||
public init(left: Int, right: Int, top: Int, bottom: Int) {
|
||||
self.left = left
|
||||
self.right = right
|
||||
self.top = top
|
||||
self.bottom = bottom
|
||||
}
|
||||
|
||||
public init(x: Int, y: Int, width: Int, height: Int) {
|
||||
self.init(left: x, right: x + width, top: y, bottom: y + height)
|
||||
}
|
||||
|
||||
init(_ rect: LCDRect) {
|
||||
self.init(left: Int(rect.left), right: Int(rect.right),
|
||||
top: Int(rect.top), bottom: Int(rect.bottom))
|
||||
}
|
||||
|
||||
var cValue: LCDRect {
|
||||
LCDRect(left: Int32(left), right: Int32(right),
|
||||
top: Int32(top), bottom: Int32(bottom))
|
||||
}
|
||||
|
||||
public func translated(dx: Int, dy: Int) -> Rect {
|
||||
Rect(left: left + dx, right: right + dx, top: top + dy, bottom: bottom + dy)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
extension Graphics {
|
||||
/// An 8×8 two-color pattern: 8 rows of image data followed by 8 rows of mask.
|
||||
public struct Pattern: Sendable {
|
||||
public var bytes: (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
|
||||
UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8)
|
||||
|
||||
public init(bytes: (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
|
||||
UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8)) {
|
||||
self.bytes = bytes
|
||||
}
|
||||
|
||||
/// Creates an opaque pattern from 8 rows of image data.
|
||||
public init(rows r: (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8)) {
|
||||
bytes = (r.0, r.1, r.2, r.3, r.4, r.5, r.6, r.7,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user