diff --git a/MacWidget/MacWidgetBundle.swift b/MacWidget/MacWidgetBundle.swift index b4e078c..faea024 100644 --- a/MacWidget/MacWidgetBundle.swift +++ b/MacWidget/MacWidgetBundle.swift @@ -13,7 +13,7 @@ struct MacWidgetBundle: WidgetBundle { DataContainer.shared.loadSave() LocationManager.shared.requestLocation(completion: nil) } - + @WidgetBundleBuilder var body: some Widget { SmallWidget() diff --git a/Shared/Delegates.swift b/Shared/Delegates.swift index f229cb9..37566f0 100644 --- a/Shared/Delegates.swift +++ b/Shared/Delegates.swift @@ -5,8 +5,8 @@ // Created by Leo Liu on 5/9/23. // -import CoreLocation import CoreData +import CoreLocation #if os(macOS) import SystemConfiguration #elseif os(iOS) @@ -31,6 +31,7 @@ class LocationManager: NSObject, ObservableObject, CLLocationManagerDelegate { } } } + let manager = CLLocationManager() private var completion: ((CGPoint?) -> Void)? private var lastUpdated = Date.distantPast @@ -56,14 +57,13 @@ class LocationManager: NSObject, ObservableObject, CLLocationManagerDelegate { case .authorizedWhenInUse, .authorizedAlways: manager.startUpdatingLocation() #endif - case .notDetermined: // Authorization not determined yet. + case .notDetermined: // Authorization not determined yet. manager.requestWhenInUseAuthorization() default: if let completion = completion { completion(nil) self.completion = nil } - break } } } @@ -82,29 +82,27 @@ class LocationManager: NSObject, ObservableObject, CLLocationManagerDelegate { func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) { switch manager.authorizationStatus { #if os(macOS) - case .authorized, .authorizedAlways: // Location services are available. + case .authorized, .authorizedAlways: // Location services are available. requestLocation(completion: nil) #else - case .authorizedWhenInUse, .authorizedAlways: // Location services are available. + case .authorizedWhenInUse, .authorizedAlways: // Location services are available. requestLocation(completion: nil) #endif case .restricted, .denied: break - case .notDetermined: // Authorization not determined yet. + case .notDetermined: // Authorization not determined yet. manager.requestWhenInUseAuthorization() @unknown default: print("Unhandled Location Authorization Case") - break } } func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { print(error) } - } class DataContainer: ObservableObject { @@ -112,11 +110,11 @@ class DataContainer: ObservableObject { lazy var persistentContainer: NSPersistentCloudKitContainer = { /* - The persistent container for the application. This implementation - creates and returns a container, having loaded the store for the - application to it. This property is optional since there are legitimate - error conditions that could cause the creation of the store to fail. - */ + The persistent container for the application. This implementation + creates and returns a container, having loaded the store for the + application to it. This property is optional since there are legitimate + error conditions that could cause the creation of the store to fail. + */ let container = NSPersistentCloudKitContainer(name: "ChineseTime") #if DEBUG do { @@ -126,21 +124,21 @@ class DataContainer: ObservableObject { print(error.localizedDescription) } #endif - #if os(macOS) +#if os(macOS) let prefix = Bundle.main.object(forInfoDictionaryKey: "GroupID") as! String let url = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: prefix)! let description = NSPersistentStoreDescription(url: url.appendingPathComponent("ChineseTime")) - #elseif os(iOS) +#elseif os(iOS) let url = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.ChineseTime")! let description = NSPersistentStoreDescription(url: url.appendingPathComponent("ChineseTime.sqlite")) - #elseif os(watchOS) +#elseif os(watchOS) let url = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.ChineseTime.Watch")! let description = NSPersistentStoreDescription(url: url.appendingPathComponent("ChineseTime.sqlite")) - #endif +#endif description.configuration = "Cloud" description.cloudKitContainerOptions = NSPersistentCloudKitContainerOptions(containerIdentifier: "iCloud.YLiu.ChineseTime") container.persistentStoreDescriptions = [description] - container.loadPersistentStores(completionHandler: { (storeDescription, error) in + container.loadPersistentStores(completionHandler: { _, error in if let error = error { // Replace this implementation with code to handle the error appropriately. // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. @@ -159,7 +157,7 @@ class DataContainer: ObservableObject { return container }() - func saveContext () { + func saveContext() { let context = persistentContainer.viewContext if context.hasChanges { do { @@ -188,13 +186,13 @@ class DataContainer: ObservableObject { } func readSave(name: String? = nil, deviceName: String? = nil) -> String? { - try? persistentContainer.viewContext.setQueryGenerationFrom(.current) - let managedContext = self.persistentContainer.viewContext + let managedContext = persistentContainer.viewContext let fetchRequest = NSFetchRequest(entityName: "Layout") fetchRequest.predicate = NSPredicate(format: "(name == %@) AND (deviceName == %@)", argumentArray: [name ?? NSLocalizedString("Default", comment: "Default save file name"), deviceName ?? self.deviceName]) if let fetchedEntities = try? managedContext.fetch(fetchRequest), - let retrievedLayout = fetchedEntities.last?.value(forKey: "code") as? String { + let retrievedLayout = fetchedEntities.last?.value(forKey: "code") as? String + { return retrievedLayout } else { return nil @@ -202,13 +200,13 @@ class DataContainer: ObservableObject { } func loadSave(name: String? = nil, deviceName: String? = nil) { - try? persistentContainer.viewContext.setQueryGenerationFrom(.current) - let managedContext = self.persistentContainer.viewContext + let managedContext = persistentContainer.viewContext let fetchRequest = NSFetchRequest(entityName: "Layout") fetchRequest.predicate = NSPredicate(format: "(name == %@) AND (deviceName == %@)", argumentArray: [name ?? NSLocalizedString("Default", comment: "Default save file name"), deviceName ?? self.deviceName]) if let fetchedEntities = try? managedContext.fetch(fetchRequest), - let savedLayout = fetchedEntities.last?.value(forKey: "code") as? String { + let savedLayout = fetchedEntities.last?.value(forKey: "code") as? String + { WatchLayout.shared.update(from: savedLayout) } else { let filePath = Bundle.main.path(forResource: "layout", ofType: "txt")! @@ -225,26 +223,26 @@ class DataContainer: ObservableObject { func listAll() -> [SavedTheme] { try? persistentContainer.viewContext.setQueryGenerationFrom(.current) - let managedContext = self.persistentContainer.viewContext + let managedContext = persistentContainer.viewContext let fetchRequest = NSFetchRequest(entityName: "Layout") var results = [SavedTheme]() if let fetchedEntities = try? managedContext.fetch(fetchRequest) { for entity in fetchedEntities { results.append(SavedTheme(name: (entity.value(forKey: "name") as? String) ?? NSLocalizedString("神祕檔", comment: "Unknown saved file"), deviceName: (entity.value(forKey: "deviceName") as? String) ?? "", - modifiedDate: (entity.value(forKey: "modifiedDate") as? Date) ?? Date.distantPast - )) + modifiedDate: (entity.value(forKey: "modifiedDate") as? Date) ?? Date.distantPast)) } } return results } func renameSave(name: String, deviceName: String, newName: String) { - let managedContext = self.persistentContainer.viewContext + let managedContext = persistentContainer.viewContext let fetchRequest = NSFetchRequest(entityName: "Layout") fetchRequest.predicate = NSPredicate(format: "(name == %@) AND (deviceName == %@)", argumentArray: [name, deviceName]) if let fetchedEntities = try? managedContext.fetch(fetchRequest), - fetchedEntities.count > 0 { + fetchedEntities.count > 0 + { let savedLayout = fetchedEntities.last! savedLayout.setValue(newName, forKey: "name") do { @@ -256,7 +254,7 @@ class DataContainer: ObservableObject { } func deleteSave(name: String, deviceName: String) { - let managedContext = self.persistentContainer.viewContext + let managedContext = persistentContainer.viewContext let fetchRequest = NSFetchRequest(entityName: "Layout") if name == NSLocalizedString("神祕檔", comment: "Unknown saved file") { fetchRequest.predicate = NSPredicate(format: "name == NULL OR name == ''") @@ -277,14 +275,14 @@ class DataContainer: ObservableObject { } func saveLayout(_ layout: String, name: String? = nil) { - let managedContext = self.persistentContainer.viewContext + let managedContext = persistentContainer.viewContext managedContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy let fetchRequest = NSFetchRequest(entityName: "Layout") fetchRequest.predicate = NSPredicate(format: "(name == %@) AND (deviceName == %@)", argumentArray: [name ?? NSLocalizedString("Default", comment: "Default save file name"), deviceName]) let savedLayout: NSManagedObject if let fetchedEntities = try? managedContext.fetch(fetchRequest), fetchedEntities.count > 0 { savedLayout = fetchedEntities.last! - for i in 0..<(fetchedEntities.count-1) { + for i in 0..<(fetchedEntities.count - 1) { managedContext.delete(fetchedEntities[i]) } } else { @@ -294,7 +292,7 @@ class DataContainer: ObservableObject { savedLayout.setValue(layout, forKey: "code") savedLayout.setValue(Date(), forKey: "modifiedDate") savedLayout.setValue(name ?? NSLocalizedString("Default", comment: "Default save file name"), forKey: "name") - savedLayout.setValue(self.deviceName, forKey: "deviceName") + savedLayout.setValue(deviceName, forKey: "deviceName") do { try managedContext.save() } catch let error as NSError { diff --git a/Shared/MetaLayout.swift b/Shared/MetaLayout.swift index 8af23af..a39cdf7 100644 --- a/Shared/MetaLayout.swift +++ b/Shared/MetaLayout.swift @@ -5,26 +5,28 @@ // Created by Leo Liu on 4/27/23. // -import Foundation import CoreGraphics +import Foundation let displayP3 = CGColorSpace(name: CGColorSpace.displayP3)! extension CGColor { var hexCode: String { var colorString = "0x" - let colorWithColorspace = self.converted(to: displayP3, intent: .defaultIntent, options: nil) ?? self - colorString += String(format:"%02X", Int(round(colorWithColorspace.components![3] * 255))) - colorString += String(format:"%02X", Int(round(colorWithColorspace.components![2] * 255))) - colorString += String(format:"%02X", Int(round(colorWithColorspace.components![1] * 255))) - colorString += String(format:"%02X", Int(round(colorWithColorspace.components![0] * 255))) + let colorWithColorspace = converted(to: displayP3, intent: .defaultIntent, options: nil) ?? self + colorString += String(format: "%02X", Int(round(colorWithColorspace.components![3] * 255))) + colorString += String(format: "%02X", Int(round(colorWithColorspace.components![2] * 255))) + colorString += String(format: "%02X", Int(round(colorWithColorspace.components![1] * 255))) + colorString += String(format: "%02X", Int(round(colorWithColorspace.components![0] * 255))) return colorString } } + extension CGPoint { func encode() -> String { - return "x: \(self.x), y: \(self.y)" + return "x: \(x), y: \(y)" } + init?(from str: String?) { self.init() guard let str = str else { return nil } @@ -32,7 +34,8 @@ extension CGPoint { let matches = regex.matches(in: str, range: NSMakeRange(0, str.endIndex.utf16Offset(in: str))) if !matches.isEmpty, let x = Double((str as NSString).substring(with: matches[0].range(at: 1))), - let y = Double((str as NSString).substring(with: matches[0].range(at: 2))) { + let y = Double((str as NSString).substring(with: matches[0].range(at: 2))) + { self.x = x self.y = y } else { @@ -42,17 +45,17 @@ extension CGPoint { } protocol OptionalType { - associatedtype Wrapped - var optional: Wrapped? { get } + associatedtype Wrapped + var optional: Wrapped? { get } } extension Optional: OptionalType { - var optional: Self { self } + var optional: Self { self } } -extension Array where Element : OptionalType { - func flattened() -> Array? { - var newArray = Array() +extension Array where Element: OptionalType { + func flattened() -> [Element.Wrapped]? { + var newArray = [Element.Wrapped]() for item in self { if item.optional == nil { return nil @@ -66,24 +69,25 @@ extension Array where Element : OptionalType { extension Date { func convertToTimeZone(initTimeZone: TimeZone, timeZone: TimeZone) -> Date { - let delta = TimeInterval(timeZone.secondsFromGMT(for: self) - initTimeZone.secondsFromGMT(for: self)) - return addingTimeInterval(delta) + let delta = TimeInterval(timeZone.secondsFromGMT(for: self) - initTimeZone.secondsFromGMT(for: self)) + return addingTimeInterval(delta) } } extension String { var floatValue: CGFloat? { - let string = self.trimmingCharacters(in: .whitespaces) + let string = trimmingCharacters(in: .whitespaces) guard !string.isEmpty else { return nil } - return Double(string).map {CGFloat($0)} + return Double(string).map { CGFloat($0) } } + var boolValue: Bool? { - guard !self.isEmpty else { + guard !isEmpty else { return nil } - let trimmedString = self.trimmingCharacters(in: .whitespaces).lowercased() + let trimmedString = trimmingCharacters(in: .whitespaces).lowercased() if ["true", "yes"].contains(trimmedString) { return true } else if ["false", "no"].contains(trimmedString) { @@ -92,14 +96,15 @@ extension String { return nil } } + var colorValue: CGColor? { - let string = self.trimmingCharacters(in: .whitespaces) + let string = trimmingCharacters(in: .whitespaces) guard !string.isEmpty else { return nil } var r = 0, g = 0, b = 0, a = 0xff - if (string.count == 10) { - // 0xffccbbaa + if string.count == 10 { + // 0xffccbbaa let regex = try! NSRegularExpression(pattern: "^0x([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$", options: .caseInsensitive) let matches = regex.matches(in: string, options: .init(rawValue: 0), range: NSMakeRange(0, string.endIndex.utf16Offset(in: string))) if matches.count == 1 { @@ -110,19 +115,19 @@ extension String { } else { return nil } - } else if (string.count == 8) { - // 0xccbbaa - let regex = try! NSRegularExpression(pattern: "^0x([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$", options: .caseInsensitive) - let matches = regex.matches(in: string, options: .init(rawValue: 0), range: NSMakeRange(0, string.endIndex.utf16Offset(in: string))) - if matches.count == 1 { - r = Int((string as NSString).substring(with: matches[0].range(at: 3)), radix: 16)! - g = Int((string as NSString).substring(with: matches[0].range(at: 2)), radix: 16)! - b = Int((string as NSString).substring(with: matches[0].range(at: 1)), radix: 16)! - } else { - return nil - } + } else if string.count == 8 { + // 0xccbbaa + let regex = try! NSRegularExpression(pattern: "^0x([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$", options: .caseInsensitive) + let matches = regex.matches(in: string, options: .init(rawValue: 0), range: NSMakeRange(0, string.endIndex.utf16Offset(in: string))) + if matches.count == 1 { + r = Int((string as NSString).substring(with: matches[0].range(at: 3)), radix: 16)! + g = Int((string as NSString).substring(with: matches[0].range(at: 2)), radix: 16)! + b = Int((string as NSString).substring(with: matches[0].range(at: 1)), radix: 16)! + } else { + return nil + } } - return CGColor(colorSpace: displayP3, components: [CGFloat(r) / 255, CGFloat(g) / 255, CGFloat(b) / 255, CGFloat(a) / 255]) + return CGColor(colorSpace: displayP3, components: [CGFloat(r)/255, CGFloat(g)/255, CGFloat(b)/255, CGFloat(a)/255]) } } @@ -140,9 +145,9 @@ class MetaWatchLayout { colorAndLocation.sort { former, latter in former.1 < latter.1 } - self._locations = colorAndLocation.map { $0.1 } - self._colors = colorAndLocation.map { $0.0 } - self.isLoop = loop + _locations = colorAndLocation.map { $0.1 } + _colors = colorAndLocation.map { $0.0 } + isLoop = loop } func interpolate(at: CGFloat) -> CGColor { @@ -154,11 +159,11 @@ class MetaWatchLayout { if previousIndex >= locations.startIndex { let leftColor = colors[previousIndex] let rightColor = colors[nextIndex] - let ratio = (at - locations[previousIndex]) / (locations[nextIndex] - locations[previousIndex]) + let ratio = (at - locations[previousIndex])/(locations[nextIndex] - locations[previousIndex]) guard ratio <= 1 && ratio >= 0 else { fatalError() } let leftComponents = leftColor.converted(to: displayP3, intent: .perceptual, options: nil)!.components! let rightComponents = rightColor.converted(to: displayP3, intent: .perceptual, options: nil)!.components! - let newComponents = zip(leftComponents, rightComponents).map { (1-ratio) * $0.0 + ratio * $0.1 } + let newComponents = zip(leftComponents, rightComponents).map { (1 - ratio) * $0.0 + ratio * $0.1 } let newColor = CGColor(colorSpace: displayP3, components: newComponents) return newColor! } else { @@ -176,6 +181,7 @@ class MetaWatchLayout { return _locations } } + var colors: [CGColor] { if isLoop { return _colors + [_colors[0]] @@ -197,7 +203,7 @@ class MetaWatchLayout { init?(from str: String?) { guard let str = str else { return nil } let regex = try! NSRegularExpression(pattern: "([a-z_0-9]+)\\s*:[\\s\"]*([^\\s\"#][^\"#]*)[\\s\"#]*(#*.*)$", options: .caseInsensitive) - var values = Dictionary() + var values = [String: String]() for line in str.split(whereSeparator: \.isNewline) { let line = String(line) let matches = regex.matches(in: line, options: .init(rawValue: 0), range: NSMakeRange(0, line.endIndex.utf16Offset(in: line))) @@ -209,12 +215,12 @@ class MetaWatchLayout { let locations = Array(newLocations).map { $0.trimmingCharacters(in: .whitespaces).floatValue } let colors = Array(newColors).map { $0.trimmingCharacters(in: .whitespaces).colorValue } guard let loc = locations.flattened(), let col = colors.flattened() else { return nil } - self._locations = loc - self._colors = col + _locations = loc + _colors = col self.isLoop = isLoop } - } + var location: CGPoint? var firstRing: Gradient var secondRing: Gradient @@ -278,19 +284,19 @@ class MetaWatchLayout { oddSolarTermTickColor = CGColor(colorSpace: displayP3, components: [102.0/255.0, 102.0/255.0, 102.0/255.0, 1.0])! evenSolarTermTickColorDark = CGColor(gray: 1.0, alpha: 1.0) oddSolarTermTickColorDark = CGColor(colorSpace: displayP3, components: [153.0/255.0, 153.0/255.0, 153.0/255.0, 1.0])! - planetIndicator = [CGColor(colorSpace: displayP3, components: [10.0/255.0, 30.0/255.0, 60.0/255.0, 1.0])!, //Mercury - CGColor(colorSpace: displayP3, components: [200.0/255.0, 190.0/255.0, 170.0/255.0, 1.0])!, //Venus - CGColor(colorSpace: displayP3, components: [210.0/255.0, 48.0/255.0, 40.0/255.0, 1.0])!, //Mars - CGColor(colorSpace: displayP3, components: [60.0/255.0, 180.0/255.0, 90.0/255.0, 1.0])!, //Jupyter - CGColor(colorSpace: displayP3, components: [170.0/255.0, 150.0/255.0, 50.0/255.0, 1.0])!, //Saturn - CGColor(colorSpace: displayP3, components: [220.0/255.0, 200.0/255.0, 60.0/255.0, 1.0])!] //Moon - sunPositionIndicator = [CGColor(colorSpace: displayP3, components: [0/255.0, 0/255.0, 0/255.0, 1.0])!, //Mid Night - CGColor(colorSpace: displayP3, components: [255.0/255.0, 80.0/255.0, 10.0/255.0, 1.0])!, //Sunrise - CGColor(colorSpace: displayP3, components: [210.0/255.0, 170.0/255.0, 120.0/255.0, 1.0])!, //Noon - CGColor(colorSpace: displayP3, components: [230.0/255.0, 120.0/255.0, 30.0/255.0, 1.0])!] //Sunset - moonPositionIndicator = [CGColor(colorSpace: displayP3, components: [190.0/255.0, 210.0/255.0, 30.0/255.0, 1.0])!, //Moon rise - CGColor(colorSpace: displayP3, components: [255.0/255.0, 255.0/255.0, 50.0/255.0, 1.0])!, //Moon at meridian - CGColor(colorSpace: displayP3, components: [120.0/255.0, 30.0/255.0, 150.0/255.0, 1.0])!] //Moon set + planetIndicator = [CGColor(colorSpace: displayP3, components: [10.0/255.0, 30.0/255.0, 60.0/255.0, 1.0])!, // Mercury + CGColor(colorSpace: displayP3, components: [200.0/255.0, 190.0/255.0, 170.0/255.0, 1.0])!, // Venus + CGColor(colorSpace: displayP3, components: [210.0/255.0, 48.0/255.0, 40.0/255.0, 1.0])!, // Mars + CGColor(colorSpace: displayP3, components: [60.0/255.0, 180.0/255.0, 90.0/255.0, 1.0])!, // Jupyter + CGColor(colorSpace: displayP3, components: [170.0/255.0, 150.0/255.0, 50.0/255.0, 1.0])!, // Saturn + CGColor(colorSpace: displayP3, components: [220.0/255.0, 200.0/255.0, 60.0/255.0, 1.0])!] // Moon + sunPositionIndicator = [CGColor(colorSpace: displayP3, components: [0/255.0, 0/255.0, 0/255.0, 1.0])!, // Mid Night + CGColor(colorSpace: displayP3, components: [255.0/255.0, 80.0/255.0, 10.0/255.0, 1.0])!, // Sunrise + CGColor(colorSpace: displayP3, components: [210.0/255.0, 170.0/255.0, 120.0/255.0, 1.0])!, // Noon + CGColor(colorSpace: displayP3, components: [230.0/255.0, 120.0/255.0, 30.0/255.0, 1.0])!] // Sunset + moonPositionIndicator = [CGColor(colorSpace: displayP3, components: [190.0/255.0, 210.0/255.0, 30.0/255.0, 1.0])!, // Moon rise + CGColor(colorSpace: displayP3, components: [255.0/255.0, 255.0/255.0, 50.0/255.0, 1.0])!, // Moon at meridian + CGColor(colorSpace: displayP3, components: [120.0/255.0, 30.0/255.0, 150.0/255.0, 1.0])!] // Moon set eclipseIndicator = CGColor(colorSpace: displayP3, components: [50.0/255.0, 68.0/255.0, 96.0/255.0, 1.0])! fullmoonIndicator = CGColor(colorSpace: displayP3, components: [255.0/255.0, 239.0/255.0, 59.0/255.0, 1.0])! oddStermIndicator = CGColor(colorSpace: displayP3, components: [153.0/255.0, 153.0/255.0, 153.0/255.0, 1.0])! @@ -330,13 +336,13 @@ class MetaWatchLayout { encoded += "fontColorDark: \(fontColorDark.hexCode)\n" encoded += "evenSolarTermTickColorDark: \(evenSolarTermTickColorDark.hexCode)\n" encoded += "oddSolarTermTickColorDark: \(oddSolarTermTickColorDark.hexCode)\n" - encoded += "planetIndicator: \(planetIndicator.map {$0.hexCode}.joined(separator: ", "))\n" + encoded += "planetIndicator: \(planetIndicator.map { $0.hexCode }.joined(separator: ", "))\n" encoded += "eclipseIndicator: \(eclipseIndicator.hexCode)\n" encoded += "fullmoonIndicator: \(fullmoonIndicator.hexCode)\n" encoded += "oddStermIndicator: \(oddStermIndicator.hexCode)\n" encoded += "evenStermIndicator: \(evenStermIndicator.hexCode)\n" - encoded += "sunPositionIndicator: \(sunPositionIndicator.map {$0.hexCode}.joined(separator: ", "))\n" - encoded += "moonPositionIndicator: \(moonPositionIndicator.map {$0.hexCode}.joined(separator: ", "))\n" + encoded += "sunPositionIndicator: \(sunPositionIndicator.map { $0.hexCode }.joined(separator: ", "))\n" + encoded += "moonPositionIndicator: \(moonPositionIndicator.map { $0.hexCode }.joined(separator: ", "))\n" encoded += "shadeAlpha: \(shadeAlpha)\n" if includeOffset { encoded += "centerTextOffset: \(centerTextOffset)\n" @@ -349,9 +355,9 @@ class MetaWatchLayout { return encoded } - func extract(from str: String) -> Dictionary { + func extract(from str: String) -> [String: String] { let regex = try! NSRegularExpression(pattern: "^([a-z_0-9]+)\\s*:[\\s\"]*([^\\s\"#][^\"#]*)[\\s\"#]*(#*.*)$", options: .caseInsensitive) - var values = Dictionary() + var values = [String: String]() for line in str.split(whereSeparator: \.isNewline) { let line = String(line) let matches = regex.matches(in: line, options: .init(rawValue: 0), range: NSMakeRange(0, line.endIndex.utf16Offset(in: line))) @@ -362,7 +368,7 @@ class MetaWatchLayout { return values } - func update(from values: Dictionary) { + func update(from values: [String: String]) { let seperatorRegex = try! NSRegularExpression(pattern: "(\\s*;|\\{\\})", options: .caseInsensitive) func readGradient(value: String?) -> Gradient? { guard let value = value else { return nil } @@ -406,13 +412,13 @@ class MetaWatchLayout { evenSolarTermTickColorDark = values["evenSolarTermTickColorDark"]?.colorValue ?? evenSolarTermTickColorDark oddSolarTermTickColorDark = values["oddSolarTermTickColorDark"]?.colorValue ?? oddSolarTermTickColorDark if let colourList = readColorList(values["planetIndicator"]), colourList.count == self.planetIndicator.count { - self.planetIndicator = colourList + planetIndicator = colourList } if let colourList = readColorList(values["sunPositionIndicator"]), colourList.count == self.sunPositionIndicator.count { - self.sunPositionIndicator = colourList + sunPositionIndicator = colourList } if let colourList = readColorList(values["moonPositionIndicator"]), colourList.count == self.moonPositionIndicator.count { - self.moonPositionIndicator = colourList + moonPositionIndicator = colourList } eclipseIndicator = values["eclipseIndicator"]?.colorValue ?? eclipseIndicator fullmoonIndicator = values["fullmoonIndicator"]?.colorValue ?? fullmoonIndicator diff --git a/Shared/MetaWatchFace.swift b/Shared/MetaWatchFace.swift index 5d95a2c..a7a1ec1 100644 --- a/Shared/MetaWatchFace.swift +++ b/Shared/MetaWatchFace.swift @@ -5,8 +5,8 @@ // Created by Leo Liu on 4/27/23. // -import Foundation import CoreGraphics +import Foundation import QuartzCore.CoreAnimation let helpString: String = NSLocalizedString("介紹全文", comment: "Markdown formatted Wiki") @@ -83,10 +83,9 @@ struct EntityNote { extension CALayer { private static let majorUpdateInterval: CGFloat = 3600 - private static let minorUpdateInterval: CGFloat = majorUpdateInterval / 12 + private static let minorUpdateInterval: CGFloat = majorUpdateInterval/12 func update(dirtyRect: CGRect, isDark: Bool, watchLayout: WatchLayout, chineseCalendar: ChineseCalendar, graphicArtifects: GraphicArtifects, keyStates: KeyStates, phase: StartingPhase) -> [EntityNote] { - func angleMask(angle: CGFloat, startingAngle: CGFloat, in circle: RoundedRect) -> CAShapeLayer { return shapeFrom(path: anglePath(angle: angle, startingAngle: startingAngle, in: circle)) } @@ -98,7 +97,7 @@ extension CALayer { gradientLayer.type = .conic if startingAngle >= 0 { gradientLayer.colors = gradient.colors.reversed() - gradientLayer.locations = gradient.locations.map { NSNumber(value: Double(1-$0)) }.reversed() + gradientLayer.locations = gradient.locations.map { NSNumber(value: Double(1 - $0)) }.reversed() } else { gradientLayer.colors = gradient.colors gradientLayer.locations = gradient.locations.map { NSNumber(value: Double($0)) } @@ -133,7 +132,7 @@ extension CALayer { func drawMark(at locations: [ChineseCalendar.NamedPosition], on ring: RoundedRect, startingAngle: CGFloat, maskPath: CGPath, colors: [CGColor], radius: CGFloat, positions: inout [EntityNote]) -> CALayer { let marks = CALayer() - let validLocations = locations.filter { 0 <= $0.pos && 1 > $0.pos } + let validLocations = locations.filter { $0.pos >= 0 && $0.pos < 1 } let points = ring.arcPoints(lambdas: changePhase(phase: startingAngle, angles: validLocations.map { CGFloat($0.pos) })) for i in 0.. CGFloat.pi / 4 && angle < CGFloat.pi * 3/4) || (angle > CGFloat.pi * 5/4 && angle < CGFloat.pi * 7/4) { + if (angle > CGFloat.pi/4 && angle < CGFloat.pi * 3/4) || (angle > CGFloat.pi * 5/4 && angle < CGFloat.pi * 7/4) { let shift = pow(size, 0.9) * watchLayout.verticalTextOffset - textLayer.frame = CGRect(x: at.x - box.width/2 - shift, y: at.y - box.height*1.8/2, width: box.width, height: box.height*1.8) + textLayer.frame = CGRect(x: at.x - box.width/2 - shift, y: at.y - box.height * 1.8/2, width: box.width, height: box.height * 1.8) attrStr.addAttributes([.verticalGlyphForm: 1], range: NSMakeRange(0, str.utf16.count)) } else { let shift = pow(size, 0.9) * watchLayout.horizontalTextOffset @@ -197,12 +196,12 @@ extension CALayer { textLayer.alignmentMode = .center var boxTransform = CGAffineTransform(translationX: -at.x, y: -at.y) let transform: CGAffineTransform - if angle <= CGFloat.pi / 4 { + if angle <= CGFloat.pi/4 { transform = CGAffineTransform(rotationAngle: -angle) } else if angle < CGFloat.pi * 3/4 { - transform = CGAffineTransform(rotationAngle: CGFloat.pi-angle) + transform = CGAffineTransform(rotationAngle: CGFloat.pi - angle) } else if angle < CGFloat.pi * 5/4 { - transform = CGAffineTransform(rotationAngle: CGFloat.pi-angle) + transform = CGAffineTransform(rotationAngle: CGFloat.pi - angle) } else if angle < CGFloat.pi * 7/4 { transform = CGAffineTransform(rotationAngle: -angle) } else { @@ -226,7 +225,7 @@ extension CALayer { attrStr.addAttributes([.font: font, .foregroundColor: CGColor(gray: 1, alpha: 1)], range: NSMakeRange(0, str.utf16.count)) let box = attrStr.boundingRect(with: CGSizeZero, options: .usesLineFragmentOrigin, context: .none) if rotate { - textLayer.frame = CGRect(x: center.x - box.width/2 - offset, y: center.y - box.height*2.3/2, width: box.width, height: box.height*2.3) + textLayer.frame = CGRect(x: center.x - box.width/2 - offset, y: center.y - box.height * 2.3/2, width: box.width, height: box.height * 2.3) textLayer.setAffineTransform(CGAffineTransform(rotationAngle: CGFloat.pi/2)) attrStr.addAttributes([.verticalGlyphForm: 1], range: NSMakeRange(0, str.utf16.count)) } else { @@ -252,7 +251,7 @@ extension CALayer { let ringShadow = applyGradient(to: ringPath, gradient: gradient, alpha: watchLayout.shadeAlpha, startingAngle: startingAngle) ringLayer.addSublayer(ringShadow) - let ringMinorTicksPath = roundedRect.arcPosition(lambdas: changePhase(phase: startingAngle, angles: ticks.minorTicks.map{CGFloat($0)}), width: 0.1 * shortEdge) + let ringMinorTicksPath = roundedRect.arcPosition(lambdas: changePhase(phase: startingAngle, angles: ticks.minorTicks.map { CGFloat($0) }), width: 0.1 * shortEdge) let ringMinorTicks = CAShapeLayer() ringMinorTicks.path = ringMinorTicksPath @@ -278,7 +277,7 @@ extension CALayer { ringMinorTicksMask.addSublayer(ringCrust) ringMinorTicksMask.addSublayer(ringBase) - let ringMajorTicksPath = roundedRect.arcPosition(lambdas: changePhase(phase: startingAngle, angles: ticks.majorTicks.map{CGFloat($0)}), width: 0.15 * shortEdge) + let ringMajorTicksPath = roundedRect.arcPosition(lambdas: changePhase(phase: startingAngle, angles: ticks.majorTicks.map { CGFloat($0) }), width: 0.15 * shortEdge) let ringMajorTicks = CAShapeLayer() ringMajorTicks.path = ringMajorTicksPath @@ -308,7 +307,7 @@ extension CALayer { finishedRingLayer.addSublayer(ringMajorTicks) finishedRingLayer.addSublayer(textLayers) - let textRing = roundedRect.shrink(by: (GraphicArtifects.paddedWidth - 0.005) / 2 * shortEdge) + let textRing = roundedRect.shrink(by: (GraphicArtifects.paddedWidth - 0.005)/2 * shortEdge) let textPoints = textRing.arcPoints(lambdas: changePhase(phase: startingAngle, angles: ticks.majorTickNames.map { CGFloat($0.position) })) let textMaskPath = CGMutablePath() let fontColor = isDark ? watchLayout.fontColorDark : watchLayout.fontColor @@ -360,7 +359,8 @@ extension CALayer { } for i in 0.. Int { - let mod = left % right - return mod >= 0 ? mod : mod + right + static func %%(_ left: Int, _ right: Int) -> Int { + let mod = left % right + return mod >= 0 ? mod : mod + right } - } func +(lhs: CGPoint, rhs: CGPoint) -> CGPoint { @@ -36,7 +34,7 @@ func /(lhs: CGPoint, rhs: CGFloat) -> CGPoint { extension Array { func insertionIndex(of value: Element, comparison: (Element, Element) -> Bool) -> Index { - var slice : SubSequence = self[...] + var slice: SubSequence = self[...] while !slice.isEmpty { let middle = slice.index(slice.startIndex, offsetBy: slice.count / 2) @@ -59,6 +57,7 @@ extension Calendar { cal.timeZone = TimeZone(abbreviation: "UTC")! return cal } + func startOfDay(for day: Date, apparent: Bool, location: CGPoint?) -> Date { func convertToApparent(date: Date, location: CGPoint) -> Date { var start = date @@ -72,11 +71,11 @@ extension Calendar { if let location = location, apparent { startToday = convertToApparent(date: startToday, location: location) if day < startToday { - var startYesterday = self.date(byAdding: .day, value: -1, to: standardStartOfDay)! + var startYesterday = date(byAdding: .day, value: -1, to: standardStartOfDay)! startYesterday = convertToApparent(date: startYesterday, location: location) return startYesterday } else { - var startTomorrow = self.date(byAdding: .day, value: 1, to: standardStartOfDay)! + var startTomorrow = date(byAdding: .day, value: 1, to: standardStartOfDay)! startTomorrow = convertToApparent(date: startTomorrow, location: location) if day >= startTomorrow { return startTomorrow @@ -97,7 +96,7 @@ private func getJD(yyyy: Int, mm: Int, dd: Int) -> Double { m1 = m1 + 12 yy = yy - 1 } - // Gregorian calendar + // Gregorian calendar let b = yy / 400 - yy / 100 + yy / 4 let jd = Double(365 * yy - 679004 + b) + floor(30.6001 * Double(m1 + 1)) + Double(dd) + 2400000.5 return jd @@ -108,10 +107,10 @@ private func DeltaT_spline_y(_ y: Double) -> Double { let t = x - 1825 return 0.00314115 * t * t + 284.8435805251424 * cos(0.4487989505128276 * (0.01 * t + 0.75)) } - if (y < -720) { - let const: Double = 1.007739546148514 + if y < -720 { + let const = 1.007739546148514 return phase(x: y) + const - } else if (y > 2019) { + } else if y > 2019 { let const: Double = -150.263031657016 return phase(x: y) + const } @@ -121,7 +120,7 @@ private func DeltaT_spline_y(_ y: Double) -> Double { while l >= 0 && !(y >= Double(n[l])) { l -= 1 } - + let year_splits = [-100, 400, 100, 1150, 1300, 1500, 1600, 1650, 1720, 1800, 1810, 1820, 1830, 1840, 1850, 1855, 1860, 1865, 1870, 1875, 1880, 1885, 1890, 1895, 1900, 1905, 1910, 1915, 1920, 1925, 1930, 1935, 1940, 1945, 1950, 1953, 1956, 1959, 1962, 1965, 1968, 1971, 1974, 1977, 1980, 1983, 1986, 1989, 1992, 1995, 1998, 2001, 2004, 2007, 2010, 2013, 2016, 2019] let r = (y - Double(n[l])) / Double(year_splits[l] - n[l]) let coef1: [Double] = [20371.848, 11557.668, 6535.116, 1650.393, 1056.647, 681.149, 292.343, 109.127, 43.952, 12.068, 18.367, 15.678, 16.516, 10.804, 7.634, 9.338, 10.357, 9.04, 8.255, 2.371, -1.126, -3.21, -4.388, -3.884, -5.017, -1.977, 4.923, 11.142, 17.479, 21.617, 23.789, 24.418, 24.164, 24.426, 27.05, 28.932, 30.002, 30.76, 32.652, 33.621, 35.093, 37.956, 40.951, 44.244, 47.291, 50.361, 52.936, 54.984, 56.373, 58.453, 60.678, 62.898, 64.083, 64.553, 65.197, 66.061, 66.92, 68.109] @@ -130,41 +129,42 @@ private func DeltaT_spline_y(_ y: Double) -> Double { let coef4: [Double] = [409.16, -503.433, 1085.087, -25.346, -24.641, -29.414, 16.197, 3.018, -2.127, -37.939, 1.918, -3.812, 3.25, -0.096, -0.539, -0.883, 1.558, -2.477, 2.72, -0.914, -0.039, 0.563, -1.438, 1.871, -0.232, -1.257, 0.72, -0.825, 0.262, 0.008, 0.127, 0.142, 0.702, -1.106, 0.614, -0.277, 0.631, -0.799, 0.507, 0.199, -0.414, 0.202, -0.229, 0.172, -0.192, 0.081, -0.165, 0.448, -0.276, 0.11, -0.313, 0.109, 0.199, -0.017, -0.084, 0.128, -0.095, -0.139] return coef1[l] + r * (coef2[l] + r * (coef3[l] + r * coef4[l])) } + // UT -> TT private func DeltaT(T: Double) -> Double { let t = 36525 * T + 2451545 - if (t > 2459580.5 || t < 2441317.5) { + if t > 2459580.5 || t < 2441317.5 { return DeltaT_spline_y(t >= 2299160.5 ? (t - 2451544.5) / 365.2425 + 2000 : (t + 0.5) / 365.25 - 4712) / 86400.0 } let l: [Double] = [2457754.5, 2457204.5, 2456109.5, 2454832.5, 2453736.5, 2451179.5, 2450630.5, 2450083.5, 2449534.5, 2449169.5, 2448804.5, 2448257.5, 2447892.5, 2447161.5, 2446247.5, 2445516.5, 2445151.5, 2444786.5, 2444239.5, 2443874.5, 2443509.5, 2443144.5, 2442778.5, 2442413.5, 2442048.5, 2441683.5, 2441499.5, 2441133.5] let n = l.count - var DT: Double = 42.184 + var DT = 42.184 for i in 0.. l[i]) { + if t > l[i] { DT += Double(n - i - 1) break } } - return DT/86400.0 + return DT / 86400.0 } private func mod2pi_de(x: Double) -> Double { - return x - 2 * Double.pi * floor(0.5 * x/Double.pi + 0.5) + return x - 2 * Double.pi * floor(0.5 * x / Double.pi + 0.5) } private func decode_solar_terms(y: Int, istart: Int, offset_comp: Int, solar_comp: [Int8]) -> [Date] { - let jd0 = getJD(yyyy: y-1,mm: 12,dd: 31) - 1.0/3 - let delta_T = DeltaT(T: (jd0-2451545 + 365.25*0.5)/36525) + let jd0 = getJD(yyyy: y - 1, mm: 12, dd: 31) - 1.0 / 3 + let delta_T = DeltaT(T: (jd0 - 2451545 + 365.25 * 0.5) / 36525) let offset = 2451545 - jd0 - delta_T - let w: [Double] = [2*Double.pi, 6.282886, 12.565772, 0.337563, 83.99505, 77.712164, 5.7533, 3.9301] + let w: [Double] = [2 * Double.pi, 6.282886, 12.565772, 0.337563, 83.99505, 77.712164, 5.7533, 3.9301] let poly_coefs: [Double] let amp: [Double] let ph: [Double] - if (y > 2500) { + if y > 2500 { poly_coefs = [-10.60617210417765, 365.2421759265393, -2.701502510496315e-08, 2.303900971263569e-12] amp = [0.1736157870707964, 1.914572713893651, 0.0113716862045686, 0.004885711219368455, 0.0004032584498264633, 0.001736052092601642, 0.002035081600709588, 0.001360448706185977] ph = [-2.012792258215681, 2.824063083728992, -0.4826844382278376, 0.9488391363261893, 2.646697770061209, -0.2675341497460084, 0.9646288791219602, -1.808852094435626] - } else if (y > 1500) { + } else if y > 1500 { poly_coefs = [-10.6111079510509, 365.2421925947405, -3.888654930760874e-08, -5.434707919089998e-12] amp = [0.1633918030382493, 1.95409759473169, 0.01184405584067255, 0.004842563463555804, 0.0004137082581449113, 0.001732513547029885, 0.002025850272284684, 0.001363226024948773] ph = [-1.767045717746641, 2.832417615687159, -0.465176623256009, 0.9461667782644696, 2.713020913181211, -0.2031148059020781, 0.9980808019332812, -1.832536089597202] @@ -176,19 +176,19 @@ private func decode_solar_terms(y: Int, istart: Int, offset_comp: Int, solar_com var sterm = [Date]() for i in 0.. [Date] { - let w = [2*Double.pi, 6.733776, 13.467552, 0.507989, 0.0273143, 0.507984, 20.201328, 6.225791, 7.24176, 5.32461, 12.058386, 0.901181, 5.832595, 12.56637061435917, 19.300146, 11.665189, 18.398965, 6.791174, 13.636974, 1.015968, 6.903198, 13.07437, 1.070354, 6.340578614359172] + let w = [2 * Double.pi, 6.733776, 13.467552, 0.507989, 0.0273143, 0.507984, 20.201328, 6.225791, 7.24176, 5.32461, 12.058386, 0.901181, 5.832595, 12.56637061435917, 19.300146, 11.665189, 18.398965, 6.791174, 13.636974, 1.015968, 6.903198, 13.07437, 1.070354, 6.340578614359172] let poly_coefs: [Double] let amp: [Double] let ph: [Double] - if (y > 2500) { + if y > 2500 { poly_coefs = [5.093879710922470, 29.53058981687484, 2.670339910922144e-11, 1.807808217274283e-15] amp = [0.00306380948959271, 6.08567588841838, 0.3023856209133756, 0.07481389897992345, 0.0001587661348338354, 0.1740759063081489, 0.0004131985233772993, 0.005796584475300004, 0.008268929076163079, 0.003256244384807976, 0.000520983165608148, 0.003742624708965854, 1.709506053530008, 28216.70389751519, 1.598844831045378, 0.314745599206173, 6.602993931108911, 0.0003387269181720862, 0.009226112317341887, 0.00196073145843697, 0.001457643607929487, 6.467401779992282e-05, 0.0007716739483064076, 0.001378880922256705] ph = [-0.0001879456766404132, -2.745704167588171, -2.348884895288619, 1.420037528559222, -2.393586904955103, -0.3914194006325855, 1.183088056748942, -2.782692143601458, 0.4430565056744425, -0.4357413971405519, -3.081209195003025, 0.7945051912707899, -0.4010911170136437, 3.003035462639878e-10, 0.4040070684461441, 2.351831380989509, 2.748612213507844, 3.133002890683667, -0.6902922380876192, 0.09563473131477442, 2.056490394534053, 2.017507533465959, 2.394015964756036, -0.3466427504049927] - } else if (y > 1500) { + } else if y > 1500 { poly_coefs = [5.097475813506625, 29.53058886049267, 1.095399949433705e-10, -6.926279905270773e-16] amp = [0.003064332812182054, 0.8973816160666801, 0.03119866094731004, 0.07068988004978655, 0.0001583070735157395, 0.1762683983928151, 0.0004131592685474231, 0.005950873973350208, 0.008489324571543966, 0.00334306526160656, 0.00052946042568393, 0.003743585488835091, 0.2156913373736315, 44576.30467073629, 0.1050203948601217, 0.01883710371633125, 0.380047745859265, 0.0003472930592917774, 0.009225665415301823, 0.002061407071938891, 0.001454599562245767, 5.856419090840883e-05, 0.0007688706809666596, 0.001415547168551922] ph = [-0.0003231124735555465, 0.380955331199635, 0.762645225819612, 1.4676293538949, -2.15595770830073, -0.3633370464549665, 1.134950591549256, -2.808169363709888, 0.422381840383887, -0.4226859182049138, -3.091797336860658, 0.7563140142610324, -0.3787677293480213, 1.863828515720658e-10, 0.3794794147818532, -0.7671105159156101, -0.3850942687637987, -3.098506117162865, -0.6738173539748421, 0.09011906278589261, 2.089832317302934, 2.160228985413543, -0.6734226930504117, -0.3333652792566645] @@ -215,28 +215,28 @@ private func decode_moon_phases(y: Int, offset_comp: Int, lunar_comp: [Int8], dp ph = [] } - let jd0 = getJD(yyyy: y-1,mm: 12,dd: 31) - 1.0/3 - let delta_T = DeltaT(T: (jd0-2451545 + 365.25*0.5)/36525) + let jd0 = getJD(yyyy: y - 1, mm: 12, dd: 31) - 1.0 / 3 + let delta_T = DeltaT(T: (jd0 - 2451545 + 365.25 * 0.5) / 36525) let offset = 2451545 - jd0 - delta_T - let lsyn: Double = 29.5306 + let lsyn = 29.5306 let p0 = lunar_comp[0] - let jdL0 = 2451550.259469 + 0.5*Double(p0)*lsyn + let jdL0 = 2451550.259469 + 0.5 * Double(p0) * lsyn // Find the lunation number of the first moon phase in the year - var Lm0 = floor((jd0 + 1 - jdL0)/lsyn)-1 + var Lm0 = floor((jd0 + 1 - jdL0) / lsyn) - 1 var Lm: Double = 0 var s: Double = 0 - var s1: Int = 0 + var s1 = 0 for i in 0..<10 { - Lm = Lm0 + 0.5*Double(p0) + Double(i) - s = poly_coefs[0] + offset + Lm*(poly_coefs[1] + Lm*(poly_coefs[2] + Lm*poly_coefs[3])) + Lm = Lm0 + 0.5 * Double(p0) + Double(i) + s = poly_coefs[0] + offset + Lm * (poly_coefs[1] + Lm * (poly_coefs[2] + Lm * poly_coefs[3])) for j in 0..<24 { - let ang = mod2pi_de(x: w[j]*Lm) + ph[j] + let ang = mod2pi_de(x: w[j] * Lm) + ph[j] s += amp[j] * sin(ang) } - s1 = Int((s-floor(s))*1440 + 0.5) - s = Double(s1) + 1441*floor(s) + Double(lunar_comp[1]) - Double(offset_comp) - if (s > 1440) { + s1 = Int((s - floor(s)) * 1440 + 0.5) + s = Double(s1) + 1441 * floor(s) + Double(lunar_comp[1]) - Double(offset_comp) + if s > 1440 { break } } @@ -244,20 +244,20 @@ private func decode_moon_phases(y: Int, offset_comp: Int, lunar_comp: [Int8], dp var mphase = [Date]() // Now decompress the remaining moon-phase times for i in 1.. Double { dateComponents.hour = 12 let noon = utcCalendar.date(from: dateComponents)! let j2000 = getJD(yyyy: dateComponents.year!, mm: dateComponents.month!, dd: dateComponents.day!) - let delta_j2000 = DeltaT(T: (j2000-2451545 + 365.25*0.5)/36525) + let delta_j2000 = DeltaT(T: (j2000 - 2451545 + 365.25 * 0.5) / 36525) var offset = j2000 + delta_j2000 - 2451544.5 offset += noon.distance(to: date) / 86400 return offset @@ -295,7 +295,7 @@ private func intraday_solar_times(chineseCalendar: ChineseCalendar, location: CG delta -= location.y / 360 * 86400 return newDate + delta } - + let approximateDate = chineseCalendar.startOfDay.addingTimeInterval(43200) let localNoon = timeOfDate(date: approximateDate, hour: 12) let noonTime = localNoon - equationOfTime(D: fromJD2000(date: localNoon)) / (2 * Double.pi) * 86400 @@ -303,12 +303,12 @@ private func intraday_solar_times(chineseCalendar: ChineseCalendar, location: CG let nextMidNight = timeOfDate(date: approximateDate, hour: 24) let priorMidNightTime = priorMidNight - equationOfTime(D: fromJD2000(date: priorMidNight)) / (2 * Double.pi) * 86400 let nextMidNightTime = nextMidNight - equationOfTime(D: fromJD2000(date: nextMidNight)) / (2 * Double.pi) * 86400 - + let sunriseSunsetOffset = daytimeOffset(latitude: location.x / 180 * Double.pi, progressInYear: chineseCalendar.sunPosition(time: chineseCalendar.time) * 2 * Double.pi) / (2 * Double.pi) * 86400 let results: [Date?] - if sunriseSunsetOffset == Double.infinity { //Extreme day + if sunriseSunsetOffset == Double.infinity { // Extreme day results = [nil, nil, noonTime, nil, nil] - } else if sunriseSunsetOffset == -Double.infinity { //Extreme night + } else if sunriseSunsetOffset == -Double.infinity { // Extreme night results = [priorMidNightTime, nil, nil, nil, nextMidNightTime] } else { var sunriseTime = localNoon - sunriseSunsetOffset @@ -321,11 +321,10 @@ private func intraday_solar_times(chineseCalendar: ChineseCalendar, location: CG } private func intraday_lunar_times(chineseCalendar: ChineseCalendar, location: CGPoint) -> [Date?] { - func riseAndSet(meridianTime: Date, latitude: Double, light: Bool) -> ([Date?], Double) { let offsetMeridian = lunarTimeOffset(latitude: location.x / 180 * Double.pi, jdTime: fromJD2000(date: meridianTime), light: light) - let moonrise = meridianTime - offsetMeridian / (2*Double.pi) * 360 / (earthSpeed - moonSpeed) - let moonset = meridianTime + offsetMeridian / (2*Double.pi) * 360 / (earthSpeed - moonSpeed) + let moonrise = meridianTime - offsetMeridian / (2 * Double.pi) * 360 / (earthSpeed - moonSpeed) + let moonset = meridianTime + offsetMeridian / (2 * Double.pi) * 360 / (earthSpeed - moonSpeed) if offsetMeridian == Double.infinity { return ([nil, meridianTime, nil], offsetMeridian) } else if offsetMeridian == -Double.infinity { @@ -337,12 +336,12 @@ private func intraday_lunar_times(chineseCalendar: ChineseCalendar, location: CG func roundHalf(_ num: Double) -> Double { return num - 1 - floor(num - 0.5) } - + func calDiff(time: Date) -> Double { let (ra, _, _) = moonEquatorPosition(D: fromJD2000(date: time)) let dayStart = chineseCalendar.calendar.startOfDay(for: time, apparent: true, location: location) let nextDay = chineseCalendar.calendar.startOfDay(for: dayStart + 86400 * 1.5, apparent: true, location: location) - return roundHalf(-chineseCalendar.sunPosition(time: time) - 1/4 - dayStart.distance(to: time) / dayStart.distance(to: nextDay) + ra / (2*Double.pi)) + return roundHalf(-chineseCalendar.sunPosition(time: time) - 1 / 4 - dayStart.distance(to: time) / dayStart.distance(to: nextDay) + ra / (2 * Double.pi)) } let longitudeDiff = calDiff(time: chineseCalendar.time) @@ -365,7 +364,7 @@ private func intraday_lunar_times(chineseCalendar: ChineseCalendar, location: CG } timeUnderMeridianPrevious += calDiff(time: timeUnderMeridianPrevious) * 360 / (earthSpeed - moonSpeed) timeUnderMeridianNext += calDiff(time: timeUnderMeridianNext) * 360 / (earthSpeed - moonSpeed) - + let (previousTimes, offset1) = riseAndSet(meridianTime: timeUnderMeridianPrevious, latitude: location.x, light: true) let (nextTimes, offset2) = riseAndSet(meridianTime: timeUnderMeridianNext, latitude: location.x, light: true) let midtime = timeUnderMeridianPrevious + timeUnderMeridianPrevious.distance(to: timeUnderMeridianNext) / 2 @@ -390,7 +389,7 @@ private func intraday_lunar_times(chineseCalendar: ChineseCalendar, location: CG results.append(nextTimes[0] ?? midTimes[2]) } } - results.append(contentsOf: [nextTimes[1], nextTimes[2]]) + results.append(contentsOf: [nextTimes[1], nextTimes[2]]) return results } @@ -423,7 +422,7 @@ extension Array { func slice(from: Int = 0, to: Int? = nil, step: Int = 1) -> Self { var sliced = Self() var i = from - let limit = to ?? self.count + let limit = to ?? count while i < limit { sliced.append(self[i]) i += step @@ -433,12 +432,11 @@ extension Array { } class ChineseCalendar { - static let month_chinese = ["冬月", "臘月", "正月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月"] static let month_chinese_compact = ["㋊", "㋋", "㋀", "㋁", "㋂", "㋃", "㋄", "㋅", "㋆", "㋇", "㋈", "㋉"] - static let day_chinese = ["一", "二", "三", "四", "五", "六", "七", "八", "九", "十" ,"十一", "十二", "十三", "十四", "十五", "十六", "十七", "十八", "十九", "二十", "廿一", "廿二", "廿三", "廿四", "廿五", "廿六", "廿七", "廿八", "廿九", "三十"] - static let day_chinese_compact = ["㏠", "㏡", "㏢", "㏣", "㏤", "㏥", "㏦", "㏧", "㏨", "㏩" ,"㏪", "㏫", "㏬", "㏭", "㏮", "㏯", "㏰", "㏱", "㏲", "㏳", "㏴", "㏵", "㏶", "㏷", "㏸", "㏹", "㏺", "㏻", "㏼", "㏽"] - static let terrestrial_branches = ["子","丑","寅","卯","辰","巳","午","未","申","酉","戌","亥"] + static let day_chinese = ["一", "二", "三", "四", "五", "六", "七", "八", "九", "十", "十一", "十二", "十三", "十四", "十五", "十六", "十七", "十八", "十九", "二十", "廿一", "廿二", "廿三", "廿四", "廿五", "廿六", "廿七", "廿八", "廿九", "三十"] + static let day_chinese_compact = ["㏠", "㏡", "㏢", "㏣", "㏤", "㏥", "㏦", "㏧", "㏨", "㏩", "㏪", "㏫", "㏬", "㏭", "㏮", "㏯", "㏰", "㏱", "㏲", "㏳", "㏴", "㏵", "㏶", "㏷", "㏸", "㏹", "㏺", "㏻", "㏼", "㏽"] + static let terrestrial_branches = ["子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥"] static let sub_hour_name = ["初", "正"] static let chinese_numbers = ["初", "一", "二", "三", "四", "五"] static let evenSolarTermChinese = ["冬 至", "大 寒", "雨 水", "春 分", "穀 雨", "小 滿", "夏 至", "大 暑", "處 暑", "秋 分", "霜 降", "小 雪"] @@ -450,7 +448,7 @@ class ChineseCalendar { static let MoonPhases = ["朔", "望"] static var globalMonth = false static var apparentTime = false - + private let _compact: Bool private var _time: Date private var _calendar: Calendar @@ -469,41 +467,46 @@ class ChineseCalendar { private lazy var _day: Int = -1 private lazy var _sunTimes: [Date?] = [] private lazy var _moonTimes: [Date?] = [] - private lazy var _startHour: Date = Date.distantPast - private lazy var _endHour: Date = Date.distantFuture + private lazy var _startHour: Date = .distantPast + private lazy var _endHour: Date = .distantFuture private lazy var _hourNames: [String] = [] private lazy var _hour_string: String = "" private lazy var _quarter_string: String = "" - + struct NamedPosition { let name: String let pos: Double } + struct NamedDate { let name: String let date: Date } + struct CelestialEvent { var eclipse = [NamedPosition]() var fullMoon = [NamedPosition]() var oddSolarTerm = [NamedPosition]() var evenSolarTerm = [NamedPosition]() } + struct DailyEvent { var solar = [NamedPosition?]() var lunar = [NamedPosition?]() } + struct Ticks { struct TickName { var position: Double = 0.0 var name: String = "" var active: Bool = false } + var majorTicks = [Double]() var majorTickNames = [TickName]() var minorTicks = [Double]() } - + init(time: Date, timezone: TimeZone, location: CGPoint?, compact: Bool = false) { self._time = time var calendar = Calendar(identifier: .iso8601) @@ -515,7 +518,7 @@ class ChineseCalendar { updateDate() updateHour() } - + private init(compact: Bool, time: Date, calendar: Calendar, location: CGPoint?, year: Int, year_length: Double, solarTerms: [Date], evenSolarTerms: [Date], oddSolarTerms: [Date], moonEclipses: [Date], fullMoons: [Date], monthNames: [String], monthNamesFull: [String], month: Int, precise_month: Int, day: Int, sunTimes: [Date?], moonTimes: [Date?], startHour: Date, endHour: Date, hourNames: [String], hour_string: String, quarter_string: String) { self._compact = compact self._time = time @@ -541,11 +544,11 @@ class ChineseCalendar { self._hour_string = hour_string self._quarter_string = quarter_string } - + var copy: ChineseCalendar { - ChineseCalendar(compact: self._compact, time: self._time, calendar: self._calendar, location: self._location, year: self._year, year_length: self._year_length, solarTerms: self._solarTerms, evenSolarTerms: self._evenSolarTerms, oddSolarTerms: self._oddSolarTerms, moonEclipses: self._moonEclipses, fullMoons: self._fullMoons, monthNames: self._monthNames, monthNamesFull: self._monthNamesFull, month: self._month, precise_month: self._precise_month, day: self._day, sunTimes: self._sunTimes, moonTimes: self._moonTimes, startHour: self._startHour, endHour: self._endHour, hourNames: self._hourNames, hour_string: self._hour_string, quarter_string: self._quarter_string) + ChineseCalendar(compact: _compact, time: _time, calendar: _calendar, location: _location, year: _year, year_length: _year_length, solarTerms: _solarTerms, evenSolarTerms: _evenSolarTerms, oddSolarTerms: _oddSolarTerms, moonEclipses: _moonEclipses, fullMoons: _fullMoons, monthNames: _monthNames, monthNamesFull: _monthNamesFull, month: _month, precise_month: _precise_month, day: _day, sunTimes: _sunTimes, moonTimes: _moonTimes, startHour: _startHour, endHour: _endHour, hourNames: _hourNames, hour_string: _hour_string, quarter_string: _quarter_string) } - + private func updateYear() { var year = calendar.component(.year, from: time) var solar_terms = solar_terms_in_year(year + 1) @@ -558,14 +561,14 @@ class ChineseCalendar { solar_terms = solar_terms_current_year } let solar_terms_previous_year = solar_terms_in_year(year - 1) - + let (moon_phase_previous_year, first_event) = moon_phase_in_year(year - 1) var (moon_phase, _) = moon_phase_in_year(year) let (moon_phase_next_year, _) = moon_phase_in_year(year + 1) moon_phase = moon_phase_previous_year + moon_phase + moon_phase_next_year var eclipse = moon_phase.slice(from: Int(first_event), step: 2) - var fullMoon = moon_phase.slice(from: Int(1-first_event), step: 2) - var start: Int? = nil, end: Int? = nil + var fullMoon = moon_phase.slice(from: Int(1 - first_event), step: 2) + var start: Int?, end: Int? for i in 0..= solar_terms[0]) { - start = i-1 + if start == nil, eclipseDate >= solar_terms[0] { + start = i - 1 } - if (end == nil) && (eclipseDate > solar_terms[24]) { + if end == nil, eclipseDate > solar_terms[24] { end = i } } - eclipse = eclipse.slice(from: start!, to: end!+2) + eclipse = eclipse.slice(from: start!, to: end! + 2) fullMoon = fullMoon.filter { $0 < eclipse.last! && $0 > eclipse[0] } let evenSolarTerms = solar_terms.slice(step: 2) - + var i = 0 var j = 0 var count = 0 var solatice_in_month = [Int]() var monthCount = Set() - while (i+1 < eclipse.count) && (j < evenSolarTerms.count) { + while i + 1 < eclipse.count, j < evenSolarTerms.count { let thisEclipse: Date let nextEclipse: Date if Self.globalMonth { thisEclipse = eclipse[i] - nextEclipse = eclipse[i+1] + nextEclipse = eclipse[i + 1] } else { thisEclipse = calendar.startOfDay(for: eclipse[i], apparent: Self.apparentTime, location: location) - nextEclipse = calendar.startOfDay(for: eclipse[i+1], apparent: Self.apparentTime, location: location) + nextEclipse = calendar.startOfDay(for: eclipse[i + 1], apparent: Self.apparentTime, location: location) } - if ((thisEclipse <= evenSolarTerms[j]) && (nextEclipse > evenSolarTerms[j])) { + if thisEclipse <= evenSolarTerms[j], nextEclipse > evenSolarTerms[j] { count += 1 j += 1 } else { @@ -607,13 +610,13 @@ class ChineseCalendar { count = 0 i += 1 } - if thisEclipse >= solar_terms[0] && thisEclipse < solar_terms[24] { + if thisEclipse >= solar_terms[0], thisEclipse < solar_terms[24] { monthCount.insert(thisEclipse) } } var months_compact = [String]() var months = [String]() - + if monthCount.count == 12 { months_compact = (Self.month_chinese_compact + Self.month_chinese_compact).slice(to: eclipse.count) months = (Self.month_chinese + Self.month_chinese).slice(to: eclipse.count) @@ -621,7 +624,7 @@ class ChineseCalendar { var leap = 0 var leapLabel = "" for i in 0.. _time && tempEndHour == nil { + if (hourIndex %% 2) == 1, hour > _time, tempEndHour == nil { tempEndHour = hour } if apparentTime { @@ -732,22 +735,22 @@ class ChineseCalendar { _startHour = tempStartHour! _endHour = tempEndHour! } - + func update(time: Date, timezone: TimeZone, location: CGPoint?) { - self._time = time - self._calendar.timeZone = timezone - self._location = location - + _time = time + _calendar.timeZone = timezone + _location = location + if (location == nil && _location != nil) || (location != nil && _location == nil) { updateYear() } else if let newLocation = location, let oldLocation = _location { if sqrt(pow(newLocation.x - oldLocation.x, 2) + pow(newLocation.y - oldLocation.y, 2)) > 1 { updateYear() } - } else if timezone != self._calendar.timeZone { + } else if timezone != _calendar.timeZone { updateYear() } - + let year = _calendar.component(.year, from: time) if !(((year == _year) && (_solarTerms[24] > time)) || ((year == _year - 1) && (_solarTerms[0] <= time))) { updateYear() @@ -759,9 +762,11 @@ class ChineseCalendar { var apparentTime: Bool { Self.apparentTime && _location != nil } + var monthString: String { _monthNamesFull[_month] } + var dayString: String { let chinese_day = Self.day_chinese[_day] if chinese_day.count > 1 { @@ -770,6 +775,7 @@ class ChineseCalendar { return "\(chinese_day)日" } } + var dateString: String { let chinese_month = _monthNamesFull[_month] let chinese_day = Self.day_chinese[_day] @@ -779,37 +785,45 @@ class ChineseCalendar { return "\(chinese_month)\(chinese_day)日" } } + var timeString: String { "\(_hour_string)\(_quarter_string)" } + var hourString: String { _hour_string } + var quarterString: String { if _quarter_string.count == 0 { - _ = self.subhourTicks + _ = subhourTicks } return _quarter_string } + var shortQuarterString: String { if _quarter_string.count == 0 { - _ = self.subhourTicks + _ = subhourTicks } return _quarter_string.count > 2 ? String(_quarter_string.dropLast(1)) : _quarter_string } + var calendar: Calendar { return _calendar } + var evenSolarTerms: [Double] { var evenSolarTermsPositions = _evenSolarTerms.map { _solarTerms[0].distance(to: $0) / _year_length as Double } evenSolarTermsPositions = evenSolarTermsPositions.filter { ($0 < 1) && ($0 > 0) } return [0] + evenSolarTermsPositions } + var oddSolarTerms: [Double] { var oddSolarTermsPositions = _oddSolarTerms.map { _solarTerms[0].distance(to: $0) / _year_length as Double } oddSolarTermsPositions = oddSolarTermsPositions.filter { ($0 < 1) && ($0 > 0) } return oddSolarTermsPositions } + var solarTerms: [NamedDate] { var terms = [NamedDate]() for i in 0..<_solarTerms.count { @@ -823,6 +837,7 @@ class ChineseCalendar { } return terms } + var moonPhases: [NamedDate] { var phases = [NamedDate]() for i in 0.. 0 && $0 < 1 } - var previousMonthDivide: Double = 0.0 + var previousMonthDivide = 0.0 var monthNames = [Ticks.TickName]() let minMonthLength: Double = (_compact ? 0.009 : 0.006) for i in 0.. 0 && $0 < 1 } + let allDayNames: [String] if _compact { allDayNames = Self.day_chinese_compact.slice(to: monthLengthInWholeDays) + [Self.day_chinese_compact[0]] @@ -909,7 +925,7 @@ class ChineseCalendar { } var dayNames = [Ticks.TickName]() - var previousDayDivide: Double = 0.0 + var previousDayDivide = 0.0 let minDayLength = (_compact ? 0.009 : 0.006) for i in 0..() - var majorTickNames = Array() + var majorTickNames = [String]() var tickTime = startHour var currentSmallHour = tickTime _quarter_string = "" @@ -982,11 +1000,11 @@ class ChineseCalendar { subHourTicks.insert(startHour.distance(to: tickTime) / startHour.distance(to: endHour)) let hourOrder: Int if apparentTime { - hourOrder = Int(round(startOfDay.distance(to: tickTime) / startOfDay.distance(to: startOfNextDay) * 24))+1 + hourOrder = Int(round(startOfDay.distance(to: tickTime) / startOfDay.distance(to: startOfNextDay) * 24)) + 1 } else { - hourOrder = _calendar.component(.hour, from: tickTime)+1 + hourOrder = _calendar.component(.hour, from: tickTime) + 1 } - majorTickNames.append(Self.terrestrial_branches[(hourOrder/2) %% 12] + Self.sub_hour_name[hourOrder %% 2]) + majorTickNames.append(Self.terrestrial_branches[(hourOrder / 2) %% 12] + Self.sub_hour_name[hourOrder %% 2]) if tickTime <= _time { currentSmallHour = tickTime } @@ -1026,7 +1044,7 @@ class ChineseCalendar { j += 1 count = 1 } else { - if min((subHourTick[i] - subHourTick[(i-1) %% subHourTick.count]) % 1.0, (subHourTick[(i+1) %% subHourTick.count] - subHourTick[i]) % 1.0) > minimumSubhourLength { + if min((subHourTick[i] - subHourTick[(i - 1) %% subHourTick.count]) % 1.0, (subHourTick[(i + 1) %% subHourTick.count] - subHourTick[i]) % 1.0) > minimumSubhourLength { subHourNames.append(Ticks.TickName( position: subHourTick[i], name: Self.chinese_numbers[count], @@ -1036,7 +1054,7 @@ class ChineseCalendar { count += 1 } } - + var subQuarterTicks = Set() var minorTickCount = 0 tickTime = startOfDay - 864 * 6 @@ -1054,12 +1072,13 @@ class ChineseCalendar { } subQuarterTicks = subQuarterTicks.subtracting(subHourTicks) let subQuarterTick = Array(subQuarterTicks).sorted() - + ticks.majorTicks = subHourTick ticks.majorTickNames = subHourNames ticks.minorTicks = subQuarterTick return ticks } + var location: CGPoint? { get { _location @@ -1067,155 +1086,175 @@ class ChineseCalendar { _location = newValue } } + var timezone: Int { _calendar.timeZone.secondsFromGMT(for: _time) } + var year: Int { _year } + var month: Int { _month } + var preciseMonth: Int { Self.globalMonth ? _precise_month : _month } + var day: Int { _day + 1 } + var time: Date { _time } + var monthLengthInWholeDays: Int { let month = Self.globalMonth ? _precise_month : _month let monthStartDate = _calendar.startOfDay(for: _moonEclipses[month], apparent: Self.apparentTime, location: _location) - let monthEndDate = _calendar.startOfDay(for: _moonEclipses[month+1], apparent: Self.apparentTime, location: _location) + let monthEndDate = _calendar.startOfDay(for: _moonEclipses[month + 1], apparent: Self.apparentTime, location: _location) return Int(round(monthStartDate.distance(to: monthEndDate) / 86400)) } + var startHour: Date { _startHour } + var endHour: Date { _endHour } + func sunPosition(time: Date) -> Double { func interpolate(f1: Double, f2: Double, f3: Double, y: Double) -> Double { let a = f2 - f1 let b = f3 - f2 - a - let ba = b - 2*a - return (ba + sqrt(pow(ba,2) + 8*b*(y-f1))) / (4*b) + let ba = b - 2 * a + return (ba + sqrt(pow(ba, 2) + 8 * b * (y - f1))) / (4 * b) } var i = 0 - while (i+1 < _solarTerms.count) && (time > _solarTerms[i+1]) { + while (i + 1 < _solarTerms.count) && (time > _solarTerms[i + 1]) { i += 1 } if i <= _solarTerms.count / 2 { - return (Double(i) + interpolate(f1: 0, f2: _solarTerms[i].distance(to: _solarTerms[i+1]), f3: _solarTerms[i].distance(to: _solarTerms[i+2]), y: _solarTerms[i].distance(to: time)) * 2) / 24 + return (Double(i) + interpolate(f1: 0, f2: _solarTerms[i].distance(to: _solarTerms[i + 1]), f3: _solarTerms[i].distance(to: _solarTerms[i + 2]), y: _solarTerms[i].distance(to: time)) * 2) / 24 } else { - return (Double(i) + (interpolate(f1: 0, f2: _solarTerms[i-2].distance(to: _solarTerms[i-1]), f3: _solarTerms[i-2].distance(to: _solarTerms[i]), y: _solarTerms[i-2].distance(to: time)) - 1) * 2) / 24 + return (Double(i) + (interpolate(f1: 0, f2: _solarTerms[i - 2].distance(to: _solarTerms[i - 1]), f3: _solarTerms[i - 2].distance(to: _solarTerms[i]), y: _solarTerms[i - 2].distance(to: time)) - 1) * 2) / 24 } } + var currentDayInYear: Double { _solarTerms[0].distance(to: _time) / _year_length } + var currentDayInMonth: Double { if Self.globalMonth { - let monthLength = _moonEclipses[_precise_month].distance(to: _moonEclipses[_precise_month+1]) + let monthLength = _moonEclipses[_precise_month].distance(to: _moonEclipses[_precise_month + 1]) return _moonEclipses[_precise_month].distance(to: _time) / monthLength } else { let monthStart = calendar.startOfDay(for: _moonEclipses[_month], apparent: Self.apparentTime, location: _location) - let monthEnd = calendar.startOfDay(for: _moonEclipses[_month+1], apparent: Self.apparentTime, location: _location) + let monthEnd = calendar.startOfDay(for: _moonEclipses[_month + 1], apparent: Self.apparentTime, location: _location) return monthStart.distance(to: _time) / monthStart.distance(to: monthEnd) } } + var currentHourInDay: Double { startOfDay.distance(to: _time) / startOfDay.distance(to: startOfNextDay) } + var subhourInHour: Double { startHour.distance(to: _time) / startHour.distance(to: endHour) } + var startOfDay: Date { _calendar.startOfDay(for: _time, apparent: Self.apparentTime, location: _location) } + var startOfNextDay: Date { let nextDay = startOfDay + 86400 * 1.5 return _calendar.startOfDay(for: nextDay, apparent: Self.apparentTime, location: _location) } + var planetPosition: [NamedPosition] { var planetPosition = planetPos(T: fromJD2000(date: _time) / 36525).enumerated().map { NamedPosition(name: planetNames[$0.offset], pos: $0.element) } let moonPosition = NamedPosition(name: "月", pos: moonElipticPosition(D: fromJD2000(date: _time))) planetPosition.append(moonPosition) return planetPosition.map { NamedPosition(name: $0.name, pos: ($0.pos / Double.pi / 2 + 0.25) % 1.0) } } + var eventInMonth: CelestialEvent { let monthStart: Date let monthLength: Double if Self.globalMonth { monthStart = _moonEclipses[_precise_month] - monthLength = _moonEclipses[_precise_month].distance(to: _moonEclipses[_precise_month+1]) + monthLength = _moonEclipses[_precise_month].distance(to: _moonEclipses[_precise_month + 1]) } else { monthStart = _calendar.startOfDay(for: _moonEclipses[_month], apparent: Self.apparentTime, location: _location) - let monthEnd = _calendar.startOfDay(for: _moonEclipses[_month+1], apparent: Self.apparentTime, location: _location) + let monthEnd = _calendar.startOfDay(for: _moonEclipses[_month + 1], apparent: Self.apparentTime, location: _location) monthLength = monthStart.distance(to: monthEnd) } var event = CelestialEvent() if !Self.globalMonth { event.eclipse = _moonEclipses.map { date in NamedPosition(name: Self.MoonPhases[0], pos: monthStart.distance(to: date) / monthLength) - }.filter { 0 <= $0.pos && 1 > $0.pos } + }.filter { $0.pos >= 0 && $0.pos < 1 } } event.fullMoon = _fullMoons.map { date in NamedPosition(name: Self.MoonPhases[1], pos: monthStart.distance(to: date) / monthLength) - }.filter { 0 <= $0.pos && 1 > $0.pos } + }.filter { $0.pos >= 0 && $0.pos < 1 } event.evenSolarTerm = _evenSolarTerms.enumerated().map { offset, date in - let name = String(Self.evenSolarTermChinese[(offset-1) %% Self.evenSolarTermChinese.count].replacingOccurrences(of: " ", with: "")) + let name = String(Self.evenSolarTermChinese[(offset - 1) %% Self.evenSolarTermChinese.count].replacingOccurrences(of: " ", with: "")) return NamedPosition(name: name, pos: monthStart.distance(to: date) / monthLength) - }.filter { 0 <= $0.pos && 1 > $0.pos } + }.filter { $0.pos >= 0 && $0.pos < 1 } event.oddSolarTerm = _oddSolarTerms.enumerated().map { offset, date in - let name = String(Self.oddSolarTermChinese[(offset-1) %% Self.oddSolarTermChinese.count].replacingOccurrences(of: " ", with: "")) + let name = String(Self.oddSolarTermChinese[(offset - 1) %% Self.oddSolarTermChinese.count].replacingOccurrences(of: " ", with: "")) return NamedPosition(name: name, pos: monthStart.distance(to: date) / monthLength) - }.filter { 0 <= $0.pos && 1 > $0.pos } + }.filter { $0.pos >= 0 && $0.pos < 1 } return event } + var eventInDay: CelestialEvent { let startOfDay = startOfDay let lengthOfDay = startOfDay.distance(to: startOfNextDay) var event = CelestialEvent() event.eclipse = _moonEclipses.map { date in - NamedPosition(name: Self.MoonPhases[0], pos: startOfDay.distance(to: date) / lengthOfDay) - }.filter { 0 <= $0.pos && 1 > $0.pos } + NamedPosition(name: Self.MoonPhases[0], pos: startOfDay.distance(to: date) / lengthOfDay) + }.filter { $0.pos >= 0 && $0.pos < 1 } event.fullMoon = _fullMoons.map { date in NamedPosition(name: Self.MoonPhases[1], pos: startOfDay.distance(to: date) / lengthOfDay) - }.filter { 0 <= $0.pos && 1 > $0.pos } + }.filter { $0.pos >= 0 && $0.pos < 1 } event.evenSolarTerm = _evenSolarTerms.enumerated().map { offset, date in - let name = String(Self.evenSolarTermChinese[(offset-1) %% Self.evenSolarTermChinese.count].replacingOccurrences(of: " ", with: "")) + let name = String(Self.evenSolarTermChinese[(offset - 1) %% Self.evenSolarTermChinese.count].replacingOccurrences(of: " ", with: "")) return NamedPosition(name: name, pos: startOfDay.distance(to: date) / lengthOfDay) - }.filter { 0 <= $0.pos && 1 > $0.pos } + }.filter { $0.pos >= 0 && $0.pos < 1 } event.oddSolarTerm = _oddSolarTerms.enumerated().map { offset, date in - let name = String(Self.oddSolarTermChinese[(offset-1) %% Self.oddSolarTermChinese.count].replacingOccurrences(of: " ", with: "")) + let name = String(Self.oddSolarTermChinese[(offset - 1) %% Self.oddSolarTermChinese.count].replacingOccurrences(of: " ", with: "")) return NamedPosition(name: name, pos: startOfDay.distance(to: date) / lengthOfDay) - }.filter { 0 <= $0.pos && 1 > $0.pos } + }.filter { $0.pos >= 0 && $0.pos < 1 } return event } + var eventInHour: CelestialEvent { var event = CelestialEvent() let hourLength = startHour.distance(to: endHour) event.eclipse = _moonEclipses.map { date in - NamedPosition(name: Self.MoonPhases[0], pos: startHour.distance(to: date) / hourLength) - }.filter { 0 <= $0.pos && 1 > $0.pos } + NamedPosition(name: Self.MoonPhases[0], pos: startHour.distance(to: date) / hourLength) + }.filter { $0.pos >= 0 && $0.pos < 1 } event.fullMoon = _fullMoons.map { date in NamedPosition(name: Self.MoonPhases[1], pos: startHour.distance(to: date) / hourLength) - }.filter { 0 <= $0.pos && 1 > $0.pos } + }.filter { $0.pos >= 0 && $0.pos < 1 } event.evenSolarTerm = _evenSolarTerms.enumerated().map { offset, date in - let name = String(Self.evenSolarTermChinese[(offset-1) %% Self.evenSolarTermChinese.count].replacingOccurrences(of: " ", with: "")) + let name = String(Self.evenSolarTermChinese[(offset - 1) %% Self.evenSolarTermChinese.count].replacingOccurrences(of: " ", with: "")) return NamedPosition(name: name, pos: startHour.distance(to: date) / hourLength) - }.filter { 0 <= $0.pos && 1 > $0.pos } + }.filter { $0.pos >= 0 && $0.pos < 1 } event.oddSolarTerm = _oddSolarTerms.enumerated().map { offset, date in - let name = String(Self.oddSolarTermChinese[(offset-1) %% Self.oddSolarTermChinese.count].replacingOccurrences(of: " ", with: "")) + let name = String(Self.oddSolarTermChinese[(offset - 1) %% Self.oddSolarTermChinese.count].replacingOccurrences(of: " ", with: "")) return NamedPosition(name: name, pos: startHour.distance(to: date) / hourLength) - }.filter { 0 <= $0.pos && 1 > $0.pos } + }.filter { $0.pos >= 0 && $0.pos < 1 } return event } - + private func datesToNamedDates(dates: [Date?], names: [String]) -> [NamedDate?] { return dates.enumerated().map { offset, element in if let date = element { @@ -1225,8 +1264,9 @@ class ChineseCalendar { } } } + var sunTimes: [NamedDate] { - let chineseCalendar = self.copy + let chineseCalendar = copy let startOfDay = startOfDay let startOfNextDay = startOfNextDay if let location = _location { @@ -1243,8 +1283,9 @@ class ChineseCalendar { return [] } } + var moonTimes: [NamedDate] { - let chineseCalendar = self.copy + let chineseCalendar = copy let startOfDay = startOfDay let startOfNextDay = startOfNextDay if let location = _location { @@ -1261,6 +1302,7 @@ class ChineseCalendar { return [] } } + var sunMoonPositions: DailyEvent { var dailyEvent = DailyEvent() if let location = _location { @@ -1293,11 +1335,12 @@ class ChineseCalendar { if apparentTime { dailyEvent.solar[0] = nil dailyEvent.solar[2] = nil - dailyEvent.solar[dailyEvent.solar.count-1] = nil + dailyEvent.solar[dailyEvent.solar.count - 1] = nil } } return dailyEvent } + var sunMoonSubhourPositions: DailyEvent { var dailyEvent = DailyEvent() func hourEventMapping(date: Date?) -> Double? { @@ -1329,7 +1372,7 @@ class ChineseCalendar { if apparentTime { dailyEvent.solar[0] = nil dailyEvent.solar[2] = nil - dailyEvent.solar[dailyEvent.solar.count-1] = nil + dailyEvent.solar[dailyEvent.solar.count - 1] = nil } } return dailyEvent diff --git a/Shared/PlanetModel.swift b/Shared/PlanetModel.swift index 3f677c9..6c10166 100644 --- a/Shared/PlanetModel.swift +++ b/Shared/PlanetModel.swift @@ -9,8 +9,8 @@ import Foundation let planetNames = ["辰", "太白", "熒惑", "歲", "填"] -func %(lhs: T, rhs: T) -> T { - lhs - rhs * floor(lhs / rhs) +func % (lhs: T, rhs: T) -> T { + lhs - rhs*floor(lhs / rhs) } private struct Matrix { @@ -25,7 +25,7 @@ private func precessionMatrixVondrak(T: Double) -> Matrix { let sOmgA = [-0.04155568953790275, 0.0257426196723017, -0.002959273392809311, 0.004475809265755418, 1.822441292043207e-05, -0.0001972760876678778, 0.000389971927172294, 0.003913904086152674, 0.0004058488092230152, -0.001787289168266385, -0.0009302656497305446, -2.067134029104406e-05, -0.0013107116813526, 5.625225752812272e-05, 0, 0, 0, 0] let cChiA = [-0.06673908312554792, 0.06550733801292973, -0.007055149797375992, 0.005111848628877972, 0, -0.0005444464620177098, 0.0009830562551572195, 0.009386235733694169, 0, -0.003177877146985308, -0.004324046613805478, 0, 0, -0.001615990759958801, 0.001587849478343136, -0.002398762740975183, 0.002838548328494804, 0.0005357813386138708] let sChiA = [-0.01069967856443793, -0.02029794993715239, 0.03266650186037179, -0.0041544791939612, 0, 0.004640389727239152, 0.008287602553739408, 0.0007486759753624905, 0, -0.00118062300801947, -0.001970956729830991, 0, 0, -0.002165451504436122, -0.005086043543188153, -0.001461733557390353, 0.0002004643484864111, 0.000690981600754813] - + var psiA = 0.04107992866630529 + T*(0.02444817476355586 + T*(-3.592047589119096e-08 + 1.401111538406559e-12*T)) var omgA = 0.4086163677095374 + T*(-2.150908863572772e-06 + T*(7.078279744199225e-12 + 7.320686584753994e-13*T)) var chiA = -9.530113429264049e-05 + T*(3.830798934518299e-07 + T*(7.13645738593237e-11 - 2.957363454768169e-13*T)) @@ -40,7 +40,7 @@ private func precessionMatrixVondrak(T: Double) -> Matrix { let sPsi = sin(psiA), cPsi = cos(psiA) let sOmg = sin(omgA), cOmg = cos(omgA) let sChi = sin(chiA), cChi = cos(chiA) - + var p = Matrix() p.p11 = cChi*cPsi + sChi*cOmg*sPsi p.p12 = (-cChi*sPsi + sChi*cOmg*cPsi)*cEps + sChi*sOmg*sEps @@ -51,16 +51,16 @@ private func precessionMatrixVondrak(T: Double) -> Matrix { p.p31 = sOmg*sPsi p.p32 = sOmg*cPsi*cEps - cOmg*sEps p.p33 = sOmg*cPsi*sEps + cOmg*cEps - + return p } private func precession_matrix(T0: Double, T: Double) -> Matrix { - if (T0==0) { + if T0==0 { return precessionMatrixVondrak(T: T) } else { let p0 = precessionMatrixVondrak(T: T0) - let p1 = precessionMatrixVondrak(T: T0+T) + let p1 = precessionMatrixVondrak(T: T0 + T) var p = Matrix() // Inverse of p0 is the transpose of p0 p.p11 = p1.p11*p0.p11 + p1.p12*p0.p12 + p1.p13*p0.p13 @@ -79,37 +79,37 @@ private func precession_matrix(T0: Double, T: Double) -> Matrix { // Solve the Kepler's equation M = - e sin E private func kepler(M: Double, e: Double) -> Double { // mean anomaly -> [-pi, pi) - let n2pi = floor(M / (2.0*Double.pi) + 0.5) * (2.0*Double.pi) + let n2pi = floor(M / (2.0*Double.pi) + 0.5)*(2.0*Double.pi) let Mp = M - n2pi // Solve Kepler's equation E - e sin E = M using Newton's iteration method var E = Mp // initial guess - if (e > 0.8) { + if e > 0.8 { E = Double.pi } // need another initial guess for very eccentric orbit var E0 = E*1.01 let tol = 1e-15 var iter = 0, maxit = 100 - while (abs(E-E0) > tol && iter < maxit) { + while abs(E - E0) > tol && iter < maxit { E0 = E - E = E0 - (E0 - e * sin(E0) - Mp)/(1.0 - e * cos(E0)) + E = E0 - (E0 - e*sin(E0) - Mp) / (1.0 - e*cos(E0)) iter += 1 } - if (iter==maxit) { + if iter==maxit { // Newton's iteration doesn't converge after 100 iterations, use bisection instead. iter = 0 maxit = 60 - if (Mp > 0.0) { + if Mp > 0.0 { E0 = 0.0 E = Double.pi } else { E = 0.0 E0 = -Double.pi } - while (E-E0 > tol && iter < maxit) { - let E1 = 0.5*(E+E0) - let z = E1 - e * sin(E1) - Mp - if (z > 0.0) { + while E - E0 > tol && iter < maxit { + let E1 = 0.5*(E + E0) + let z = E1 - e*sin(E1) - Mp + if z > 0.0 { E = E1 } else { E0 = E1 @@ -118,7 +118,7 @@ private func kepler(M: Double, e: Double) -> Double { } } - return E + return E } // Planet positions at T @@ -134,16 +134,16 @@ private func kepler(M: Double, e: Double) -> Double { // For planets whose positions are not calculated, as indicated // in the variable 'calculate', ra and dec are not defined. func planetPos(T: Double) -> [Double] { - let pi2 = 2 * Double.pi; + let pi2 = 2*Double.pi // 1/light speed in century/AU let f1oc = 1.58125073358306e-07 let cosEps = cos(eps) let sinEps = sin(eps) - + // Angles have been converted to radians - let a0: [Double], adot: [Double], e0: [Double],edot: [Double], I0: [Double], Idot: [Double], L0: [Double], Ldot: [Double], pom0: [Double], pomdot: [Double], Omg0: [Double], Omgdot: [Double] + let a0: [Double], adot: [Double], e0: [Double], edot: [Double], I0: [Double], Idot: [Double], L0: [Double], Ldot: [Double], pom0: [Double], pomdot: [Double], Omg0: [Double], Omgdot: [Double] let b: [Double], c: [Double], s: [Double], f: [Double] - if (T > -2 && T < 0.5) { + if T > -2, T < 0.5 { // use the parameters for 1800 AD - 2050 AD a0 = [1.00000261, 0.38709927, 0.72333566, 1.52371034, 5.202887, 9.53667594] adot = [0.00000562, 0.00000037, 0.0000039, 0.00001847, -0.00011607, -0.0012506] @@ -168,7 +168,7 @@ func planetPos(T: Double) -> [Double] { e0 = [0.01673163, 0.20563661, 0.00676399, 0.09336511, 0.0485359, 0.05550825] edot = [-0.00003661, 0.00002123, -0.00005107, 0.00009149, 0.00018026, -0.00032044] I0 = [-9.48516635288838e-06, 0.122270686943013, 0.059302368845932, 0.0323203332904682, 0.0226650928050204, 0.0435327181373017] - Idot = [-0.000233381587852327, -0.000103002002069847, 7.59113504862414e-06, -0.000126493959268765, -5.63216004289318e-05, 7.88834716694625e-05] + Idot = [-0.000233381587852327, -0.000103002002069847, 7.59113504862414e-06, -0.000126493959268765, -5.63216004289318e-05, 7.88834716694625e-05] L0 = [1.75347846863765, 4.40262213698312, 3.17614508514451, -0.0797289377825283, 0.599255160009829, 0.873986072195182] Ldot = [628.307588608167, 2608.79031817869, 1021.32855334028, 334.061243342709, 52.9690623526126, 21.3299296671748] pom0 = [1.79646842620403, 1.35189222676191, 2.29977771922823, -0.417438213482006, 0.249144920643598, 1.62073649087534] @@ -180,7 +180,7 @@ func planetPos(T: Double) -> [Double] { s = [0, 0, 0, 0, -0.00621955723490303, 0.0152402406847545] f = [0, 0, 0, 0, 0.669355584755475, 0.669355584755475] } - + var xp: Double, yp: Double, zp: Double var x: [Double] = [0, 0, 0, 0, 0, 0] var y: [Double] = [0, 0, 0, 0, 0, 0] @@ -199,12 +199,12 @@ func planetPos(T: Double) -> [Double] { let Omg = Omg0[i] + Omgdot[i]*T let omg = pom - Omg var M = L - pom - M += b[i] * T*T + c[i] * cos(f[i]*T) + s[i] * sin(f[i]*T) + M += b[i]*T*T + c[i]*cos(f[i]*T) + s[i]*sin(f[i]*T) let E = kepler(M: M, e: e) - let bb = a * sqrt(1-e*e) - let Edot = Ldot[i] / (1-e*cos(E)) - xp = a * (cos(E)-e) - yp = bb * sin(E) + let bb = a*sqrt(1 - e*e) + let Edot = Ldot[i] / (1 - e*cos(E)) + xp = a*(cos(E) - e) + yp = bb*sin(E) let vxp = -a*sin(E)*Edot let vyp = bb*cos(E)*Edot let m11 = cos(omg)*cos(Omg) - sin(omg)*sin(Omg)*cos(I) @@ -220,11 +220,11 @@ func planetPos(T: Double) -> [Double] { z[i] = m31*xp + m32*yp vz[i] = m31*vxp + m32*vyp } - + // heliocentric position -> geocentric position // index 2 becomes Sun's geocentric position x[0] = -x[0]; y[0] = -y[0]; z[0] = -z[0] - //let dT; + // let dT; for i in 1..<6 { x[i] = x[i] + x[0] y[i] = y[i] + y[0] @@ -238,8 +238,8 @@ func planetPos(T: Double) -> [Double] { } // RA and Dec with respect to J2000 - let p = precession_matrix(T0: 0,T: T) - + let p = precession_matrix(T0: 0, T: T) + var output = [Double]() for i in 1..<6 { // equatorial coordinates @@ -251,69 +251,69 @@ func planetPos(T: Double) -> [Double] { xp = p.p11*xeq + p.p12*yeq + p.p13*zeq yp = p.p21*xeq + p.p22*yeq + p.p23*zeq zp = p.p31*xeq + p.p32*yeq + p.p33*zeq - - //to eliptic coordinates + + // to eliptic coordinates let xel = xp let yel = cosEps*yp + sinEps*zp - //let zel = - sinEps*yp + cosEps*zp - output.append(atan2(yel,xel)) + // let zel = - sinEps*yp + cosEps*zp + output.append(atan2(yel, xel)) } return output } func moonCoordinate(D: Double) -> (Double, Double, Double) { - var l = 0.606434 + 0.03660110129 * D - var m = 0.374897 + 0.03629164709 * D - var f = 0.259091 + 0.03674819520 * D - var d = 0.827362 + 0.03386319198 * D - var n = 0.347343 - 0.00014709391 * D - var g = 0.993126 + 0.00273777850 * D - - l = 2 * Double.pi * (l - floor(l)) - m = 2 * Double.pi * (m - floor(m)) - f = 2 * Double.pi * (f - floor(f)) - d = 2 * Double.pi * (d - floor(d)) - n = 2 * Double.pi * (n - floor(n)) - g = 2 * Double.pi * (g - floor(g)) + var l = 0.606434 + 0.03660110129*D + var m = 0.374897 + 0.03629164709*D + var f = 0.259091 + 0.03674819520*D + var d = 0.827362 + 0.03386319198*D + var n = 0.347343 - 0.00014709391*D + var g = 0.993126 + 0.00273777850*D + + l = 2*Double.pi*(l - floor(l)) + m = 2*Double.pi*(m - floor(m)) + f = 2*Double.pi*(f - floor(f)) + d = 2*Double.pi*(d - floor(d)) + n = 2*Double.pi*(n - floor(n)) + g = 2*Double.pi*(g - floor(g)) var v: Double, u: Double, w: Double - v = 0.39558 * sin(f + n) - + 0.08200 * sin(f) - + 0.03257 * sin(m - f - n) - + 0.01092 * sin(m + f + n) - + 0.00666 * sin(m - f) - - 0.00644 * sin(m + f - 2*d + n) - - 0.00331 * sin(f - 2*d + n) - - 0.00304 * sin(f - 2*d) - - 0.00240 * sin(m - f - 2*d - n) - + 0.00226 * sin(m + f) - - 0.00108 * sin(m + f - 2*d) - - 0.00079 * sin(f - n) - + 0.00078 * sin(f + 2*d + n) - + v = 0.39558*sin(f + n) + + 0.08200*sin(f) + + 0.03257*sin(m - f - n) + + 0.01092*sin(m + f + n) + + 0.00666*sin(m - f) + - 0.00644*sin(m + f - 2*d + n) + - 0.00331*sin(f - 2*d + n) + - 0.00304*sin(f - 2*d) + - 0.00240*sin(m - f - 2*d - n) + + 0.00226*sin(m + f) + - 0.00108*sin(m + f - 2*d) + - 0.00079*sin(f - n) + + 0.00078*sin(f + 2*d + n) + u = 1 - - 0.10828 * cos(m) - - 0.01880 * cos(m - 2*d) - - 0.01479 * cos(2*d) - + 0.00181 * cos(2*m - 2*d) - - 0.00147 * cos(2*m) - - 0.00105 * cos(2*d - g) - - 0.00075 * cos(m - 2*d + g) - - w = 0.10478 * sin(m) - - 0.04105 * sin(2*f + 2*n) - - 0.02130 * sin(m - 2*d) - - 0.01779 * sin(2*f + n) - + 0.01774 * sin(n) - + 0.00987 * sin(2*d) - - 0.00338 * sin(m - 2*f - 2*n) - - 0.00309 * sin(g) - - 0.00190 * sin(2*f) - - 0.00144 * sin(m + n) - - 0.00144 * sin(m - 2*f - n) - - 0.00113 * sin(m + 2*f + 2*n) - - 0.00094 * sin(m - 2*d + g) - - 0.00092 * sin(2*m - 2*d) + - 0.10828*cos(m) + - 0.01880*cos(m - 2*d) + - 0.01479*cos(2*d) + + 0.00181*cos(2*m - 2*d) + - 0.00147*cos(2*m) + - 0.00105*cos(2*d - g) + - 0.00075*cos(m - 2*d + g) + + w = 0.10478*sin(m) + - 0.04105*sin(2*f + 2*n) + - 0.02130*sin(m - 2*d) + - 0.01779*sin(2*f + n) + + 0.01774*sin(n) + + 0.00987*sin(2*d) + - 0.00338*sin(m - 2*f - 2*n) + - 0.00309*sin(g) + - 0.00190*sin(2*f) + - 0.00144*sin(m + n) + - 0.00144*sin(m - 2*f - n) + - 0.00113*sin(m + 2*f + 2*n) + - 0.00094*sin(m - 2*d + g) + - 0.00092*sin(2*m - 2*d) var s: Double s = w / sqrt(u - v*v) @@ -322,49 +322,49 @@ func moonCoordinate(D: Double) -> (Double, Double, Double) { s = v / sqrt(u) let declination = atan(s / sqrt(1 - s*s)) - let distance = 60.40974 * sqrt(u) + let distance = 60.40974*sqrt(u) + + let x = cos(rightAscension)*cos(declination)*distance + let y = sin(rightAscension)*cos(declination)*distance + let z = sin(declination)*distance - let x = cos(rightAscension) * cos(declination) * distance - let y = sin(rightAscension) * cos(declination) * distance - let z = sin(declination) * distance - // RA and Dec with respect to J2000 - let p = precession_matrix(T0: 0,T: D/36525) + let p = precession_matrix(T0: 0, T: D / 36525) // precessed to the mean equator and equinox of the date let x_new = p.p11*x + p.p12*y + p.p13*z let y_new = p.p21*x + p.p22*y + p.p23*z let z_new = p.p31*x + p.p32*y + p.p33*z - + return (x: x_new, y: y_new, z: z_new) } func moonElipticPosition(D: Double) -> Double { let (x, y, z) = moonCoordinate(D: D) // Back to eliptic - let yel = cos(eps) * y + sin(eps) * z + let yel = cos(eps)*y + sin(eps)*z return atan2(yel, x) } func moonEquatorPosition(D: Double) -> (Double, Double, Double) { let (x, y, z) = moonCoordinate(D: D) - return (ra: atan2(y, x), dec: atan2(z, sqrt(x*x+y*y)), sqrt(x*x+y*y+z*z)) + return (ra: atan2(y, x), dec: atan2(z, sqrt(x*x + y*y)), sqrt(x*x + y*y + z*z)) } func equationOfTime(D: Double) -> Double { let d = D / 36525 - let epsilon = (23.4393 - 0.013 * d - 2e-7 * pow(d, 2) + 5e-7 * pow(d, 3)) / 180 * Double.pi - let e = 1.6709e-2 - 4.193e-5 * d - 1.26e-7 * pow(d, 2) - let lambdaP = (282.93807 + 1.7195 * d + 3.025e-4 * pow(d, 2)) / 180 * Double.pi + let epsilon = (23.4393 - 0.013*d - 2e-7*pow(d, 2) + 5e-7*pow(d, 3)) / 180*Double.pi + let e = 1.6709e-2 - 4.193e-5*d - 1.26e-7*pow(d, 2) + let lambdaP = (282.93807 + 1.7195*d + 3.025e-4*pow(d, 2)) / 180*Double.pi let y = pow(tan(epsilon / 2), 2) - let m = 6.24004077 + 0.01720197 * D - var deltaT = -2 * e * sin(m) + y * sin(2 * (m + lambdaP)) - deltaT += -1.25 * pow(e, 2) * sin(2 * m) + 4 * e * y * sin(m) * cos(2 * (m + lambdaP)) - 0.5 * pow(y, 2) * sin(4 * (m + lambdaP)) + let m = 6.24004077 + 0.01720197*D + var deltaT = -2*e*sin(m) + y*sin(2*(m + lambdaP)) + deltaT += -1.25*pow(e, 2)*sin(2*m) + 4*e*y*sin(m)*cos(2*(m + lambdaP)) - 0.5*pow(y, 2)*sin(4*(m + lambdaP)) return deltaT } func daytimeOffset(latitude: Double, progressInYear: Double) -> Double { - let denominator = sqrt(pow(cos(eps), 2) + pow(sin(eps) * sin(progressInYear), 2)) * cos(latitude) - let numerator = sin(latitude) * sin(eps) * cos(progressInYear) - sin(aeroAdj) + let denominator = sqrt(pow(cos(eps), 2) + pow(sin(eps)*sin(progressInYear), 2))*cos(latitude) + let numerator = sin(latitude)*sin(eps)*cos(progressInYear) - sin(aeroAdj) let cosValue = numerator / denominator if cosValue >= 1 { return -Double.infinity @@ -377,8 +377,8 @@ func daytimeOffset(latitude: Double, progressInYear: Double) -> Double { func lunarTimeOffset(latitude: Double, jdTime: Double, light: Bool) -> Double { let (_, dec, dist) = moonEquatorPosition(D: jdTime) - let parallaxAdj = asin((1-0.273) / dist) - let cosValue = (sin(latitude) * sin(dec) - sin(aeroAdj - parallaxAdj)) / (cos(dec) * cos(latitude)) * (light ? 1 : -1) + let parallaxAdj = asin((1 - 0.273) / dist) + let cosValue = (sin(latitude)*sin(dec) - sin(aeroAdj - parallaxAdj)) / (cos(dec)*cos(latitude))*(light ? 1 : -1) if cosValue >= 1 { return Double.infinity } else if cosValue <= -1 { diff --git a/Shared/RoundedRect.swift b/Shared/RoundedRect.swift index c28e69c..9f57e5d 100644 --- a/Shared/RoundedRect.swift +++ b/Shared/RoundedRect.swift @@ -5,8 +5,8 @@ // Created by Leo Liu on 5/3/23. // -import Foundation import CoreGraphics +import Foundation class RoundedRect { let _boundBox: CGRect @@ -23,9 +23,9 @@ class RoundedRect { return RoundedRect(rect: _boundBox, nodePos: _nodePos, ankorPos: _ankorPos) } - private func drawPath(vertex: Array) -> CGMutablePath { + private func drawPath(vertex: [CGPoint]) -> CGMutablePath { let path = CGMutablePath() - var previousPoint = vertex[vertex.count-1] + var previousPoint = vertex[vertex.count - 1] var point = vertex[0] var nextPoint: CGPoint var control1: CGPoint @@ -35,13 +35,13 @@ class RoundedRect { target.x -= _nodePos path.move(to: target) for i in 0..= abs(diff.y)) { + if abs(diff.x) >= abs(diff.y) { target.x -= diff.x >= 0 ? _nodePos : -_nodePos control1.x -= diff.x >= 0 ? _ankorPos : -_ankorPos } else { @@ -52,7 +52,7 @@ class RoundedRect { target = point control2 = point diff = nextPoint - point - if (abs(diff.x) > abs(diff.y)) { + if abs(diff.x) > abs(diff.y) { control2.x += diff.x >= 0 ? _ankorPos : -_ankorPos target.x += diff.x >= 0 ? _nodePos : -_nodePos } else { @@ -66,18 +66,18 @@ class RoundedRect { } var path: CGMutablePath { - let vertex: Array = [CGPoint(x: _boundBox.minX, y: _boundBox.minY), - CGPoint(x: _boundBox.minX, y: _boundBox.maxY), - CGPoint(x: _boundBox.maxX, y: _boundBox.maxY), - CGPoint(x: _boundBox.maxX, y: _boundBox.minY)] + let vertex: [CGPoint] = [CGPoint(x: _boundBox.minX, y: _boundBox.minY), + CGPoint(x: _boundBox.minX, y: _boundBox.maxY), + CGPoint(x: _boundBox.maxX, y: _boundBox.maxY), + CGPoint(x: _boundBox.maxX, y: _boundBox.minY)] return drawPath(vertex: vertex) } func shrink(by diameterChange: CGFloat) -> RoundedRect { let shortEdgeLength = min(_boundBox.width, _boundBox.height) - let horizontalShift = diameterChange * (shortEdgeLength - _nodePos) / max(_boundBox.width, _boundBox.height) - let newNodePos = max(0, min(shortEdgeLength / 2 - diameterChange, _nodePos + horizontalShift - diameterChange)) - let newAnkorPos = max(0, (_ankorPos + 0.55 * diameterChange + 0.31 * horizontalShift) - diameterChange) + let horizontalShift = diameterChange * (shortEdgeLength - _nodePos)/max(_boundBox.width, _boundBox.height) + let newNodePos = max(0, min(shortEdgeLength/2 - diameterChange, _nodePos+horizontalShift - diameterChange)) + let newAnkorPos = max(0, (_ankorPos+0.55 * diameterChange+0.31 * horizontalShift) - diameterChange) var newBoundBox = _boundBox newBoundBox.origin.x += diameterChange newBoundBox.origin.y += diameterChange @@ -88,13 +88,13 @@ class RoundedRect { func bezierLength(t: CGFloat) -> CGFloat { guard _nodePos > 0 else { return 0 } - let alpha = _ankorPos / _nodePos + let alpha = _ankorPos/_nodePos - var length = pow(t, 2) * (-1 - 2 * alpha + 9 * pow(alpha, 2)) - 5 * t * alpha * (1 - alpha) - length *= 0.3 * (-1 + 2 * alpha + pow(alpha, 2)) - length += pow(1 - alpha, 2) * (1 - 4 * alpha + 5 * pow(alpha, 2)) - length *= pow(t / (1 - alpha), 3) - length += 3 * t * ((1 - alpha) + t * (2 * alpha - 1)) + var length = pow(t, 2) * (-1 - 2 * alpha+9 * pow(alpha, 2)) - 5 * t * alpha * (1 - alpha) + length *= 0.3 * (-1+2 * alpha+pow(alpha, 2)) + length += pow(1 - alpha, 2) * (1 - 4 * alpha+5 * pow(alpha, 2)) + length *= pow(t/(1 - alpha), 3) + length += 3 * t * ((1 - alpha)+t * (2 * alpha - 1)) return length * _nodePos } @@ -105,11 +105,10 @@ class RoundedRect { } func arcPoints(lambdas: [CGFloat]) -> [OrientedPoint] { - let arcLength = bezierLength(t: 0.5) * 2 let innerWidth = _boundBox.width - 2 * _nodePos let innerHeight = _boundBox.height - 2 * _nodePos - let totalLength = 2 * (innerWidth + innerHeight) + 4 * arcLength + let totalLength = 2 * (innerWidth+innerHeight)+4 * arcLength func bezierNorm(l: CGFloat) -> (CGPoint, CGFloat) { var t: CGFloat = 0.0 @@ -131,15 +130,15 @@ class RoundedRect { } currL = bezierLength(t: t) } - t -= (currL - effectiveL * arcLength) / (currL - prevL) * stepSize + t -= (currL - effectiveL * arcLength)/(currL - prevL) * stepSize if otherSide { t = 1.0 - t } - let alpha = _ankorPos / _nodePos - let xt = pow(t, 3) + 3 * alpha * (1 - t) * pow(t, 2) - let yt = pow(1 - t, 3) + 3 * alpha * t * pow(1 - t, 2) - let dxt = 3 * (1 - alpha) * pow(1 - t, 2) + 6 * alpha * (1 - t) * t - let dyt = 3 * (1 - alpha) * pow(t, 2) + 6 * alpha * (1 - t) * t + let alpha = _ankorPos/_nodePos + let xt = pow(t, 3)+3 * alpha * (1 - t) * pow(t, 2) + let yt = pow(1 - t, 3)+3 * alpha * t * pow(1 - t, 2) + let dxt = 3 * (1 - alpha) * pow(1 - t, 2)+6 * alpha * (1 - t) * t + let dyt = 3 * (1 - alpha) * pow(t, 2)+6 * alpha * (1 - t) * t let angle: CGFloat = atan2(dyt, dxt) let midPoint = CGPoint(x: xt * _nodePos, y: yt * _nodePos) return (midPoint, angle) @@ -154,21 +153,21 @@ class RoundedRect { case 0.0.. CGPath { - let center = CGPoint(x: _boundBox.midX, y: _boundBox.midY) func getEnd(start: CGPoint, center: CGPoint, width: CGFloat) -> CGPoint { var direction = start - center - direction = direction / sqrt(pow(direction.x, 2) + pow(direction.y, 2)) + direction = direction/sqrt(pow(direction.x, 2)+pow(direction.y, 2)) let end = start - direction * width return end } @@ -261,14 +259,14 @@ class RoundedRect { } func anglePath(angle: CGFloat, startingAngle: CGFloat, in circle: RoundedRect) -> CGMutablePath { - let radius = sqrt(pow(circle._boundBox.width, 2) + pow(circle._boundBox.height, 2)) + let radius = sqrt(pow(circle._boundBox.width, 2)+pow(circle._boundBox.height, 2)) let center = CGPoint(x: circle._boundBox.midX, y: circle._boundBox.midY) - let anglePoints = circle.arcPoints(lambdas: [startingAngle.truncatingRemainder(dividingBy: 1.0), (startingAngle + (startingAngle >= 0 ? angle : -angle)).truncatingRemainder(dividingBy: 1.0)]) + let anglePoints = circle.arcPoints(lambdas: [startingAngle.truncatingRemainder(dividingBy: 1.0), (startingAngle+(startingAngle >= 0 ? angle : -angle)).truncatingRemainder(dividingBy: 1.0)]) let realStartingAngle = atan2(anglePoints[0].position.y - center.y, anglePoints[0].position.x - center.x) let realAngle = atan2(anglePoints[1].position.y - center.y, anglePoints[1].position.x - center.x) let path = CGMutablePath() path.move(to: center) - path.addLine(to: center + CGPoint(x: radius * cos(realStartingAngle), y: radius * sin(realStartingAngle))) + path.addLine(to: center+CGPoint(x: radius * cos(realStartingAngle), y: radius * sin(realStartingAngle))) path.addArc(center: center, radius: radius, startAngle: realStartingAngle, endAngle: realAngle, clockwise: startingAngle >= 0) path.closeSubpath() return path diff --git a/Shared/Utilities.swift b/Shared/Utilities.swift index d45fb80..807c63b 100644 --- a/Shared/Utilities.swift +++ b/Shared/Utilities.swift @@ -11,7 +11,6 @@ enum MarkdownElement { } class MarkdownParser { - func parse(_ markdownString: String) -> [MarkdownElement] { var elements: [MarkdownElement] = [] var lines = markdownString.components(separatedBy: .newlines) @@ -67,11 +66,10 @@ class MarkdownParser { } extension String { - var boldRanges: [Range] { var ranges: [Range] = [] var startIndex = self.startIndex - while startIndex < self.endIndex, let range = self[startIndex...].range(of: "**") { + while startIndex < endIndex, let range = self[startIndex...].range(of: "**") { startIndex = range.upperBound if let range2 = self[startIndex...].range(of: "**") { ranges.append(range.upperBound.. + private var registry: [String: Int] init(name: String) { nodeName = name @@ -121,9 +118,11 @@ class DataTree: CustomStringConvertible { return nil } } + func index(of element: String) -> Int? { return registry[element] } + subscript(index: Int) -> DataTree? { if (0.. Gradient { - let colors: [CGColor] let locations: [CGFloat] if startingAngle >= 0 { colors = gradient.colors.reversed() - locations = gradient.locations.map { 1-$0 }.reversed() + locations = gradient.locations.map { 1 - $0 }.reversed() } else { colors = gradient.colors locations = gradient.locations diff --git a/Watch/WatchFaceBasics.swift b/Watch/WatchFaceBasics.swift index c3eb9c8..b90eede 100644 --- a/Watch/WatchFaceBasics.swift +++ b/Watch/WatchFaceBasics.swift @@ -7,19 +7,18 @@ import SwiftUI - struct WatchFont { -#if os(macOS) - var font: NSFont - init(_ font: NSFont) { - self.font = font - } -#else - var font: UIFont - init(_ font: UIFont) { - self.font = font - } -#endif + #if os(macOS) + var font: NSFont + init(_ font: NSFont) { + self.font = font + } + #else + var font: UIFont + init(_ font: UIFont) { + self.font = font + } + #endif } struct ZeroRing: View { @@ -41,11 +40,11 @@ struct ZeroRing: View { let shortEdge = min(self.viewSize.width, self.viewSize.height) let longEdge = max(self.viewSize.width, self.viewSize.height) let fontSize: CGFloat = min(shortEdge * 0.03, longEdge * 0.025) * (compact ? 1.5 : 1.0) - let majorLineWidth = shortEdge / 300 + let majorLineWidth = shortEdge/300 - Canvas { context, size in + Canvas { context, _ in - let textRing = outerRing.shrink(by: (width + 0.003) / 2 * shortEdge) + let textRing = outerRing.shrink(by: (width + 0.003)/2 * shortEdge) let innerBound = outerRing.shrink(by: width * shortEdge) let ringBoundPath = outerRing.path ringBoundPath.addPath(innerBound.path) @@ -114,8 +113,8 @@ struct Ring: View { let shortEdge = min(self.viewSize.width, self.viewSize.height) let longEdge = max(self.viewSize.width, self.viewSize.height) let fontSize: CGFloat = min(shortEdge * 0.03, longEdge * 0.025) * (compact ? 1.5 : 1.0) - let minorLineWidth = shortEdge / 500 - let majorLineWidth = shortEdge / 300 + let minorLineWidth = shortEdge/500 + let majorLineWidth = shortEdge/300 Canvas { graphicsContext, size in @@ -126,8 +125,8 @@ struct Ring: View { var context = graphicsContext context.clip(to: Path(outerRingPath), style: FillStyle(eoFill: true)) - let majorTicksPath = outerRing.arcPosition(lambdas: changePhase(phase: startingAngle, angles: ticks.majorTicks.map{CGFloat($0)}), width: 0.15 * shortEdge) - let minorTicksPath = outerRing.arcPosition(lambdas: changePhase(phase: startingAngle, angles: ticks.minorTicks.map{CGFloat($0)}), width: 0.15 * shortEdge) + let majorTicksPath = outerRing.arcPosition(lambdas: changePhase(phase: startingAngle, angles: ticks.majorTicks.map { CGFloat($0) }), width: 0.15 * shortEdge) + let minorTicksPath = outerRing.arcPosition(lambdas: changePhase(phase: startingAngle, angles: ticks.minorTicks.map { CGFloat($0) }), width: 0.15 * shortEdge) let minorTrackOuter = outerRing.shrink(by: 0.01 * shortEdge) let minorTrackInner = outerRing.shrink(by: (width - 0.015) * shortEdge) @@ -135,7 +134,7 @@ struct Ring: View { minorTrackPath.addPath(minorTrackInner.path) let font = textFont.font.withSize(fontSize) - let textRing = outerRing.shrink(by: (width - 0.005) / 2 * shortEdge) + let textRing = outerRing.shrink(by: (width - 0.005)/2 * shortEdge) let tickPositions = textRing.arcPoints(lambdas: changePhase(phase: startingAngle, angles: ticks.majorTickNames.map { CGFloat($0.position) })) var drawableTexts = [DrawableText]() let textMaskPath = CGMutablePath() @@ -156,9 +155,9 @@ struct Ring: View { ctx.addFilter(.luminanceToAlpha) ctx.fill(Path(roundedRect: CGRect(origin: .zero, size: size), cornerSize: .zero), with: .color(white: 0)) ctx.clip(to: Path(textMaskPath), options: .inverse) - ctx.stroke(Path(majorTicksPath), with: .color(white: 1-majorTickAlpha), style: StrokeStyle(lineWidth: majorLineWidth, lineCap: .square, lineJoin: .bevel, miterLimit: .leastNonzeroMagnitude)) + ctx.stroke(Path(majorTicksPath), with: .color(white: 1 - majorTickAlpha), style: StrokeStyle(lineWidth: majorLineWidth, lineCap: .square, lineJoin: .bevel, miterLimit: .leastNonzeroMagnitude)) ctx.clip(to: Path(minorTrackPath), style: FillStyle(eoFill: true)) - ctx.stroke(Path(minorTicksPath), with: .color(white: 1-minorTickAlpha), style: StrokeStyle(lineWidth: minorLineWidth, lineCap: .square, lineJoin: .bevel, miterLimit: .leastNonzeroMagnitude)) + ctx.stroke(Path(minorTicksPath), with: .color(white: 1 - minorTickAlpha), style: StrokeStyle(lineWidth: minorLineWidth, lineCap: .square, lineJoin: .bevel, miterLimit: .leastNonzeroMagnitude)) } var inactiveRingContext = gradientContext @@ -176,7 +175,6 @@ struct Ring: View { tickContext.clip(to: Path(minorTrackPath), style: FillStyle(eoFill: true)) tickContext.stroke(Path(minorTicksPath), with: .color(Color(cgColor: minorTickColor)), style: StrokeStyle(lineWidth: minorLineWidth, lineCap: .square, lineJoin: .bevel, miterLimit: .leastNonzeroMagnitude)) - var transform = CGAffineTransform() var textContext = context for drawabeText in drawableTexts { @@ -191,12 +189,12 @@ struct Ring: View { for mark in marks { let points: [RoundedRect.OrientedPoint] if mark.outer { - points = outerRing.arcPoints(lambdas: changePhase(phase: startingAngle, angles: mark.locations.filter { 0 <= $0 && 1 > $0} )) + points = outerRing.arcPoints(lambdas: changePhase(phase: startingAngle, angles: mark.locations.filter { $0 >= 0 && $0 < 1 })) } else { - points = innerRing.arcPoints(lambdas: changePhase(phase: startingAngle, angles: mark.locations.filter { 0 <= $0 && 1 > $0} )) + points = innerRing.arcPoints(lambdas: changePhase(phase: startingAngle, angles: mark.locations.filter { $0 >= 0 && $0 < 1 })) } var markContext = context - markContext.addFilter(.shadow(color: Color(white: 0, opacity: 0.5), radius: mark.radius / 2, x: 0, y: 0)) + markContext.addFilter(.shadow(color: Color(white: 0, opacity: 0.5), radius: mark.radius/2, x: 0, y: 0)) for i in 0.. [DrawableText] { let centerTextShortSize = min(outerBound._boundBox.width, outerBound._boundBox.height) * 0.31 let centerTextLongSize = max(outerBound._boundBox.width, outerBound._boundBox.height) * 0.17 - let centerTextSize = min(centerTextShortSize, centerTextLongSize) * (compact ? 1.1 : 1.0) * sqrt(5 / CGFloat(maxLength)) + let centerTextSize = min(centerTextShortSize, centerTextLongSize) * (compact ? 1.1 : 1.0) * sqrt(5/CGFloat(maxLength)) let isVertical = viewSize.height >= viewSize.width - let offset = centerTextSize * offsetRatio * sqrt(CGFloat(maxLength) / 3) + let offset = centerTextSize * offsetRatio * sqrt(CGFloat(maxLength)/3) var drawableTexts = [DrawableText]() let centerFont = font.font.withSize(centerTextSize) @@ -245,19 +243,19 @@ struct Core: View { let attrStr = NSMutableAttributedString(string: text) attrStr.addAttributes([.font: centerFont, .foregroundColor: CGColor(gray: 1, alpha: 1)], range: NSMakeRange(0, attrStr.length)) - var characters = attrStr.string.map{NSMutableAttributedString(string: String($0), attributes: attrStr.attributes(at: 0, effectiveRange: nil))} + var characters = attrStr.string.map { NSMutableAttributedString(string: String($0), attributes: attrStr.attributes(at: 0, effectiveRange: nil)) } if characters.count > maxLength { characters = Array(characters[.. [DrawableText] { - let string: String var hasSpace = false let fontSize = font.font.pointSize @@ -358,22 +355,22 @@ private func prepareText(tickName: String, at point: RoundedRect.OrientedPoint, var boxTransform = CGAffineTransform(translationX: -point.position.x, y: -point.position.y) let transform: CGAffineTransform - if point.direction <= CGFloat.pi / 4 { + if point.direction <= CGFloat.pi/4 { transform = CGAffineTransform(rotationAngle: -point.direction) } else if point.direction < CGFloat.pi * 3/4 { - transform = CGAffineTransform(rotationAngle: CGFloat.pi/2-point.direction) + transform = CGAffineTransform(rotationAngle: CGFloat.pi/2 - point.direction) } else if point.direction < CGFloat.pi * 5/4 { - transform = CGAffineTransform(rotationAngle: CGFloat.pi-point.direction) + transform = CGAffineTransform(rotationAngle: CGFloat.pi - point.direction) } else if point.direction < CGFloat.pi * 7/4 { - transform = CGAffineTransform(rotationAngle: -point.direction-CGFloat.pi/2) + transform = CGAffineTransform(rotationAngle: -point.direction - CGFloat.pi/2) } else { transform = CGAffineTransform(rotationAngle: -point.direction) } boxTransform = boxTransform.concatenating(transform) boxTransform = boxTransform.concatenating(CGAffineTransform(translationX: point.position.x, y: point.position.y)) - let characters = string.map{NSMutableAttributedString(string: String($0), attributes: attrStr.attributes(at: 0, effectiveRange: nil))} - let mean = CGFloat(characters.count - 1) / 2 + let characters = string.map { NSMutableAttributedString(string: String($0), attributes: attrStr.attributes(at: 0, effectiveRange: nil)) } + let mean = CGFloat(characters.count - 1)/2 var text = [DrawableText]() for i in 0.. CGFloat.pi / 4 && point.direction < CGFloat.pi * 3/4) || (point.direction > CGFloat.pi * 5/4 && point.direction < CGFloat.pi * 7/4) { + if (point.direction > CGFloat.pi/4 && point.direction < CGFloat.pi * 3/4) || (point.direction > CGFloat.pi * 5/4 && point.direction < CGFloat.pi * 7/4) { box.origin.y += shift } else { box.origin.x -= shift diff --git a/Watch/WatchFaceView.swift b/Watch/WatchFaceView.swift index f400b50..9c2a557 100644 --- a/Watch/WatchFaceView.swift +++ b/Watch/WatchFaceView.swift @@ -22,7 +22,7 @@ private func allRingMarks(watchLayout: WatchLayout, chineseCalendar: ChineseCale let firstRingMarks = [Marks(outer: true, locations: chineseCalendar.planetPosition.map { $0.pos }, colors: watchLayout.planetIndicator, radius: radius)] let secondRingMarks = [ Marks(outer: true, locations: eventInMonth.eclipse.map { $0.pos }, colors: [watchLayout.eclipseIndicator], radius: radius), - Marks(outer: true, locations: eventInMonth.fullMoon.map { $0.pos }, colors: [watchLayout.fullmoonIndicator], radius: radius), + Marks(outer: true, locations: eventInMonth.fullMoon.map { $0.pos }, colors: [watchLayout.fullmoonIndicator], radius: radius), Marks(outer: true, locations: eventInMonth.oddSolarTerm.map { $0.pos }, colors: [watchLayout.oddStermIndicator], radius: radius), Marks(outer: true, locations: eventInMonth.evenSolarTerm.map { $0.pos }, colors: [watchLayout.evenStermIndicator], radius: radius) ] @@ -82,7 +82,7 @@ struct Watch: View { var body: some View { let shortEdge = min(self.size.width, self.size.height) let cornerSize = watchLayout.cornerRadiusRatio * shortEdge - let outerBound = RoundedRect(rect: CGRect(origin: .zero, size: size), nodePos: cornerSize, ankorPos: cornerSize*0.2).shrink(by: Watch.frameOffset * shortEdge) + let outerBound = RoundedRect(rect: CGRect(origin: .zero, size: size), nodePos: cornerSize, ankorPos: cornerSize * 0.2).shrink(by: Watch.frameOffset * shortEdge) let firstRingOuter = outerBound.shrink(by: ZeroRing.width * shortEdge) let secondRingOuter = firstRingOuter.shrink(by: Ring.paddedWidth * shortEdge) let thirdRingOuter = secondRingOuter.shrink(by: Ring.paddedWidth * shortEdge) @@ -98,14 +98,14 @@ struct Watch: View { GeometryReader { proxy in ZStack { - ZeroRing(width: ZeroRing.width, viewSize: size, compact: compact, textFont: WatchFont(watchLayout.textFont), outerRing: outerBound, startingAngle: phase.zeroRing, oddTicks: chineseCalendar.oddSolarTerms.map{CGFloat($0)}, evenTicks: chineseCalendar.evenSolarTerms.map{CGFloat($0)}, oddColor: watchLayout.oddSolarTermTickColorDark, evenColor: watchLayout.evenSolarTermTickColorDark, oddTexts: ChineseCalendar.oddSolarTermChinese, evenTexts: ChineseCalendar.evenSolarTermChinese) + ZeroRing(width: ZeroRing.width, viewSize: size, compact: compact, textFont: WatchFont(watchLayout.textFont), outerRing: outerBound, startingAngle: phase.zeroRing, oddTicks: chineseCalendar.oddSolarTerms.map { CGFloat($0) }, evenTicks: chineseCalendar.evenSolarTerms.map { CGFloat($0) }, oddColor: watchLayout.oddSolarTermTickColorDark, evenColor: watchLayout.evenSolarTermTickColorDark, oddTexts: ChineseCalendar.oddSolarTermChinese, evenTexts: ChineseCalendar.evenSolarTermChinese) Ring(width: Ring.paddedWidth, viewSize: size, compact: compact, cornerSize: watchLayout.cornerRadiusRatio, ticks: chineseCalendar.monthTicks, startingAngle: phase.firstRing, angle: chineseCalendar.currentDayInYear, textFont: WatchFont(watchLayout.textFont), textColor: watchLayout.fontColorDark, alpha: watchLayout.shadeAlpha, majorTickAlpha: watchLayout.majorTickAlpha, minorTickAlpha: watchLayout.minorTickAlpha, majorTickColor: watchLayout.majorTickColorDark, minorTickColor: watchLayout.minorTickColorDark, gradientColor: watchLayout.firstRing, outerRing: firstRingOuter, marks: firstRingMarks, shadowDirection: shadowDirection) Ring(width: Ring.paddedWidth, viewSize: size, compact: compact, cornerSize: watchLayout.cornerRadiusRatio, ticks: chineseCalendar.dayTicks, startingAngle: phase.secondRing, angle: chineseCalendar.currentDayInMonth, textFont: WatchFont(watchLayout.textFont), textColor: watchLayout.fontColorDark, alpha: watchLayout.shadeAlpha, majorTickAlpha: watchLayout.majorTickAlpha, minorTickAlpha: watchLayout.minorTickAlpha, majorTickColor: watchLayout.majorTickColorDark, minorTickColor: watchLayout.minorTickColorDark, gradientColor: watchLayout.secondRing, outerRing: secondRingOuter, marks: secondRingMarks, shadowDirection: shadowDirection) Ring(width: Ring.paddedWidth, viewSize: size, compact: compact, cornerSize: watchLayout.cornerRadiusRatio, ticks: chineseCalendar.hourTicks, startingAngle: phase.thirdRing, angle: chineseCalendar.currentHourInDay, textFont: WatchFont(watchLayout.textFont), textColor: watchLayout.fontColorDark, alpha: watchLayout.shadeAlpha, majorTickAlpha: watchLayout.majorTickAlpha, minorTickAlpha: watchLayout.minorTickAlpha, majorTickColor: watchLayout.majorTickColorDark, minorTickColor: watchLayout.minorTickColorDark, gradientColor: watchLayout.thirdRing, outerRing: thirdRingOuter, marks: thirdRingMarks, shadowDirection: shadowDirection) Ring(width: Ring.paddedWidth, viewSize: size, compact: compact, cornerSize: watchLayout.cornerRadiusRatio, ticks: chineseCalendar.subhourTicks, startingAngle: phase.fourthRing, angle: chineseCalendar.subhourInHour, textFont: WatchFont(watchLayout.textFont), textColor: watchLayout.fontColorDark, alpha: watchLayout.shadeAlpha, majorTickAlpha: watchLayout.majorTickAlpha, minorTickAlpha: watchLayout.minorTickAlpha, majorTickColor: watchLayout.majorTickColorDark, minorTickColor: watchLayout.minorTickColorDark, gradientColor: fourthRingColor, outerRing: fourthRingOuter, marks: fourthRingMarks, shadowDirection: shadowDirection) Core(viewSize: size, compact: compact, dateString: chineseCalendar.dateString, timeString: chineseCalendar.timeString, font: WatchFont(watchLayout.centerFont), maxLength: 5, textColor: watchLayout.centerFontColor, outerBound: innerBound, backColor: watchLayout.innerColorDark, centerOffset: 0.1, shadowDirection: shadowDirection) } - .onAppear() { + .onAppear { size = proxy.size } } @@ -114,7 +114,6 @@ struct Watch: View { } struct DualWatch: View { - private static let majorUpdateInterval: CGFloat = 3600 private static let minorUpdateInterval: CGFloat = majorUpdateInterval / 12 static let updateInterval: CGFloat = 14.4 @@ -148,10 +147,9 @@ struct DualWatch: View { } var body: some View { - let shortEdge = min(self.size.width, self.size.height) let cornerSize = watchLayout.cornerRadiusRatio * shortEdge - let outerBound = RoundedRect(rect: CGRect(origin: .zero, size: size), nodePos: cornerSize, ankorPos: cornerSize*0.2).shrink(by: Watch.frameOffset * shortEdge) + let outerBound = RoundedRect(rect: CGRect(origin: .zero, size: size), nodePos: cornerSize, ankorPos: cornerSize * 0.2).shrink(by: Watch.frameOffset * shortEdge) let firstRingOuter = outerBound.shrink(by: ZeroRing.width * shortEdge * 1.2) let secondRingOuter = firstRingOuter.shrink(by: Ring.paddedWidth * shortEdge * 1.3) let innerBound = secondRingOuter.shrink(by: Ring.paddedWidth * shortEdge * 1.3) @@ -166,13 +164,13 @@ struct DualWatch: View { GeometryReader { proxy in TabView(selection: $selectedPageIndex) { ZStack { - ZeroRing(width: ZeroRing.width * 1.2, viewSize: size, compact: compact, textFont: WatchFont(watchLayout.textFont), outerRing: outerBound, startingAngle: phase.zeroRing, oddTicks: chineseCalendar.oddSolarTerms.map{CGFloat($0)}, evenTicks: chineseCalendar.evenSolarTerms.map{CGFloat($0)}, oddColor: watchLayout.oddSolarTermTickColorDark, evenColor: watchLayout.evenSolarTermTickColorDark, oddTexts: ChineseCalendar.oddSolarTermChinese, evenTexts: ChineseCalendar.evenSolarTermChinese) + ZeroRing(width: ZeroRing.width * 1.2, viewSize: size, compact: compact, textFont: WatchFont(watchLayout.textFont), outerRing: outerBound, startingAngle: phase.zeroRing, oddTicks: chineseCalendar.oddSolarTerms.map { CGFloat($0) }, evenTicks: chineseCalendar.evenSolarTerms.map { CGFloat($0) }, oddColor: watchLayout.oddSolarTermTickColorDark, evenColor: watchLayout.evenSolarTermTickColorDark, oddTexts: ChineseCalendar.oddSolarTermChinese, evenTexts: ChineseCalendar.evenSolarTermChinese) Ring(width: Ring.paddedWidth * 1.3, viewSize: size, compact: compact, cornerSize: watchLayout.cornerRadiusRatio, ticks: chineseCalendar.monthTicks, startingAngle: phase.firstRing, angle: chineseCalendar.currentDayInYear, textFont: WatchFont(watchLayout.textFont), textColor: watchLayout.fontColorDark, alpha: watchLayout.shadeAlpha, majorTickAlpha: watchLayout.majorTickAlpha, minorTickAlpha: watchLayout.minorTickAlpha, majorTickColor: watchLayout.majorTickColorDark, minorTickColor: watchLayout.minorTickColorDark, gradientColor: watchLayout.firstRing, outerRing: firstRingOuter, marks: firstRingMarks, shadowDirection: shadowDirection) Ring(width: Ring.paddedWidth * 1.3, viewSize: size, compact: compact, cornerSize: watchLayout.cornerRadiusRatio, ticks: chineseCalendar.dayTicks, startingAngle: phase.secondRing, angle: chineseCalendar.currentDayInMonth, textFont: WatchFont(watchLayout.textFont), textColor: watchLayout.fontColorDark, alpha: watchLayout.shadeAlpha, majorTickAlpha: watchLayout.majorTickAlpha, minorTickAlpha: watchLayout.minorTickAlpha, majorTickColor: watchLayout.majorTickColorDark, minorTickColor: watchLayout.minorTickColorDark, gradientColor: watchLayout.secondRing, outerRing: secondRingOuter, marks: secondRingMarks, shadowDirection: shadowDirection) Core(viewSize: size, compact: compact, dateString: chineseCalendar.monthString, timeString: chineseCalendar.dayString, font: WatchFont(watchLayout.centerFont), maxLength: 3, textColor: watchLayout.centerFontColor, outerBound: innerBound, backColor: watchLayout.innerColorDark, centerOffset: 0.05, shadowDirection: shadowDirection) } - .onAppear() { + .onAppear { size = proxy.size } .ignoresSafeArea() @@ -184,7 +182,7 @@ struct DualWatch: View { Core(viewSize: size, compact: compact, dateString: chineseCalendar.hourString, timeString: chineseCalendar.quarterString, font: WatchFont(watchLayout.centerFont), maxLength: 3, textColor: watchLayout.centerFontColor, outerBound: innerBound, backColor: watchLayout.innerColorDark, centerOffset: 0.05, shadowDirection: shadowDirection) } - .onAppear() { + .onAppear { size = proxy.size } .ignoresSafeArea() diff --git a/Watch/WatchLayout.swift b/Watch/WatchLayout.swift index 0bf4607..8b0f979 100644 --- a/Watch/WatchLayout.swift +++ b/Watch/WatchLayout.swift @@ -8,7 +8,7 @@ import SwiftUI class WatchLayout: MetaWatchLayout, ObservableObject { - static var shared: WatchLayout = WatchLayout() + static var shared: WatchLayout = .init() var textFont: UIFont var centerFont: UIFont @@ -24,5 +24,4 @@ class WatchLayout: MetaWatchLayout, ObservableObject { super.update(from: str) refresh.toggle() } - } diff --git a/WatchWidget/IconView.swift b/WatchWidget/IconView.swift index a6e7c5e..049ab69 100644 --- a/WatchWidget/IconView.swift +++ b/WatchWidget/IconView.swift @@ -28,12 +28,12 @@ struct SolarTerm: View { connectionPath.move(to: pointOnCircle(center: center, radius: centerRadius, angle: 0.4 * CGFloat.pi)) connectionPath.addLine(to: pointOnCircle(center: center, radius: centerRadius, angle: 0.6 * CGFloat.pi)) connectionPath.addCurve(to: pointOnCircle(center: CGPoint(x: ringRadius, y: 0) + center, radius: starRadius, angle: 1.3 * CGFloat.pi), - control1: CGPoint(x: centerRadius, y: 0) + center, - control2: CGPoint(x: ringRadius - starRadius * 1.2, y: 0) + center) + control1: CGPoint(x: centerRadius, y: 0) + center, + control2: CGPoint(x: ringRadius - starRadius * 1.2, y: 0) + center) connectionPath.addLine(to: pointOnCircle(center: CGPoint(x: ringRadius, y: 0) + center, radius: starRadius, angle: 1.7 * CGFloat.pi)) connectionPath.addCurve(to: pointOnCircle(center: center, radius: centerRadius, angle: 0.4 * CGFloat.pi), - control1: CGPoint(x: ringRadius - starRadius * 1.2, y: 0) + center, - control2: CGPoint(x: centerRadius, y: 0) + center) + control1: CGPoint(x: ringRadius - starRadius * 1.2, y: 0) + center, + control2: CGPoint(x: centerRadius, y: 0) + center) connectionPath.closeSubpath() let circlesPath = CGMutablePath() @@ -48,19 +48,17 @@ struct SolarTerm: View { context.fill(Path(connectionPath), with: .color(Color(cgColor: color))) context.fill(Path(circlesPath), with: .color(Color(cgColor: color))) context.fill(Path(dotsPath), with: .color(.yellow.opacity(0.5))) - } .rotationEffect(.radians(-(angle - 0.25) * CGFloat.pi * 2.0)) } } private func starPath(x: CGFloat, y: CGFloat, radius: CGFloat, sides: Int, pointyness: CGFloat) -> CGPath { - - func polygonPointArray(sides: Int, x: CGFloat, y: CGFloat, radius: CGFloat, adjustment: CGFloat=0)->[CGPoint] { + func polygonPointArray(sides: Int, x: CGFloat, y: CGFloat, radius: CGFloat, adjustment: CGFloat = 0) -> [CGPoint] { let angle = 2 * CGFloat.pi / CGFloat(sides) let cx = x // x origin let cy = y // y origin - let r = radius // radius of circle + let r = radius // radius of circle var i = sides var points = [CGPoint]() while points.count <= sides { @@ -77,7 +75,7 @@ private func starPath(x: CGFloat, y: CGFloat, radius: CGFloat, sides: Int, point let points = polygonPointArray(sides: sides, x: x, y: y, radius: radius) let points2 = polygonPointArray(sides: sides, x: x, y: y, radius: radius * pointyness, adjustment: adjustment) path.move(to: points[0]) - for i in 0.. LineEntry { @@ -53,7 +53,7 @@ struct LineEntry: TimelineEntry { let chinsesCalendar: ChineseCalendar } -struct LineEntryView : View { +struct LineEntryView: View { var entry: LineProvider.Entry var body: some View { @@ -129,9 +129,9 @@ struct CircularEntry: TimelineEntry { let chinsesCalendar: ChineseCalendar } -struct CircularEntryView : View { +struct CircularEntryView: View { var entry: CircularProvider.Entry - + func sunTimes(times: [ChineseCalendar.NamedPosition?]) -> (start: CGFloat, end: CGFloat)? { guard times.count == 5 else { return nil } if let sunrise = times[1]?.pos, let sunset = times[3]?.pos { @@ -152,7 +152,7 @@ struct CircularEntryView : View { } } } - + func moonTimes(times: [ChineseCalendar.NamedPosition?]) -> ((start: CGFloat, end: CGFloat)?, CGFloat?) { guard times.count == 6 else { return (nil, nil) } if let firstMoonRise = times[0]?.pos { @@ -193,10 +193,10 @@ struct CircularEntryView : View { let innerGradient = applyGradient(gradient: layout.secondRing, startingAngle: phase.secondRing) Circular(outer: (start: phase.firstRing, end: entry.chinsesCalendar.currentDayInYear + phase.firstRing), inner: (start: phase.secondRing, end: entry.chinsesCalendar.currentDayInMonth + phase.secondRing), - outerGradient:outerGradient, innerGradient: innerGradient) - .widgetLabel() { - Text(String(entry.chinsesCalendar.dateString.reversed())) - } + outerGradient: outerGradient, innerGradient: innerGradient) + .widgetLabel { + Text(String(entry.chinsesCalendar.dateString.reversed())) + } default: let outerGradient = applyGradient(gradient: layout.thirdRing, startingAngle: phase.thirdRing) let innerGradient = applyGradient(gradient: layout.secondRing, startingAngle: phase.secondRing) @@ -206,18 +206,18 @@ struct CircularEntryView : View { inner: (start: inner.start + phase.secondRing, end: inner.end + phase.secondRing), current: entry.chinsesCalendar.currentHourInDay, innerDirection: innerDirection, - outerGradient:outerGradient, innerGradient: innerGradient, + outerGradient: outerGradient, innerGradient: innerGradient, currentColor: Color(cgColor: layout.thirdRing.interpolate(at: entry.chinsesCalendar.currentHourInDay))) - .widgetLabel() { - Text(String((entry.chinsesCalendar.hourString + entry.chinsesCalendar.shortQuarterString).reversed())) - } + .widgetLabel { + Text(String((entry.chinsesCalendar.hourString + entry.chinsesCalendar.shortQuarterString).reversed())) + } } else { Circular(outer: (start: 0, end: 1e-7), inner: (start: 0, end: 1e-7), - current: entry.chinsesCalendar.currentHourInDay, outerGradient:outerGradient, innerGradient: innerGradient, + current: entry.chinsesCalendar.currentHourInDay, outerGradient: outerGradient, innerGradient: innerGradient, currentColor: Color(cgColor: layout.thirdRing.interpolate(at: entry.chinsesCalendar.currentHourInDay))) - .widgetLabel() { - Text(String((entry.chinsesCalendar.hourString + entry.chinsesCalendar.shortQuarterString).reversed())) - } + .widgetLabel { + Text(String((entry.chinsesCalendar.hourString + entry.chinsesCalendar.shortQuarterString).reversed())) + } } } } @@ -233,7 +233,7 @@ struct CircularWidget: Widget { .configurationDisplayName("Circular") .description("Circular View.") #if os(watchOS) - .supportedFamilies([.accessoryCircular, .accessoryCorner]) + .supportedFamilies([.accessoryCircular, .accessoryCorner]) #else .supportedFamilies([.accessoryCircular]) #endif diff --git a/WatchWidget/WatchWidgetBundle.swift b/WatchWidget/WatchWidgetBundle.swift index 5c620f3..51f5bd5 100644 --- a/WatchWidget/WatchWidgetBundle.swift +++ b/WatchWidget/WatchWidgetBundle.swift @@ -5,8 +5,8 @@ // Created by Leo Liu on 5/11/23. // -import WidgetKit import SwiftUI +import WidgetKit @main struct WatchWidgetBundle: WidgetBundle { @@ -29,17 +29,17 @@ struct Curve: View { case moon(view: MoonPhase) case sunrise(view: Sun) } - + @State var size: CGSize = .zero var icon: Icon var barColor: Color var start: Date? var end: Date? - + var body: some View { GeometryReader { proxy in ZStack { - switch(icon) { + switch icon { case .solarTerm(view: let view): view case .moon(view: let view): @@ -51,20 +51,19 @@ struct Curve: View { .frame(width: size.width, height: size.height) .widgetLabel { if let start = start, let end = end { - ProgressView(timerInterval: start...end, countsDown: false) { + ProgressView(timerInterval: start ... end, countsDown: false) { Text(end, style: .relative) } .tint(barColor) } } - .onAppear() { + .onAppear { size = proxy.size } } } } - struct CurveProvider: IntentTimelineProvider { func placeholder(in context: Context) -> CurveEntry { let chineseCalendar = ChineseCalendar(time: Date(), timezone: Calendar.current.timeZone, location: LocationManager.shared.location ?? WatchLayout.shared.location, compact: true) @@ -97,7 +96,7 @@ struct CurveProvider: IntentTimelineProvider { entryDate = chineseCalendar.startOfNextDay chineseCalendar.update(time: entryDate, timezone: chineseCalendar.calendar.timeZone, location: chineseCalendar.location) } - + let entry = CurveEntry(date: entryDate, configuration: configuration, chinsesCalendar: chineseCalendar.copy) entries.append(entry) } @@ -107,10 +106,10 @@ struct CurveProvider: IntentTimelineProvider { } func recommendations() -> [IntentRecommendation] { - let solarTerms = {let intent = CurveIntent(); intent.target = .solarTerms; return intent}() - let lunarPhases = {let intent = CurveIntent(); intent.target = .lunarPhases; return intent}() - let sunriseSet = {let intent = CurveIntent(); intent.target = .sunriseSet; return intent}() - let moonriseSet = {let intent = CurveIntent(); intent.target = .moonriseSet; return intent}() + let solarTerms = { let intent = CurveIntent(); intent.target = .solarTerms; return intent }() + let lunarPhases = { let intent = CurveIntent(); intent.target = .lunarPhases; return intent }() + let sunriseSet = { let intent = CurveIntent(); intent.target = .sunriseSet; return intent }() + let moonriseSet = { let intent = CurveIntent(); intent.target = .moonriseSet; return intent }() return [ IntentRecommendation(intent: solarTerms, description: "次節氣"), @@ -127,9 +126,9 @@ struct CurveEntry: TimelineEntry { let chinsesCalendar: ChineseCalendar } -struct CurveEntryView : View { +struct CurveEntryView: View { var entry: CurveProvider.Entry - + private func find(in dates: [ChineseCalendar.NamedDate], at date: Date) -> (previous: ChineseCalendar.NamedDate?, next: ChineseCalendar.NamedDate?) { if dates.count > 1 { let atDate = ChineseCalendar.NamedDate(name: "", date: date) @@ -145,6 +144,7 @@ struct CurveEntryView : View { return (previous: nil, next: nil) } } + private func findSolarTerm(_ solarTerm: String) -> Int { if let even = ChineseCalendar.evenSolarTermChinese.firstIndex(of: solarTerm) { return even * 2 @@ -189,8 +189,8 @@ struct CurveEntryView : View { start: previous.date, end: next.date) } else { Curve(icon: .moon(view: MoonPhase(angle: entry.chinsesCalendar.currentDayInMonth, color: layout.moonPositionIndicator[1], rise: nil)), - barColor: Color(cgColor: layout.thirdRing.interpolate(at: entry.chinsesCalendar.currentDayInMonth)), - start: nil, end: nil) + barColor: Color(cgColor: layout.thirdRing.interpolate(at: entry.chinsesCalendar.currentDayInMonth)), + start: nil, end: nil) } default: let (previous, next) = find(in: entry.chinsesCalendar.solarTerms, at: entry.chinsesCalendar.time) diff --git a/WatchWidget/WatchWidgetView.swift b/WatchWidget/WatchWidgetView.swift index 489ce84..e645388 100644 --- a/WatchWidget/WatchWidgetView.swift +++ b/WatchWidget/WatchWidgetView.swift @@ -7,9 +7,9 @@ import SwiftUI -struct LineDescription : View { +struct LineDescription: View { @State var text: String - + init(chineseCalendar: ChineseCalendar) { var text = chineseCalendar.dateString for solarTerm in chineseCalendar.eventInDay.oddSolarTerm { @@ -37,10 +37,10 @@ struct CircularLine: View { var lineWidth: CGFloat var start: CGFloat var end: CGFloat - + var body: some View { let length = end > start ? end - start : end - start + 1 - + ZStack { Circle() .stroke( @@ -64,7 +64,7 @@ struct CircularLine: View { } } -struct Circular : View { +struct Circular: View { @State var size: CGSize = .zero var outer: (start: CGFloat, end: CGFloat) var inner: (start: CGFloat, end: CGFloat) @@ -73,7 +73,7 @@ struct Circular : View { var outerGradient: Gradient var innerGradient: Gradient var currentColor: Color? - + var body: some View { GeometryReader { proxy in ZStack { @@ -94,7 +94,7 @@ struct Circular : View { .shadow(color: .black, radius: min(size.width, size.height) * 0.05) } } - .onAppear() { + .onAppear { size = proxy.size } } diff --git a/Widget/Widget.swift b/Widget/Widget.swift index 38c4403..9ae914b 100644 --- a/Widget/Widget.swift +++ b/Widget/Widget.swift @@ -5,14 +5,14 @@ // Created by Leo Liu on 5/9/23. // -import WidgetKit -import SwiftUI import Intents +import SwiftUI +import WidgetKit struct SmallProvider: IntentTimelineProvider { func placeholder(in context: Context) -> SmallEntry { let chineseCalendar = ChineseCalendar(time: Date(), timezone: Calendar.current.timeZone, location: LocationManager.shared.location ?? WatchLayout.shared.location, compact: true) - return SmallEntry(date: Date(), configuration: SmallIntent(),chineseCalendar: chineseCalendar) + return SmallEntry(date: Date(), configuration: SmallIntent(), chineseCalendar: chineseCalendar) } func getSnapshot(for configuration: SmallIntent, in context: Context, completion: @escaping (SmallEntry) -> ()) { @@ -81,7 +81,7 @@ struct MediumProvider: IntentTimelineProvider { // Generate a timeline consisting of five entries an hour apart, starting from the current date. let currentDate = Date() - + #if os(macOS) let count = 50 #else @@ -155,9 +155,9 @@ struct LargeEntry: TimelineEntry { let chineseCalendar: ChineseCalendar } -struct SmallWidgetEntryView : View { +struct SmallWidgetEntryView: View { var entry: SmallProvider.Entry - + init(entry: SmallEntry) { self.entry = entry } @@ -177,9 +177,9 @@ struct SmallWidgetEntryView : View { } } -struct MediumWidgetEntryView : View { +struct MediumWidgetEntryView: View { var entry: MediumProvider.Entry - + init(entry: MediumEntry) { self.entry = entry } @@ -207,9 +207,9 @@ struct MediumWidgetEntryView : View { } } -struct LargeWidgetEntryView : View { +struct LargeWidgetEntryView: View { var entry: LargeProvider.Entry - + init(entry: LargeEntry) { self.entry = entry } @@ -226,7 +226,7 @@ struct LargeWidgetEntryView : View { struct SmallWidget: Widget { let kind: String = "Small" - + init() { DataContainer.shared.loadSave() LocationManager.shared.manager.requestWhenInUseAuthorization() @@ -244,7 +244,7 @@ struct SmallWidget: Widget { struct MediumWidget: Widget { let kind: String = "Medium" - + init() { DataContainer.shared.loadSave() LocationManager.shared.manager.requestWhenInUseAuthorization() @@ -262,7 +262,7 @@ struct MediumWidget: Widget { struct LargeWidget: Widget { let kind: String = "Large" - + init() { DataContainer.shared.loadSave() LocationManager.shared.manager.requestWhenInUseAuthorization() @@ -279,7 +279,6 @@ struct LargeWidget: Widget { } struct Widget_Previews: PreviewProvider { - static var previews: some View { let chineseCalendar = ChineseCalendar(time: Date(), timezone: Calendar.current.timeZone, location: LocationManager.shared.location ?? WatchLayout.shared.location, compact: true) SmallWidgetEntryView(entry: SmallEntry(date: Date(), configuration: SmallIntent(), chineseCalendar: chineseCalendar)) diff --git a/Widget/WidgetFaceView.swift b/Widget/WidgetFaceView.swift index 2dae82f..340c0ec 100644 --- a/Widget/WidgetFaceView.swift +++ b/Widget/WidgetFaceView.swift @@ -29,7 +29,7 @@ private func ringMarks(for ring: Rings, watchLayout: WatchLayout, chineseCalenda let firstRingMarks = [Marks(outer: true, locations: chineseCalendar.planetPosition.map { $0.pos }, colors: watchLayout.planetIndicator, radius: radius)] let secondRingMarks = [ Marks(outer: true, locations: eventInMonth.eclipse.map { $0.pos }, colors: [watchLayout.eclipseIndicator], radius: radius), - Marks(outer: true, locations: eventInMonth.fullMoon.map { $0.pos }, colors: [watchLayout.fullmoonIndicator], radius: radius), + Marks(outer: true, locations: eventInMonth.fullMoon.map { $0.pos }, colors: [watchLayout.fullmoonIndicator], radius: radius), Marks(outer: true, locations: eventInMonth.oddSolarTerm.map { $0.pos }, colors: [watchLayout.oddStermIndicator], radius: radius), Marks(outer: true, locations: eventInMonth.evenSolarTerm.map { $0.pos }, colors: [watchLayout.evenStermIndicator], radius: radius) ] @@ -79,7 +79,7 @@ struct Watch: View { var body: some View { let shortEdge = min(self.size.width, self.size.height) let cornerSize = watchLayout.cornerRadiusRatio * shortEdge - let outerBound = RoundedRect(rect: CGRect(origin: .zero, size: size), nodePos: cornerSize, ankorPos: cornerSize*0.2).shrink(by: Self.frameOffset * shortEdge) + let outerBound = RoundedRect(rect: CGRect(origin: .zero, size: size), nodePos: cornerSize, ankorPos: cornerSize * 0.2).shrink(by: Self.frameOffset * shortEdge) let firstRingOuter = outerBound.shrink(by: ZeroRing.width * shortEdge * 0.8) let secondRingOuter = firstRingOuter.shrink(by: Ring.paddedWidth * shortEdge * 0.8) let thirdRingOuter = secondRingOuter.shrink(by: Ring.paddedWidth * shortEdge * 0.8) @@ -101,7 +101,7 @@ struct Watch: View { GeometryReader { proxy in ZStack { - ZeroRing(width: ZeroRing.width * 0.8, viewSize: size, compact: compact, textFont: WatchFont(watchLayout.textFont), outerRing: outerBound, startingAngle: phase.zeroRing, oddTicks: chineseCalendar.oddSolarTerms.map{CGFloat($0)}, evenTicks: chineseCalendar.evenSolarTerms.map{CGFloat($0)}, oddColor: oddSTColor, evenColor: evenSTColor, oddTexts: ChineseCalendar.oddSolarTermChinese, evenTexts: ChineseCalendar.evenSolarTermChinese) + ZeroRing(width: ZeroRing.width * 0.8, viewSize: size, compact: compact, textFont: WatchFont(watchLayout.textFont), outerRing: outerBound, startingAngle: phase.zeroRing, oddTicks: chineseCalendar.oddSolarTerms.map { CGFloat($0) }, evenTicks: chineseCalendar.evenSolarTerms.map { CGFloat($0) }, oddColor: oddSTColor, evenColor: evenSTColor, oddTexts: ChineseCalendar.oddSolarTermChinese, evenTexts: ChineseCalendar.evenSolarTermChinese) Ring(width: Ring.paddedWidth * 0.8, viewSize: size, compact: compact, cornerSize: watchLayout.cornerRadiusRatio, ticks: chineseCalendar.monthTicks, startingAngle: phase.firstRing, angle: chineseCalendar.currentDayInYear, textFont: WatchFont(watchLayout.textFont), textColor: textColor, alpha: watchLayout.shadeAlpha, majorTickAlpha: watchLayout.majorTickAlpha, minorTickAlpha: watchLayout.minorTickAlpha, majorTickColor: majorTickColor, minorTickColor: minorTickColor, gradientColor: watchLayout.firstRing, outerRing: firstRingOuter, marks: firstRingMarks, shadowDirection: shadowDirection) Ring(width: Ring.paddedWidth * 0.8, viewSize: size, compact: compact, cornerSize: watchLayout.cornerRadiusRatio, ticks: chineseCalendar.dayTicks, startingAngle: phase.secondRing, angle: chineseCalendar.currentDayInMonth, textFont: WatchFont(watchLayout.textFont), textColor: textColor, alpha: watchLayout.shadeAlpha, majorTickAlpha: watchLayout.majorTickAlpha, minorTickAlpha: watchLayout.minorTickAlpha, majorTickColor: majorTickColor, minorTickColor: minorTickColor, gradientColor: watchLayout.secondRing, outerRing: secondRingOuter, marks: secondRingMarks, shadowDirection: shadowDirection) @@ -109,7 +109,7 @@ struct Watch: View { Ring(width: Ring.paddedWidth * 0.8, viewSize: size, compact: compact, cornerSize: watchLayout.cornerRadiusRatio, ticks: chineseCalendar.subhourTicks, startingAngle: phase.fourthRing, angle: chineseCalendar.subhourInHour, textFont: WatchFont(watchLayout.textFont), textColor: textColor, alpha: watchLayout.shadeAlpha, majorTickAlpha: watchLayout.majorTickAlpha, minorTickAlpha: watchLayout.minorTickAlpha, majorTickColor: majorTickColor, minorTickColor: minorTickColor, gradientColor: fourthRingColor, outerRing: fourthRingOuter, marks: fourthRingMarks, shadowDirection: shadowDirection) Core(viewSize: size, compact: compact, dateString: chineseCalendar.dateString, timeString: chineseCalendar.hourString + chineseCalendar.shortQuarterString, font: WatchFont(watchLayout.centerFont), maxLength: 5, textColor: watchLayout.centerFontColor, outerBound: innerBound, backColor: coreColor, centerOffset: 0.1, shadowDirection: shadowDirection) } - .onAppear() { + .onAppear { size = proxy.size } } @@ -136,7 +136,7 @@ struct DateWatch: View { var body: some View { let shortEdge = min(self.size.width, self.size.height) let cornerSize = watchLayout.cornerRadiusRatio * shortEdge - let outerBound = RoundedRect(rect: CGRect(origin: .zero, size: size), nodePos: cornerSize, ankorPos: cornerSize*0.2).shrink(by: Self.frameOffset * shortEdge) + let outerBound = RoundedRect(rect: CGRect(origin: .zero, size: size), nodePos: cornerSize, ankorPos: cornerSize * 0.2).shrink(by: Self.frameOffset * shortEdge) let firstRingOuter = outerBound let secondRingOuter = firstRingOuter.shrink(by: Ring.paddedWidth * shortEdge * 1.3) let innerBound = secondRingOuter.shrink(by: Ring.paddedWidth * shortEdge * 1.3) @@ -157,7 +157,7 @@ struct DateWatch: View { Ring(width: Ring.paddedWidth * 1.3, viewSize: size, compact: compact, cornerSize: watchLayout.cornerRadiusRatio, ticks: chineseCalendar.dayTicks, startingAngle: phase.secondRing, angle: chineseCalendar.currentDayInMonth, textFont: WatchFont(watchLayout.textFont), textColor: textColor, alpha: watchLayout.shadeAlpha, majorTickAlpha: watchLayout.majorTickAlpha, minorTickAlpha: watchLayout.minorTickAlpha, majorTickColor: majorTickColor, minorTickColor: minorTickColor, gradientColor: watchLayout.secondRing, outerRing: secondRingOuter, marks: secondRingMarks, shadowDirection: shadowDirection) Core(viewSize: size, compact: compact, dateString: chineseCalendar.monthString, timeString: chineseCalendar.dayString, font: WatchFont(watchLayout.centerFont), maxLength: 3, textColor: watchLayout.centerFontColor, outerBound: innerBound, backColor: coreColor, centerOffset: 0.1, shadowDirection: shadowDirection) } - .onAppear() { + .onAppear { size = proxy.size } } @@ -184,7 +184,7 @@ struct TimeWatch: View { var body: some View { let shortEdge = min(self.size.width, self.size.height) let cornerSize = watchLayout.cornerRadiusRatio * shortEdge - let outerBound = RoundedRect(rect: CGRect(origin: .zero, size: size), nodePos: cornerSize, ankorPos: cornerSize*0.2).shrink(by: Self.frameOffset * shortEdge) + let outerBound = RoundedRect(rect: CGRect(origin: .zero, size: size), nodePos: cornerSize, ankorPos: cornerSize * 0.2).shrink(by: Self.frameOffset * shortEdge) let firstRingOuter = outerBound let secondRingOuter = firstRingOuter.shrink(by: Ring.paddedWidth * shortEdge * 1.3) let innerBound = secondRingOuter.shrink(by: Ring.paddedWidth * shortEdge * 1.3) @@ -205,7 +205,7 @@ struct TimeWatch: View { Ring(width: Ring.paddedWidth * 1.3, viewSize: size, compact: compact, cornerSize: watchLayout.cornerRadiusRatio, ticks: chineseCalendar.subhourTicks, startingAngle: phase.fourthRing, angle: chineseCalendar.subhourInHour, textFont: WatchFont(watchLayout.textFont), textColor: textColor, alpha: watchLayout.shadeAlpha, majorTickAlpha: watchLayout.majorTickAlpha, minorTickAlpha: watchLayout.minorTickAlpha, majorTickColor: majorTickColor, minorTickColor: minorTickColor, gradientColor: fourthRingColor, outerRing: secondRingOuter, marks: secondRingMarks, shadowDirection: shadowDirection) Core(viewSize: size, compact: compact, dateString: chineseCalendar.hourString, timeString: chineseCalendar.shortQuarterString, font: WatchFont(watchLayout.centerFont), maxLength: 3, textColor: watchLayout.centerFontColor, outerBound: innerBound, backColor: coreColor, centerOffset: 0.1, shadowDirection: shadowDirection) } - .onAppear() { + .onAppear { size = proxy.size } } diff --git a/iOS/AppDelegate.swift b/iOS/AppDelegate.swift index ba4aa7c..35459c5 100644 --- a/iOS/AppDelegate.swift +++ b/iOS/AppDelegate.swift @@ -9,15 +9,14 @@ import UIKit @main class AppDelegate: UIResponder, UIApplicationDelegate { - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. DataContainer.shared.loadSave() - - LocationManager.shared.requestLocation() { _ in + + LocationManager.shared.requestLocation { _ in WatchFaceView.currentInstance?.drawView(forceRefresh: true) } - + return true } @@ -35,4 +34,3 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // Use this method to release any resources that were specific to the discarded scenes, as they will not return. } } - diff --git a/iOS/Layout.swift b/iOS/Layout.swift index 7e13b04..7cde491 100644 --- a/iOS/Layout.swift +++ b/iOS/Layout.swift @@ -9,10 +9,10 @@ import UIKit class WatchLayout: MetaWatchLayout { static let shared = WatchLayout() - + var textFont: UIFont var centerFont: UIFont - + override init() { textFont = UIFont.systemFont(ofSize: UIFont.systemFontSize, weight: .regular) centerFont = UIFont(name: "SourceHanSansKR-Heavy", size: UIFont.systemFontSize)! diff --git a/iOS/SceneDelegate.swift b/iOS/SceneDelegate.swift index e0408b4..ddef062 100644 --- a/iOS/SceneDelegate.swift +++ b/iOS/SceneDelegate.swift @@ -9,10 +9,8 @@ import UIKit import WidgetKit class SceneDelegate: UIResponder, UIWindowSceneDelegate { - var window: UIWindow? - func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. @@ -52,7 +50,4 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { // Save changes in the application's managed object context when the application transitions to the background. DataContainer.shared.saveContext() } - - } - diff --git a/iOS/ViewController.swift b/iOS/ViewController.swift index 8cf1ceb..7bd5639 100644 --- a/iOS/ViewController.swift +++ b/iOS/ViewController.swift @@ -31,12 +31,11 @@ func coordinateDesp(coordinate: CGPoint) -> (String, String) { extension UINavigationController { @objc func closeSetting(_ sender: UIView) { - self.dismiss(animated: true) + dismiss(animated: true) } } class TooltipView: UIView { - private let text: String init(text: String) { @@ -61,14 +60,14 @@ class TooltipView: UIView { ]) } + @available(*, unavailable) required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } } class ViewController: UIViewController, UIGestureRecognizerDelegate { - - @IBOutlet weak var watchFace: WatchFaceView! + @IBOutlet var watchFace: WatchFaceView! private var tooltipView: NoteView? func newSize(frame: CGSize, idealSize: CGSize) -> CGSize { @@ -110,7 +109,7 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { if !launchedBefore { let storyBoard = UIStoryboard(name: "Main", bundle: nil) let welcome = storyBoard.instantiateViewController(withIdentifier: "WelcomeView") as! WelcomeViewController - self.present(welcome, animated: true) + present(welcome, animated: true) } _ = WatchConnectivityManager.shared } @@ -128,10 +127,10 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { } } if entities.count > 0 { - let width: CGFloat = CGFloat(entities.count) * (UIFont.systemFontSize + 8) + 8 - let height: CGFloat = CGFloat(entities.map { $0.name.count }.reduce(0) { max($0, $1) }) * (UIFont.systemFontSize + 6) + 30 - let frame = CGRect(x: point.x - width/2, y: point.y - height/2, width: width, height: height) - var newFrame = watchFace.convert(frame, to: self.view) + let width = CGFloat(entities.count) * (UIFont.systemFontSize + 8) + 8 + let height = CGFloat(entities.map { $0.name.count }.reduce(0) { max($0, $1) }) * (UIFont.systemFontSize + 6) + 30 + let frame = CGRect(x: point.x - width / 2, y: point.y - height / 2, width: width, height: height) + var newFrame = watchFace.convert(frame, to: view) if newFrame.maxX > view.bounds.maxX - WatchFaceView.frameOffset { newFrame.origin.x -= newFrame.maxX - view.bounds.maxX + WatchFaceView.frameOffset @@ -142,7 +141,7 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { if newFrame.minX >= view.bounds.minX + WatchFaceView.frameOffset && newFrame.minY >= view.bounds.minY + WatchFaceView.frameOffset { tooltipView?.removeFromSuperview() let tooltipView = NoteView(frame: newFrame, entities: entities) - self.view.addSubview(tooltipView) + view.addSubview(tooltipView) self.tooltipView = tooltipView } } @@ -151,11 +150,11 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { @objc func longPressed(gestureRecognizer: UILongPressGestureRecognizer) { switch gestureRecognizer.state { case .began: - UIImpactFeedbackGenerator.init(style: .rigid).impactOccurred() + UIImpactFeedbackGenerator(style: .rigid).impactOccurred() case .ended: let storyBoard = UIStoryboard(name: "Main", bundle: nil) let settingsViewController = storyBoard.instantiateViewController(withIdentifier: "Settings") as! UINavigationController - self.present(settingsViewController, animated:true, completion:nil) + present(settingsViewController, animated: true, completion: nil) default: break } @@ -168,25 +167,25 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { } class WelcomeViewController: UIViewController { - @IBOutlet weak var appName: UILabel! - @IBOutlet weak var height: NSLayoutConstraint! - @IBOutlet weak var watchFaceTop: NSLayoutConstraint! - @IBOutlet weak var contentTop: NSLayoutConstraint! - @IBOutlet weak var text1: UITextView! - @IBOutlet weak var text2: UITextView! + @IBOutlet var appName: UILabel! + @IBOutlet var height: NSLayoutConstraint! + @IBOutlet var watchFaceTop: NSLayoutConstraint! + @IBOutlet var contentTop: NSLayoutConstraint! + @IBOutlet var text1: UITextView! + @IBOutlet var text2: UITextView! @IBOutlet var button: UIButton! @IBAction func close(_ sender: UIButton) { UserDefaults.standard.set(true, forKey: "ChineseTimeLaunchedBefore") - self.dismiss(animated: true) + dismiss(animated: true) } override func viewDidLoad() { super.viewDidLoad() appName.font = UIFont.systemFont(ofSize: UIFont.preferredFont(forTextStyle: .largeTitle).pointSize, weight: .black) button.configuration?.cornerStyle = .large - contentTop.constant = max(0.25 * self.view.bounds.height - 100, 20) - watchFaceTop.constant = max(0.12 * self.view.bounds.height - 40, 10) + contentTop.constant = max(0.25 * view.bounds.height - 100, 20) + watchFaceTop.constant = max(0.12 * view.bounds.height - 40, 10) height.constant = 510.0 + contentTop.constant + watchFaceTop.constant - 60 text1.text = NSLocalizedString("輪試設計介紹", comment: "Details about Ring Design") text2.text = NSLocalizedString("設置介紹", comment: "Details about Settings") @@ -228,7 +227,7 @@ class TableCell: UITableViewCell { arrow.tintColor = .systemGray elements.addSubview(arrow) - if let desp1 = self.desp1, let desp2 = self.desp2 { + if let desp1 = desp1, let desp2 = desp2 { let label1 = UILabel() label1.text = desp1 label1.textColor = .secondaryLabel @@ -242,10 +241,10 @@ class TableCell: UITableViewCell { } } else if segment != nil { segment!.frame = CGRect(x: CGRectGetMaxX(label.frame) + 15, y: (bounds.height - labelSize.height * 1.6) / 2, width: bounds.width - CGRectGetMaxX(label.frame) - 30, height: labelSize.height * 1.6) - self.addSubview(segment!) + addSubview(segment!) } - self.addSubview(elements) + addSubview(elements) } override func prepareForReuse() { @@ -265,22 +264,26 @@ class SettingsViewController: UITableViewController { struct ButtonOption { let title: String let color: UIColor - let action: (() -> Void) + let action: () -> Void } + struct DuelOption { let title: String let segment: UISegmentedControl } + struct DetailOption { let title: String let action: (() -> Void)? let desp1: String? let desp2: String? } + enum SettingsOption { case detail(model: DetailOption) case dual(model: DuelOption) } + struct Section { let title: String let options: [SettingsOption] @@ -321,7 +324,7 @@ class SettingsViewController: UITableViewController { .dual(model: DuelOption(title: NSLocalizedString("置閏法", comment: "Leap month setting"), segment: globalMonthSegment)), .dual(model: DuelOption(title: NSLocalizedString("時間", comment: "Time setting"), segment: apparentTimeSegment)), .detail(model: DetailOption(title: NSLocalizedString("顯示時間", comment: "Display time"), action: createNextView(name: "DateTime"), - desp1: time.formatted(date: .numeric, time: .shortened), desp2: timezone.localizedName(for: .generic, locale: Locale.current))), + desp1: time.formatted(date: .numeric, time: .shortened), desp2: timezone.localizedName(for: .generic, locale: Locale.current))), .detail(model: DetailOption(title: NSLocalizedString("經緯度", comment: "Location"), action: createNextView(name: "Location"), desp1: locationString?.0, desp2: locationString?.1)) ]), Section(title: NSLocalizedString("樣式", comment: "Styles"), options: [ @@ -348,12 +351,15 @@ class SettingsViewController: UITableViewController { override func numberOfSections(in tableView: UITableView) -> Int { return models.count } + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return models[section].options.count } + override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { return models[section].title } + override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { let data = models[indexPath.section].options[indexPath.row] switch data { @@ -398,9 +404,10 @@ class SettingsViewController: UITableViewController { } else if segment.selectedSegmentIndex == 1 { ChineseCalendar.globalMonth = false } - UIImpactFeedbackGenerator.init(style: .rigid).impactOccurred() + UIImpactFeedbackGenerator(style: .rigid).impactOccurred() WatchFaceView.currentInstance?.drawView(forceRefresh: true) } + @objc func apparentTimeToggled(segment: UISegmentedControl) { if segment.selectedSegmentIndex == 0 { ChineseCalendar.apparentTime = true @@ -410,7 +417,7 @@ class SettingsViewController: UITableViewController { } else if segment.selectedSegmentIndex == 1 { ChineseCalendar.apparentTime = false } - UIImpactFeedbackGenerator.init(style: .rigid).impactOccurred() + UIImpactFeedbackGenerator(style: .rigid).impactOccurred() WatchFaceView.currentInstance?.drawView(forceRefresh: true) } } @@ -418,16 +425,16 @@ class SettingsViewController: UITableViewController { class LocationView: UIViewController, UIPickerViewDataSource, UIPickerViewDelegate { static var currentInstance: LocationView? - @IBOutlet weak var viewHeight: NSLayoutConstraint! - @IBOutlet weak var longitudePicker: UIPickerView! - @IBOutlet weak var latitudePicker: UIPickerView! - @IBOutlet weak var display: UITextField! - @IBOutlet weak var toggleView: UIView! - @IBOutlet weak var displayView: UIView! - @IBOutlet weak var pickerView: UIView! - @IBOutlet weak var locationOptions: UISegmentedControl! - @IBOutlet weak var locationTitle: UILabel! - @IBOutlet weak var currentLocationSwitch: UISwitch! + @IBOutlet var viewHeight: NSLayoutConstraint! + @IBOutlet var longitudePicker: UIPickerView! + @IBOutlet var latitudePicker: UIPickerView! + @IBOutlet var display: UITextField! + @IBOutlet var toggleView: UIView! + @IBOutlet var displayView: UIView! + @IBOutlet var pickerView: UIView! + @IBOutlet var locationOptions: UISegmentedControl! + @IBOutlet var locationTitle: UILabel! + @IBOutlet var currentLocationSwitch: UISwitch! var longitude: [Int] = [0, 0, 0, 0] var latitude: [Int] = [0, 0, 0, 0] @@ -439,14 +446,14 @@ class LocationView: UIViewController, UIPickerViewDataSource, UIPickerViewDelega let tempValue = Int(round(abs(value) * 3600)) values[0] = tempValue / 3600 if picker === longitudePicker { - picker.selectRow(values[0] + 180*10, inComponent: 0, animated: true) + picker.selectRow(values[0] + 180 * 10, inComponent: 0, animated: true) } else if picker == latitudePicker { - picker.selectRow(values[0] + 90*10, inComponent: 0, animated: true) + picker.selectRow(values[0] + 90 * 10, inComponent: 0, animated: true) } values[1] = (tempValue % 3600) / 60 - picker.selectRow(values[1] + 60*10, inComponent: 1, animated: true) + picker.selectRow(values[1] + 60 * 10, inComponent: 1, animated: true) values[2] = tempValue % 60 - picker.selectRow(values[2] + 60*10, inComponent: 2, animated: true) + picker.selectRow(values[2] + 60 * 10, inComponent: 2, animated: true) if picker === longitudePicker { longitude = values } else if picker === latitudePicker { @@ -476,7 +483,7 @@ class LocationView: UIViewController, UIPickerViewDataSource, UIPickerViewDelega viewHeight.constant = CGRectGetMaxY(displayView.frame) + 20 if let location = LocationManager.shared.location { let locationString = coordinateDesp(coordinate: location) - self.display.text = "\(locationString.0), \(locationString.1)" + display.text = "\(locationString.0), \(locationString.1)" } else { display.text = NSLocalizedString("虚無", comment: "Location fails to load") } @@ -490,7 +497,7 @@ class LocationView: UIViewController, UIPickerViewDataSource, UIPickerViewDelega locationOptions.isEnabled = true if LocationManager.shared.enabled { chooseLocationOption(of: 1) - LocationManager.shared.requestLocation() { location in + LocationManager.shared.requestLocation { location in if location != nil { self.chooseLocationOption(of: 1) } else { @@ -625,7 +632,7 @@ class LocationView: UIViewController, UIPickerViewDataSource, UIPickerViewDelega locationOptions.isEnabled = true locationTitle.isHidden = false if LocationManager.shared.enabled { - LocationManager.shared.requestLocation() { location in + LocationManager.shared.requestLocation { location in if location != nil { self.chooseLocationOption(of: 1) WatchFaceView.currentInstance?.drawView(forceRefresh: true) @@ -638,7 +645,7 @@ class LocationView: UIViewController, UIPickerViewDataSource, UIPickerViewDelega chooseLocationOption(of: 0) } WatchFaceView.currentInstance?.drawView(forceRefresh: true) - (self.navigationController?.viewControllers.first as? SettingsViewController)?.reload() + (navigationController?.viewControllers.first as? SettingsViewController)?.reload() } else { locationTitle.isHidden = true pickerView.isHidden = true @@ -662,10 +669,10 @@ class LocationView: UIViewController, UIPickerViewDataSource, UIPickerViewDelega @IBAction func locationOptionToggled(_ sender: UISegmentedControl) { chooseLocationOption(of: sender.selectedSegmentIndex) - UIImpactFeedbackGenerator.init(style: .rigid).impactOccurred() + UIImpactFeedbackGenerator(style: .rigid).impactOccurred() if sender.selectedSegmentIndex == 1 { LocationManager.shared.enabled = true - LocationManager.shared.requestLocation() { location in + LocationManager.shared.requestLocation { location in if location != nil { self.chooseLocationOption(of: 1) WatchFaceView.currentInstance?.drawView(forceRefresh: true) @@ -689,10 +696,10 @@ class LocationView: UIViewController, UIPickerViewDataSource, UIPickerViewDelega } class DateTimeView: UIViewController, UIPickerViewDataSource, UIPickerViewDelegate { - @IBOutlet weak var datetimePicker: UIDatePicker! - @IBOutlet weak var timezonePicker: UIPickerView! - @IBOutlet weak var currentTime: UISwitch! - @IBOutlet weak var contentView: UIView! + @IBOutlet var datetimePicker: UIDatePicker! + @IBOutlet var timezonePicker: UIPickerView! + @IBOutlet var currentTime: UISwitch! + @IBOutlet var contentView: UIView! var panelTimezone = Calendar.current.timeZone var timeZones = DataTree(name: "Root") @@ -702,7 +709,7 @@ class DateTimeView: UIViewController, UIPickerViewDataSource, UIPickerViewDelega let allTimezones = TimeZone.knownTimeZoneIdentifiers for timezone in allTimezones { let components = timezone.split(separator: "/") - var currentNode: DataTree? = timeZones + var currentNode: DataTree? = timeZones for component in components { currentNode = currentNode?.add(element: String(component)) } @@ -719,7 +726,7 @@ class DateTimeView: UIViewController, UIPickerViewDataSource, UIPickerViewDelega currentNode = currentNode?[index] } } - for i in components.count.. Int { @@ -783,7 +789,7 @@ class DateTimeView: UIViewController, UIPickerViewDataSource, UIPickerViewDelega func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { var currentNode: DataTree? = timeZones var timezoneId = [String]() - for i in 0...component { + for i in 0 ... component { let previousRow = pickerView.selectedRow(inComponent: i) currentNode = currentNode?[previousRow] if let node = currentNode { @@ -798,7 +804,7 @@ class DateTimeView: UIViewController, UIPickerViewDataSource, UIPickerViewDelega WatchFaceView.currentInstance?.drawView(forceRefresh: true) (navigationController?.viewControllers.first as? SettingsViewController)?.reload() } - for i in (component+1).., with event: UIEvent?) { if let location = touches.first?.location(in: self) { let ratio = (location.x - bounds.origin.x - controlRadius) / (bounds.width - controlRadius * 2) - let color = UIColor(cgColor: self.gradient.interpolate(at: ratio)) + let color = UIColor(cgColor: gradient.interpolate(at: ratio)) addControl(at: ratio, color: color) values.append(ratio) colors.append(color) - UIImpactFeedbackGenerator.init(style: .rigid).impactOccurred() + UIImpactFeedbackGenerator(style: .rigid).impactOccurred() updateGradient() if let action = action { action() @@ -951,7 +958,7 @@ class GradientSlider: UIControl, UIGestureRecognizerDelegate { control.addTarget(control, action: #selector(ColorWell.colorWellChanged(_:)), for: .allEvents) controls.append(control) control.index = controls.count - 1 - self.addSubview(control) + addSubview(control) } private func initializeControls() { @@ -1007,35 +1014,35 @@ class GradientSlider: UIControl, UIGestureRecognizerDelegate { } class CircleColorView: UIViewController { - @IBOutlet weak var yearColor: GradientSlider! - @IBOutlet weak var monthColor: GradientSlider! - @IBOutlet weak var dayColor: GradientSlider! - @IBOutlet weak var centerTextColor: GradientSlider! - @IBOutlet weak var yearColorLoop: UISwitch! - @IBOutlet weak var monthColorLoop: UISwitch! - @IBOutlet weak var dayColorLoop: UISwitch! - @IBOutlet weak var circleTransparancy: UISlider! - @IBOutlet weak var majorTickTransparancy: UISlider! - @IBOutlet weak var minorTickTransparancy: UISlider! - @IBOutlet weak var circleTransparancyReading: UILabel! - @IBOutlet weak var majorTickTransparancyReading: UILabel! - @IBOutlet weak var minorTickTransparancyReading: UILabel! - @IBOutlet weak var firstSection: UIView! - @IBOutlet weak var secondSection: UIView! - @IBOutlet weak var thirdSection: UIView! - - @IBOutlet weak var majorTickColor: UIColorWell! - @IBOutlet weak var majorTickColorDark: UIColorWell! - @IBOutlet weak var minorTickColor: UIColorWell! - @IBOutlet weak var minorTickColorDark: UIColorWell! - @IBOutlet weak var oddSolarTermColor: UIColorWell! - @IBOutlet weak var oddSolarTermColorDark: UIColorWell! - @IBOutlet weak var evenSolarTermColor: UIColorWell! - @IBOutlet weak var evenSolarTermColorDark: UIColorWell! - @IBOutlet weak var textColor: UIColorWell! - @IBOutlet weak var textColorDark: UIColorWell! - @IBOutlet weak var coreColor: UIColorWell! - @IBOutlet weak var coreColorDark: UIColorWell! + @IBOutlet var yearColor: GradientSlider! + @IBOutlet var monthColor: GradientSlider! + @IBOutlet var dayColor: GradientSlider! + @IBOutlet var centerTextColor: GradientSlider! + @IBOutlet var yearColorLoop: UISwitch! + @IBOutlet var monthColorLoop: UISwitch! + @IBOutlet var dayColorLoop: UISwitch! + @IBOutlet var circleTransparancy: UISlider! + @IBOutlet var majorTickTransparancy: UISlider! + @IBOutlet var minorTickTransparancy: UISlider! + @IBOutlet var circleTransparancyReading: UILabel! + @IBOutlet var majorTickTransparancyReading: UILabel! + @IBOutlet var minorTickTransparancyReading: UILabel! + @IBOutlet var firstSection: UIView! + @IBOutlet var secondSection: UIView! + @IBOutlet var thirdSection: UIView! + + @IBOutlet var majorTickColor: UIColorWell! + @IBOutlet var majorTickColorDark: UIColorWell! + @IBOutlet var minorTickColor: UIColorWell! + @IBOutlet var minorTickColorDark: UIColorWell! + @IBOutlet var oddSolarTermColor: UIColorWell! + @IBOutlet var oddSolarTermColorDark: UIColorWell! + @IBOutlet var evenSolarTermColor: UIColorWell! + @IBOutlet var evenSolarTermColorDark: UIColorWell! + @IBOutlet var textColor: UIColorWell! + @IBOutlet var textColorDark: UIColorWell! + @IBOutlet var coreColor: UIColorWell! + @IBOutlet var coreColorDark: UIColorWell! func fillData() { let layout = WatchLayout.shared @@ -1184,45 +1191,44 @@ class CircleColorView: UIViewController { watchLayout.fontColorDark = color } } else if sender === coreColor { - if let color = sender.selectedColor?.cgColor { - watchLayout.innerColor = color - } - } else if sender === coreColorDark { - if let color = sender.selectedColor?.cgColor { - watchLayout.innerColorDark = color - } - } + if let color = sender.selectedColor?.cgColor { + watchLayout.innerColor = color + } + } else if sender === coreColorDark { + if let color = sender.selectedColor?.cgColor { + watchLayout.innerColorDark = color + } + } WatchFaceView.currentInstance?.drawView(forceRefresh: true) } - } class MarkColorView: UIViewController { - @IBOutlet weak var firstSection: UIView! - @IBOutlet weak var secondSection: UIView! - @IBOutlet weak var thirdSection: UIView! - @IBOutlet weak var fourthSection: UIView! - - @IBOutlet weak var mercuryColor: UIColorWell! - @IBOutlet weak var venusColor: UIColorWell! - @IBOutlet weak var marsColor: UIColorWell! - @IBOutlet weak var jupiterColor: UIColorWell! - @IBOutlet weak var saturnColor: UIColorWell! - @IBOutlet weak var moonColor: UIColorWell! - - @IBOutlet weak var newmoonMarkColor: UIColorWell! - @IBOutlet weak var fullmoonMarkColor: UIColorWell! - @IBOutlet weak var oddSolarTermMarkColor: UIColorWell! - @IBOutlet weak var evenSolarTermMarkColor: UIColorWell! - - @IBOutlet weak var sunriseMarkColor: UIColorWell! - @IBOutlet weak var sunsetMarkColor: UIColorWell! - @IBOutlet weak var noonMarkColor: UIColorWell! - @IBOutlet weak var midnightMarkColor: UIColorWell! - - @IBOutlet weak var moonriseMarkColor: UIColorWell! - @IBOutlet weak var moonsetMarkColor: UIColorWell! - @IBOutlet weak var moonnoonMarkColor: UIColorWell! + @IBOutlet var firstSection: UIView! + @IBOutlet var secondSection: UIView! + @IBOutlet var thirdSection: UIView! + @IBOutlet var fourthSection: UIView! + + @IBOutlet var mercuryColor: UIColorWell! + @IBOutlet var venusColor: UIColorWell! + @IBOutlet var marsColor: UIColorWell! + @IBOutlet var jupiterColor: UIColorWell! + @IBOutlet var saturnColor: UIColorWell! + @IBOutlet var moonColor: UIColorWell! + + @IBOutlet var newmoonMarkColor: UIColorWell! + @IBOutlet var fullmoonMarkColor: UIColorWell! + @IBOutlet var oddSolarTermMarkColor: UIColorWell! + @IBOutlet var evenSolarTermMarkColor: UIColorWell! + + @IBOutlet var sunriseMarkColor: UIColorWell! + @IBOutlet var sunsetMarkColor: UIColorWell! + @IBOutlet var noonMarkColor: UIColorWell! + @IBOutlet var midnightMarkColor: UIColorWell! + + @IBOutlet var moonriseMarkColor: UIColorWell! + @IBOutlet var moonsetMarkColor: UIColorWell! + @IBOutlet var moonnoonMarkColor: UIColorWell! func fillData() { let layout = WatchLayout.shared @@ -1354,13 +1360,13 @@ class MarkColorView: UIViewController { } class LayoutsView: UIViewController { - @IBOutlet weak var contentView: UIView! - @IBOutlet weak var widthField: UITextField! - @IBOutlet weak var heightField: UITextField! - @IBOutlet weak var roundedCornerField: UITextField! - @IBOutlet weak var largeTextShiftField: UITextField! - @IBOutlet weak var textVerticalShiftField: UITextField! - @IBOutlet weak var textHorizontalShiftField: UITextField! + @IBOutlet var contentView: UIView! + @IBOutlet var widthField: UITextField! + @IBOutlet var heightField: UITextField! + @IBOutlet var roundedCornerField: UITextField! + @IBOutlet var largeTextShiftField: UITextField! + @IBOutlet var textVerticalShiftField: UITextField! + @IBOutlet var textHorizontalShiftField: UITextField! func fillData() { let layout = WatchLayout.shared @@ -1382,47 +1388,52 @@ class LayoutsView: UIViewController { } @IBAction func widthChanged(_ sender: UITextField) { - if let value = sender.text.flatMap({Double($0)}) { + if let value = sender.text.flatMap({ Double($0) }) { WatchLayout.shared.watchSize.width = value (WatchFaceView.currentInstance?.window?.rootViewController as? ViewController)?.resize() } else { sender.text = nil } } + @IBAction func heightChanged(_ sender: UITextField) { - if let value = sender.text.flatMap({Double($0)}) { + if let value = sender.text.flatMap({ Double($0) }) { WatchLayout.shared.watchSize.height = value (WatchFaceView.currentInstance?.window?.rootViewController as? ViewController)?.resize() } else { sender.text = nil } } + @IBAction func radiusChanged(_ sender: UITextField) { - if let value = sender.text.flatMap({Double($0)}) { + if let value = sender.text.flatMap({ Double($0) }) { WatchLayout.shared.cornerRadiusRatio = value WatchFaceView.currentInstance?.drawView(forceRefresh: true) } else { sender.text = nil } } + @IBAction func largeTextShiftChanged(_ sender: UITextField) { - if let value = sender.text.flatMap({Double($0)}) { + if let value = sender.text.flatMap({ Double($0) }) { WatchLayout.shared.centerTextOffset = value WatchFaceView.currentInstance?.drawView(forceRefresh: true) } else { sender.text = nil } } + @IBAction func textVerticalShiftChanged(_ sender: UITextField) { - if let value = sender.text.flatMap({Double($0)}) { + if let value = sender.text.flatMap({ Double($0) }) { WatchLayout.shared.verticalTextOffset = value WatchFaceView.currentInstance?.drawView(forceRefresh: true) } else { sender.text = nil } } + @IBAction func textHorizontalShiftChanged(_ sender: UITextField) { - if let value = sender.text.flatMap({Double($0)}) { + if let value = sender.text.flatMap({ Double($0) }) { WatchLayout.shared.horizontalTextOffset = value WatchFaceView.currentInstance?.drawView(forceRefresh: true) } else { @@ -1432,7 +1443,6 @@ class LayoutsView: UIViewController { } class HelpViewController: UIViewController { - @IBOutlet var stackView: UIStackView! private let parser = MarkdownParser() @@ -1598,7 +1608,7 @@ class ThemeCell: UITableViewCell { label.textColor = .systemGreen } elements.addSubview(label) - self.addSubview(elements) + addSubview(elements) } override func prepareForReuse() { @@ -1607,7 +1617,6 @@ class ThemeCell: UITableViewCell { deviceName = nil date = nil } - } class ThemeListViewController: UITableViewController { @@ -1626,13 +1635,13 @@ class ThemeListViewController: UITableViewController { refreshControl = UIRefreshControl() refreshControl!.largeContentTitle = NSLocalizedString("刷新", comment: "Pull to refresh") - refreshControl!.addTarget(self, action: #selector(self.refresh), for: .valueChanged) + refreshControl!.addTarget(self, action: #selector(refresh), for: .valueChanged) let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(longPress(_:))) longPressRecognizer.minimumPressDuration = 0.5 - self.tableView.addGestureRecognizer(longPressRecognizer) + tableView.addGestureRecognizer(longPressRecognizer) - tableView.tableFooterView = {() -> UIView in + tableView.tableFooterView = { () -> UIView in let footnote = UILabel(frame: CGRect(x: 0, y: 0, width: view.bounds.width, height: 21)) footnote.text = NSLocalizedString("短按換主題,長按易名", comment: "Comment: tap to change theme, long press to rename") footnote.textAlignment = .center @@ -1647,7 +1656,7 @@ class ThemeListViewController: UITableViewController { @objc func refresh() { loadThemes() tableView.reloadData() - self.refreshControl!.endRefreshing() + refreshControl!.endRefreshing() } @objc func longPress(_ sender: UILongPressGestureRecognizer) { @@ -1693,6 +1702,7 @@ class ThemeListViewController: UITableViewController { override func numberOfSections(in tableView: UITableView) -> Int { return themes.count + 1 } + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { if section == 0 { return 1 @@ -1702,6 +1712,7 @@ class ThemeListViewController: UITableViewController { return themes[key]?.count ?? 0 } } + override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { if section == 0 { return nil @@ -1746,7 +1757,7 @@ class ThemeListViewController: UITableViewController { let alertController = UIAlertController(title: NSLocalizedString("換主題", comment: "Confirm to select theme title"), message: NSLocalizedString("換爲:", comment: "Confirm to select theme message") + (cell.title ?? ""), preferredStyle: .alert) let cancelAction = UIAlertAction(title: NSLocalizedString("容吾三思", comment: "Cancel Resetting Settings"), style: .default) - let confirmAction = UIAlertAction(title: NSLocalizedString("吾意已決", comment: "Confirm Resetting Settings"), style: .destructive) {[self] _ in + let confirmAction = UIAlertAction(title: NSLocalizedString("吾意已決", comment: "Confirm Resetting Settings"), style: .destructive) { [self] _ in DataContainer.shared.loadSave(name: cell.title, deviceName: cell.deviceName) (WatchFaceView.currentInstance?.window?.rootViewController as? ViewController)?.resize() (navigationController?.viewControllers.first as? SettingsViewController)?.reload() @@ -1758,7 +1769,7 @@ class ThemeListViewController: UITableViewController { DataContainer.shared.loadSave(name: cell.title, deviceName: cell.deviceName) - // New + // New } else { let alertController = UIAlertController(title: NSLocalizedString("取名", comment: "set a name"), message: NSLocalizedString("不得爲空,不得重名", comment: "no blank, no duplicate name"), preferredStyle: .alert) let addNewAction = UIAlertAction(title: NSLocalizedString("此名甚善", comment: "Confirm adding Settings"), style: .default) { _ in @@ -1789,7 +1800,7 @@ class ThemeListViewController: UITableViewController { } @objc func validateName(_ sender: UITextField) { - var resp : UIResponder! = sender + var resp: UIResponder! = sender while !(resp is UIAlertController) { resp = resp.next } let alert = resp as! UIAlertController if let fileName = sender.text, fileName != "" { @@ -1800,7 +1811,6 @@ class ThemeListViewController: UITableViewController { } } alert.actions[1].isEnabled = false - return } // Delete @@ -1810,7 +1820,7 @@ class ThemeListViewController: UITableViewController { let alertController = UIAlertController(title: NSLocalizedString("刪主題", comment: "Confirm to delete theme title"), message: NSLocalizedString("刪:", comment: "Confirm to delete theme message") + (cell.title ?? ""), preferredStyle: .alert) let cancelAction = UIAlertAction(title: NSLocalizedString("容吾三思", comment: "Cancel Resetting Settings"), style: .default) - let confirmAction = UIAlertAction(title: NSLocalizedString("吾意已決", comment: "Confirm Resetting Settings"), style: .destructive) {_ in + let confirmAction = UIAlertAction(title: NSLocalizedString("吾意已決", comment: "Confirm Resetting Settings"), style: .destructive) { _ in DataContainer.shared.deleteSave(name: cell.title!, deviceName: cell.deviceName!) self.loadThemes() self.tableView.reloadData() diff --git a/iOS/WatchFace.swift b/iOS/WatchFace.swift index 02b3e75..306066f 100644 --- a/iOS/WatchFace.swift +++ b/iOS/WatchFace.swift @@ -17,7 +17,7 @@ class WatchFaceView: UIView { let watchLayout = WatchLayout.shared var displayTime: Date? = nil var timezone: TimeZone = Calendar.current.timeZone - var phase: StartingPhase = StartingPhase(zeroRing: 0, firstRing: 0, secondRing: 0, thirdRing: 0, fourthRing: 0) + var phase: StartingPhase = .init(zeroRing: 0, firstRing: 0, secondRing: 0, thirdRing: 0, fourthRing: 0) var timer: Timer? var entityNotes: [EntityNote] = [] @@ -47,7 +47,7 @@ class WatchFaceView: UIView { } var isDark: Bool { - self.traitCollection.userInterfaceStyle == .dark + traitCollection.userInterfaceStyle == .dark } func update() { @@ -72,7 +72,7 @@ class WatchFaceView: UIView { override func draw(_ rawRect: CGRect) { let dirtyRect = rawRect.insetBy(dx: Self.frameOffset, dy: Self.frameOffset) - entityNotes = self.layer.update(dirtyRect: dirtyRect, isDark: isDark, watchLayout: watchLayout, chineseCalendar: chineseCalendar, graphicArtifects: graphicArtifects, keyStates: keyStates, phase: phase) + entityNotes = layer.update(dirtyRect: dirtyRect, isDark: isDark, watchLayout: watchLayout, chineseCalendar: chineseCalendar, graphicArtifects: graphicArtifects, keyStates: keyStates, phase: phase) } @objc func tapped(_ gesture: UITapGestureRecognizer) { @@ -88,9 +88,9 @@ class WatchFaceView: UIView { } } if entities.count > 0 { - let width: CGFloat = CGFloat(entities.count) * (UIFont.systemFontSize + 6) + 8 - let height: CGFloat = CGFloat(entities.map { $0.name.count }.reduce(0) { max($0, $1) }) * UIFont.systemFontSize + 30 - var frame = CGRect(x: point.x - width/2, y: point.y - height/2, width: width, height: height) + let width = CGFloat(entities.count) * (UIFont.systemFontSize + 6) + 8 + let height = CGFloat(entities.map { $0.name.count }.reduce(0) { max($0, $1) }) * UIFont.systemFontSize + 30 + var frame = CGRect(x: point.x - width / 2, y: point.y - height / 2, width: width, height: height) if frame.maxX > bounds.maxX - Self.frameOffset { frame.origin.x -= frame.maxX - bounds.maxX + Self.frameOffset @@ -103,24 +103,23 @@ class WatchFaceView: UIView { tooltip.layer.shadowOffset = CGSize(width: 3, height: -3) tooltip.layer.shadowRadius = 5 tooltip.layer.shadowColor = UIColor.black.withAlphaComponent(0.2).cgColor - self.addSubview(tooltip) + addSubview(tooltip) } } } } class NoteView: UIView { - private var visualEffectView: UIVisualEffectView! private var entities: [EntityNote] = [] init(frame frameRect: CGRect, entities: [EntityNote]) { self.entities = entities super.init(frame: frameRect) - self.layer.shadowOffset = CGSize(width: 3, height: -3) - self.layer.shadowRadius = 5 - self.layer.shadowOpacity = 0.2 - self.layer.shadowColor = UIColor.black.cgColor + layer.shadowOffset = CGSize(width: 3, height: -3) + layer.shadowRadius = 5 + layer.shadowOpacity = 0.2 + layer.shadowColor = UIColor.black.cgColor setupView() } @@ -128,13 +127,14 @@ class NoteView: UIView { return false } + @available(*, unavailable) required init?(coder: NSCoder) { fatalError("Not implemented") } override func layoutSubviews() { super.layoutSubviews() - self.alpha = 0 + alpha = 0 UIView.animate(withDuration: 0.2) { self.alpha = 1.0 } @@ -150,12 +150,12 @@ class NoteView: UIView { private func setupView() { let blurEffect = UIBlurEffect(style: .systemUltraThinMaterial) visualEffectView = UIVisualEffectView(effect: blurEffect) - self.addSubview(visualEffectView) + addSubview(visualEffectView) visualEffectView.layer.masksToBounds = true - visualEffectView.frame = self.bounds + visualEffectView.frame = bounds let mask = CAShapeLayer() - mask.path = RoundedRect(rect: self.bounds, nodePos: 10, ankorPos: 2).path + mask.path = RoundedRect(rect: bounds, nodePos: 10, ankorPos: 2).path visualEffectView.layer.mask = mask var lastView: UIView? = nil diff --git a/macOS/AppDelegate.swift b/macOS/AppDelegate.swift index 943a0a2..0f7576b 100644 --- a/macOS/AppDelegate.swift +++ b/macOS/AppDelegate.swift @@ -15,6 +15,7 @@ func updateStatusTitle(title: String) { statusItem?.length = button.intrinsicContentSize.width } } + func updatePosition() { if let frame = statusItem?.button?.window?.frame { WatchFace.currentInstance?.moveTopCenter(to: NSMakePoint(NSMidX(frame), NSMinY(frame))) @@ -23,20 +24,19 @@ func updatePosition() { @main class AppDelegate: NSObject, NSApplicationDelegate { - func applicationWillFinishLaunching(_ aNotification: Notification) { statusItem = NSStatusBar.system.statusItem(withLength: 0) statusItem?.button?.action = #selector(self.toggleDisplay(sender:)) statusItem?.button?.sendAction(on: [.leftMouseDown]) } - + @objc func toggleDisplay(sender: NSStatusItem) { if let watchFace = WatchFace.currentInstance { if watchFace.isVisible { WidgetCenter.shared.reloadAllTimelines() watchFace.hide() } else { - LocationManager.shared.requestLocation() { _ in + LocationManager.shared.requestLocation { _ in watchFace._view.drawView(forceRefresh: true) } watchFace.show() @@ -45,15 +45,15 @@ class AppDelegate: NSObject, NSApplicationDelegate { } } } - + func applicationDidResignActive(_ notification: Notification) { WatchFace.currentInstance?.hide() } - + func applicationDidFinishLaunching(_ aNotification: Notification) { DataContainer.shared.loadSave() let preview = WatchFace(position: NSZeroRect) - LocationManager.shared.requestLocation() { _ in + LocationManager.shared.requestLocation { _ in WatchFace.currentInstance?._view.drawView(forceRefresh: true) } preview.show() @@ -74,16 +74,16 @@ class AppDelegate: NSObject, NSApplicationDelegate { func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply { // Save changes in the application's managed object context before the application terminates. let context = DataContainer.shared.persistentContainer.viewContext - + if !context.commitEditing() { NSLog("\(NSStringFromClass(type(of: self))) unable to commit editing to terminate") return .terminateCancel } - + if !context.hasChanges { return .terminateNow } - + do { try context.save() } catch { @@ -91,12 +91,12 @@ class AppDelegate: NSObject, NSApplicationDelegate { // Customize this code block to include application-specific recovery steps. let result = sender.presentError(nserror) - if (result) { + if result { return .terminateCancel } - + let question = NSLocalizedString("Could not save changes while quitting. Quit anyway?", comment: "Quit without saves error question message") - let info = NSLocalizedString("Quitting now will lose any changes you have made since the last successful save", comment: "Quit without saves error question info"); + let info = NSLocalizedString("Quitting now will lose any changes you have made since the last successful save", comment: "Quit without saves error question info") let quitButton = NSLocalizedString("Quit anyway", comment: "Quit anyway button title") let cancelButton = NSLocalizedString("Cancel", comment: "Cancel button title") let alert = NSAlert() @@ -104,7 +104,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { alert.informativeText = info alert.addButton(withTitle: quitButton) alert.addButton(withTitle: cancelButton) - + let answer = alert.runModal() if answer == .alertSecondButtonReturn { return .terminateCancel @@ -113,6 +113,4 @@ class AppDelegate: NSObject, NSApplicationDelegate { // If we got here, it is time to quit. return .terminateNow } - } - diff --git a/macOS/Layout.swift b/macOS/Layout.swift index 0e7cf31..ae3a041 100644 --- a/macOS/Layout.swift +++ b/macOS/Layout.swift @@ -8,8 +8,8 @@ import AppKit class WatchLayout: MetaWatchLayout { - static var shared: WatchLayout = WatchLayout() - + static var shared: WatchLayout = .init() + var textFont: NSFont var centerFont: NSFont override init() { @@ -18,13 +18,15 @@ class WatchLayout: MetaWatchLayout { traits: .boldFontMask, weight: 900, size: NSFont.systemFontSize)! super.init() } + override func encode(includeOffset: Bool = true) -> String { var encoded = super.encode() encoded += "textFont: \(textFont.fontName)\n" encoded += "centerFont: \(centerFont.fontName)\n" return encoded } - override func update(from values: Dictionary) { + + override func update(from values: [String: String]) { super.update(from: values) if let name = values["textFont"] { textFont = NSFont(name: name, size: NSFont.systemFontSize) ?? textFont diff --git a/macOS/ViewController.swift b/macOS/ViewController.swift index 21a25c6..7d1d6b2 100644 --- a/macOS/ViewController.swift +++ b/macOS/ViewController.swift @@ -9,10 +9,11 @@ import AppKit class ColorWell: NSColorWell { override func mouseDown(with event: NSEvent) { - self.window?.makeFirstResponder(self) + window?.makeFirstResponder(self) NSColorPanel.shared.showsAlpha = true super.mouseDown(with: event) } + override func resignFirstResponder() -> Bool { NSColorPanel.shared.close() return super.resignFirstResponder() @@ -71,9 +72,7 @@ class GradientSlider: NSControl, NSColorChanging { } override var acceptsFirstResponder: Bool { - get { - return true - } + return true } private func addControl(at value: CGFloat, color: NSColor) { @@ -147,9 +146,9 @@ class GradientSlider: NSControl, NSColorChanging { } override func mouseDown(with event: NSEvent) { - self.window?.makeFirstResponder(self) + window?.makeFirstResponder(self) previousLocation = event.locationInWindow - previousLocation = self.convert(previousLocation!, from: window?.contentView) + previousLocation = convert(previousLocation!, from: window?.contentView) var hit = false var i = 0 for control in controls { @@ -165,7 +164,7 @@ class GradientSlider: NSControl, NSColorChanging { if !hit { var newValue = (previousLocation!.x - trackLayer.frame.minX) / trackLayer.frame.width newValue = min(max(newValue, minimumValue), maximumValue) - let color = self.gradient.interpolate(at: newValue) + let color = gradient.interpolate(at: newValue) values.append(newValue) colors.append(NSColor(cgColor: color)!) addControl(at: newValue, color: NSColor(cgColor: color)!) @@ -175,11 +174,12 @@ class GradientSlider: NSControl, NSColorChanging { movingControl!.strokeColor = NSColor.controlAccentColor.cgColor } } + override func mouseDragged(with event: NSEvent) { dragging = true if (movingIndex != nil) && (previousLocation != nil) && (movingIndex! < values.count) { var location = event.locationInWindow - location = self.convert(location, from: window?.contentView) + location = convert(location, from: window?.contentView) let deltaLocation = location.x - previousLocation!.x let deltaValue = (maximumValue - minimumValue) * deltaLocation / trackLayer.frame.width previousLocation = location @@ -218,6 +218,7 @@ class GradientSlider: NSControl, NSColorChanging { updateGradient() } } + override func performKeyEquivalent(with event: NSEvent) -> Bool { if event.keyCode == 51 && movingControl != nil && movingIndex != nil && values.count > 2 && colors.count > 2 { return true @@ -233,91 +234,90 @@ class GradientSlider: NSControl, NSColorChanging { updateGradient() } } - } - class ConfigurationViewController: NSViewController, NSWindowDelegate { static var currentInstance: ConfigurationViewController? = nil - @IBOutlet weak var clearLocationButton: NSButton! - @IBOutlet weak var globalMonthPicker: NSPopUpButton! - @IBOutlet weak var apparentTimePicker: NSPopUpButton! - @IBOutlet weak var datetimePicker: NSDatePicker! - @IBOutlet weak var currentTimeToggle: NSButton! - @IBOutlet weak var timezonePicker: NSPopUpButton! - @IBOutlet weak var latitudeDegreePicker: NSTextField! - @IBOutlet weak var latitudeMinutePicker: NSTextField! - @IBOutlet weak var latitudeSecondPicker: NSTextField! - @IBOutlet weak var latitudeSpherePicker: NSSegmentedControl! - @IBOutlet weak var longitudeDegreePicker: NSTextField! - @IBOutlet weak var longitudeMinutePicker: NSTextField! - @IBOutlet weak var longitudeSecondPicker: NSTextField! - @IBOutlet weak var longitudeSpherePicker: NSSegmentedControl! - @IBOutlet weak var currentLocationToggle: NSButton! - @IBOutlet weak var firstRingGradientPicker: GradientSlider! - @IBOutlet weak var secondRingGradientPicker: GradientSlider! - @IBOutlet weak var thirdRingGradientPicker: GradientSlider! - @IBOutlet weak var shadeAlphaValuePicker: NSSlider! - @IBOutlet weak var shadeAlphaValueLabel: NSTextField! - @IBOutlet weak var firstRingIsLoop: NSButton! - @IBOutlet weak var secondRingIsLoop: NSButton! - @IBOutlet weak var thirdRingIsLoop: NSButton! - @IBOutlet weak var innerTextGradientPicker: GradientSlider! - @IBOutlet weak var minorTickAlphaValuePicker: NSSlider! - @IBOutlet weak var minorTickAlphaValueLabel: NSTextField! - @IBOutlet weak var majorTickAlphaValuePicker: NSSlider! - @IBOutlet weak var majorTickAlphaValueLabel: NSTextField! - @IBOutlet weak var innerColorPicker: NSColorWell! - @IBOutlet weak var majorTickColorPicker: NSColorWell! - @IBOutlet weak var minorTickColorPicker: NSColorWell! - @IBOutlet weak var textColorPicker: NSColorWell! - @IBOutlet weak var oddStermTickColorPicker: NSColorWell! - @IBOutlet weak var evenStermTickColorPicker: NSColorWell! - @IBOutlet weak var innerColorPickerDark: NSColorWell! - @IBOutlet weak var majorTickColorPickerDark: NSColorWell! - @IBOutlet weak var minorTickColorPickerDark: NSColorWell! - @IBOutlet weak var textColorPickerDark: NSColorWell! - @IBOutlet weak var oddStermTickColorPickerDark: NSColorWell! - @IBOutlet weak var evenStermTickColorPickerDark: NSColorWell! - @IBOutlet weak var mercuryIndicatorColorPicker: NSColorWell! - @IBOutlet weak var venusIndicatorColorPicker: NSColorWell! - @IBOutlet weak var marsIndicatorColorPicker: NSColorWell! - @IBOutlet weak var jupyterIndicatorColorPicker: NSColorWell! - @IBOutlet weak var saturnIndicatorColorPicker: NSColorWell! - @IBOutlet weak var moonIndicatorColorPicker: NSColorWell! - @IBOutlet weak var eclipseIndicatorColorPicker: NSColorWell! - @IBOutlet weak var fullmoonIndicatorColorPicker: NSColorWell! - @IBOutlet weak var oddStermIndicatorColorPicker: NSColorWell! - @IBOutlet weak var evenStermIndicatorColorPicker: NSColorWell! - @IBOutlet weak var sunriseIndicatorColorPicker: NSColorWell! - @IBOutlet weak var sunsetIndicatorColorPicker: NSColorWell! - @IBOutlet weak var noonIndicatorColorPicker: NSColorWell! - @IBOutlet weak var midnightIndicatorColorPicker: NSColorWell! - @IBOutlet weak var moonriseIndicatorColorPicker: NSColorWell! - @IBOutlet weak var moonsetIndicatorColorPicker: NSColorWell! - @IBOutlet weak var moonmeridianIndicatorColorPicker: NSColorWell! - @IBOutlet weak var textFontFamilyPicker: NSPopUpButton! - @IBOutlet weak var textFontTraitPicker: NSPopUpButton! - @IBOutlet weak var centerTextFontFamilyPicker: NSPopUpButton! - @IBOutlet weak var centerTextFontTraitPicker: NSPopUpButton! - @IBOutlet weak var widthPicker: NSTextField! - @IBOutlet weak var heightPicker: NSTextField! - @IBOutlet weak var cornerRadiusRatioPicker: NSTextField! - @IBOutlet weak var centerTextOffsetPicker: NSTextField! - @IBOutlet weak var textHorizontalOffsetPicker: NSTextField! - @IBOutlet weak var textVerticalOffsetPicker: NSTextField! - @IBOutlet weak var doneButton: NSButton! - @IBOutlet weak var themesButton: NSButton! - @IBOutlet weak var applyButton: NSButton! - @IBOutlet weak var scrollView: NSScrollView! - @IBOutlet weak var contentView: NSView! + @IBOutlet var clearLocationButton: NSButton! + @IBOutlet var globalMonthPicker: NSPopUpButton! + @IBOutlet var apparentTimePicker: NSPopUpButton! + @IBOutlet var datetimePicker: NSDatePicker! + @IBOutlet var currentTimeToggle: NSButton! + @IBOutlet var timezonePicker: NSPopUpButton! + @IBOutlet var latitudeDegreePicker: NSTextField! + @IBOutlet var latitudeMinutePicker: NSTextField! + @IBOutlet var latitudeSecondPicker: NSTextField! + @IBOutlet var latitudeSpherePicker: NSSegmentedControl! + @IBOutlet var longitudeDegreePicker: NSTextField! + @IBOutlet var longitudeMinutePicker: NSTextField! + @IBOutlet var longitudeSecondPicker: NSTextField! + @IBOutlet var longitudeSpherePicker: NSSegmentedControl! + @IBOutlet var currentLocationToggle: NSButton! + @IBOutlet var firstRingGradientPicker: GradientSlider! + @IBOutlet var secondRingGradientPicker: GradientSlider! + @IBOutlet var thirdRingGradientPicker: GradientSlider! + @IBOutlet var shadeAlphaValuePicker: NSSlider! + @IBOutlet var shadeAlphaValueLabel: NSTextField! + @IBOutlet var firstRingIsLoop: NSButton! + @IBOutlet var secondRingIsLoop: NSButton! + @IBOutlet var thirdRingIsLoop: NSButton! + @IBOutlet var innerTextGradientPicker: GradientSlider! + @IBOutlet var minorTickAlphaValuePicker: NSSlider! + @IBOutlet var minorTickAlphaValueLabel: NSTextField! + @IBOutlet var majorTickAlphaValuePicker: NSSlider! + @IBOutlet var majorTickAlphaValueLabel: NSTextField! + @IBOutlet var innerColorPicker: NSColorWell! + @IBOutlet var majorTickColorPicker: NSColorWell! + @IBOutlet var minorTickColorPicker: NSColorWell! + @IBOutlet var textColorPicker: NSColorWell! + @IBOutlet var oddStermTickColorPicker: NSColorWell! + @IBOutlet var evenStermTickColorPicker: NSColorWell! + @IBOutlet var innerColorPickerDark: NSColorWell! + @IBOutlet var majorTickColorPickerDark: NSColorWell! + @IBOutlet var minorTickColorPickerDark: NSColorWell! + @IBOutlet var textColorPickerDark: NSColorWell! + @IBOutlet var oddStermTickColorPickerDark: NSColorWell! + @IBOutlet var evenStermTickColorPickerDark: NSColorWell! + @IBOutlet var mercuryIndicatorColorPicker: NSColorWell! + @IBOutlet var venusIndicatorColorPicker: NSColorWell! + @IBOutlet var marsIndicatorColorPicker: NSColorWell! + @IBOutlet var jupyterIndicatorColorPicker: NSColorWell! + @IBOutlet var saturnIndicatorColorPicker: NSColorWell! + @IBOutlet var moonIndicatorColorPicker: NSColorWell! + @IBOutlet var eclipseIndicatorColorPicker: NSColorWell! + @IBOutlet var fullmoonIndicatorColorPicker: NSColorWell! + @IBOutlet var oddStermIndicatorColorPicker: NSColorWell! + @IBOutlet var evenStermIndicatorColorPicker: NSColorWell! + @IBOutlet var sunriseIndicatorColorPicker: NSColorWell! + @IBOutlet var sunsetIndicatorColorPicker: NSColorWell! + @IBOutlet var noonIndicatorColorPicker: NSColorWell! + @IBOutlet var midnightIndicatorColorPicker: NSColorWell! + @IBOutlet var moonriseIndicatorColorPicker: NSColorWell! + @IBOutlet var moonsetIndicatorColorPicker: NSColorWell! + @IBOutlet var moonmeridianIndicatorColorPicker: NSColorWell! + @IBOutlet var textFontFamilyPicker: NSPopUpButton! + @IBOutlet var textFontTraitPicker: NSPopUpButton! + @IBOutlet var centerTextFontFamilyPicker: NSPopUpButton! + @IBOutlet var centerTextFontTraitPicker: NSPopUpButton! + @IBOutlet var widthPicker: NSTextField! + @IBOutlet var heightPicker: NSTextField! + @IBOutlet var cornerRadiusRatioPicker: NSTextField! + @IBOutlet var centerTextOffsetPicker: NSTextField! + @IBOutlet var textHorizontalOffsetPicker: NSTextField! + @IBOutlet var textVerticalOffsetPicker: NSTextField! + @IBOutlet var doneButton: NSButton! + @IBOutlet var themesButton: NSButton! + @IBOutlet var applyButton: NSButton! + @IBOutlet var scrollView: NSScrollView! + @IBOutlet var contentView: NSView! - var panelTimezone = TimeZone.init(secondsFromGMT: 0)! + var panelTimezone = TimeZone(secondsFromGMT: 0)! func scrollToTop() { let maxHeight = max(scrollView.bounds.height, contentView.bounds.height) contentView.scroll(NSPoint(x: 0, y: maxHeight)) } + func toggle(button: NSButton, with bool: Bool) { if bool { button.state = .on @@ -325,9 +325,11 @@ class ConfigurationViewController: NSViewController, NSWindowDelegate { button.state = .off } } + func readToggle(button: NSButton) -> Bool { return button.state == .on } + func populateFontMember(_ picker: NSPopUpButton, inFamily familyPicker: NSPopUpButton) { picker.removeAllItems() if let family = familyPicker.titleOfSelectedItem { @@ -340,23 +342,26 @@ class ConfigurationViewController: NSViewController, NSWindowDelegate { } picker.selectItem(at: 0) } + func populateFontFamilies(_ picker: NSPopUpButton) { picker.removeAllItems() picker.addItem(withTitle: NSFont.systemFont(ofSize: NSFont.systemFontSize).familyName!) picker.addItems(withTitles: NSFontManager.shared.availableFontFamilies) } + func readFont(family: NSPopUpButton, style: NSPopUpButton) -> NSFont? { let size = NSFont.systemFontSize let fontFamily: String = family.titleOfSelectedItem! let fontTraits: String = style.titleOfSelectedItem! - if let font = NSFont(name: "\(fontFamily.filter {!$0.isWhitespace})-\(fontTraits.filter {!$0.isWhitespace})", size: size) { + if let font = NSFont(name: "\(fontFamily.filter { !$0.isWhitespace })-\(fontTraits.filter { !$0.isWhitespace })", size: size) { return font } let members = NSFontManager.shared.availableMembers(ofFontFamily: fontFamily) ?? [[Any]]() for i in 0.. screen.minX { window.setFrameOrigin(NSMakePoint(watchFace.frame.minX - 10 - window.frame.width, watchFace.frame.midY - window.frame.height / 2)) @@ -931,13 +949,12 @@ class HelpViewController: NSViewController { } override func viewDidDisappear() { - Self.currentInstance = nil + Self.currentInstance = nil super.viewDidDisappear() } } class ThemesListViewController: NSViewController, NSTableViewDelegate, NSTableViewDataSource { - @IBOutlet var tableView: NSTableView! var themes: [DataContainer.SavedTheme] = [] @@ -958,13 +975,14 @@ class ThemesListViewController: NSViewController, NSTableViewDelegate, NSTableVi loadThemes() } - @IBAction func refreshButtonClicked(_ sender: NSButton) { refresh() } + @IBAction func dismissView(_ sender: NSButton) { - self.dismiss(nil) + dismiss(nil) } + @IBAction func readFile(_ sender: Any) { let panel = NSOpenPanel() panel.level = NSWindow.Level.floating @@ -983,7 +1001,7 @@ class ThemesListViewController: NSViewController, NSTableViewDelegate, NSTableVi DataContainer.shared.saveLayout(content, name: self.generateNewName(baseName: (name as NSString).substring(with: nameRange))) self.refresh() self.tableView.selectRowIndexes(IndexSet(integer: 0), byExtendingSelection: false) - } catch let error { + } catch { let alert = NSAlert() alert.messageText = NSLocalizedString("Load Failed", comment: "Load Failed") alert.informativeText = error.localizedDescription @@ -992,6 +1010,7 @@ class ThemesListViewController: NSViewController, NSTableViewDelegate, NSTableVi } } } + @IBAction func writeFile(_ sender: Any) { let row = tableView.selectedRow guard row >= 0 && row < themes.count else { return } @@ -1000,14 +1019,14 @@ class ThemesListViewController: NSViewController, NSTableViewDelegate, NSTableVi panel.level = NSWindow.Level.floating panel.title = NSLocalizedString("Select Location", comment: "Save File") panel.nameFieldStringValue = "\(theme.name).txt" - panel.begin() { + panel.begin { result in if result == .OK, let file = panel.url { do { if let layout = DataContainer.shared.readSave(name: theme.name, deviceName: theme.deviceName) { try layout.data(using: .utf8)?.write(to: file, options: .atomicWrite) } - } catch let error { + } catch { let alert = NSAlert() alert.messageText = NSLocalizedString("Save Failed", comment: "Save Failed") alert.informativeText = error.localizedDescription @@ -1016,9 +1035,11 @@ class ThemesListViewController: NSViewController, NSTableViewDelegate, NSTableVi } } } + @IBAction func deleteTheme(_ sender: NSButton) { tableViewEditItemClicked(sender) } + @IBAction func addNew(_ sender: NSButton) { let timeFormatter = DateFormatter() timeFormatter.dateStyle = .none @@ -1043,6 +1064,7 @@ class ThemesListViewController: NSViewController, NSTableViewDelegate, NSTableVi themeNameView.textField?.action = #selector(self.newTheme(_:)) } } + func generateNewName(baseName: String) -> String { var newFileName = baseName let currentDeviceThemes = themes.filter { $0.deviceName == self.currentDeviceName }.map { $0.name } @@ -1055,7 +1077,7 @@ class ThemesListViewController: NSViewController, NSTableViewDelegate, NSTableVi } @objc func preventDeselect(_ sender: NSTextField) { - self.tableView.selectRowIndexes(IndexSet(integer: 0), byExtendingSelection: false) + tableView.selectRowIndexes(IndexSet(integer: 0), byExtendingSelection: false) tableView.editColumn(0, row: 0, with: nil, select: true) } @@ -1146,7 +1168,7 @@ class ThemesListViewController: NSViewController, NSTableViewDelegate, NSTableVi alert.alertStyle = .warning alert.addButton(withTitle: NSLocalizedString("作罷", comment: "Ok")) alert.runModal() - self.tableView.selectRowIndexes(IndexSet(integer: row), byExtendingSelection: false) + tableView.selectRowIndexes(IndexSet(integer: row), byExtendingSelection: false) tableView.editColumn(0, row: row, with: nil, select: true) } diff --git a/macOS/WatchFace.swift b/macOS/WatchFace.swift index faaab36..36e1026 100644 --- a/macOS/WatchFace.swift +++ b/macOS/WatchFace.swift @@ -10,11 +10,11 @@ import AppKit class WatchFaceView: NSView { static let frameOffset: CGFloat = 5 - let watchLayout: WatchLayout = WatchLayout.shared + let watchLayout: WatchLayout = .shared var displayTime: Date? = nil var timezone: TimeZone = Calendar.current.timeZone - var shape: CAShapeLayer = CAShapeLayer() - var phase: StartingPhase = StartingPhase(zeroRing: 0, firstRing: 0, secondRing: 0, thirdRing: 0, fourthRing: 0) + var shape: CAShapeLayer = .init() + var phase: StartingPhase = .init(zeroRing: 0, firstRing: 0, secondRing: 0, thirdRing: 0, fourthRing: 0) var entityNotes: [EntityNote] = [] var tooltipView: NoteView? @@ -31,12 +31,14 @@ class WatchFaceView: NSView { override init(frame frameRect: NSRect) { super.init(frame: frameRect) self.wantsLayer = true - self.layer?.masksToBounds = true + layer?.masksToBounds = true } + @available(*, unavailable) required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + // Need flipped coordinate system, as required by textStorage override var isFlipped: Bool { true @@ -47,9 +49,9 @@ class WatchFaceView: NSView { } override func viewDidChangeEffectiveAppearance() { - self.layer?.sublayers = nil - self.graphicArtifects = GraphicArtifects() - self.needsDisplay = true + layer?.sublayers = nil + graphicArtifects = GraphicArtifects() + needsDisplay = true } func update() { @@ -58,12 +60,12 @@ class WatchFaceView: NSView { } func drawView(forceRefresh: Bool) { - self.layer?.sublayers = nil + layer?.sublayers = nil update() if forceRefresh { graphicArtifects = GraphicArtifects() } - self.needsDisplay = true + needsDisplay = true } func updateStatusBar(title: String) { @@ -76,15 +78,15 @@ class WatchFaceView: NSView { let dirtyRect = rawRect.insetBy(dx: Self.frameOffset, dy: Self.frameOffset) if graphicArtifects.outerBound == nil { let shortEdge = min(dirtyRect.width, dirtyRect.height) - shape.path = RoundedRect(rect: dirtyRect, nodePos: shortEdge * 0.08, ankorPos: shortEdge*0.08*0.2).path + shape.path = RoundedRect(rect: dirtyRect, nodePos: shortEdge * 0.08, ankorPos: shortEdge * 0.08 * 0.2).path } - entityNotes = (self.layer!.update(dirtyRect: dirtyRect, isDark: isDark, watchLayout: watchLayout, chineseCalendar: chineseCalendar, graphicArtifects: graphicArtifects, keyStates: keyStates, phase: phase)) + entityNotes = (layer!.update(dirtyRect: dirtyRect, isDark: isDark, watchLayout: watchLayout, chineseCalendar: chineseCalendar, graphicArtifects: graphicArtifects, keyStates: keyStates, phase: phase)) updateStatusBar(title: "\(chineseCalendar.dateString) \(chineseCalendar.timeString)") } override func rightMouseUp(with event: NSEvent) { let shortEdge = min(bounds.width, bounds.height) - let point = self.convert(event.locationInWindow, from: nil) + let point = convert(event.locationInWindow, from: nil) var entities = [EntityNote]() for entity in entityNotes { let diff = point - entity.position @@ -94,10 +96,10 @@ class WatchFaceView: NSView { } } if let contentView = window?.contentView, entities.count > 0 { - let width: CGFloat = CGFloat(entities.count) * (NSFont.systemFontSize + 6) + 8 - let height: CGFloat = CGFloat(entities.map { $0.name.count }.reduce(0) { max($0, $1) }) * (NSFont.systemFontSize + 2) + 32 - let frame = CGRect(x: point.x - width/2, y: point.y - height/2, width: width, height: height) - var newFrame = self.convert(frame, to: contentView) + let width = CGFloat(entities.count) * (NSFont.systemFontSize + 6) + 8 + let height = CGFloat(entities.map { $0.name.count }.reduce(0) { max($0, $1) }) * (NSFont.systemFontSize + 2) + 32 + let frame = CGRect(x: point.x - width / 2, y: point.y - height / 2, width: width, height: height) + var newFrame = convert(frame, to: contentView) if newFrame.maxX > contentView.bounds.maxX - Self.frameOffset { newFrame.origin.x -= newFrame.maxX - contentView.bounds.maxX + Self.frameOffset @@ -124,7 +126,6 @@ class WatchFaceView: NSView { } class NoteView: NSView { - private var visualEffectView: NSVisualEffectView! private var entities: [EntityNote] = [] @@ -134,13 +135,14 @@ class NoteView: NSView { setupView() } + @available(*, unavailable) required init?(coder: NSCoder) { fatalError("Not implemented") } override func viewWillDraw() { super.viewWillDraw() - self.alphaValue = 0 + alphaValue = 0 NSAnimationContext.runAnimationGroup { context in context.duration = 0.2 self.animator().alphaValue = 1 @@ -156,11 +158,11 @@ class NoteView: NSView { } private func setupView() { - visualEffectView = NSVisualEffectView(frame: self.bounds) + visualEffectView = NSVisualEffectView(frame: bounds) visualEffectView.autoresizingMask = [.width, .height] visualEffectView.blendingMode = .withinWindow visualEffectView.state = .active - self.addSubview(visualEffectView) + addSubview(visualEffectView) visualEffectView.wantsLayer = true let mask = CAShapeLayer() @@ -246,8 +248,8 @@ class OptionView: NSView { self.background = background self.button = button super.init(frame: frameRect) - self.addSubview(self.background) - self.addSubview(self.button) + addSubview(self.background) + addSubview(self.button) } required init?(coder: NSCoder) { @@ -263,6 +265,7 @@ class OptionView: NSView { button.title = newValue } } + var image: NSImage? { get { button.image @@ -270,6 +273,7 @@ class OptionView: NSView { button.image = newValue } } + var action: Selector? { get { button.action @@ -277,6 +281,7 @@ class OptionView: NSView { button.action = newValue } } + var target: AnyObject? { get { button.target @@ -301,26 +306,26 @@ class WatchFace: NSPanel { } init(position: NSRect) { - _view = WatchFaceView(frame: position) + self._view = WatchFaceView(frame: position) let blurView = NSVisualEffectView() blurView.blendingMode = .behindWindow blurView.material = .popover blurView.state = .active blurView.wantsLayer = true - _backView = blurView - _settingButton = { + self._backView = blurView + self._settingButton = { let view = OptionView(frame: NSZeroRect) view.image = NSImage(systemSymbolName: "gearshape", accessibilityDescription: "Setting") view.button.contentTintColor = .systemGray return view }() - _helpButton = { + self._helpButton = { let view = OptionView(frame: NSZeroRect) view.image = NSImage(systemSymbolName: "info.bubble", accessibilityDescription: "Help") view.button.contentTintColor = .systemGray return view }() - _closingButton = { + self._closingButton = { let view = OptionView(frame: NSZeroRect) view.image = NSImage(systemSymbolName: "xmark", accessibilityDescription: "Quit") view.button.contentTintColor = .systemRed @@ -328,11 +333,11 @@ class WatchFace: NSPanel { }() super.init(contentRect: position, styleMask: .borderless, backing: .buffered, defer: true) _settingButton.target = self - _settingButton.action = #selector(self.openSetting(_:)) + _settingButton.action = #selector(openSetting(_:)) _helpButton.target = self - _helpButton.action = #selector(self.openHelp(_:)) + _helpButton.action = #selector(openHelp(_:)) _closingButton.target = self - _closingButton.action = #selector(self.closeApp(_:)) + _closingButton.action = #selector(closeApp(_:)) self.alphaValue = 1 self.level = NSWindow.Level.floating self.hasShadow = true @@ -361,6 +366,7 @@ class WatchFace: NSPanel { } } } + @objc func openHelp(_ sender: NSButton) { if let helperView = HelpViewController.currentInstance { helperView.view.window?.close() @@ -374,6 +380,7 @@ class WatchFace: NSPanel { } } } + @objc func closeApp(_ sender: NSButton) { NSApp.terminate(sender) } @@ -387,8 +394,8 @@ class WatchFace: NSPanel { } func setCenter() { - let windowRect = self.getCurrentScreen() - self.setFrame(NSMakeRect( + let windowRect = getCurrentScreen() + setFrame(NSMakeRect( windowRect.midX - WatchLayout.shared.watchSize.width / 2, windowRect.midY - WatchLayout.shared.watchSize.height / 2 - buttonSize.height * 0.85, WatchLayout.shared.watchSize.width, @@ -396,25 +403,24 @@ class WatchFace: NSPanel { } func moveTopCenter(to: CGPoint) { - let windowRect = self.getCurrentScreen() + let windowRect = getCurrentScreen() var frame = NSMakeRect( to.x - WatchLayout.shared.watchSize.width / 2, to.y - WatchLayout.shared.watchSize.height - buttonSize.height * 1.7, WatchLayout.shared.watchSize.width, - WatchLayout.shared.watchSize.height + buttonSize.height * 1.7 - ) + WatchLayout.shared.watchSize.height + buttonSize.height * 1.7) if NSMaxX(frame) >= NSMaxX(windowRect) { frame.origin.x = NSMaxX(windowRect) - frame.width } else if NSMinX(frame) <= NSMinX(windowRect) { frame.origin.x = NSMinX(windowRect) } - self.setFrame(frame, display: true) + setFrame(frame, display: true) } func getCurrentScreen() -> NSRect { var screenRect = NSScreen.main!.frame let screens = NSScreen.screens - for i in 0..