Merge pull request #60 from YusukeHosonuma/cross-platform/render-thumbnail
This commit is contained in:
@@ -57,7 +57,6 @@
|
||||
E55F7AA624DD0C1000DBC175 /* MacRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E55F7AA524DD0C1000DBC175 /* MacRootView.swift */; };
|
||||
E55F7AA824DD0D9300DBC175 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E55F7AA724DD0D9300DBC175 /* ContentView.swift */; };
|
||||
E55F7AAA24DD101900DBC175 /* PresetListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E55F7AA924DD101900DBC175 /* PresetListView.swift */; };
|
||||
E55F7AAC24DD119700DBC175 /* BoardThumnailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E55F7AAB24DD119700DBC175 /* BoardThumnailView.swift */; };
|
||||
E5604AA124CC3ED600EA59C3 /* PreferenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5604AA024CC3ED600EA59C3 /* PreferenceView.swift */; };
|
||||
E56A422824E2BB6800F9AC24 /* BoardDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = E52FE5DC24DC012A00D93764 /* BoardDocument.swift */; };
|
||||
E56A422924E2C0B100F9AC24 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = E558A97224DB70D00012E443 /* GoogleService-Info.plist */; };
|
||||
@@ -98,6 +97,13 @@
|
||||
E5D9F27424BEBDBD009A526B /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5D9F24A24BEBDBC009A526B /* Application.swift */; };
|
||||
E5D9F27524BEBDBD009A526B /* MainGameView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5D9F24B24BEBDBC009A526B /* MainGameView.swift */; };
|
||||
E5DB4C9524D853C000AADA66 /* PhotoPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5DB4C9424D853C000AADA66 /* PhotoPicker.swift */; };
|
||||
E5E50A2524E52C5300CD64B6 /* GraphicsImageRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5E50A2424E52C5300CD64B6 /* GraphicsImageRenderer.swift */; };
|
||||
E5E50A2624E52EE000CD64B6 /* BoardThumbnailImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5E6EB1524DFF33E0012E911 /* BoardThumbnailImage.swift */; };
|
||||
E5E50A2724E5327E00CD64B6 /* GraphicsImageRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5E50A2424E52C5300CD64B6 /* GraphicsImageRenderer.swift */; };
|
||||
E5E50A2824E535AD00CD64B6 /* GraphicsImageRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5E50A2424E52C5300CD64B6 /* GraphicsImageRenderer.swift */; };
|
||||
E5E50A2A24E539C900CD64B6 /* Image+.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5E50A2924E539C900CD64B6 /* Image+.swift */; };
|
||||
E5E50A2B24E539C900CD64B6 /* Image+.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5E50A2924E539C900CD64B6 /* Image+.swift */; };
|
||||
E5E50A2C24E539C900CD64B6 /* Image+.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5E50A2924E539C900CD64B6 /* Image+.swift */; };
|
||||
E5E6EB1424DFF27C0012E911 /* BoardSelectCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5E6EB1324DFF27C0012E911 /* BoardSelectCell.swift */; };
|
||||
E5E6EB1624DFF33E0012E911 /* BoardThumbnailImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5E6EB1524DFF33E0012E911 /* BoardThumbnailImage.swift */; };
|
||||
E5FA934224C0570800229AAA /* ButtonStyleCircle.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5FA934124C0570800229AAA /* ButtonStyleCircle.swift */; };
|
||||
@@ -186,7 +192,6 @@
|
||||
E55F7AA524DD0C1000DBC175 /* MacRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacRootView.swift; sourceTree = "<group>"; };
|
||||
E55F7AA724DD0D9300DBC175 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
||||
E55F7AA924DD101900DBC175 /* PresetListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PresetListView.swift; sourceTree = "<group>"; };
|
||||
E55F7AAB24DD119700DBC175 /* BoardThumnailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoardThumnailView.swift; sourceTree = "<group>"; };
|
||||
E5604AA024CC3ED600EA59C3 /* PreferenceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferenceView.swift; sourceTree = "<group>"; };
|
||||
E56DBCAB24E4ED5000849CE4 /* LifeGameConfig.intentdefinition */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; path = LifeGameConfig.intentdefinition; sourceTree = "<group>"; };
|
||||
E56DBCAE24E4FE3200849CE4 /* Array+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+.swift"; sourceTree = "<group>"; };
|
||||
@@ -219,6 +224,8 @@
|
||||
E5D9F27024BEBDBD009A526B /* Tests_macOS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests_macOS.swift; sourceTree = "<group>"; };
|
||||
E5D9F27224BEBDBD009A526B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
E5DB4C9424D853C000AADA66 /* PhotoPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoPicker.swift; sourceTree = "<group>"; };
|
||||
E5E50A2424E52C5300CD64B6 /* GraphicsImageRenderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphicsImageRenderer.swift; sourceTree = "<group>"; };
|
||||
E5E50A2924E539C900CD64B6 /* Image+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Image+.swift"; sourceTree = "<group>"; };
|
||||
E5E6EB1324DFF27C0012E911 /* BoardSelectCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoardSelectCell.swift; sourceTree = "<group>"; };
|
||||
E5E6EB1524DFF33E0012E911 /* BoardThumbnailImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoardThumbnailImage.swift; sourceTree = "<group>"; };
|
||||
E5FA934124C0570800229AAA /* ButtonStyleCircle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonStyleCircle.swift; sourceTree = "<group>"; };
|
||||
@@ -377,6 +384,7 @@
|
||||
E571536424D51B5B003B4C9E /* Util */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E5E50A2424E52C5300CD64B6 /* GraphicsImageRenderer.swift */,
|
||||
E571536524D51B73003B4C9E /* UserDefaultConvertible.swift */,
|
||||
E571536824D51B9F003B4C9E /* UserDefault.swift */,
|
||||
E518C88E24E3670C00B02027 /* UserDefaultGroup.swift */,
|
||||
@@ -460,7 +468,6 @@
|
||||
E55F7AA524DD0C1000DBC175 /* MacRootView.swift */,
|
||||
E55F7AA724DD0D9300DBC175 /* ContentView.swift */,
|
||||
E55F7AA924DD101900DBC175 /* PresetListView.swift */,
|
||||
E55F7AAB24DD119700DBC175 /* BoardThumnailView.swift */,
|
||||
);
|
||||
path = View;
|
||||
sourceTree = "<group>";
|
||||
@@ -624,6 +631,7 @@
|
||||
E5FA934F24C07AE300229AAA /* Collection+.swift */,
|
||||
E518213224CC2E5300ABD16B /* Color+.swift */,
|
||||
E56DBCAE24E4FE3200849CE4 /* Array+.swift */,
|
||||
E5E50A2924E539C900CD64B6 /* Image+.swift */,
|
||||
);
|
||||
path = Extensions;
|
||||
sourceTree = "<group>";
|
||||
@@ -952,6 +960,7 @@
|
||||
E5B7516124E3D7A00004D8AE /* FirestoreBoardRepository.swift in Sources */,
|
||||
E56DBCB324E4FFF400849CE4 /* Array+.swift in Sources */,
|
||||
E5438E3A24E2AB0B007A3EB7 /* LifeGameWidget.swift in Sources */,
|
||||
E5E50A2824E535AD00CD64B6 /* GraphicsImageRenderer.swift in Sources */,
|
||||
E518C89824E3690600B02027 /* Color+.swift in Sources */,
|
||||
E56DBCAD24E4ED5000849CE4 /* LifeGameConfig.intentdefinition in Sources */,
|
||||
E56A422824E2BB6800F9AC24 /* BoardDocument.swift in Sources */,
|
||||
@@ -959,6 +968,7 @@
|
||||
E518C89624E368A700B02027 /* UserDefaultGroup.swift in Sources */,
|
||||
E5438E4724E2AD1D007A3EB7 /* BoardPreset.swift in Sources */,
|
||||
E5438E4524E2ACD4007A3EB7 /* BoardThumbnailImage.swift in Sources */,
|
||||
E5E50A2C24E539C900CD64B6 /* Image+.swift in Sources */,
|
||||
E518C89724E368D600B02027 /* UserDefaultConvertible.swift in Sources */,
|
||||
E518C89524E3684A00B02027 /* UserDefaultSettingGroup.swift in Sources */,
|
||||
);
|
||||
@@ -997,6 +1007,7 @@
|
||||
E547C30A24E3F4CF0022DF3A /* MemoryStorage.swift in Sources */,
|
||||
E5D9F27324BEBDBD009A526B /* Application.swift in Sources */,
|
||||
E5B0572824CC3465005EE1AA /* CellView.swift in Sources */,
|
||||
E5E50A2724E5327E00CD64B6 /* GraphicsImageRenderer.swift in Sources */,
|
||||
E5FA935024C07AE300229AAA /* Collection+.swift in Sources */,
|
||||
E518C89324E3684A00B02027 /* UserDefaultSettingGroup.swift in Sources */,
|
||||
E5B7519B24E3DD6D0004D8AE /* DesigntimeFirestoreBoardRepository.swift in Sources */,
|
||||
@@ -1004,6 +1015,7 @@
|
||||
E51169D224D644E3002D40B6 /* SpeedSliderView.swift in Sources */,
|
||||
E5DB4C9524D853C000AADA66 /* PhotoPicker.swift in Sources */,
|
||||
E5FA934224C0570800229AAA /* ButtonStyleCircle.swift in Sources */,
|
||||
E5E50A2A24E539C900CD64B6 /* Image+.swift in Sources */,
|
||||
E54904AF24DD5E2D003AE7C7 /* BoardView.swift in Sources */,
|
||||
E5CE237324C199F200BC5820 /* LifeGameContext.swift in Sources */,
|
||||
);
|
||||
@@ -1036,14 +1048,16 @@
|
||||
E5B7519C24E3DD6D0004D8AE /* DesigntimeFirestoreBoardRepository.swift in Sources */,
|
||||
E5B0572924CC3465005EE1AA /* CellView.swift in Sources */,
|
||||
E5FA935124C07AE300229AAA /* Collection+.swift in Sources */,
|
||||
E55F7AAC24DD119700DBC175 /* BoardThumnailView.swift in Sources */,
|
||||
E5FA934324C0570800229AAA /* ButtonStyleCircle.swift in Sources */,
|
||||
E580AA6D24E12F34007C60F1 /* BoardPresetFile.swift in Sources */,
|
||||
E571536A24D51B9F003B4C9E /* UserDefault.swift in Sources */,
|
||||
E5CE237424C199F200BC5820 /* LifeGameContext.swift in Sources */,
|
||||
E5E50A2624E52EE000CD64B6 /* BoardThumbnailImage.swift in Sources */,
|
||||
E5E50A2524E52C5300CD64B6 /* GraphicsImageRenderer.swift in Sources */,
|
||||
E55F7AA824DD0D9300DBC175 /* ContentView.swift in Sources */,
|
||||
E547C30B24E3F4CF0022DF3A /* MemoryStorage.swift in Sources */,
|
||||
E5604AA124CC3ED600EA59C3 /* PreferenceView.swift in Sources */,
|
||||
E5E50A2B24E539C900CD64B6 /* Image+.swift in Sources */,
|
||||
E573BD8224D053F90096BF75 /* LifeGameCommands.swift in Sources */,
|
||||
E518C89424E3684A00B02027 /* UserDefaultSettingGroup.swift in Sources */,
|
||||
);
|
||||
|
||||
@@ -7,6 +7,16 @@
|
||||
|
||||
import SwiftUI
|
||||
|
||||
extension Color {
|
||||
var cgColor: CGColor {
|
||||
#if os(macOS)
|
||||
return NSColor(self).cgColor
|
||||
#else
|
||||
return UIColor(self).cgColor
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
extension Color: RawRepresentable {
|
||||
public var rawValue: Data {
|
||||
#if os(macOS)
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
//
|
||||
// Image+.swift
|
||||
// LifeGameApp
|
||||
//
|
||||
// Created by Yusuke Hosonuma on 2020/08/13.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
extension Image {
|
||||
#if os(macOS)
|
||||
init(image: NSImage) {
|
||||
self = Image(nsImage: image)
|
||||
}
|
||||
#else
|
||||
init(image: UIImage) {
|
||||
self = Image(uiImage: image)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -1,23 +1,20 @@
|
||||
//
|
||||
// MemoryStorage.swift
|
||||
// MemoryCacheStorage.swift
|
||||
// LifeGameApp
|
||||
//
|
||||
// Created by Yusuke Hosonuma on 2020/08/12.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
final class ThumbnailImageCacheStorage {
|
||||
static var shared = ThumbnailImageCacheStorage()
|
||||
final class MemoryCacheStorage<T: AnyObject> {
|
||||
private let cache = NSCache<NSString, T>()
|
||||
|
||||
private let cache = NSCache<NSString, UIImage>()
|
||||
|
||||
func store(key: String, image: UIImage) {
|
||||
func store(key: String, image: T) {
|
||||
cache.setObject(image, forKey: key as NSString)
|
||||
}
|
||||
|
||||
func value(forKey key: String) -> UIImage? {
|
||||
func value(forKey key: String) -> T? {
|
||||
guard let value = cache.object(forKey: key as NSString) else { return nil }
|
||||
return value
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
//
|
||||
// GraphicsImageRenderer.swift
|
||||
// LifeGameApp (macOS)
|
||||
//
|
||||
// Created by Yusuke Hosonuma on 2020/08/13.
|
||||
//
|
||||
|
||||
#if os(macOS)
|
||||
import AppKit
|
||||
#else
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
final class GraphicsImageRenderer {
|
||||
let size: CGSize
|
||||
|
||||
init(size: CGSize) {
|
||||
self.size = size
|
||||
}
|
||||
|
||||
#if os(macOS)
|
||||
func image(actions: (CGContext) -> ()) -> NSImage {
|
||||
let image = NSImage(size: size)
|
||||
image.lockFocusFlipped(true)
|
||||
actions(NSGraphicsContext.current!.cgContext)
|
||||
image.unlockFocus()
|
||||
return image
|
||||
}
|
||||
#else
|
||||
func image(actions: @escaping (CGContext) -> ()) -> UIImage {
|
||||
UIGraphicsImageRenderer(size: size).image { context in
|
||||
actions(context.cgContext)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -18,7 +18,7 @@ struct BoardSelectCell: View {
|
||||
switch style {
|
||||
case .grid:
|
||||
VStack {
|
||||
BoardThumbnailImage(board: item.makeBoard().extended(by: .die), cacheKey: item.id)
|
||||
BoardThumbnailImage(board: item.makeBoard(), cacheKey: item.id)
|
||||
.matchedGeometryEffect(id: "thumbnail-\(item.title)", in: nspace)
|
||||
|
||||
HStack {
|
||||
@@ -54,7 +54,7 @@ struct BoardSelectCell: View {
|
||||
.foregroundColor(.yellow)
|
||||
}
|
||||
Spacer()
|
||||
BoardThumbnailImage(board: item.makeBoard().extended(by: .die), cacheKey: item.id)
|
||||
BoardThumbnailImage(board: item.makeBoard(), cacheKey: item.id)
|
||||
.frame(width: 60, height: 60, alignment: .center)
|
||||
.matchedGeometryEffect(id: "thumbnail-\(item.title)", in: nspace)
|
||||
}.padding()
|
||||
|
||||
@@ -8,17 +8,29 @@
|
||||
import SwiftUI
|
||||
import LifeGame
|
||||
|
||||
#if os(macOS)
|
||||
private typealias XImage = NSImage
|
||||
#else
|
||||
private typealias XImage = UIImage
|
||||
#endif
|
||||
|
||||
private let cacheStorage = MemoryCacheStorage<XImage>()
|
||||
|
||||
struct BoardThumbnailImage: View {
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
|
||||
var board: Board<Cell>
|
||||
var cellColor: Color?
|
||||
var cacheKey: String?
|
||||
private let board: Board<Cell>
|
||||
private let cellColor: Color?
|
||||
private let cacheKey: String?
|
||||
|
||||
private let cacheStorage = ThumbnailImageCacheStorage.shared
|
||||
init(board: Board<Cell>, cellColor: Color? = nil, cacheKey: String? = nil) {
|
||||
self.board = board.extended(by: .die)
|
||||
self.cellColor = cellColor
|
||||
self.cacheKey = cacheKey
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Image(uiImage: thumbnailImage)
|
||||
Image(image: thumbnailImage)
|
||||
.antialiased(false)
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
@@ -26,19 +38,19 @@ struct BoardThumbnailImage: View {
|
||||
|
||||
private var fillColor: CGColor {
|
||||
if let color = cellColor {
|
||||
return UIColor(color).cgColor
|
||||
return color.cgColor
|
||||
} else {
|
||||
return colorScheme == .dark
|
||||
? UIColor(Color.white).cgColor
|
||||
: UIColor(Color.black).cgColor
|
||||
? Color.white.cgColor
|
||||
: Color.black.cgColor
|
||||
}
|
||||
}
|
||||
|
||||
private var gridColor: CGColor {
|
||||
UIColor(Color.gray.opacity(0.3)).cgColor
|
||||
Color.gray.opacity(0.3).cgColor
|
||||
}
|
||||
|
||||
private var thumbnailImage: UIImage {
|
||||
|
||||
private var thumbnailImage: XImage {
|
||||
guard let cacheKey = cacheKey else {
|
||||
return renderImage()
|
||||
}
|
||||
@@ -53,14 +65,14 @@ struct BoardThumbnailImage: View {
|
||||
return image
|
||||
}
|
||||
}
|
||||
|
||||
private func renderImage() -> UIImage {
|
||||
|
||||
private func renderImage() -> XImage {
|
||||
let scale = max(2, 140 / board.size)
|
||||
let size = CGSize(width: board.size * scale + 1, height: board.size * scale + 1)
|
||||
|
||||
return UIGraphicsImageRenderer(size: size)
|
||||
return GraphicsImageRenderer(size: size)
|
||||
.image(actions: { context in
|
||||
context.cgContext.setFillColor(fillColor)
|
||||
context.setFillColor(fillColor)
|
||||
|
||||
for (index, cell) in board.cells.enumerated() {
|
||||
let x = (index % board.size) * scale
|
||||
@@ -71,7 +83,7 @@ struct BoardThumbnailImage: View {
|
||||
}
|
||||
|
||||
// Draw grid
|
||||
context.cgContext.setFillColor(gridColor)
|
||||
context.setFillColor(gridColor)
|
||||
for index in 0...board.size + 1 {
|
||||
let length = board.size * scale + 1
|
||||
context.fill(CGRect(x: scale * index, y: 0, width: 1, height: length)) // vertical lines
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
//
|
||||
// BoardThumnailView.swift
|
||||
// LifeGameApp (macOS)
|
||||
//
|
||||
// Created by Yusuke Hosonuma on 2020/08/07.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import LifeGame
|
||||
|
||||
struct BoardThumnailView: View {
|
||||
@Environment(\.colorScheme) private var colorScheme: ColorScheme
|
||||
|
||||
var board: LifeGameBoard
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
ForEach(board.rows.withIndex(), id: \.0) { y, row in
|
||||
HStack(spacing: 0) {
|
||||
ForEach(row.withIndex(), id: \.0) { x, cell in
|
||||
CellView(color: cellColor(cell), size: 100.0 / CGFloat(board.size)) // TODO: 盤面サイズで全体の大きさが変わってしまうのでそのうち修正
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func cellColor(_ cell: Cell) -> Color {
|
||||
switch (cell, colorScheme) {
|
||||
case (.die, _): return .clear
|
||||
case (.alive, .light): return .black
|
||||
case (.alive, .dark): return .white
|
||||
@unknown default: fatalError()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct BoardThumnailView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
BoardThumnailView(board: BoardPreset.nebura.board)
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,8 @@ struct PresetListView: View {
|
||||
Section(header: Text("Presets")) {
|
||||
ForEach(boardRepository.items) { item in
|
||||
HStack {
|
||||
BoardThumnailView(board: LifeGameBoard(board: item.makeBoard()))
|
||||
BoardThumbnailImage(board: item.makeBoard(), cacheKey: item.id)
|
||||
.frame(width: 120, height: 120, alignment: .center)
|
||||
Text("\(item.title)")
|
||||
}
|
||||
.contentShape(Rectangle()) // ❗not working in beta4
|
||||
|
||||
Reference in New Issue
Block a user