Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions WatchFaceDumper.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@
EA9D41352520BFD300838E68 /* Face.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA9D41342520BFD300838E68 /* Face.swift */; };
EA9D41382520C00800838E68 /* Resources.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA9D41372520C00800838E68 /* Resources.swift */; };
EA9D41442520C11A00838E68 /* Watchface+FileWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA9D41432520C11A00838E68 /* Watchface+FileWrapper.swift */; };
EA9D8B7527E36AB5002917A9 /* NewDocumentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA9D8B7427E36AB5002917A9 /* NewDocumentViewController.swift */; };
EAA4F2912744F57900AE226B /* PortraitWatchface.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAA4F2902744F57900AE226B /* PortraitWatchface.swift */; };
EAD954F1256E7D5D004EBB02 /* EditableImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAD954F0256E7D5D004EBB02 /* EditableImageView.swift */; };
/* End PBXBuildFile section */

Expand Down Expand Up @@ -71,6 +73,8 @@
EA9D41342520BFD300838E68 /* Face.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Face.swift; sourceTree = "<group>"; };
EA9D41372520C00800838E68 /* Resources.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Resources.swift; sourceTree = "<group>"; };
EA9D41432520C11A00838E68 /* Watchface+FileWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Watchface+FileWrapper.swift"; sourceTree = "<group>"; };
EA9D8B7427E36AB5002917A9 /* NewDocumentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewDocumentViewController.swift; sourceTree = "<group>"; };
EAA4F2902744F57900AE226B /* PortraitWatchface.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PortraitWatchface.swift; sourceTree = "<group>"; };
EAD954F0256E7D5D004EBB02 /* EditableImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditableImageView.swift; sourceTree = "<group>"; };
FE8E86D8C8D1F1C494C8E5E4 /* Pods-WatchFaceDumper.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WatchFaceDumper.release.xcconfig"; path = "Target Support Files/Pods-WatchFaceDumper/Pods-WatchFaceDumper.release.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
Expand Down Expand Up @@ -127,6 +131,7 @@
isa = PBXGroup;
children = (
EA4D31B924FFE37100104A33 /* AppDelegate.swift */,
EA9D8B7427E36AB5002917A9 /* NewDocumentViewController.swift */,
EA4E96FF251267B5008D822B /* WindowController.swift */,
EA4D31BB24FFE37100104A33 /* ViewController.swift */,
EA4E96F82512642D008D822B /* ImageItemRowView.swift */,
Expand All @@ -147,6 +152,7 @@
isa = PBXGroup;
children = (
EA52949D2520DAA400CE938E /* PhotosWatchface.swift */,
EAA4F2902744F57900AE226B /* PortraitWatchface.swift */,
);
path = SpecificWatchfaces;
sourceTree = "<group>";
Expand Down Expand Up @@ -317,6 +323,7 @@
EA9D41252520BE8900838E68 /* ComplicationTemplate.swift in Sources */,
EA4E96F92512642D008D822B /* ImageItemRowView.swift in Sources */,
EA9D41442520C11A00838E68 /* Watchface+FileWrapper.swift in Sources */,
EA9D8B7527E36AB5002917A9 /* NewDocumentViewController.swift in Sources */,
EA4E96FC25126454008D822B /* EditableAVPlayerView.swift in Sources */,
EA9D41382520C00800838E68 /* Resources.swift in Sources */,
EA4E9700251267B5008D822B /* WindowController.swift in Sources */,
Expand All @@ -329,6 +336,7 @@
EAD954F1256E7D5D004EBB02 /* EditableImageView.swift in Sources */,
EA9D412C2520BF5100838E68 /* ImageProvider.swift in Sources */,
EA76F8D8251B94E700B395EC /* MetadataViewModel.swift in Sources */,
EAA4F2912744F57900AE226B /* PortraitWatchface.swift in Sources */,
EA4D31BE24FFE37100104A33 /* Document.swift in Sources */,
EA76F8D3251B906800B395EC /* ImageListOutlineViewModel.swift in Sources */,
);
Expand Down
6 changes: 6 additions & 0 deletions WatchFaceDumper/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,10 @@ import Cocoa

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
@IBAction func newDocument(_ sender: Any?) {
let wc = NSWindowController(window: .init(contentViewController: NewDocumentViewController()))
wc.window?.title = "New watch face"
wc.window?.styleMask = [.titled]
wc.showWindow(nil)
}
}
18 changes: 15 additions & 3 deletions WatchFaceDumper/Document.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@ import ZIPFoundation
import Ikemen

class Document: NSDocument {
var watchface: Watchface = .init(
photosWatchface: PhotosWatchface(
device_size: 2, position: .top, snapshot: Data(), no_borders_snapshot: Data(), topComplication: nil, bottomComplication: nil, resources: .init(images: .init(imageList: []), files: [:])))
var watchface: Watchface
private var isLossyReading = false
private var allowLossyAutosaving = false

Expand All @@ -18,6 +16,20 @@ class Document: NSDocument {
}
}

convenience override init() {
self.init(photos: ())
}

init(photos: Void) {
watchface = .init(photosWatchface: PhotosWatchface(device_size: 2, position: .top, snapshot: Data(), no_borders_snapshot: Data(), topComplication: nil, bottomComplication: nil, resources: .init(images: .photos(.init(imageList: [])), files: [:])))
super.init()
}

init(portrait: Void) {
watchface = .init(portraitWatchface: PortraitWatchface(device_size: 2, style: .style3, snapshot: Data(), no_borders_snapshot: Data(), dateComplication: nil, bottomComplication: nil, resources: .init(images: .init(imageList: []), files: [:])))
super.init()
}

override func makeWindowControllers() {
addWindowController(WindowController(document: self))
}
Expand Down
64 changes: 63 additions & 1 deletion WatchFaceDumper/ImageItemRowView.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import AppKit
import Ikemen
import Combine

final class ImageItemRowView: NSTableRowView {
private let titleLabel = NSTextField(labelWithString: "")
Expand All @@ -20,7 +21,7 @@ final class ImageItemRowView: NSTableRowView {
&& lhs.movie?.data == rhs.movie?.data
&& lhs.movie?.duration == rhs.movie?.duration
}

var image: NSImage?
var movie: (data: Data, duration: Double?)?
}
Expand Down Expand Up @@ -79,3 +80,64 @@ final class ImageItemRowView: NSTableRowView {
movieView.controlsStyle = item.movie != nil ? .minimal : .none
}
}

final class UltraCubeImageItemRowView: NSTableRowView {
private let titleLabel = NSTextField(labelWithString: "")
private let baseImageView = EditableImageView()
private let backImageView = EditableImageView()
private let maskImageView = EditableImageView()

struct ImageItem: Equatable {
var baseImage: NSImage?
var backImage: NSImage?
var maskImage: NSImage?

static func == (lhs: Self, rhs: Self) -> Bool {
lhs.baseImage?.tiffRepresentation == rhs.baseImage?.tiffRepresentation
&& lhs.backImage?.tiffRepresentation == rhs.backImage?.tiffRepresentation
&& lhs.maskImage?.tiffRepresentation == rhs.maskImage?.tiffRepresentation
}
}

@Published var item: ImageItem {
didSet {
reloadItem()
}
}

init(item: ImageItem) {
self.item = item
super.init(frame: .zero)

let inpaintButton = NSButton(title: "Inpaint...", target: self, action: nil)

let autolayout = northLayoutFormat([:], [
"title": titleLabel,
"base": baseImageView ※ {$0.imageDidChange = {[weak self] in self?.item.baseImage = $0}},
"back": backImageView ※ {$0.imageDidChange = {[weak self] in self?.item.backImage = $0}},
"mask": maskImageView ※ {$0.imageDidChange = {[weak self] in self?.item.maskImage = $0}},
"inpaint": inpaintButton])
autolayout("H:|-[title]-|")
autolayout("H:|-[base]-[back(base)]-[mask(base)]-|")
autolayout("V:|-[title]-[base(240)]-|")
autolayout("V:|-[title]-[back(base)]-|")
autolayout("V:|-[title]-[mask(base)]-|")
autolayout("V:[inpaint]-|")
inpaintButton.centerXAnchor.constraint(equalTo: backImageView.centerXAnchor).isActive = true
addSubview(inpaintButton, positioned: .above, relativeTo: nil)

reloadItem()
}

required init?(coder: NSCoder) {fatalError("init(coder:) has not been implemented")}

private func reloadItem() {
titleLabel.stringValue = [
item.baseImage.map {"\(Int($0.size.width))×\(Int($0.size.height))"} ?? "no image (Portrait)",
(item.backImage != nil && item.maskImage != nil) ? "Portrait Photo" : "(Missing Portrait Support)"
].joined(separator: ", ")
baseImageView.image = item.baseImage
backImageView.image = item.backImage
maskImageView.image = item.maskImage
}
}
9 changes: 7 additions & 2 deletions WatchFaceDumper/ImageListOutlineViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,13 @@ final class ImageListOutlineViewModel: NSObject, NSOutlineViewDelegate, NSOutlin
}

func setWatchface(_ watchface: Watchface) {
imageListPropertyList = (try? PropertyListEncoder().encode(watchface.resources?.images.imageList))
.flatMap {try? PropertyListSerialization.propertyList(from: $0, options: [], format: nil)} as? [Any]
let imageList: Data?
switch watchface.resources?.images {
case .photos(let v)?: imageList = try? PropertyListEncoder().encode(v.imageList)
case .ultraCube(let v)?: imageList = try? PropertyListEncoder().encode(v.imageList)
case nil: imageList = nil
}
imageListPropertyList = imageList.flatMap {try? PropertyListSerialization.propertyList(from: $0, options: [], format: nil)} as? [Any]
}

func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
Expand Down
53 changes: 53 additions & 0 deletions WatchFaceDumper/NewDocumentViewController.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import NorthLayout
import Ikemen

class NewDocumentViewController: NSViewController {
private lazy var photosRadioButton: NSButton = NSButton(radioButtonWithTitle: "Photos", target: self, action: #selector(faceTypeChanged(_:))) ※ {
$0.state = .on
}
private lazy var portraitRadioButton: NSButton = NSButton(radioButtonWithTitle: "Portrait", target: self, action: #selector(faceTypeChanged(_:)))

override func loadView() {
view = NSView()
}

override func viewDidLoad() {
super.viewDidLoad()

let autolayout = view.northLayoutFormat(["p": 20], [
"photos": photosRadioButton,
"portrait": portraitRadioButton,
"cancel": NSButton(title: "Cancel", target: self, action: #selector(cancel(_:))) ※ {
$0.keyEquivalent = "\u{1b}" // esc
},
"new": NSButton(title: "New", target: self, action: #selector(new(_:))) ※ {
$0.keyEquivalent = "\r"
},])
autolayout("H:|-p-[photos]-p-|")
autolayout("H:|-p-[portrait]-p-|")
autolayout("H:|-(>=p)-[cancel(new)]-p-[new]-p-|")
autolayout("V:|-p-[photos]-[portrait]-(>=p)-[cancel]-p-|")
autolayout("V:[new]-p-|")
}

@IBAction func faceTypeChanged(_ sender: Any?) {
}

@IBAction func cancel(_ sender: Any?) {
view.window?.close()
}

@IBAction func new(_ sender: Any?) {
if photosRadioButton.state == .on {
openNewDocument(.init(photos: ()))
} else if portraitRadioButton.state == .on {
openNewDocument(.init(portrait: ()))
}
view.window?.close()
}

func openNewDocument(_ document: Document) {
document.makeWindowControllers()
document.showWindows()
}
}
Loading