├── .github
└── FUNDING.yml
├── .gitignore
├── .swift-version
├── .swiftlint.yml
├── CHANGES.md
├── Design
├── screensaver
│ ├── amiantos-screenshots.pxm
│ ├── lifesaver-screenshots-1.1.png
│ ├── lifesaver-screenshots.png
│ └── screenshots.pxm
└── tvos
│ ├── tvos-ss-1.png
│ └── tvos-ss-2.png
├── Installer Resources
├── Life Saver.pkgproj
├── introduction.rtf
├── post-install.sh
└── signing.sh
├── LICENSE
├── Life Saver.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── xcshareddata
│ └── xcschemes
│ │ ├── Life Saver Screensaver.xcscheme
│ │ ├── Life Saver macOS Debug.xcscheme
│ │ ├── Life Saver tvOS Debug.xcscheme
│ │ └── Life Saver tvOS.xcscheme
└── xcuserdata
│ └── bradroot.xcuserdatad
│ └── xcschemes
│ └── xcschememanagement.plist
├── Life Saver.xcworkspace
├── contents.xcworkspacedata
└── xcshareddata
│ └── IDEWorkspaceChecks.plist
├── Podfile
├── Podfile.lock
├── Pods
├── Manifest.lock
├── Pods.xcodeproj
│ └── project.pbxproj
├── SwiftLint
│ ├── LICENSE
│ └── swiftlint
└── Target Support Files
│ ├── Pods-Life Saver Screensaver
│ ├── Pods-Life Saver Screensaver-Info.plist
│ ├── Pods-Life Saver Screensaver-acknowledgements.markdown
│ ├── Pods-Life Saver Screensaver-acknowledgements.plist
│ ├── Pods-Life Saver Screensaver-dummy.m
│ ├── Pods-Life Saver Screensaver-umbrella.h
│ ├── Pods-Life Saver Screensaver.debug.xcconfig
│ ├── Pods-Life Saver Screensaver.modulemap
│ └── Pods-Life Saver Screensaver.release.xcconfig
│ ├── Pods-Life Saver macOS
│ ├── Pods-Life Saver macOS-Info.plist
│ ├── Pods-Life Saver macOS-acknowledgements.markdown
│ ├── Pods-Life Saver macOS-acknowledgements.plist
│ ├── Pods-Life Saver macOS-dummy.m
│ ├── Pods-Life Saver macOS-umbrella.h
│ ├── Pods-Life Saver macOS.debug.xcconfig
│ ├── Pods-Life Saver macOS.modulemap
│ └── Pods-Life Saver macOS.release.xcconfig
│ ├── Pods-Life Saver tvOS
│ ├── Pods-Life Saver tvOS-Info.plist
│ ├── Pods-Life Saver tvOS-acknowledgements.markdown
│ ├── Pods-Life Saver tvOS-acknowledgements.plist
│ ├── Pods-Life Saver tvOS-dummy.m
│ ├── Pods-Life Saver tvOS-umbrella.h
│ ├── Pods-Life Saver tvOS.debug.xcconfig
│ ├── Pods-Life Saver tvOS.modulemap
│ └── Pods-Life Saver tvOS.release.xcconfig
│ ├── SwiftLint-macOS
│ └── SwiftLint-macOS.xcconfig
│ └── SwiftLint-tvOS
│ └── SwiftLint-tvOS.xcconfig
├── README.md
├── Shared
├── Assets.xcassets
│ ├── AppIcon.appiconset
│ │ └── Contents.json
│ ├── Contents.json
│ ├── github.imageset
│ │ ├── Contents.json
│ │ ├── github_dark-1.png
│ │ ├── github_dark.png
│ │ ├── github_light-1.png
│ │ └── github_light.png
│ ├── square.imageset
│ │ ├── Contents.json
│ │ └── aeonSquare.pdf
│ ├── thumbnail.imageset
│ │ ├── Contents.json
│ │ ├── thumbnail.png
│ │ └── thumbnail@2x.png
│ └── twitter.imageset
│ │ ├── Contents.json
│ │ ├── twitter_dark-1.png
│ │ ├── twitter_dark.png
│ │ ├── twitter_light-1.png
│ │ └── twitter_light.png
├── LifeDatabase.swift
├── LifeManager.swift
├── LifeNode.swift
├── LifePreset.swift
├── LifeScene.swift
└── Utilities
│ ├── Extensions.swift
│ ├── FileGrabber.swift
│ ├── ToroidalMatrix.swift
│ └── URLType.swift
├── macOS Screensaver
├── Configuration
│ ├── ConfigureSheet.xib
│ └── ConfigureSheetController.swift
├── Info.plist
├── LifeDatabase.swift
└── LifeScreenSaverView.swift
├── macOS
├── AppDelegate.swift
├── Base.lproj
│ └── Main.storyboard
├── Info.plist
├── Life_Saver.entitlements
└── ViewController.swift
└── tvOS
├── AppDelegate.swift
├── Assets.xcassets
├── Brand Assets.brandassets
│ ├── App Icon - App Store.imagestack
│ │ ├── Back.imagestacklayer
│ │ │ ├── Content.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── TVA - Bottom Layer.png
│ │ │ └── Contents.json
│ │ ├── Contents.json
│ │ ├── Front.imagestacklayer
│ │ │ ├── Content.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── TVA - Top Layer.png
│ │ │ └── Contents.json
│ │ └── Middle.imagestacklayer
│ │ │ ├── Content.imageset
│ │ │ ├── Contents.json
│ │ │ └── TVA - Middle Layer.png
│ │ │ └── Contents.json
│ ├── App Icon.imagestack
│ │ ├── Back.imagestacklayer
│ │ │ ├── Content.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ ├── TV - Bottom Layer.png
│ │ │ │ └── TV - Bottom Layer@2x.png
│ │ │ └── Contents.json
│ │ ├── Contents.json
│ │ ├── Front.imagestacklayer
│ │ │ ├── Content.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ ├── TV - Top Layer.png
│ │ │ │ └── TV - Top Layer@2x.png
│ │ │ └── Contents.json
│ │ └── Middle.imagestacklayer
│ │ │ ├── Content.imageset
│ │ │ ├── Contents.json
│ │ │ ├── TV - Middle Layer.png
│ │ │ └── TV - Middle Layer@2x.png
│ │ │ └── Contents.json
│ ├── Contents.json
│ ├── Top Shelf Image Wide.imageset
│ │ ├── Contents.json
│ │ ├── Top Shelf.png
│ │ └── Top Shelf@2x.png
│ └── Top Shelf Image.imageset
│ │ └── Contents.json
├── Contents.json
└── LaunchImage.launchimage
│ ├── Contents.json
│ ├── LaunchImage.png
│ └── LaunchImage@2x.png
├── Base.lproj
└── Main.storyboard
├── Info.plist
├── LifeViewController.swift
└── Menus
├── ColorPresetTableViewCell.swift
└── MenuTableViewController.swift
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [amiantos]
4 | patreon: amiantos
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | custom: # Replace with a single custom sponsorship URL
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## Build generated
6 | build/
7 | DerivedData/
8 | *.pkg
9 | *.saver
10 |
11 | ## Various settings
12 | *.pbxuser
13 | !default.pbxuser
14 | *.mode1v3
15 | !default.mode1v3
16 | *.mode2v3
17 | !default.mode2v3
18 | *.perspectivev3
19 | !default.perspectivev3
20 | xcuserdata/
21 |
22 | ## Other
23 | *.moved-aside
24 | *.xccheckout
25 | *.xcscmblueprint
26 |
27 | ## Obj-C/Swift specific
28 | *.hmap
29 | *.ipa
30 | *.dSYM.zip
31 | *.dSYM
32 |
33 | ## Playgrounds
34 | timeline.xctimeline
35 | playground.xcworkspace
36 |
37 | # Swift Package Manager
38 | #
39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
40 | # Packages/
41 | # Package.pins
42 | # Package.resolved
43 | .build/
44 |
45 | # CocoaPods
46 | #
47 | # We recommend against adding the Pods directory to your .gitignore. However
48 | # you should judge for yourself, the pros and cons are mentioned at:
49 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
50 | #
51 | # Pods/
52 |
53 | # Carthage
54 | #
55 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
56 | # Carthage/Checkouts
57 |
58 | Carthage/Build
59 |
60 | # fastlane
61 | #
62 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
63 | # screenshots whenever they are needed.
64 | # For more information about the recommended setup visit:
65 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
66 |
67 | fastlane/report.xml
68 | fastlane/Preview.html
69 | fastlane/screenshots/**/*.png
70 | fastlane/test_output
71 |
--------------------------------------------------------------------------------
/.swift-version:
--------------------------------------------------------------------------------
1 | 5.0
--------------------------------------------------------------------------------
/.swiftlint.yml:
--------------------------------------------------------------------------------
1 | disabled_rules:
2 | - trailing_comma
3 |
4 | excluded: # paths to ignore during linting. Takes precedence over `included`.
5 | - Carthage
6 | - Pods
7 |
8 | line_length:
9 | - 150 # warning
10 | - 200 # error
11 |
12 | type_body_length:
13 | - 250
14 |
15 | cyclomatic_complexity:
16 | ignores_case_statements: true
--------------------------------------------------------------------------------
/CHANGES.md:
--------------------------------------------------------------------------------
1 | # Screensaver
2 |
3 | ## 1.2
4 | - Added two smaller size options ((#18 by lordlycastle)[https://www.youtube.com/watch?v=A3pq9xL3kIs])
5 |
6 | ## 1.1
7 | - Config sheet detects if colors match existing preset and selects it in picker
8 | - Added option for selecting a random color preset on launch
9 | - Improvements to "extra" cell death animation
10 | - Many optimizations to reduce memory, CPU, and energy usage
11 | - Neighbor generation is much faster due to usage of ToroidalMatrix
12 | - Reduced preferred FPS to 30 fps
13 | - Uses shared SKTexture to reduce per-frame draw count to 1
14 |
15 | ## 1.0
16 | - Initial release
--------------------------------------------------------------------------------
/Design/screensaver/amiantos-screenshots.pxm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amiantos/lifesaver/158ac5378c0686480e57709d25d8488b79a26eeb/Design/screensaver/amiantos-screenshots.pxm
--------------------------------------------------------------------------------
/Design/screensaver/lifesaver-screenshots-1.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amiantos/lifesaver/158ac5378c0686480e57709d25d8488b79a26eeb/Design/screensaver/lifesaver-screenshots-1.1.png
--------------------------------------------------------------------------------
/Design/screensaver/lifesaver-screenshots.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amiantos/lifesaver/158ac5378c0686480e57709d25d8488b79a26eeb/Design/screensaver/lifesaver-screenshots.png
--------------------------------------------------------------------------------
/Design/screensaver/screenshots.pxm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amiantos/lifesaver/158ac5378c0686480e57709d25d8488b79a26eeb/Design/screensaver/screenshots.pxm
--------------------------------------------------------------------------------
/Design/tvos/tvos-ss-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amiantos/lifesaver/158ac5378c0686480e57709d25d8488b79a26eeb/Design/tvos/tvos-ss-1.png
--------------------------------------------------------------------------------
/Design/tvos/tvos-ss-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amiantos/lifesaver/158ac5378c0686480e57709d25d8488b79a26eeb/Design/tvos/tvos-ss-2.png
--------------------------------------------------------------------------------
/Installer Resources/Life Saver.pkgproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PACKAGES
6 |
7 |
8 | MUST-CLOSE-APPLICATION-ITEMS
9 |
10 |
11 | APPLICATION_ID
12 | x-apple.systempreferences
13 | STATE
14 |
15 |
16 |
17 | MUST-CLOSE-APPLICATIONS
18 |
19 | PACKAGE_FILES
20 |
21 | DEFAULT_INSTALL_LOCATION
22 | /
23 | HIERARCHY
24 |
25 | CHILDREN
26 |
27 |
28 | CHILDREN
29 |
30 | GID
31 | 80
32 | PATH
33 | Applications
34 | PATH_TYPE
35 | 0
36 | PERMISSIONS
37 | 509
38 | TYPE
39 | 1
40 | UID
41 | 0
42 |
43 |
44 | CHILDREN
45 |
46 |
47 | CHILDREN
48 |
49 | GID
50 | 80
51 | PATH
52 | Application Support
53 | PATH_TYPE
54 | 0
55 | PERMISSIONS
56 | 493
57 | TYPE
58 | 1
59 | UID
60 | 0
61 |
62 |
63 | CHILDREN
64 |
65 | GID
66 | 0
67 | PATH
68 | Automator
69 | PATH_TYPE
70 | 0
71 | PERMISSIONS
72 | 493
73 | TYPE
74 | 1
75 | UID
76 | 0
77 |
78 |
79 | CHILDREN
80 |
81 | GID
82 | 0
83 | PATH
84 | Documentation
85 | PATH_TYPE
86 | 0
87 | PERMISSIONS
88 | 493
89 | TYPE
90 | 1
91 | UID
92 | 0
93 |
94 |
95 | CHILDREN
96 |
97 | GID
98 | 0
99 | PATH
100 | Extensions
101 | PATH_TYPE
102 | 0
103 | PERMISSIONS
104 | 493
105 | TYPE
106 | 1
107 | UID
108 | 0
109 |
110 |
111 | CHILDREN
112 |
113 | GID
114 | 0
115 | PATH
116 | Filesystems
117 | PATH_TYPE
118 | 0
119 | PERMISSIONS
120 | 493
121 | TYPE
122 | 1
123 | UID
124 | 0
125 |
126 |
127 | CHILDREN
128 |
129 | GID
130 | 0
131 | PATH
132 | Frameworks
133 | PATH_TYPE
134 | 0
135 | PERMISSIONS
136 | 493
137 | TYPE
138 | 1
139 | UID
140 | 0
141 |
142 |
143 | CHILDREN
144 |
145 | GID
146 | 0
147 | PATH
148 | Input Methods
149 | PATH_TYPE
150 | 0
151 | PERMISSIONS
152 | 493
153 | TYPE
154 | 1
155 | UID
156 | 0
157 |
158 |
159 | CHILDREN
160 |
161 | GID
162 | 0
163 | PATH
164 | Internet Plug-Ins
165 | PATH_TYPE
166 | 0
167 | PERMISSIONS
168 | 493
169 | TYPE
170 | 1
171 | UID
172 | 0
173 |
174 |
175 | CHILDREN
176 |
177 | GID
178 | 0
179 | PATH
180 | LaunchAgents
181 | PATH_TYPE
182 | 0
183 | PERMISSIONS
184 | 493
185 | TYPE
186 | 1
187 | UID
188 | 0
189 |
190 |
191 | CHILDREN
192 |
193 | GID
194 | 0
195 | PATH
196 | LaunchDaemons
197 | PATH_TYPE
198 | 0
199 | PERMISSIONS
200 | 493
201 | TYPE
202 | 1
203 | UID
204 | 0
205 |
206 |
207 | CHILDREN
208 |
209 | GID
210 | 0
211 | PATH
212 | PreferencePanes
213 | PATH_TYPE
214 | 0
215 | PERMISSIONS
216 | 493
217 | TYPE
218 | 1
219 | UID
220 | 0
221 |
222 |
223 | CHILDREN
224 |
225 | GID
226 | 0
227 | PATH
228 | Preferences
229 | PATH_TYPE
230 | 0
231 | PERMISSIONS
232 | 493
233 | TYPE
234 | 1
235 | UID
236 | 0
237 |
238 |
239 | CHILDREN
240 |
241 | GID
242 | 80
243 | PATH
244 | Printers
245 | PATH_TYPE
246 | 0
247 | PERMISSIONS
248 | 493
249 | TYPE
250 | 1
251 | UID
252 | 0
253 |
254 |
255 | CHILDREN
256 |
257 | GID
258 | 0
259 | PATH
260 | PrivilegedHelperTools
261 | PATH_TYPE
262 | 0
263 | PERMISSIONS
264 | 1005
265 | TYPE
266 | 1
267 | UID
268 | 0
269 |
270 |
271 | CHILDREN
272 |
273 | GID
274 | 0
275 | PATH
276 | QuickLook
277 | PATH_TYPE
278 | 0
279 | PERMISSIONS
280 | 493
281 | TYPE
282 | 1
283 | UID
284 | 0
285 |
286 |
287 | CHILDREN
288 |
289 | GID
290 | 0
291 | PATH
292 | QuickTime
293 | PATH_TYPE
294 | 0
295 | PERMISSIONS
296 | 493
297 | TYPE
298 | 1
299 | UID
300 | 0
301 |
302 |
303 | CHILDREN
304 |
305 |
306 | BUNDLE_CAN_DOWNGRADE
307 |
308 | BUNDLE_POSTINSTALL_PATH
309 |
310 | PATH_TYPE
311 | 0
312 |
313 | BUNDLE_PREINSTALL_PATH
314 |
315 | PATH_TYPE
316 | 0
317 |
318 | CHILDREN
319 |
320 | GID
321 | 0
322 | PATH
323 | Life Saver.saver
324 | PATH_TYPE
325 | 1
326 | PERMISSIONS
327 | 493
328 | TYPE
329 | 3
330 | UID
331 | 0
332 |
333 |
334 | GID
335 | 0
336 | PATH
337 | Screen Savers
338 | PATH_TYPE
339 | 0
340 | PERMISSIONS
341 | 493
342 | TYPE
343 | 1
344 | UID
345 | 0
346 |
347 |
348 | CHILDREN
349 |
350 | GID
351 | 0
352 | PATH
353 | Scripts
354 | PATH_TYPE
355 | 0
356 | PERMISSIONS
357 | 493
358 | TYPE
359 | 1
360 | UID
361 | 0
362 |
363 |
364 | CHILDREN
365 |
366 | GID
367 | 0
368 | PATH
369 | Services
370 | PATH_TYPE
371 | 0
372 | PERMISSIONS
373 | 493
374 | TYPE
375 | 1
376 | UID
377 | 0
378 |
379 |
380 | CHILDREN
381 |
382 | GID
383 | 0
384 | PATH
385 | Widgets
386 | PATH_TYPE
387 | 0
388 | PERMISSIONS
389 | 493
390 | TYPE
391 | 1
392 | UID
393 | 0
394 |
395 |
396 | GID
397 | 0
398 | PATH
399 | Library
400 | PATH_TYPE
401 | 0
402 | PERMISSIONS
403 | 493
404 | TYPE
405 | 1
406 | UID
407 | 0
408 |
409 |
410 | CHILDREN
411 |
412 |
413 | CHILDREN
414 |
415 | GID
416 | 0
417 | PATH
418 | Shared
419 | PATH_TYPE
420 | 0
421 | PERMISSIONS
422 | 1023
423 | TYPE
424 | 1
425 | UID
426 | 0
427 |
428 |
429 | GID
430 | 80
431 | PATH
432 | Users
433 | PATH_TYPE
434 | 0
435 | PERMISSIONS
436 | 493
437 | TYPE
438 | 1
439 | UID
440 | 0
441 |
442 |
443 | GID
444 | 0
445 | PATH
446 | /
447 | PATH_TYPE
448 | 0
449 | PERMISSIONS
450 | 493
451 | TYPE
452 | 1
453 | UID
454 | 0
455 |
456 | PAYLOAD_TYPE
457 | 0
458 | SHOW_INVISIBLE
459 |
460 | SPLIT_FORKS
461 |
462 | TREAT_MISSING_FILES_AS_WARNING
463 |
464 | VERSION
465 | 5
466 |
467 | PACKAGE_SCRIPTS
468 |
469 | POSTINSTALL_PATH
470 |
471 | PATH
472 | /Users/bradroot/Coding/Life Saver/Installer Resources/post-install.sh
473 | PATH_TYPE
474 | 0
475 |
476 | PREINSTALL_PATH
477 |
478 | PATH_TYPE
479 | 0
480 |
481 | RESOURCES
482 |
483 |
484 | PACKAGE_SETTINGS
485 |
486 | AUTHENTICATION
487 | 1
488 | CONCLUSION_ACTION
489 | 0
490 | FOLLOW_SYMBOLIC_LINKS
491 |
492 | IDENTIFIER
493 | net.amiantos.pkg.LifeSaver11
494 | LOCATION
495 | 0
496 | NAME
497 | Life Saver 1.1
498 | OVERWRITE_PERMISSIONS
499 |
500 | PAYLOAD_SIZE
501 | -1
502 | REFERENCE_PATH
503 |
504 | RELOCATABLE
505 |
506 | USE_HFS+_COMPRESSION
507 |
508 | VERSION
509 | 1.1
510 |
511 | TYPE
512 | 0
513 | UUID
514 | A2078BF3-F182-4A46-8691-00485201F940
515 |
516 |
517 | PROJECT
518 |
519 | PROJECT_COMMENTS
520 |
521 | NOTES
522 |
523 | PCFET0NUWVBFIGh0bWwgUFVCTElDICItLy9XM0MvL0RURCBIVE1M
524 | IDQuMDEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvVFIvaHRtbDQv
525 | c3RyaWN0LmR0ZCI+CjxodG1sPgo8aGVhZD4KPG1ldGEgaHR0cC1l
526 | cXVpdj0iQ29udGVudC1UeXBlIiBjb250ZW50PSJ0ZXh0L2h0bWw7
527 | IGNoYXJzZXQ9VVRGLTgiPgo8bWV0YSBodHRwLWVxdWl2PSJDb250
528 | ZW50LVN0eWxlLVR5cGUiIGNvbnRlbnQ9InRleHQvY3NzIj4KPHRp
529 | dGxlPjwvdGl0bGU+CjxtZXRhIG5hbWU9IkdlbmVyYXRvciIgY29u
530 | dGVudD0iQ29jb2EgSFRNTCBXcml0ZXIiPgo8bWV0YSBuYW1lPSJD
531 | b2NvYVZlcnNpb24iIGNvbnRlbnQ9IjE2NzEuNSI+CjxzdHlsZSB0
532 | eXBlPSJ0ZXh0L2NzcyI+Cjwvc3R5bGU+CjwvaGVhZD4KPGJvZHk+
533 | CjwvYm9keT4KPC9odG1sPgo=
534 |
535 |
536 | PROJECT_PRESENTATION
537 |
538 | BACKGROUND
539 |
540 | APPAREANCES
541 |
542 | DARK_AQUA
543 |
544 | LIGHT_AQUA
545 |
546 |
547 | SHARED_SETTINGS_FOR_ALL_APPAREANCES
548 |
549 |
550 | INSTALLATION_STEPS
551 |
552 |
553 | ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS
554 | ICPresentationViewIntroductionController
555 | INSTALLER_PLUGIN
556 | Introduction
557 | LIST_TITLE_KEY
558 | InstallerSectionTitle
559 |
560 |
561 | ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS
562 | ICPresentationViewReadMeController
563 | INSTALLER_PLUGIN
564 | ReadMe
565 | LIST_TITLE_KEY
566 | InstallerSectionTitle
567 |
568 |
569 | ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS
570 | ICPresentationViewLicenseController
571 | INSTALLER_PLUGIN
572 | License
573 | LIST_TITLE_KEY
574 | InstallerSectionTitle
575 |
576 |
577 | ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS
578 | ICPresentationViewDestinationSelectController
579 | INSTALLER_PLUGIN
580 | TargetSelect
581 | LIST_TITLE_KEY
582 | InstallerSectionTitle
583 |
584 |
585 | ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS
586 | ICPresentationViewInstallationTypeController
587 | INSTALLER_PLUGIN
588 | PackageSelection
589 | LIST_TITLE_KEY
590 | InstallerSectionTitle
591 |
592 |
593 | ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS
594 | ICPresentationViewInstallationController
595 | INSTALLER_PLUGIN
596 | Install
597 | LIST_TITLE_KEY
598 | InstallerSectionTitle
599 |
600 |
601 | ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS
602 | ICPresentationViewSummaryController
603 | INSTALLER_PLUGIN
604 | Summary
605 | LIST_TITLE_KEY
606 | InstallerSectionTitle
607 |
608 |
609 | INTRODUCTION
610 |
611 | LOCALIZATIONS
612 |
613 |
614 | LANGUAGE
615 | English
616 | VALUE
617 |
618 | PATH
619 | /Users/bradroot/Coding/Life Saver/Installer Resources/introduction.rtf
620 | PATH_TYPE
621 | 1
622 |
623 |
624 |
625 |
626 | LICENSE
627 |
628 | LOCALIZATIONS
629 |
630 | MODE
631 | 0
632 |
633 | README
634 |
635 | LOCALIZATIONS
636 |
637 |
638 | SUMMARY
639 |
640 | LOCALIZATIONS
641 |
642 |
643 | TITLE
644 |
645 | LOCALIZATIONS
646 |
647 |
648 | LANGUAGE
649 | English
650 | VALUE
651 | Life Saver 1.1
652 |
653 |
654 |
655 |
656 | PROJECT_REQUIREMENTS
657 |
658 | LIST
659 |
660 | RESOURCES
661 |
662 | ROOT_VOLUME_ONLY
663 |
664 |
665 | PROJECT_SETTINGS
666 |
667 | BUILD_FORMAT
668 | 0
669 | BUILD_PATH
670 |
671 | PATH
672 | .
673 | PATH_TYPE
674 | 1
675 |
676 | EXCLUDED_FILES
677 |
678 |
679 | PATTERNS_ARRAY
680 |
681 |
682 | REGULAR_EXPRESSION
683 |
684 | STRING
685 | .DS_Store
686 | TYPE
687 | 0
688 |
689 |
690 | PROTECTED
691 |
692 | PROXY_NAME
693 | Remove .DS_Store files
694 | PROXY_TOOLTIP
695 | Remove ".DS_Store" files created by the Finder.
696 | STATE
697 |
698 |
699 |
700 | PATTERNS_ARRAY
701 |
702 |
703 | REGULAR_EXPRESSION
704 |
705 | STRING
706 | .pbdevelopment
707 | TYPE
708 | 0
709 |
710 |
711 | PROTECTED
712 |
713 | PROXY_NAME
714 | Remove .pbdevelopment files
715 | PROXY_TOOLTIP
716 | Remove ".pbdevelopment" files created by ProjectBuilder or Xcode.
717 | STATE
718 |
719 |
720 |
721 | PATTERNS_ARRAY
722 |
723 |
724 | REGULAR_EXPRESSION
725 |
726 | STRING
727 | CVS
728 | TYPE
729 | 1
730 |
731 |
732 | REGULAR_EXPRESSION
733 |
734 | STRING
735 | .cvsignore
736 | TYPE
737 | 0
738 |
739 |
740 | REGULAR_EXPRESSION
741 |
742 | STRING
743 | .cvspass
744 | TYPE
745 | 0
746 |
747 |
748 | REGULAR_EXPRESSION
749 |
750 | STRING
751 | .svn
752 | TYPE
753 | 1
754 |
755 |
756 | REGULAR_EXPRESSION
757 |
758 | STRING
759 | .git
760 | TYPE
761 | 1
762 |
763 |
764 | REGULAR_EXPRESSION
765 |
766 | STRING
767 | .gitignore
768 | TYPE
769 | 0
770 |
771 |
772 | PROTECTED
773 |
774 | PROXY_NAME
775 | Remove SCM metadata
776 | PROXY_TOOLTIP
777 | Remove helper files and folders used by the CVS, SVN or Git Source Code Management systems.
778 | STATE
779 |
780 |
781 |
782 | PATTERNS_ARRAY
783 |
784 |
785 | REGULAR_EXPRESSION
786 |
787 | STRING
788 | classes.nib
789 | TYPE
790 | 0
791 |
792 |
793 | REGULAR_EXPRESSION
794 |
795 | STRING
796 | designable.db
797 | TYPE
798 | 0
799 |
800 |
801 | REGULAR_EXPRESSION
802 |
803 | STRING
804 | info.nib
805 | TYPE
806 | 0
807 |
808 |
809 | PROTECTED
810 |
811 | PROXY_NAME
812 | Optimize nib files
813 | PROXY_TOOLTIP
814 | Remove "classes.nib", "info.nib" and "designable.nib" files within .nib bundles.
815 | STATE
816 |
817 |
818 |
819 | PATTERNS_ARRAY
820 |
821 |
822 | REGULAR_EXPRESSION
823 |
824 | STRING
825 | Resources Disabled
826 | TYPE
827 | 1
828 |
829 |
830 | PROTECTED
831 |
832 | PROXY_NAME
833 | Remove Resources Disabled folders
834 | PROXY_TOOLTIP
835 | Remove "Resources Disabled" folders.
836 | STATE
837 |
838 |
839 |
840 | SEPARATOR
841 |
842 |
843 |
844 | NAME
845 | lifesaver-1.1-unsigned
846 | PAYLOAD_ONLY
847 |
848 | TREAT_MISSING_PRESENTATION_DOCUMENTS_AS_WARNING
849 |
850 |
851 |
852 | TYPE
853 | 0
854 | VERSION
855 | 2
856 |
857 |
858 |
--------------------------------------------------------------------------------
/Installer Resources/introduction.rtf:
--------------------------------------------------------------------------------
1 | {\rtf1\ansi\ansicpg1252\cocoartf1671\cocoasubrtf500
2 | {\fonttbl\f0\fswiss\fcharset0 Helvetica-Bold;\f1\fswiss\fcharset0 Helvetica;}
3 | {\colortbl;\red255\green255\blue255;}
4 | {\*\expandedcolortbl;;}
5 | \margl1440\margr1440\vieww12600\viewh7800\viewkind0
6 | \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\partightenfactor0
7 |
8 | \f0\b\fs36 \cf0 Life Saver 1.1\
9 |
10 | \fs28 A Conway's Game of Life Screensaver
11 | \fs36 \
12 | \
13 |
14 | \f1\b0\fs26 Thanks for downloading Life Saver!\
15 | \
16 | The installer will install the screensaver into the \ul /Library/Screen Savers\ulnone folder on your computer.
17 | \fs24 \
18 | \
19 |
20 | \f0\b\fs26 After installation completes, your screen saver configuration panel will open. Select "Life Saver" and you're good to go.\
21 |
22 | \f1\b0\fs24 \
23 | {\field{\*\fldinst{HYPERLINK "https://www.amiantos.net/lifesaver"}}{\fldrslt
24 | \fs28 https://www.amiantos.net/lifesaver}}}
--------------------------------------------------------------------------------
/Installer Resources/post-install.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | open /System/Library/PreferencePanes/DesktopScreenEffectsPref.prefPane
--------------------------------------------------------------------------------
/Installer Resources/signing.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | /usr/bin/productsign --sign "Developer ID Installer: Brad Root (2Y9M69QJKZ)" lifesaver-1.1-unsigned.pkg lifesaver-1.1.pkg
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Mozilla Public License Version 2.0
2 | ==================================
3 |
4 | 1. Definitions
5 | --------------
6 |
7 | 1.1. "Contributor"
8 | means each individual or legal entity that creates, contributes to
9 | the creation of, or owns Covered Software.
10 |
11 | 1.2. "Contributor Version"
12 | means the combination of the Contributions of others (if any) used
13 | by a Contributor and that particular Contributor's Contribution.
14 |
15 | 1.3. "Contribution"
16 | means Covered Software of a particular Contributor.
17 |
18 | 1.4. "Covered Software"
19 | means Source Code Form to which the initial Contributor has attached
20 | the notice in Exhibit A, the Executable Form of such Source Code
21 | Form, and Modifications of such Source Code Form, in each case
22 | including portions thereof.
23 |
24 | 1.5. "Incompatible With Secondary Licenses"
25 | means
26 |
27 | (a) that the initial Contributor has attached the notice described
28 | in Exhibit B to the Covered Software; or
29 |
30 | (b) that the Covered Software was made available under the terms of
31 | version 1.1 or earlier of the License, but not also under the
32 | terms of a Secondary License.
33 |
34 | 1.6. "Executable Form"
35 | means any form of the work other than Source Code Form.
36 |
37 | 1.7. "Larger Work"
38 | means a work that combines Covered Software with other material, in
39 | a separate file or files, that is not Covered Software.
40 |
41 | 1.8. "License"
42 | means this document.
43 |
44 | 1.9. "Licensable"
45 | means having the right to grant, to the maximum extent possible,
46 | whether at the time of the initial grant or subsequently, any and
47 | all of the rights conveyed by this License.
48 |
49 | 1.10. "Modifications"
50 | means any of the following:
51 |
52 | (a) any file in Source Code Form that results from an addition to,
53 | deletion from, or modification of the contents of Covered
54 | Software; or
55 |
56 | (b) any new file in Source Code Form that contains any Covered
57 | Software.
58 |
59 | 1.11. "Patent Claims" of a Contributor
60 | means any patent claim(s), including without limitation, method,
61 | process, and apparatus claims, in any patent Licensable by such
62 | Contributor that would be infringed, but for the grant of the
63 | License, by the making, using, selling, offering for sale, having
64 | made, import, or transfer of either its Contributions or its
65 | Contributor Version.
66 |
67 | 1.12. "Secondary License"
68 | means either the GNU General Public License, Version 2.0, the GNU
69 | Lesser General Public License, Version 2.1, the GNU Affero General
70 | Public License, Version 3.0, or any later versions of those
71 | licenses.
72 |
73 | 1.13. "Source Code Form"
74 | means the form of the work preferred for making modifications.
75 |
76 | 1.14. "You" (or "Your")
77 | means an individual or a legal entity exercising rights under this
78 | License. For legal entities, "You" includes any entity that
79 | controls, is controlled by, or is under common control with You. For
80 | purposes of this definition, "control" means (a) the power, direct
81 | or indirect, to cause the direction or management of such entity,
82 | whether by contract or otherwise, or (b) ownership of more than
83 | fifty percent (50%) of the outstanding shares or beneficial
84 | ownership of such entity.
85 |
86 | 2. License Grants and Conditions
87 | --------------------------------
88 |
89 | 2.1. Grants
90 |
91 | Each Contributor hereby grants You a world-wide, royalty-free,
92 | non-exclusive license:
93 |
94 | (a) under intellectual property rights (other than patent or trademark)
95 | Licensable by such Contributor to use, reproduce, make available,
96 | modify, display, perform, distribute, and otherwise exploit its
97 | Contributions, either on an unmodified basis, with Modifications, or
98 | as part of a Larger Work; and
99 |
100 | (b) under Patent Claims of such Contributor to make, use, sell, offer
101 | for sale, have made, import, and otherwise transfer either its
102 | Contributions or its Contributor Version.
103 |
104 | 2.2. Effective Date
105 |
106 | The licenses granted in Section 2.1 with respect to any Contribution
107 | become effective for each Contribution on the date the Contributor first
108 | distributes such Contribution.
109 |
110 | 2.3. Limitations on Grant Scope
111 |
112 | The licenses granted in this Section 2 are the only rights granted under
113 | this License. No additional rights or licenses will be implied from the
114 | distribution or licensing of Covered Software under this License.
115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a
116 | Contributor:
117 |
118 | (a) for any code that a Contributor has removed from Covered Software;
119 | or
120 |
121 | (b) for infringements caused by: (i) Your and any other third party's
122 | modifications of Covered Software, or (ii) the combination of its
123 | Contributions with other software (except as part of its Contributor
124 | Version); or
125 |
126 | (c) under Patent Claims infringed by Covered Software in the absence of
127 | its Contributions.
128 |
129 | This License does not grant any rights in the trademarks, service marks,
130 | or logos of any Contributor (except as may be necessary to comply with
131 | the notice requirements in Section 3.4).
132 |
133 | 2.4. Subsequent Licenses
134 |
135 | No Contributor makes additional grants as a result of Your choice to
136 | distribute the Covered Software under a subsequent version of this
137 | License (see Section 10.2) or under the terms of a Secondary License (if
138 | permitted under the terms of Section 3.3).
139 |
140 | 2.5. Representation
141 |
142 | Each Contributor represents that the Contributor believes its
143 | Contributions are its original creation(s) or it has sufficient rights
144 | to grant the rights to its Contributions conveyed by this License.
145 |
146 | 2.6. Fair Use
147 |
148 | This License is not intended to limit any rights You have under
149 | applicable copyright doctrines of fair use, fair dealing, or other
150 | equivalents.
151 |
152 | 2.7. Conditions
153 |
154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
155 | in Section 2.1.
156 |
157 | 3. Responsibilities
158 | -------------------
159 |
160 | 3.1. Distribution of Source Form
161 |
162 | All distribution of Covered Software in Source Code Form, including any
163 | Modifications that You create or to which You contribute, must be under
164 | the terms of this License. You must inform recipients that the Source
165 | Code Form of the Covered Software is governed by the terms of this
166 | License, and how they can obtain a copy of this License. You may not
167 | attempt to alter or restrict the recipients' rights in the Source Code
168 | Form.
169 |
170 | 3.2. Distribution of Executable Form
171 |
172 | If You distribute Covered Software in Executable Form then:
173 |
174 | (a) such Covered Software must also be made available in Source Code
175 | Form, as described in Section 3.1, and You must inform recipients of
176 | the Executable Form how they can obtain a copy of such Source Code
177 | Form by reasonable means in a timely manner, at a charge no more
178 | than the cost of distribution to the recipient; and
179 |
180 | (b) You may distribute such Executable Form under the terms of this
181 | License, or sublicense it under different terms, provided that the
182 | license for the Executable Form does not attempt to limit or alter
183 | the recipients' rights in the Source Code Form under this License.
184 |
185 | 3.3. Distribution of a Larger Work
186 |
187 | You may create and distribute a Larger Work under terms of Your choice,
188 | provided that You also comply with the requirements of this License for
189 | the Covered Software. If the Larger Work is a combination of Covered
190 | Software with a work governed by one or more Secondary Licenses, and the
191 | Covered Software is not Incompatible With Secondary Licenses, this
192 | License permits You to additionally distribute such Covered Software
193 | under the terms of such Secondary License(s), so that the recipient of
194 | the Larger Work may, at their option, further distribute the Covered
195 | Software under the terms of either this License or such Secondary
196 | License(s).
197 |
198 | 3.4. Notices
199 |
200 | You may not remove or alter the substance of any license notices
201 | (including copyright notices, patent notices, disclaimers of warranty,
202 | or limitations of liability) contained within the Source Code Form of
203 | the Covered Software, except that You may alter any license notices to
204 | the extent required to remedy known factual inaccuracies.
205 |
206 | 3.5. Application of Additional Terms
207 |
208 | You may choose to offer, and to charge a fee for, warranty, support,
209 | indemnity or liability obligations to one or more recipients of Covered
210 | Software. However, You may do so only on Your own behalf, and not on
211 | behalf of any Contributor. You must make it absolutely clear that any
212 | such warranty, support, indemnity, or liability obligation is offered by
213 | You alone, and You hereby agree to indemnify every Contributor for any
214 | liability incurred by such Contributor as a result of warranty, support,
215 | indemnity or liability terms You offer. You may include additional
216 | disclaimers of warranty and limitations of liability specific to any
217 | jurisdiction.
218 |
219 | 4. Inability to Comply Due to Statute or Regulation
220 | ---------------------------------------------------
221 |
222 | If it is impossible for You to comply with any of the terms of this
223 | License with respect to some or all of the Covered Software due to
224 | statute, judicial order, or regulation then You must: (a) comply with
225 | the terms of this License to the maximum extent possible; and (b)
226 | describe the limitations and the code they affect. Such description must
227 | be placed in a text file included with all distributions of the Covered
228 | Software under this License. Except to the extent prohibited by statute
229 | or regulation, such description must be sufficiently detailed for a
230 | recipient of ordinary skill to be able to understand it.
231 |
232 | 5. Termination
233 | --------------
234 |
235 | 5.1. The rights granted under this License will terminate automatically
236 | if You fail to comply with any of its terms. However, if You become
237 | compliant, then the rights granted under this License from a particular
238 | Contributor are reinstated (a) provisionally, unless and until such
239 | Contributor explicitly and finally terminates Your grants, and (b) on an
240 | ongoing basis, if such Contributor fails to notify You of the
241 | non-compliance by some reasonable means prior to 60 days after You have
242 | come back into compliance. Moreover, Your grants from a particular
243 | Contributor are reinstated on an ongoing basis if such Contributor
244 | notifies You of the non-compliance by some reasonable means, this is the
245 | first time You have received notice of non-compliance with this License
246 | from such Contributor, and You become compliant prior to 30 days after
247 | Your receipt of the notice.
248 |
249 | 5.2. If You initiate litigation against any entity by asserting a patent
250 | infringement claim (excluding declaratory judgment actions,
251 | counter-claims, and cross-claims) alleging that a Contributor Version
252 | directly or indirectly infringes any patent, then the rights granted to
253 | You by any and all Contributors for the Covered Software under Section
254 | 2.1 of this License shall terminate.
255 |
256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all
257 | end user license agreements (excluding distributors and resellers) which
258 | have been validly granted by You or Your distributors under this License
259 | prior to termination shall survive termination.
260 |
261 | ************************************************************************
262 | * *
263 | * 6. Disclaimer of Warranty *
264 | * ------------------------- *
265 | * *
266 | * Covered Software is provided under this License on an "as is" *
267 | * basis, without warranty of any kind, either expressed, implied, or *
268 | * statutory, including, without limitation, warranties that the *
269 | * Covered Software is free of defects, merchantable, fit for a *
270 | * particular purpose or non-infringing. The entire risk as to the *
271 | * quality and performance of the Covered Software is with You. *
272 | * Should any Covered Software prove defective in any respect, You *
273 | * (not any Contributor) assume the cost of any necessary servicing, *
274 | * repair, or correction. This disclaimer of warranty constitutes an *
275 | * essential part of this License. No use of any Covered Software is *
276 | * authorized under this License except under this disclaimer. *
277 | * *
278 | ************************************************************************
279 |
280 | ************************************************************************
281 | * *
282 | * 7. Limitation of Liability *
283 | * -------------------------- *
284 | * *
285 | * Under no circumstances and under no legal theory, whether tort *
286 | * (including negligence), contract, or otherwise, shall any *
287 | * Contributor, or anyone who distributes Covered Software as *
288 | * permitted above, be liable to You for any direct, indirect, *
289 | * special, incidental, or consequential damages of any character *
290 | * including, without limitation, damages for lost profits, loss of *
291 | * goodwill, work stoppage, computer failure or malfunction, or any *
292 | * and all other commercial damages or losses, even if such party *
293 | * shall have been informed of the possibility of such damages. This *
294 | * limitation of liability shall not apply to liability for death or *
295 | * personal injury resulting from such party's negligence to the *
296 | * extent applicable law prohibits such limitation. Some *
297 | * jurisdictions do not allow the exclusion or limitation of *
298 | * incidental or consequential damages, so this exclusion and *
299 | * limitation may not apply to You. *
300 | * *
301 | ************************************************************************
302 |
303 | 8. Litigation
304 | -------------
305 |
306 | Any litigation relating to this License may be brought only in the
307 | courts of a jurisdiction where the defendant maintains its principal
308 | place of business and such litigation shall be governed by laws of that
309 | jurisdiction, without reference to its conflict-of-law provisions.
310 | Nothing in this Section shall prevent a party's ability to bring
311 | cross-claims or counter-claims.
312 |
313 | 9. Miscellaneous
314 | ----------------
315 |
316 | This License represents the complete agreement concerning the subject
317 | matter hereof. If any provision of this License is held to be
318 | unenforceable, such provision shall be reformed only to the extent
319 | necessary to make it enforceable. Any law or regulation which provides
320 | that the language of a contract shall be construed against the drafter
321 | shall not be used to construe this License against a Contributor.
322 |
323 | 10. Versions of the License
324 | ---------------------------
325 |
326 | 10.1. New Versions
327 |
328 | Mozilla Foundation is the license steward. Except as provided in Section
329 | 10.3, no one other than the license steward has the right to modify or
330 | publish new versions of this License. Each version will be given a
331 | distinguishing version number.
332 |
333 | 10.2. Effect of New Versions
334 |
335 | You may distribute the Covered Software under the terms of the version
336 | of the License under which You originally received the Covered Software,
337 | or under the terms of any subsequent version published by the license
338 | steward.
339 |
340 | 10.3. Modified Versions
341 |
342 | If you create software not governed by this License, and you want to
343 | create a new license for such software, you may create and use a
344 | modified version of this License if you rename the license and remove
345 | any references to the name of the license steward (except to note that
346 | such modified license differs from this License).
347 |
348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary
349 | Licenses
350 |
351 | If You choose to distribute Source Code Form that is Incompatible With
352 | Secondary Licenses under the terms of this version of the License, the
353 | notice described in Exhibit B of this License must be attached.
354 |
355 | Exhibit A - Source Code Form License Notice
356 | -------------------------------------------
357 |
358 | This Source Code Form is subject to the terms of the Mozilla Public
359 | License, v. 2.0. If a copy of the MPL was not distributed with this
360 | file, You can obtain one at http://mozilla.org/MPL/2.0/.
361 |
362 | If it is not possible or desirable to put the notice in a particular
363 | file, then You may include the notice in a location (such as a LICENSE
364 | file in a relevant directory) where a recipient would be likely to look
365 | for such a notice.
366 |
367 | You may add additional accurate notices of copyright ownership.
368 |
369 | Exhibit B - "Incompatible With Secondary Licenses" Notice
370 | ---------------------------------------------------------
371 |
372 | This Source Code Form is "Incompatible With Secondary Licenses", as
373 | defined by the Mozilla Public License, v. 2.0.
374 |
--------------------------------------------------------------------------------
/Life Saver.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Life Saver.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Life Saver.xcodeproj/xcshareddata/xcschemes/Life Saver Screensaver.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
43 |
44 |
50 |
51 |
52 |
53 |
59 |
60 |
66 |
67 |
68 |
69 |
71 |
72 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/Life Saver.xcodeproj/xcshareddata/xcschemes/Life Saver macOS Debug.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
41 |
42 |
52 |
54 |
60 |
61 |
62 |
63 |
69 |
71 |
77 |
78 |
79 |
80 |
82 |
83 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/Life Saver.xcodeproj/xcshareddata/xcschemes/Life Saver tvOS Debug.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
41 |
42 |
52 |
54 |
60 |
61 |
62 |
63 |
69 |
71 |
77 |
78 |
79 |
80 |
82 |
83 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/Life Saver.xcodeproj/xcshareddata/xcschemes/Life Saver tvOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
41 |
42 |
52 |
54 |
60 |
61 |
62 |
63 |
69 |
71 |
77 |
78 |
79 |
80 |
82 |
83 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/Life Saver.xcodeproj/xcuserdata/bradroot.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | Life Saver Screensaver.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 0
11 |
12 | Life Saver macOS Debug.xcscheme_^#shared#^_
13 |
14 | orderHint
15 | 1
16 |
17 | Life Saver tvOS Debug.xcscheme_^#shared#^_
18 |
19 | orderHint
20 | 7
21 |
22 | Life Saver tvOS.xcscheme_^#shared#^_
23 |
24 | orderHint
25 | 6
26 |
27 |
28 | SuppressBuildableAutocreation
29 |
30 | B492215E2291F59C00D5DEA4
31 |
32 | primary
33 |
34 |
35 | B4FD88C32290B9DB00AE066A
36 |
37 | primary
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/Life Saver.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Life Saver.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment the next line to define a global platform for your project
2 | # platform :ios, '9.0'
3 |
4 | target 'Life Saver macOS' do
5 | # Comment the next line if you don't want to use dynamic frameworks
6 | use_frameworks!
7 |
8 | # Pods for Life Saver macOS
9 | pod 'SwiftLint'
10 |
11 | end
12 |
13 | target 'Life Saver Screensaver' do
14 | # Comment the next line if you don't want to use dynamic frameworks
15 | use_frameworks!
16 |
17 | # Pods for Life Saver Screensaver
18 |
19 | end
20 |
21 | target 'Life Saver tvOS' do
22 | # Comment the next line if you don't want to use dynamic frameworks
23 | use_frameworks!
24 |
25 | # Pods for Life Saver tvOS
26 | pod 'SwiftLint'
27 | end
28 |
--------------------------------------------------------------------------------
/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - SwiftLint (0.33.0)
3 |
4 | DEPENDENCIES:
5 | - SwiftLint
6 |
7 | SPEC REPOS:
8 | https://github.com/cocoapods/specs.git:
9 | - SwiftLint
10 |
11 | SPEC CHECKSUMS:
12 | SwiftLint: fed9c66336e41fc74dc48a73678380718f0c8b0e
13 |
14 | PODFILE CHECKSUM: 4e098b7aa804dffdefce9b1afb1a86d385d687ae
15 |
16 | COCOAPODS: 1.7.2
17 |
--------------------------------------------------------------------------------
/Pods/Manifest.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - SwiftLint (0.33.0)
3 |
4 | DEPENDENCIES:
5 | - SwiftLint
6 |
7 | SPEC REPOS:
8 | https://github.com/cocoapods/specs.git:
9 | - SwiftLint
10 |
11 | SPEC CHECKSUMS:
12 | SwiftLint: fed9c66336e41fc74dc48a73678380718f0c8b0e
13 |
14 | PODFILE CHECKSUM: 4e098b7aa804dffdefce9b1afb1a86d385d687ae
15 |
16 | COCOAPODS: 1.7.2
17 |
--------------------------------------------------------------------------------
/Pods/SwiftLint/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Realm Inc.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Pods/SwiftLint/swiftlint:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amiantos/lifesaver/158ac5378c0686480e57709d25d8488b79a26eeb/Pods/SwiftLint/swiftlint
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-Life Saver Screensaver/Pods-Life Saver Screensaver-Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | ${EXECUTABLE_NAME}
9 | CFBundleIdentifier
10 | ${PRODUCT_BUNDLE_IDENTIFIER}
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | ${PRODUCT_NAME}
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | ${CURRENT_PROJECT_VERSION}
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-Life Saver Screensaver/Pods-Life Saver Screensaver-acknowledgements.markdown:
--------------------------------------------------------------------------------
1 | # Acknowledgements
2 | This application makes use of the following third party libraries:
3 | Generated by CocoaPods - https://cocoapods.org
4 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-Life Saver Screensaver/Pods-Life Saver Screensaver-acknowledgements.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreferenceSpecifiers
6 |
7 |
8 | FooterText
9 | This application makes use of the following third party libraries:
10 | Title
11 | Acknowledgements
12 | Type
13 | PSGroupSpecifier
14 |
15 |
16 | FooterText
17 | Generated by CocoaPods - https://cocoapods.org
18 | Title
19 |
20 | Type
21 | PSGroupSpecifier
22 |
23 |
24 | StringsTable
25 | Acknowledgements
26 | Title
27 | Acknowledgements
28 |
29 |
30 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-Life Saver Screensaver/Pods-Life Saver Screensaver-dummy.m:
--------------------------------------------------------------------------------
1 | #import
2 | @interface PodsDummy_Pods_Life_Saver_Screensaver : NSObject
3 | @end
4 | @implementation PodsDummy_Pods_Life_Saver_Screensaver
5 | @end
6 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-Life Saver Screensaver/Pods-Life Saver Screensaver-umbrella.h:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 |
14 | FOUNDATION_EXPORT double Pods_Life_Saver_ScreensaverVersionNumber;
15 | FOUNDATION_EXPORT const unsigned char Pods_Life_Saver_ScreensaverVersionString[];
16 |
17 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-Life Saver Screensaver/Pods-Life Saver Screensaver.debug.xcconfig:
--------------------------------------------------------------------------------
1 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
2 | PODS_BUILD_DIR = ${BUILD_DIR}
3 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
4 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
5 | PODS_ROOT = ${SRCROOT}/Pods
6 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-Life Saver Screensaver/Pods-Life Saver Screensaver.modulemap:
--------------------------------------------------------------------------------
1 | framework module Pods_Life_Saver_Screensaver {
2 | umbrella header "Pods-Life Saver Screensaver-umbrella.h"
3 |
4 | export *
5 | module * { export * }
6 | }
7 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-Life Saver Screensaver/Pods-Life Saver Screensaver.release.xcconfig:
--------------------------------------------------------------------------------
1 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
2 | PODS_BUILD_DIR = ${BUILD_DIR}
3 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
4 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
5 | PODS_ROOT = ${SRCROOT}/Pods
6 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-Life Saver macOS/Pods-Life Saver macOS-Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | ${EXECUTABLE_NAME}
9 | CFBundleIdentifier
10 | ${PRODUCT_BUNDLE_IDENTIFIER}
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | ${PRODUCT_NAME}
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | ${CURRENT_PROJECT_VERSION}
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-Life Saver macOS/Pods-Life Saver macOS-acknowledgements.markdown:
--------------------------------------------------------------------------------
1 | # Acknowledgements
2 | This application makes use of the following third party libraries:
3 |
4 | ## SwiftLint
5 |
6 | The MIT License (MIT)
7 |
8 | Copyright (c) 2015 Realm Inc.
9 |
10 | Permission is hereby granted, free of charge, to any person obtaining a copy
11 | of this software and associated documentation files (the "Software"), to deal
12 | in the Software without restriction, including without limitation the rights
13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14 | copies of the Software, and to permit persons to whom the Software is
15 | furnished to do so, subject to the following conditions:
16 |
17 | The above copyright notice and this permission notice shall be included in all
18 | copies or substantial portions of the Software.
19 |
20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26 | SOFTWARE.
27 |
28 | Generated by CocoaPods - https://cocoapods.org
29 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-Life Saver macOS/Pods-Life Saver macOS-acknowledgements.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreferenceSpecifiers
6 |
7 |
8 | FooterText
9 | This application makes use of the following third party libraries:
10 | Title
11 | Acknowledgements
12 | Type
13 | PSGroupSpecifier
14 |
15 |
16 | FooterText
17 | The MIT License (MIT)
18 |
19 | Copyright (c) 2015 Realm Inc.
20 |
21 | Permission is hereby granted, free of charge, to any person obtaining a copy
22 | of this software and associated documentation files (the "Software"), to deal
23 | in the Software without restriction, including without limitation the rights
24 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
25 | copies of the Software, and to permit persons to whom the Software is
26 | furnished to do so, subject to the following conditions:
27 |
28 | The above copyright notice and this permission notice shall be included in all
29 | copies or substantial portions of the Software.
30 |
31 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
32 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
33 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
34 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
35 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
36 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
37 | SOFTWARE.
38 |
39 | License
40 | MIT
41 | Title
42 | SwiftLint
43 | Type
44 | PSGroupSpecifier
45 |
46 |
47 | FooterText
48 | Generated by CocoaPods - https://cocoapods.org
49 | Title
50 |
51 | Type
52 | PSGroupSpecifier
53 |
54 |
55 | StringsTable
56 | Acknowledgements
57 | Title
58 | Acknowledgements
59 |
60 |
61 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-Life Saver macOS/Pods-Life Saver macOS-dummy.m:
--------------------------------------------------------------------------------
1 | #import
2 | @interface PodsDummy_Pods_Life_Saver_macOS : NSObject
3 | @end
4 | @implementation PodsDummy_Pods_Life_Saver_macOS
5 | @end
6 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-Life Saver macOS/Pods-Life Saver macOS-umbrella.h:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 |
14 | FOUNDATION_EXPORT double Pods_Life_Saver_macOSVersionNumber;
15 | FOUNDATION_EXPORT const unsigned char Pods_Life_Saver_macOSVersionString[];
16 |
17 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-Life Saver macOS/Pods-Life Saver macOS.debug.xcconfig:
--------------------------------------------------------------------------------
1 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
2 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/../Frameworks' '@loader_path/Frameworks'
3 | PODS_BUILD_DIR = ${BUILD_DIR}
4 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
5 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
6 | PODS_ROOT = ${SRCROOT}/Pods
7 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-Life Saver macOS/Pods-Life Saver macOS.modulemap:
--------------------------------------------------------------------------------
1 | framework module Pods_Life_Saver_macOS {
2 | umbrella header "Pods-Life Saver macOS-umbrella.h"
3 |
4 | export *
5 | module * { export * }
6 | }
7 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-Life Saver macOS/Pods-Life Saver macOS.release.xcconfig:
--------------------------------------------------------------------------------
1 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
2 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/../Frameworks' '@loader_path/Frameworks'
3 | PODS_BUILD_DIR = ${BUILD_DIR}
4 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
5 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
6 | PODS_ROOT = ${SRCROOT}/Pods
7 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-Life Saver tvOS/Pods-Life Saver tvOS-Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | ${EXECUTABLE_NAME}
9 | CFBundleIdentifier
10 | ${PRODUCT_BUNDLE_IDENTIFIER}
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | ${PRODUCT_NAME}
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | ${CURRENT_PROJECT_VERSION}
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-Life Saver tvOS/Pods-Life Saver tvOS-acknowledgements.markdown:
--------------------------------------------------------------------------------
1 | # Acknowledgements
2 | This application makes use of the following third party libraries:
3 |
4 | ## SwiftLint
5 |
6 | The MIT License (MIT)
7 |
8 | Copyright (c) 2015 Realm Inc.
9 |
10 | Permission is hereby granted, free of charge, to any person obtaining a copy
11 | of this software and associated documentation files (the "Software"), to deal
12 | in the Software without restriction, including without limitation the rights
13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14 | copies of the Software, and to permit persons to whom the Software is
15 | furnished to do so, subject to the following conditions:
16 |
17 | The above copyright notice and this permission notice shall be included in all
18 | copies or substantial portions of the Software.
19 |
20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26 | SOFTWARE.
27 |
28 | Generated by CocoaPods - https://cocoapods.org
29 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-Life Saver tvOS/Pods-Life Saver tvOS-acknowledgements.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreferenceSpecifiers
6 |
7 |
8 | FooterText
9 | This application makes use of the following third party libraries:
10 | Title
11 | Acknowledgements
12 | Type
13 | PSGroupSpecifier
14 |
15 |
16 | FooterText
17 | The MIT License (MIT)
18 |
19 | Copyright (c) 2015 Realm Inc.
20 |
21 | Permission is hereby granted, free of charge, to any person obtaining a copy
22 | of this software and associated documentation files (the "Software"), to deal
23 | in the Software without restriction, including without limitation the rights
24 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
25 | copies of the Software, and to permit persons to whom the Software is
26 | furnished to do so, subject to the following conditions:
27 |
28 | The above copyright notice and this permission notice shall be included in all
29 | copies or substantial portions of the Software.
30 |
31 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
32 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
33 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
34 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
35 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
36 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
37 | SOFTWARE.
38 |
39 | License
40 | MIT
41 | Title
42 | SwiftLint
43 | Type
44 | PSGroupSpecifier
45 |
46 |
47 | FooterText
48 | Generated by CocoaPods - https://cocoapods.org
49 | Title
50 |
51 | Type
52 | PSGroupSpecifier
53 |
54 |
55 | StringsTable
56 | Acknowledgements
57 | Title
58 | Acknowledgements
59 |
60 |
61 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-Life Saver tvOS/Pods-Life Saver tvOS-dummy.m:
--------------------------------------------------------------------------------
1 | #import
2 | @interface PodsDummy_Pods_Life_Saver_tvOS : NSObject
3 | @end
4 | @implementation PodsDummy_Pods_Life_Saver_tvOS
5 | @end
6 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-Life Saver tvOS/Pods-Life Saver tvOS-umbrella.h:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 |
14 | FOUNDATION_EXPORT double Pods_Life_Saver_tvOSVersionNumber;
15 | FOUNDATION_EXPORT const unsigned char Pods_Life_Saver_tvOSVersionString[];
16 |
17 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-Life Saver tvOS/Pods-Life Saver tvOS.debug.xcconfig:
--------------------------------------------------------------------------------
1 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
2 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
3 | PODS_BUILD_DIR = ${BUILD_DIR}
4 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
5 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
6 | PODS_ROOT = ${SRCROOT}/Pods
7 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-Life Saver tvOS/Pods-Life Saver tvOS.modulemap:
--------------------------------------------------------------------------------
1 | framework module Pods_Life_Saver_tvOS {
2 | umbrella header "Pods-Life Saver tvOS-umbrella.h"
3 |
4 | export *
5 | module * { export * }
6 | }
7 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-Life Saver tvOS/Pods-Life Saver tvOS.release.xcconfig:
--------------------------------------------------------------------------------
1 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
2 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
3 | PODS_BUILD_DIR = ${BUILD_DIR}
4 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
5 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
6 | PODS_ROOT = ${SRCROOT}/Pods
7 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/SwiftLint-macOS/SwiftLint-macOS.xcconfig:
--------------------------------------------------------------------------------
1 | CODE_SIGN_IDENTITY =
2 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SwiftLint-macOS
3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
4 | PODS_BUILD_DIR = ${BUILD_DIR}
5 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
6 | PODS_ROOT = ${SRCROOT}
7 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/SwiftLint
8 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
9 | SKIP_INSTALL = YES
10 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/SwiftLint-tvOS/SwiftLint-tvOS.xcconfig:
--------------------------------------------------------------------------------
1 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SwiftLint-tvOS
2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
3 | PODS_BUILD_DIR = ${BUILD_DIR}
4 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
5 | PODS_ROOT = ${SRCROOT}
6 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/SwiftLint
7 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
8 | SKIP_INSTALL = YES
9 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Life Saver
2 |
3 | Life Saver is an abstract, artistic implementation of [Conway's Game of Life](https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life) built with SpriteKit. With a variety of settings and color schemes, Life Saver should satisfy designers and geeks alike. Comes in two flavors: a macOS screensaver, and an Apple TV app.
4 |
5 | ## 🖥 Screensaver
6 |
7 | 📼 [Watch a YouTube video to see it in action](https://www.youtube.com/watch?v=N4nCFUVThgg).
8 |
9 | 
10 |
11 | ### 👉 [Download Life Saver v1.2 for macOS](https://amiantos.s3.amazonaws.com/lifesaver-1.2.zip)
12 |
13 | ## 📺 Apple TV
14 |
15 | 
16 | 
17 |
18 | ### 👉 [Get Life Saver TV on the App Store](https://apps.apple.com/us/app/life-saver-tv/id1470667717)
19 |
20 | ## To Install from Source
21 |
22 | 1. `git clone https://github.com/amiantos/lifesaver.git`
23 | 2. Open `Life Saver.xcworkspace`
24 | 3. Pick a target and run!
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Shared/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "mac",
5 | "size" : "16x16",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "mac",
10 | "size" : "16x16",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "mac",
15 | "size" : "32x32",
16 | "scale" : "1x"
17 | },
18 | {
19 | "idiom" : "mac",
20 | "size" : "32x32",
21 | "scale" : "2x"
22 | },
23 | {
24 | "idiom" : "mac",
25 | "size" : "128x128",
26 | "scale" : "1x"
27 | },
28 | {
29 | "idiom" : "mac",
30 | "size" : "128x128",
31 | "scale" : "2x"
32 | },
33 | {
34 | "idiom" : "mac",
35 | "size" : "256x256",
36 | "scale" : "1x"
37 | },
38 | {
39 | "idiom" : "mac",
40 | "size" : "256x256",
41 | "scale" : "2x"
42 | },
43 | {
44 | "idiom" : "mac",
45 | "size" : "512x512",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "mac",
50 | "size" : "512x512",
51 | "scale" : "2x"
52 | }
53 | ],
54 | "info" : {
55 | "version" : 1,
56 | "author" : "xcode"
57 | }
58 | }
--------------------------------------------------------------------------------
/Shared/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/Shared/Assets.xcassets/github.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "mac",
5 | "filename" : "github_dark.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "mac",
10 | "filename" : "github_light.png",
11 | "appearances" : [
12 | {
13 | "appearance" : "luminosity",
14 | "value" : "dark"
15 | }
16 | ],
17 | "scale" : "1x"
18 | },
19 | {
20 | "idiom" : "mac",
21 | "filename" : "github_dark-1.png",
22 | "scale" : "2x"
23 | },
24 | {
25 | "idiom" : "mac",
26 | "filename" : "github_light-1.png",
27 | "appearances" : [
28 | {
29 | "appearance" : "luminosity",
30 | "value" : "dark"
31 | }
32 | ],
33 | "scale" : "2x"
34 | }
35 | ],
36 | "info" : {
37 | "version" : 1,
38 | "author" : "xcode"
39 | }
40 | }
--------------------------------------------------------------------------------
/Shared/Assets.xcassets/github.imageset/github_dark-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amiantos/lifesaver/158ac5378c0686480e57709d25d8488b79a26eeb/Shared/Assets.xcassets/github.imageset/github_dark-1.png
--------------------------------------------------------------------------------
/Shared/Assets.xcassets/github.imageset/github_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amiantos/lifesaver/158ac5378c0686480e57709d25d8488b79a26eeb/Shared/Assets.xcassets/github.imageset/github_dark.png
--------------------------------------------------------------------------------
/Shared/Assets.xcassets/github.imageset/github_light-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amiantos/lifesaver/158ac5378c0686480e57709d25d8488b79a26eeb/Shared/Assets.xcassets/github.imageset/github_light-1.png
--------------------------------------------------------------------------------
/Shared/Assets.xcassets/github.imageset/github_light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amiantos/lifesaver/158ac5378c0686480e57709d25d8488b79a26eeb/Shared/Assets.xcassets/github.imageset/github_light.png
--------------------------------------------------------------------------------
/Shared/Assets.xcassets/square.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "aeonSquare.pdf"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | },
12 | "properties" : {
13 | "preserves-vector-representation" : true
14 | }
15 | }
--------------------------------------------------------------------------------
/Shared/Assets.xcassets/square.imageset/aeonSquare.pdf:
--------------------------------------------------------------------------------
1 | %PDF-1.7
2 |
3 | 1 0 obj
4 | << >>
5 | endobj
6 |
7 | 2 0 obj
8 | << /Length 3 0 R >>
9 | stream
10 | /DeviceRGB CS
11 | /DeviceRGB cs
12 | q
13 | 1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm
14 | 0.000000 60.000000 m
15 | 60.000000 60.000000 l
16 | 60.000000 0.000000 l
17 | 0.000000 0.000000 l
18 | 0.000000 60.000000 l
19 | h
20 | 1.000000 1.000000 1.000000 scn
21 | f
22 | n
23 | Q
24 |
25 | endstream
26 | endobj
27 |
28 | 3 0 obj
29 | 232
30 | endobj
31 |
32 | 4 0 obj
33 | << /MediaBox [ 0.000000 0.000000 60.000000 60.000000 ]
34 | /Resources 1 0 R
35 | /Contents 2 0 R
36 | /Parent 5 0 R
37 | /Type /Page
38 | >>
39 | endobj
40 |
41 | 5 0 obj
42 | << /Kids [ 4 0 R ]
43 | /Count 1
44 | /Type /Pages
45 | >>
46 | endobj
47 |
48 | 6 0 obj
49 | << /Type /Catalog
50 | /Pages 5 0 R
51 | >>
52 | endobj
53 |
54 | xref
55 | 0 7
56 | 0000000000 65535 f
57 | 0000000010 00000 n
58 | 0000000034 00000 n
59 | 0000000322 00000 n
60 | 0000000344 00000 n
61 | 0000000501 00000 n
62 | 0000000575 00000 n
63 | trailer
64 | << /ID [ (some) (id) ]
65 | /Root 6 0 R
66 | /Size 7
67 | >>
68 | startxref
69 | 634
70 | %%EOF
--------------------------------------------------------------------------------
/Shared/Assets.xcassets/thumbnail.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "mac",
5 | "filename" : "thumbnail.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "mac",
10 | "filename" : "thumbnail@2x.png",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "version" : 1,
16 | "author" : "xcode"
17 | }
18 | }
--------------------------------------------------------------------------------
/Shared/Assets.xcassets/thumbnail.imageset/thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amiantos/lifesaver/158ac5378c0686480e57709d25d8488b79a26eeb/Shared/Assets.xcassets/thumbnail.imageset/thumbnail.png
--------------------------------------------------------------------------------
/Shared/Assets.xcassets/thumbnail.imageset/thumbnail@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amiantos/lifesaver/158ac5378c0686480e57709d25d8488b79a26eeb/Shared/Assets.xcassets/thumbnail.imageset/thumbnail@2x.png
--------------------------------------------------------------------------------
/Shared/Assets.xcassets/twitter.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "mac",
5 | "filename" : "twitter_dark.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "mac",
10 | "filename" : "twitter_light.png",
11 | "appearances" : [
12 | {
13 | "appearance" : "luminosity",
14 | "value" : "dark"
15 | }
16 | ],
17 | "scale" : "1x"
18 | },
19 | {
20 | "idiom" : "mac",
21 | "filename" : "twitter_dark-1.png",
22 | "scale" : "2x"
23 | },
24 | {
25 | "idiom" : "mac",
26 | "filename" : "twitter_light-1.png",
27 | "appearances" : [
28 | {
29 | "appearance" : "luminosity",
30 | "value" : "dark"
31 | }
32 | ],
33 | "scale" : "2x"
34 | }
35 | ],
36 | "info" : {
37 | "version" : 1,
38 | "author" : "xcode"
39 | }
40 | }
--------------------------------------------------------------------------------
/Shared/Assets.xcassets/twitter.imageset/twitter_dark-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amiantos/lifesaver/158ac5378c0686480e57709d25d8488b79a26eeb/Shared/Assets.xcassets/twitter.imageset/twitter_dark-1.png
--------------------------------------------------------------------------------
/Shared/Assets.xcassets/twitter.imageset/twitter_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amiantos/lifesaver/158ac5378c0686480e57709d25d8488b79a26eeb/Shared/Assets.xcassets/twitter.imageset/twitter_dark.png
--------------------------------------------------------------------------------
/Shared/Assets.xcassets/twitter.imageset/twitter_light-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amiantos/lifesaver/158ac5378c0686480e57709d25d8488b79a26eeb/Shared/Assets.xcassets/twitter.imageset/twitter_light-1.png
--------------------------------------------------------------------------------
/Shared/Assets.xcassets/twitter.imageset/twitter_light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amiantos/lifesaver/158ac5378c0686480e57709d25d8488b79a26eeb/Shared/Assets.xcassets/twitter.imageset/twitter_light.png
--------------------------------------------------------------------------------
/Shared/LifeDatabase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LifeDatabase.swift
3 | // Life Saver
4 | //
5 | // Created by Bradley Root on 6/23/19.
6 | // Copyright © 2019 Brad Root. All rights reserved.
7 | //
8 | // This Source Code Form is subject to the terms of the Mozilla Public
9 | // License, v. 2.0. If a copy of the MPL was not distributed with this
10 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
11 |
12 | import SpriteKit
13 |
14 | struct LifeDatabase {
15 | fileprivate enum Key {
16 | static let appearanceMode = "appearanceMode"
17 | static let squareSize = "squareSize"
18 | static let blurAmount = "blurAmount"
19 | static let animationSpeed = "animationSpeed"
20 | static let color1 = "color1"
21 | static let color2 = "color2"
22 | static let color3 = "color3"
23 | static let randomColorPreset = "randomColorPreset"
24 | static let selectedPresetTitle = "selectedPresetTitle"
25 | static let deathFade = "deathFade"
26 | static let shiftingColors = "shiftingColors"
27 | static let hasPressedMenuButton = "hasPressedMenuButton"
28 | }
29 |
30 | static var standard: UserDefaults {
31 | let database = UserDefaults.standard
32 |
33 | database.register(defaults:
34 | [Key.appearanceMode: Appearance.dark.rawValue,
35 | Key.animationSpeed: AnimationSpeed.normal.rawValue,
36 | Key.squareSize: SquareSize.medium.rawValue,
37 | Key.color1: archiveData(SKColor.defaultColor1),
38 | Key.color2: archiveData(SKColor.defaultColor2),
39 | Key.color3: archiveData(SKColor.defaultColor3),
40 | Key.randomColorPreset: false,
41 | Key.deathFade: true,
42 | Key.shiftingColors: false,
43 | Key.selectedPresetTitle: "Santa Fe",
44 | Key.hasPressedMenuButton: false])
45 |
46 | return database
47 | }
48 | }
49 |
50 | extension UserDefaults {
51 | var appearanceMode: Appearance {
52 | return Appearance(rawValue: integer(forKey: LifeDatabase.Key.appearanceMode))!
53 | }
54 |
55 | func set(appearanceMode: Appearance) {
56 | set(appearanceMode.rawValue, for: LifeDatabase.Key.appearanceMode)
57 | }
58 |
59 | var squareSize: SquareSize {
60 | return SquareSize(rawValue: integer(forKey: LifeDatabase.Key.squareSize))!
61 | }
62 |
63 | func set(squareSize: SquareSize) {
64 | set(squareSize.rawValue, for: LifeDatabase.Key.squareSize)
65 | }
66 |
67 | var animationSpeed: AnimationSpeed {
68 | return AnimationSpeed(rawValue: integer(forKey: LifeDatabase.Key.animationSpeed))!
69 | }
70 |
71 | func set(animationSpeed: AnimationSpeed) {
72 | set(animationSpeed.rawValue, for: LifeDatabase.Key.animationSpeed)
73 | }
74 |
75 | var randomColorPreset: Bool {
76 | return bool(forKey: LifeDatabase.Key.randomColorPreset)
77 | }
78 |
79 | func set(randomColorPreset: Bool) {
80 | set(randomColorPreset, for: LifeDatabase.Key.randomColorPreset)
81 | }
82 |
83 | var shiftingColors: Bool {
84 | return bool(forKey: LifeDatabase.Key.shiftingColors)
85 | }
86 |
87 | func set(shiftingColors: Bool) {
88 | set(shiftingColors, for: LifeDatabase.Key.shiftingColors)
89 | }
90 |
91 | var deathFade: Bool {
92 | return bool(forKey: LifeDatabase.Key.deathFade)
93 | }
94 |
95 | func set(deathFade: Bool) {
96 | set(deathFade, for: LifeDatabase.Key.deathFade)
97 | }
98 |
99 | var hasPressedMenuButton: Bool {
100 | return bool(forKey: LifeDatabase.Key.hasPressedMenuButton)
101 | }
102 |
103 | func set(hasPressedMenuButton: Bool) {
104 | set(hasPressedMenuButton, for: LifeDatabase.Key.hasPressedMenuButton)
105 | }
106 |
107 | var selectedPresetTitle: String {
108 | return string(forKey: LifeDatabase.Key.selectedPresetTitle) ?? ""
109 | }
110 |
111 | func set(selectedPresetTitle: String) {
112 | set(selectedPresetTitle, for: LifeDatabase.Key.selectedPresetTitle)
113 | }
114 |
115 | func getColor(_ color: Colors) -> SKColor {
116 | switch color {
117 | case .color1:
118 | return unarchiveColor(data(forKey: LifeDatabase.Key.color1)!)
119 | case .color2:
120 | return unarchiveColor(data(forKey: LifeDatabase.Key.color2)!)
121 | case .color3:
122 | return unarchiveColor(data(forKey: LifeDatabase.Key.color3)!)
123 | }
124 | }
125 |
126 | func set(_ color: SKColor, for colors: Colors) {
127 | switch colors {
128 | case .color1:
129 | set(archiveData(color), for: LifeDatabase.Key.color1)
130 | case .color2:
131 | set(archiveData(color), for: LifeDatabase.Key.color2)
132 | case .color3:
133 | set(archiveData(color), for: LifeDatabase.Key.color3)
134 | }
135 | }
136 | }
137 |
138 | private extension UserDefaults {
139 | func set(_ object: Any?, for key: String) {
140 | set(object, forKey: key)
141 | synchronize()
142 | }
143 | }
144 |
145 | private func archiveData(_ data: Any) -> Data {
146 | do {
147 | let data = try NSKeyedArchiver.archivedData(withRootObject: data, requiringSecureCoding: false)
148 | return data
149 | } catch {
150 | fatalError("Failed to archive data")
151 | }
152 | }
153 |
154 | private func unarchiveColor(_ data: Data) -> SKColor {
155 | do {
156 | let color = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? SKColor
157 | return color!
158 | } catch {
159 | fatalError("Failed to unarchive data")
160 | }
161 | }
162 |
--------------------------------------------------------------------------------
/Shared/LifeManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LifeManager.swift
3 | // Life Saver
4 | //
5 | // Created by Brad Root on 5/21/19.
6 | // Copyright © 2019 Brad Root. All rights reserved.
7 | //
8 | // This Source Code Form is subject to the terms of the Mozilla Public
9 | // License, v. 2.0. If a copy of the MPL was not distributed with this
10 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
11 |
12 | import Foundation
13 | import SpriteKit
14 |
15 | protocol LifeManagerDelegate: AnyObject {
16 | func updatedSettings()
17 | }
18 |
19 | final class LifeManager {
20 | private(set) var appearanceMode: Appearance
21 | private(set) var squareSize: SquareSize
22 | private(set) var animationSpeed: AnimationSpeed
23 | private(set) var color1: SKColor
24 | private(set) var color2: SKColor
25 | private(set) var color3: SKColor
26 | private(set) var randomColorPreset: Bool
27 | private(set) var shiftingColors: Bool
28 | private(set) var deathFade: Bool
29 | private(set) var selectedPresetTitle: String
30 | private(set) var hasPressedMenuButton: Bool
31 |
32 | private var usingPreset: Bool = false
33 |
34 | weak var delegate: LifeManagerDelegate?
35 | weak var settingsDelegate: LifeManagerDelegate?
36 |
37 | init() {
38 | appearanceMode = LifeDatabase.standard.appearanceMode
39 | squareSize = LifeDatabase.standard.squareSize
40 | animationSpeed = LifeDatabase.standard.animationSpeed
41 | color1 = LifeDatabase.standard.getColor(.color1)
42 | color2 = LifeDatabase.standard.getColor(.color2)
43 | color3 = LifeDatabase.standard.getColor(.color3)
44 | randomColorPreset = LifeDatabase.standard.randomColorPreset
45 | deathFade = LifeDatabase.standard.deathFade
46 | shiftingColors = LifeDatabase.standard.shiftingColors
47 | selectedPresetTitle = LifeDatabase.standard.selectedPresetTitle
48 | hasPressedMenuButton = LifeDatabase.standard.hasPressedMenuButton
49 | }
50 |
51 | func configure(with preset: LifePreset) {
52 | usingPreset = true
53 |
54 | if let appearanceMode = preset.appearanceMode {
55 | setAppearanceMode(appearanceMode)
56 | }
57 |
58 | if let squareSize = preset.squareSize {
59 | setSquareSize(squareSize)
60 | }
61 |
62 | if let animationSpeed = preset.animationSpeed {
63 | setAnimationSpeed(animationSpeed)
64 | }
65 |
66 | if let deathFade = preset.deathFade {
67 | setDeathFade(deathFade)
68 | }
69 |
70 | if let shiftingColors = preset.shiftingColors {
71 | setShiftingColors(shiftingColors)
72 | }
73 |
74 | if let color1 = preset.color1 {
75 | setColor(color1, for: .color1)
76 | }
77 |
78 | if let color2 = preset.color2 {
79 | setColor(color2, for: .color2)
80 | }
81 |
82 | if let color3 = preset.color3 {
83 | setColor(color3, for: .color3)
84 | }
85 |
86 | selectedPresetTitle = preset.title
87 | LifeDatabase.standard.set(selectedPresetTitle: selectedPresetTitle)
88 |
89 | usingPreset = false
90 | sendUpdateMessage()
91 | }
92 |
93 | func setRandomColorPreset(_ randomColorPreset: Bool) {
94 | self.randomColorPreset = randomColorPreset
95 | LifeDatabase.standard.set(randomColorPreset: randomColorPreset)
96 | sendUpdateMessage()
97 | }
98 |
99 | func setShiftingColors(_ shiftingColors: Bool) {
100 | self.shiftingColors = shiftingColors
101 | LifeDatabase.standard.set(shiftingColors: shiftingColors)
102 | sendUpdateMessage()
103 | }
104 |
105 | func setDeathFade(_ deathFade: Bool) {
106 | self.deathFade = deathFade
107 | LifeDatabase.standard.set(deathFade: deathFade)
108 | sendUpdateMessage()
109 | }
110 |
111 | func setHasPressedMenuButton(_ hasPressedMenuButton: Bool) {
112 | self.hasPressedMenuButton = hasPressedMenuButton
113 | LifeDatabase.standard.set(hasPressedMenuButton: hasPressedMenuButton)
114 | }
115 |
116 | func setAppearanceMode(_ appearanceMode: Appearance) {
117 | self.appearanceMode = appearanceMode
118 | LifeDatabase.standard.set(appearanceMode: appearanceMode)
119 | sendUpdateMessage()
120 | }
121 |
122 | func setSquareSize(_ squareSize: SquareSize) {
123 | self.squareSize = squareSize
124 | LifeDatabase.standard.set(squareSize: squareSize)
125 | sendUpdateMessage()
126 | }
127 |
128 | func setAnimationSpeed(_ animationSpeed: AnimationSpeed) {
129 | self.animationSpeed = animationSpeed
130 | LifeDatabase.standard.set(animationSpeed: animationSpeed)
131 | sendUpdateMessage()
132 | }
133 |
134 | func setColor(_ color: SKColor, for colors: Colors) {
135 | switch colors {
136 | case .color1:
137 | color1 = color
138 | case .color2:
139 | color2 = color
140 | case .color3:
141 | color3 = color
142 | }
143 | LifeDatabase.standard.set(color, for: colors)
144 |
145 | if !usingPreset {
146 | selectedPresetTitle = ""
147 | LifeDatabase.standard.set(selectedPresetTitle: selectedPresetTitle)
148 | }
149 |
150 | sendUpdateMessage()
151 | }
152 |
153 | func sendUpdateMessage() {
154 | if !usingPreset {
155 | delegate?.updatedSettings()
156 | settingsDelegate?.updatedSettings()
157 | }
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/Shared/LifeNode.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LifeNode.swift
3 | // Life Saver
4 | //
5 | // Created by Brad Root on 5/23/19.
6 | // Copyright © 2019 Brad Root. All rights reserved.
7 | //
8 | // This Source Code Form is subject to the terms of the Mozilla Public
9 | // License, v. 2.0. If a copy of the MPL was not distributed with this
10 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
11 |
12 | import SpriteKit
13 |
14 | let squareTexture = FileGrabber.shared.getSKTexture(named: "square")
15 |
16 | class LifeNode: SKSpriteNode {
17 | let relativePosition: CGPoint
18 | var alive: Bool
19 | var timeInState: Int = 0
20 | var aliveColor: SKColor
21 | var deadColor: SKColor
22 | var neighbors: [LifeNode] = []
23 |
24 | init(relativePosition: CGPoint, alive: Bool, color: SKColor, size: CGSize) {
25 | self.relativePosition = relativePosition
26 | self.alive = alive
27 | aliveColor = color
28 | deadColor = color
29 | super.init(texture: squareTexture, color: aliveColor, size: size)
30 | isUserInteractionEnabled = false
31 | anchorPoint = CGPoint(x: 0, y: 0)
32 | colorBlendFactor = 1
33 | zPosition = 0
34 | }
35 |
36 | required init?(coder _: NSCoder) {
37 | fatalError("init(coder:) has not been implemented")
38 | }
39 |
40 | // MARK: - Lifecycle
41 |
42 | public func live(duration: TimeInterval) {
43 | if alive {
44 | timeInState += 1
45 | return
46 | }
47 |
48 | timeInState = 0
49 | alive = true
50 |
51 | if duration > 0 {
52 | removeAllActions()
53 | let fadeAction = SKAction.fadeAlpha(to: 1, duration: duration)
54 | let colorAction = SKAction.colorize(with: aliveColor, colorBlendFactor: 1, duration: duration)
55 | let actionGroup = SKAction.group([fadeAction, colorAction])
56 | actionGroup.timingMode = .easeInEaseOut
57 | run(actionGroup)
58 | } else {
59 | alpha = 1
60 | color = aliveColor
61 | }
62 | }
63 |
64 | public func die(duration: TimeInterval, fade: Bool) {
65 | if !alive {
66 | timeInState += 1
67 |
68 | // 30 for slow modes... 120 for fast?
69 | if timeInState == 120, duration > 0, fade {
70 | removeAllActions()
71 | let fadeAction = SKAction.fadeAlpha(to: 0, duration: duration)
72 | let colorAction = SKAction.colorize(with: deadColor, colorBlendFactor: 1, duration: duration)
73 | let actionGroup = SKAction.group([fadeAction, colorAction])
74 | actionGroup.timingMode = .easeIn
75 | run(actionGroup)
76 | }
77 |
78 | return
79 | }
80 |
81 | timeInState = 0
82 | alive = false
83 |
84 | if duration > 0, fade {
85 | removeAllActions()
86 | let fadeAction = SKAction.fadeAlpha(to: 0.2, duration: duration)
87 | fadeAction.timingMode = .easeInEaseOut
88 | run(fadeAction)
89 | } else if fade {
90 | alpha = 0.2
91 | }
92 | }
93 |
94 | public func remove(duration: TimeInterval) {
95 | removeAllActions()
96 | timeInState = 0
97 | alive = false
98 |
99 | let fadeAction = SKAction.fadeAlpha(to: 0, duration: duration)
100 | fadeAction.timingMode = .easeInEaseOut
101 | run(fadeAction) {
102 | self.removeFromParent()
103 | }
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/Shared/LifePreset.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LifePreset.swift
3 | // Life Saver Screensaver
4 | //
5 | // Created by Brad Root on 5/23/19.
6 | // Copyright © 2019 Brad Root. All rights reserved.
7 | //
8 | // This Source Code Form is subject to the terms of the Mozilla Public
9 | // License, v. 2.0. If a copy of the MPL was not distributed with this
10 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
11 |
12 | import SpriteKit
13 |
14 | struct LifePreset {
15 | let title: String
16 | let appearanceMode: Appearance?
17 | let squareSize: SquareSize?
18 | let animationSpeed: AnimationSpeed?
19 | let deathFade: Bool?
20 | let shiftingColors: Bool?
21 | let color1: SKColor?
22 | let color2: SKColor?
23 | let color3: SKColor?
24 | }
25 |
26 | enum Appearance: Int {
27 | case light = 0
28 | case dark = 1
29 | }
30 |
31 | enum SquareSize: Int {
32 | case superSmall = -2
33 | case verySmall = -1
34 | case small = 0
35 | case medium = 1
36 | case large = 2
37 | }
38 |
39 | enum AnimationSpeed: Int {
40 | case fast = 0
41 | case normal = 1
42 | case slow = 2
43 | case off = 3
44 | }
45 |
46 | enum Colors: Int {
47 | case color1 = 0
48 | case color2 = 1
49 | case color3 = 2
50 | }
51 |
52 | extension SKColor {
53 | static let defaultColor1 = SKColor(red: 172 / 255.0, green: 48 / 255.0, blue: 17 / 255.0, alpha: 1.00)
54 | static let defaultColor2 = SKColor(red: 6 / 255.0, green: 66 / 255.0, blue: 110 / 255.0, alpha: 1.00)
55 | static let defaultColor3 = SKColor(red: 174 / 255.0, green: 129 / 255.0, blue: 0 / 255.0, alpha: 1.00)
56 | }
57 |
58 | let settingsPresets = [
59 | LifePreset(
60 | title: "Defaults",
61 | appearanceMode: .dark,
62 | squareSize: .medium,
63 | animationSpeed: .normal,
64 | deathFade: true,
65 | shiftingColors: false,
66 | color1: SKColor.defaultColor1,
67 | color2: SKColor.defaultColor2,
68 | color3: SKColor.defaultColor3
69 | ),
70 | LifePreset(
71 | title: "Meditation",
72 | appearanceMode: .light,
73 | squareSize: .large,
74 | animationSpeed: .normal,
75 | deathFade: false,
76 | shiftingColors: true,
77 | color1: SKColor(red: 237 / 255.0, green: 200 / 255.0, blue: 195 / 255.0, alpha: 1.00),
78 | color2: SKColor(red: 16 / 255.0, green: 103 / 255.0, blue: 110 / 255.0, alpha: 1.00),
79 | color3: SKColor(red: 247 / 255.0, green: 172 / 255.0, blue: 153 / 255.0, alpha: 1.00)
80 | ),
81 | LifePreset(
82 | title: "8-bit Fireplace",
83 | appearanceMode: .dark,
84 | squareSize: .medium,
85 | animationSpeed: .fast,
86 | deathFade: true,
87 | shiftingColors: false,
88 | color1: SKColor(red: 0.98, green: 0.75, blue: 0.00, alpha: 1.0),
89 | color2: SKColor(red: 1.00, green: 0.46, blue: 0.00, alpha: 1.0),
90 | color3: SKColor(red: 0.71, green: 0.13, blue: 0.01, alpha: 1.0)
91 | ),
92 | LifePreset(
93 | title: "Technicolor Dream",
94 | appearanceMode: .light,
95 | squareSize: .verySmall,
96 | animationSpeed: .off,
97 | deathFade: false,
98 | shiftingColors: true,
99 | color1: SKColor(red: 252 / 255.0, green: 98 / 255.0, blue: 101 / 255.0, alpha: 1.00),
100 | color2: SKColor(red: 88 / 255.0, green: 137 / 255.0, blue: 251 / 255.0, alpha: 1.00),
101 | color3: SKColor(red: 38 / 255.0, green: 205 / 255.0, blue: 105 / 255.0, alpha: 1.00)
102 | ),
103 | LifePreset(
104 | title: "Basic Life",
105 | appearanceMode: .dark,
106 | squareSize: .verySmall,
107 | animationSpeed: .off,
108 | deathFade: true,
109 | shiftingColors: false,
110 | color1: SKColor.white,
111 | color2: SKColor.gray,
112 | color3: SKColor.lightGray
113 | ),
114 | ]
115 |
116 | let colorPresets = [
117 | LifePreset(
118 | title: "Santa Fe",
119 | appearanceMode: .dark,
120 | squareSize: nil,
121 | animationSpeed: nil,
122 | deathFade: nil,
123 | shiftingColors: nil,
124 | color1: SKColor.defaultColor1,
125 | color2: SKColor.defaultColor2,
126 | color3: SKColor.defaultColor3
127 | ),
128 | LifePreset(
129 | title: "Braineater",
130 | appearanceMode: .dark,
131 | squareSize: nil,
132 | animationSpeed: nil,
133 | deathFade: nil,
134 | shiftingColors: nil,
135 | color1: SKColor(red: 103 / 255.0, green: 22 / 255.0, blue: 169 / 255.0, alpha: 1.00),
136 | color2: SKColor(red: 13 / 255.0, green: 17 / 255.0, blue: 108 / 255.0, alpha: 1.00),
137 | color3: SKColor(red: 12 / 255.0, green: 67 / 255.0, blue: 108 / 255.0, alpha: 1.00)
138 | ),
139 | LifePreset(
140 | title: "Reign In Blood",
141 | appearanceMode: .dark,
142 | squareSize: nil,
143 | animationSpeed: nil,
144 | deathFade: nil,
145 | shiftingColors: nil,
146 | color1: SKColor(red: 113 / 255.0, green: 17 / 255.0, blue: 8 / 255.0, alpha: 1.00),
147 | color2: SKColor(red: 95 / 255.0, green: 7 / 255.0, blue: 0 / 255.0, alpha: 1.00),
148 | color3: SKColor(red: 55 / 255.0, green: 55 / 255.0, blue: 55 / 255.0, alpha: 1.00)
149 | ),
150 | LifePreset(
151 | title: "Swamp Girl",
152 | appearanceMode: .dark,
153 | squareSize: nil,
154 | animationSpeed: nil,
155 | deathFade: nil,
156 | shiftingColors: nil,
157 | color1: SKColor(red: 173 / 255.0, green: 255 / 255.0, blue: 14 / 255.0, alpha: 1.00),
158 | color2: SKColor(red: 174 / 255.0, green: 129 / 255.0, blue: 255 / 255.0, alpha: 1.00),
159 | color3: SKColor(red: 6 / 255.0, green: 66 / 255.0, blue: 110 / 255.0, alpha: 1.00)
160 | ),
161 | LifePreset(
162 | title: "This Is America",
163 | appearanceMode: .dark,
164 | squareSize: nil,
165 | animationSpeed: nil,
166 | deathFade: nil,
167 | shiftingColors: nil,
168 | color1: SKColor(red: 190 / 255.0, green: 14 / 255.0, blue: 19 / 255.0, alpha: 1.00),
169 | color2: SKColor(red: 39 / 255.0, green: 65 / 255.0, blue: 110 / 255.0, alpha: 1.00),
170 | color3: SKColor(red: 212 / 255.0, green: 205 / 255.0, blue: 196 / 255.0, alpha: 1.00)
171 | ),
172 | LifePreset(
173 | title: "The Noun Project",
174 | appearanceMode: .dark,
175 | squareSize: nil,
176 | animationSpeed: nil,
177 | deathFade: nil,
178 | shiftingColors: nil,
179 | color1: SKColor(red: 255 / 255.0, green: 255 / 255.0, blue: 255 / 255.0, alpha: 1.00),
180 | color2: SKColor(red: 255 / 255.0, green: 255 / 255.0, blue: 255 / 255.0, alpha: 1.00),
181 | color3: SKColor(red: 255 / 255.0, green: 255 / 255.0, blue: 255 / 255.0, alpha: 1.00)
182 | ),
183 | LifePreset(
184 | title: "Lingo",
185 | appearanceMode: .light,
186 | squareSize: nil,
187 | animationSpeed: nil,
188 | deathFade: nil,
189 | shiftingColors: nil,
190 | color1: SKColor(red: 252 / 255.0, green: 98 / 255.0, blue: 101 / 255.0, alpha: 1.00),
191 | color2: SKColor(red: 88 / 255.0, green: 137 / 255.0, blue: 251 / 255.0, alpha: 1.00),
192 | color3: SKColor(red: 38 / 255.0, green: 205 / 255.0, blue: 105 / 255.0, alpha: 1.00)
193 | ),
194 | LifePreset(
195 | title: "Deuteranopia",
196 | appearanceMode: .dark,
197 | squareSize: nil,
198 | animationSpeed: nil,
199 | deathFade: nil,
200 | shiftingColors: nil,
201 | color1: SKColor(red: 211 / 255.0, green: 208 / 255.0, blue: 203 / 255.0, alpha: 1.00),
202 | color2: SKColor(red: 88 / 255.0, green: 123 / 255.0, blue: 127 / 255.0, alpha: 1.00),
203 | color3: SKColor(red: 255 / 255.0, green: 173 / 255.0, blue: 105 / 255.0, alpha: 1.00)
204 | ),
205 | LifePreset(
206 | title: "Retro Pastel",
207 | appearanceMode: .light,
208 | squareSize: nil,
209 | animationSpeed: nil,
210 | deathFade: nil,
211 | shiftingColors: nil,
212 | color1: SKColor(red: 229 / 255.0, green: 167 / 255.0, blue: 177 / 255.0, alpha: 1.00),
213 | color2: SKColor(red: 244 / 255.0, green: 243 / 255.0, blue: 216 / 255.0, alpha: 1.00),
214 | color3: SKColor(red: 175 / 255.0, green: 211 / 255.0, blue: 213 / 255.0, alpha: 1.00)
215 | ),
216 | LifePreset(
217 | title: "Better Days",
218 | appearanceMode: .dark,
219 | squareSize: nil,
220 | animationSpeed: nil,
221 | deathFade: nil,
222 | shiftingColors: nil,
223 | color1: SKColor(red: 95 / 255.0, green: 67 / 255.0, blue: 107 / 255.0, alpha: 1.00),
224 | color2: SKColor(red: 205 / 255.0, green: 170 / 255.0, blue: 37 / 255.0, alpha: 1.00),
225 | color3: SKColor(red: 114 / 255.0, green: 100 / 255.0, blue: 87 / 255.0, alpha: 1.00)
226 | ),
227 | LifePreset(
228 | title: "Bubblegum",
229 | appearanceMode: .light,
230 | squareSize: nil,
231 | animationSpeed: nil,
232 | deathFade: nil,
233 | shiftingColors: nil,
234 | color1: SKColor(red: 1 / 255.0, green: 153 / 255.0, blue: 138 / 255.0, alpha: 1.00),
235 | color2: SKColor(red: 255 / 255.0, green: 203 / 255.0, blue: 213 / 255.0, alpha: 1.00),
236 | color3: SKColor(red: 191 / 255.0, green: 178 / 255.0, blue: 95 / 255.0, alpha: 1.00)
237 | ),
238 | LifePreset(
239 | title: "Teenage Chapstick",
240 | appearanceMode: .dark,
241 | squareSize: nil,
242 | animationSpeed: nil,
243 | deathFade: nil,
244 | shiftingColors: nil,
245 | color1: SKColor(red: 208 / 255.0, green: 117 / 255.0, blue: 126 / 255.0, alpha: 1.00),
246 | color2: SKColor(red: 44 / 255.0, green: 80 / 255.0, blue: 80 / 255.0, alpha: 1.00),
247 | color3: SKColor(red: 156 / 255.0, green: 154 / 255.0, blue: 23 / 255.0, alpha: 1.00)
248 | ),
249 | LifePreset(
250 | title: "Trial by Fire",
251 | appearanceMode: .dark,
252 | squareSize: nil,
253 | animationSpeed: nil,
254 | deathFade: nil,
255 | shiftingColors: nil,
256 | color1: SKColor(red: 0.98, green: 0.75, blue: 0.00, alpha: 1.0),
257 | color2: SKColor(red: 1.00, green: 0.46, blue: 0.00, alpha: 1.0),
258 | color3: SKColor(red: 0.71, green: 0.13, blue: 0.01, alpha: 1.0)
259 | ),
260 | LifePreset(
261 | title: "Georgia",
262 | appearanceMode: .light,
263 | squareSize: nil,
264 | animationSpeed: nil,
265 | deathFade: nil,
266 | shiftingColors: nil,
267 | color1: SKColor(red: 237 / 255.0, green: 200 / 255.0, blue: 195 / 255.0, alpha: 1.00),
268 | color2: SKColor(red: 16 / 255.0, green: 103 / 255.0, blue: 110 / 255.0, alpha: 1.00),
269 | color3: SKColor(red: 247 / 255.0, green: 172 / 255.0, blue: 153 / 255.0, alpha: 1.00)
270 | ),
271 | LifePreset(
272 | title: "Boysenberry",
273 | appearanceMode: .dark,
274 | squareSize: nil,
275 | animationSpeed: nil,
276 | deathFade: nil,
277 | shiftingColors: nil,
278 | color1: SKColor(red: 122 / 255.0, green: 55 / 255.0, blue: 100 / 255.0, alpha: 1.00),
279 | color2: SKColor(red: 56 / 255.0, green: 66 / 255.0, blue: 109 / 255.0, alpha: 1.00),
280 | color3: SKColor(red: 160 / 255.0, green: 121 / 255.0, blue: 72 / 255.0, alpha: 1.00)
281 | ),
282 | LifePreset(
283 | title: "Tal Véz",
284 | appearanceMode: .dark,
285 | squareSize: nil,
286 | animationSpeed: nil,
287 | deathFade: nil,
288 | shiftingColors: nil,
289 | color1: SKColor(red: 59 / 255.0, green: 67 / 255.0, blue: 72 / 255.0, alpha: 1.00),
290 | color2: SKColor(red: 247 / 255.0, green: 201 / 255.0, blue: 177 / 255.0, alpha: 1.00),
291 | color3: SKColor(red: 176 / 255.0, green: 197 / 255.0, blue: 223 / 255.0, alpha: 1.00)
292 | ),
293 | LifePreset(
294 | title: "Deep Forest",
295 | appearanceMode: .dark,
296 | squareSize: nil,
297 | animationSpeed: nil,
298 | deathFade: nil,
299 | shiftingColors: nil,
300 | color1: SKColor(red: 36 / 255.0, green: 55 / 255.0, blue: 13 / 255.0, alpha: 1.00),
301 | color2: SKColor(red: 55 / 255.0, green: 60 / 255.0, blue: 13 / 255.0, alpha: 1.00),
302 | color3: SKColor(red: 10 / 255.0, green: 44 / 255.0, blue: 24 / 255.0, alpha: 1.00)
303 | ),
304 | LifePreset(
305 | title: "Bite Me",
306 | appearanceMode: .dark,
307 | squareSize: nil,
308 | animationSpeed: nil,
309 | deathFade: nil,
310 | shiftingColors: nil,
311 | color1: SKColor(red: 243 / 255.0, green: 136 / 255.0, blue: 103 / 255.0, alpha: 1.00),
312 | color2: SKColor(red: 241 / 255.0, green: 188 / 255.0, blue: 151 / 255.0, alpha: 1.00),
313 | color3: SKColor(red: 252 / 255.0, green: 53 / 255.0, blue: 113 / 255.0, alpha: 1.00)
314 | ),
315 | LifePreset(
316 | title: "Monochrome Red",
317 | appearanceMode: .dark,
318 | squareSize: nil,
319 | animationSpeed: nil,
320 | deathFade: nil,
321 | shiftingColors: nil,
322 | color1: SKColor(red: 229 / 255, green: 0 / 255, blue: 0 / 255, alpha: 1.0),
323 | color2: SKColor(red: 204 / 255, green: 0 / 255, blue: 0 / 255, alpha: 1.0),
324 | color3: SKColor(red: 178 / 255, green: 0 / 255, blue: 0 / 255, alpha: 1.0)
325 | ),
326 | LifePreset(
327 | title: "Custom",
328 | appearanceMode: nil,
329 | squareSize: nil,
330 | animationSpeed: nil,
331 | deathFade: nil,
332 | shiftingColors: nil,
333 | color1: nil,
334 | color2: nil,
335 | color3: nil
336 | ),
337 | ]
338 |
--------------------------------------------------------------------------------
/Shared/LifeScene.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LifeScene.swift
3 | // Life Saver
4 | //
5 | // Created by Bradley Root on 5/18/19.
6 | // Copyright © 2019 Brad Root. All rights reserved.
7 | //
8 | // This Source Code Form is subject to the terms of the Mozilla Public
9 | // License, v. 2.0. If a copy of the MPL was not distributed with this
10 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
11 |
12 | import GameplayKit
13 | import SpriteKit
14 |
15 | final class LifeScene: SKScene, LifeManagerDelegate {
16 | // MARK: - Settings
17 |
18 | var aliveColors: [SKColor] = [
19 | SKColor.defaultColor1,
20 | SKColor.defaultColor2,
21 | SKColor.defaultColor3,
22 | ]
23 |
24 | var appearanceColor: SKColor = .black
25 | var appearanceMode: Appearance = .dark {
26 | didSet {
27 | switch appearanceMode {
28 | case .dark:
29 | appearanceColor = .black
30 | case .light:
31 | appearanceColor = .white
32 | }
33 | }
34 | }
35 |
36 | var deathFade: Bool = true
37 | var shiftingColors: Bool = false
38 |
39 | private var animationTime: TimeInterval = 2
40 | private var updateTime: TimeInterval = 2
41 | var animationSpeed: AnimationSpeed = .normal {
42 | didSet {
43 | switch animationSpeed {
44 | case .fast:
45 | animationTime = 0.6
46 | updateTime = 0.6
47 | case .normal:
48 | animationTime = 2
49 | updateTime = 2
50 | case .slow:
51 | animationTime = 5
52 | updateTime = 5
53 | case .off:
54 | animationTime = 0
55 | updateTime = 0.1
56 | }
57 | }
58 | }
59 |
60 | var squareSize: SquareSize = .medium
61 |
62 | // MARK: - Manager
63 |
64 | var manager: LifeManager? {
65 | didSet {
66 | manager?.delegate = self
67 | }
68 | }
69 |
70 | func updatedSettings() {
71 | print("Updated Settings")
72 | isUpdating = false
73 | endLife()
74 | perform(#selector(createField), with: nil, afterDelay: 0.5)
75 | }
76 |
77 | // MARK: - Scene Lifecycle
78 |
79 | override func sceneDidLoad() {
80 | size.width = frame.size.width * 2
81 | size.height = frame.size.height * 2
82 | backgroundColor = .black
83 | }
84 |
85 | override func didMove(to _: SKView) {
86 | backgroundNode = SKSpriteNode(texture: squareTexture, color: appearanceColor, size: frame.size)
87 | backgroundNode.alpha = 0
88 | addChild(backgroundNode)
89 | backgroundNode.position = CGPoint(x: frame.width / 2, y: frame.height / 2)
90 | backgroundNode.zPosition = 0
91 |
92 | let fadeIn = SKAction.fadeIn(withDuration: 0.5)
93 | backgroundNode.run(fadeIn)
94 |
95 | createField()
96 | }
97 |
98 | private var lastUpdate: TimeInterval = 0
99 |
100 | private var isUpdating: Bool = true
101 |
102 | override func update(_ currentTime: TimeInterval) {
103 | if lastUpdate == 0 || currentTime - lastUpdate >= updateTime {
104 | lastUpdate = currentTime
105 | if isUpdating {
106 | updateLife()
107 | }
108 | }
109 | }
110 |
111 | // MARK: - Life Parameters
112 |
113 | private var backgroundNode: SKSpriteNode = SKSpriteNode()
114 | private var allNodes: [LifeNode] = []
115 | private var aliveNodes: [LifeNode] = []
116 | private var livingNodeHistory: [Int] = []
117 | private var lengthSquares: CGFloat = 16
118 | private var heightSquares: CGFloat = 9
119 | private var matrix: ToroidalMatrix = ToroidalMatrix(
120 | rows: 0,
121 | columns: 0,
122 | defaultValue: LifeNode(
123 | relativePosition: .zero,
124 | alive: false,
125 | color: .black,
126 | size: .zero
127 | )
128 | )
129 |
130 | // MARK: - Life Creation
131 |
132 | @objc func createField() {
133 | if let manager = manager {
134 | appearanceMode = manager.appearanceMode
135 | squareSize = manager.squareSize
136 | animationSpeed = manager.animationSpeed
137 | aliveColors = [manager.color1, manager.color2, manager.color3]
138 | deathFade = manager.deathFade
139 | shiftingColors = manager.shiftingColors
140 | }
141 |
142 | if backgroundNode.color != appearanceColor {
143 | let colorize = SKAction.colorize(with: appearanceColor, colorBlendFactor: 1.0, duration: 0.5)
144 | backgroundNode.run(colorize) {
145 | self.isUpdating = true
146 | }
147 | }
148 |
149 | scaleMode = .aspectFill
150 |
151 | switch squareSize {
152 | case .large:
153 | lengthSquares = 7
154 | heightSquares = 4
155 | case .medium:
156 | lengthSquares = 16
157 | heightSquares = 9
158 | case .small:
159 | lengthSquares = 32
160 | heightSquares = 18
161 | case .verySmall:
162 | lengthSquares = 64
163 | heightSquares = 36
164 | case .superSmall:
165 | lengthSquares = 128
166 | heightSquares = 74
167 | }
168 |
169 | createLife()
170 | }
171 |
172 | fileprivate func createLife() {
173 | matrix = ToroidalMatrix(
174 | rows: Int(lengthSquares),
175 | columns: Int(heightSquares),
176 | defaultValue: LifeNode(
177 | relativePosition: .zero,
178 | alive: false,
179 | color: .black,
180 | size: .zero
181 | )
182 | )
183 |
184 | let totalSquares: CGFloat = lengthSquares * heightSquares
185 | let squareWidth: CGFloat = size.width / lengthSquares
186 | let squareHeight: CGFloat = size.height / heightSquares
187 |
188 | // Create Nodes
189 | var nextXValue: Int = 0
190 | var nextYValue: Int = 0
191 | var nextXPosition: CGFloat = 0
192 | var nextYPosition: CGFloat = 0
193 | for _ in 1 ... Int(totalSquares) {
194 | let actualPosition = CGPoint(x: nextXPosition, y: nextYPosition)
195 | let relativePosition = CGPoint(x: nextXValue, y: nextYValue)
196 | let squareSize = CGSize(width: squareWidth, height: squareHeight)
197 |
198 | createLifeSquare(relativePosition, squareSize, actualPosition)
199 |
200 | if nextXValue == Int(lengthSquares) - 1 {
201 | nextXValue = 0
202 | nextXPosition = 0
203 | nextYValue += 1
204 | nextYPosition += squareHeight
205 | } else {
206 | nextXValue += 1
207 | nextXPosition += squareWidth
208 | }
209 | }
210 |
211 | // Pre-fetch Neighbors
212 | for node in allNodes {
213 | createNeighbors(node)
214 | }
215 | }
216 |
217 | fileprivate func createLifeSquare(_ relativePosition: CGPoint, _ squareSize: CGSize, _ actualPosition: CGPoint) {
218 | let newSquare = LifeNode(
219 | relativePosition: relativePosition,
220 | alive: false,
221 | color: appearanceColor,
222 | size: squareSize
223 | )
224 | addChild(newSquare)
225 | newSquare.position = actualPosition
226 | newSquare.alpha = 0
227 |
228 | if newSquare.alive {
229 | aliveNodes.append(newSquare)
230 | newSquare.color = aliveColors.randomElement()!
231 | }
232 | allNodes.append(newSquare)
233 | matrix[Int(relativePosition.x), Int(relativePosition.y)] = newSquare
234 | }
235 |
236 | fileprivate func createNeighbors(_ node: LifeNode) {
237 | var neighbors: [LifeNode] = []
238 | neighbors.append(matrix[Int(node.relativePosition.x - 1), Int(node.relativePosition.y)])
239 | neighbors.append(matrix[Int(node.relativePosition.x + 1), Int(node.relativePosition.y)])
240 | neighbors.append(matrix[Int(node.relativePosition.x), Int(node.relativePosition.y + 1)])
241 | neighbors.append(matrix[Int(node.relativePosition.x), Int(node.relativePosition.y - 1)])
242 | neighbors.append(matrix[Int(node.relativePosition.x + 1), Int(node.relativePosition.y + 1)])
243 | neighbors.append(matrix[Int(node.relativePosition.x - 1), Int(node.relativePosition.y - 1)])
244 | neighbors.append(matrix[Int(node.relativePosition.x - 1), Int(node.relativePosition.y + 1)])
245 | neighbors.append(matrix[Int(node.relativePosition.x + 1), Int(node.relativePosition.y - 1)])
246 | node.neighbors = neighbors
247 | }
248 |
249 | // MARK: - Life Ending
250 |
251 | fileprivate func destroyField() {
252 | removeAllChildren()
253 | }
254 |
255 | fileprivate func endLife() {
256 | allNodes.forEach { $0.remove(duration: 0.5) }
257 | allNodes.removeAll()
258 | aliveNodes.removeAll()
259 | livingNodeHistory.removeAll()
260 | }
261 |
262 | // MARK: - Life Updates
263 |
264 | fileprivate func updateLife() {
265 | var dyingNodes: [LifeNode] = []
266 | var livingNodes: [LifeNode] = []
267 | for node in allNodes {
268 | // Get living neighbors...
269 | let livingNeighbors = node.neighbors.filter { $0.alive }
270 |
271 | if node.alive {
272 | if livingNeighbors.count > 3 || livingNeighbors.count < 2 {
273 | dyingNodes.append(node)
274 | } else {
275 | livingNodes.append(node)
276 | }
277 | } else if livingNeighbors.count == 3 {
278 | var livingColor = livingNeighbors.randomElement()!.color
279 | #if os(tvOS)
280 | if shiftingColors {
281 | livingColor = livingColor.modified(withAdditionalHue: 0.005, additionalSaturation: 0, additionalBrightness: 0)
282 | }
283 | #endif
284 | node.aliveColor = livingColor
285 | livingNodes.append(node)
286 | } else {
287 | dyingNodes.append(node)
288 | }
289 | }
290 |
291 | // If entire tank is dead, generate a new tank!
292 | if CGFloat(livingNodes.count) == 0 {
293 | createRandomShapes(&dyingNodes, &livingNodes)
294 | }
295 |
296 | // Static tank prevention
297 | if livingNodeHistory.count >= 10 {
298 | livingNodeHistory.removeFirst()
299 | livingNodeHistory.append(livingNodes.count)
300 | if 1 ... 2 ~= Set(livingNodeHistory).count {
301 | dyingNodes.append(contentsOf: livingNodes)
302 | livingNodes.removeAll()
303 | }
304 | } else {
305 | livingNodeHistory.append(livingNodes.count)
306 | }
307 |
308 | // Update nodes here
309 | dyingNodes.forEach {
310 | $0.die(duration: animationTime * 5, fade: deathFade)
311 | }
312 |
313 | livingNodes.forEach {
314 | $0.live(duration: animationTime)
315 | }
316 |
317 | aliveNodes = livingNodes
318 | }
319 |
320 | fileprivate func createRandomShapes(_: inout [LifeNode], _ livingNodes: inout [LifeNode]) {
321 | var totalShapes: Int = 0
322 | switch squareSize {
323 | case .superSmall:
324 | totalShapes = 500
325 | case .verySmall:
326 | totalShapes = 50
327 | case .small:
328 | totalShapes = 20
329 | case .medium:
330 | totalShapes = 10
331 | case .large:
332 | totalShapes = 4
333 | }
334 | for _ in 1 ... totalShapes {
335 | let nodeNumber = GKRandomSource.sharedRandom().nextInt(upperBound: allNodes.count)
336 | let color = aliveColors.randomElement()!
337 | let node = allNodes[nodeNumber]
338 | for neighborNode in node.neighbors where Int.random(in: 0 ... 1) == 1 {
339 | neighborNode.aliveColor = color
340 | livingNodes.append(neighborNode)
341 | }
342 | }
343 | }
344 | }
345 |
--------------------------------------------------------------------------------
/Shared/Utilities/Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Extensions.swift
3 | // Life Saver
4 | //
5 | // Created by Bradley Root on 6/29/19.
6 | // Copyright © 2019 Brad Root. All rights reserved.
7 | //
8 |
9 | import SpriteKit
10 |
11 | extension SKColor {
12 | func modified(withAdditionalHue hue: CGFloat, additionalSaturation: CGFloat, additionalBrightness: CGFloat) -> SKColor {
13 | var currentHue: CGFloat = 0.0
14 | var currentSaturation: CGFloat = 0.0
15 | var currentBrigthness: CGFloat = 0.0
16 | var currentAlpha: CGFloat = 0.0
17 |
18 | if getHue(¤tHue, saturation: ¤tSaturation, brightness: ¤tBrigthness, alpha: ¤tAlpha) {
19 | return SKColor(
20 | hue: currentHue + hue > 1 ? hue : currentHue + hue,
21 | saturation: currentSaturation + additionalSaturation,
22 | brightness: currentBrigthness + additionalBrightness,
23 | alpha: currentAlpha
24 | )
25 | } else {
26 | return self
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Shared/Utilities/FileGrabber.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FileGrabber.swift
3 | // Life Saver
4 | //
5 | // Created by Brad Root on 5/21/19.
6 | // Copyright © 2019 Brad Root. All rights reserved.
7 | //
8 | // This Source Code Form is subject to the terms of the Mozilla Public
9 | // License, v. 2.0. If a copy of the MPL was not distributed with this
10 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
11 |
12 | import SpriteKit
13 |
14 | #if os(macOS)
15 | import Cocoa
16 |
17 | // Step 1: Typealias UIImage to NSImage
18 | typealias UIImage = NSImage
19 | #endif
20 |
21 | class FileGrabber {
22 | let bundle: Bundle
23 |
24 | static let shared: FileGrabber = FileGrabber()
25 |
26 | init() {
27 | let bundle = Bundle(for: LifeScene.self)
28 | self.bundle = bundle
29 | }
30 |
31 | public func getSKTexture(named: String) -> SKTexture? {
32 | #if os(macOS)
33 | guard let image = bundle.image(forResource: named) else { return nil }
34 | #else
35 | guard let image = UIImage(named: named, in: bundle, compatibleWith: nil) else { return nil }
36 | #endif
37 | return SKTexture(image: image)
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Shared/Utilities/ToroidalMatrix.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ToroidalMatrix.swift
3 | // Derived from dimo hamdy https://stackoverflow.com/a/53421491/2117288
4 | // https://gist.github.com/amiantos/bb0f313da1ee686f4f69b8b44f3cd184
5 | //
6 | // This Source Code Form is subject to the terms of the Mozilla Public
7 | // License, v. 2.0. If a copy of the MPL was not distributed with this
8 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 |
10 | struct ToroidalMatrix {
11 | let rows: Int, columns: Int
12 | var grid: [T]
13 |
14 | init(rows: Int, columns: Int, defaultValue: T) {
15 | self.rows = rows
16 | self.columns = columns
17 | grid = Array(repeating: defaultValue, count: rows * columns)
18 | }
19 |
20 | func indexIsValid(row: Int, column: Int) -> Bool {
21 | return row >= 0 && row < rows && column >= 0 && column < columns
22 | }
23 |
24 | subscript(row: Int, column: Int) -> T {
25 | get {
26 | let safeRow = 0 ... rows - 1 ~= row ? row : row > rows - 1 ? 0 : row < 0 ? rows - 1 : -1
27 | let safeColumn = 0 ... columns - 1 ~= column ? column : column > columns - 1 ? 0 : column < 0 ? columns - 1 : -1
28 | assert(indexIsValid(row: safeRow, column: safeColumn), "Index out of range")
29 | return grid[(safeRow * columns) + safeColumn]
30 | }
31 |
32 | set {
33 | assert(indexIsValid(row: row, column: column), "Index out of range")
34 | grid[(row * columns) + column] = newValue
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Shared/Utilities/URLType.swift:
--------------------------------------------------------------------------------
1 | //
2 | // URLType.swift
3 | // Life Saver Screensaver
4 | //
5 | // Created by Brad Root on 5/22/19.
6 | // Copyright © 2019 Brad Root. All rights reserved.
7 | //
8 | // This Source Code Form is subject to the terms of the Mozilla Public
9 | // License, v. 2.0. If a copy of the MPL was not distributed with this
10 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
11 |
12 | import Cocoa
13 |
14 | enum URLType: String {
15 | case brad = "https://amiantos.net"
16 | case github = "https://github.com/amiantos/lifesaver"
17 | case twitter = "https://twitter.com/amiantos"
18 | case website = "https://amiantos.net/lifesaver"
19 | }
20 |
21 | extension URLType {
22 | func open() {
23 | guard let url = URL(string: rawValue) else { return }
24 | NSWorkspace.shared.open(url)
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/macOS Screensaver/Configuration/ConfigureSheetController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ConfigureSheetController.swift
3 | // Life Saver Screensaver
4 | //
5 | // Created by Brad Root on 5/21/19.
6 | // Copyright © 2019 Brad Root. All rights reserved.
7 | //
8 | // This Source Code Form is subject to the terms of the Mozilla Public
9 | // License, v. 2.0. If a copy of the MPL was not distributed with this
10 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
11 |
12 | import Cocoa
13 | import SpriteKit
14 |
15 | final class ConfigureSheetController: NSObject {
16 | private let manager = LifeManager()
17 |
18 | // MARK: - Presets
19 |
20 | fileprivate let presets: [LifePreset] = colorPresets
21 |
22 | // MARK: - Config Actions and Outlets
23 |
24 | @IBOutlet var window: NSWindow?
25 |
26 | @IBOutlet var stylePresetsButton: NSSegmentedControl!
27 | @IBAction func stylePresetsAction(_ sender: NSSegmentedControl) {
28 | switch sender.selectedSegment {
29 | case 0:
30 | let simulationSettings = LifePreset(
31 | title: "Simulation",
32 | appearanceMode: nil,
33 | squareSize: .small,
34 | animationSpeed: .fast,
35 | deathFade: nil,
36 | shiftingColors: nil,
37 | color1: nil,
38 | color2: nil,
39 | color3: nil
40 | )
41 | loadPreset(simulationSettings)
42 | case 2:
43 | let abstractSettings = LifePreset(
44 | title: "Abstract",
45 | appearanceMode: nil,
46 | squareSize: .large,
47 | animationSpeed: .slow,
48 | deathFade: nil,
49 | shiftingColors: nil,
50 | color1: nil,
51 | color2: nil,
52 | color3: nil
53 | )
54 | loadPreset(abstractSettings)
55 | default:
56 | let defaultSettings = LifePreset(
57 | title: "Defaults",
58 | appearanceMode: nil,
59 | squareSize: .medium,
60 | animationSpeed: .normal,
61 | deathFade: nil,
62 | shiftingColors: nil,
63 | color1: nil,
64 | color2: nil,
65 | color3: nil
66 | )
67 | loadPreset(defaultSettings)
68 | }
69 | }
70 |
71 | @IBOutlet var presetsButton: NSPopUpButton!
72 | @IBAction func presetsAction(_ sender: NSPopUpButton) {
73 | guard let title = sender.titleOfSelectedItem else { return }
74 | let soughtPreset = presets.filter { $0.title == title }.first
75 | if let preset = soughtPreset {
76 | loadPreset(preset)
77 | }
78 | }
79 |
80 | @IBOutlet var appearanceControl: NSSegmentedControl!
81 | @IBAction func appearanceAction(_ sender: NSSegmentedControl) {
82 | switch sender.selectedSegment {
83 | case 1:
84 | manager.setAppearanceMode(Appearance.light)
85 | default:
86 | manager.setAppearanceMode(Appearance.dark)
87 | }
88 | updateColorPresetsControl()
89 | }
90 |
91 | @IBOutlet var squareSizeControl: NSSegmentedControl!
92 | @IBAction func squareSizeAction(_ sender: NSSegmentedControl) {
93 | switch sender.selectedSegment {
94 | case 0:
95 | manager.setSquareSize(.superSmall)
96 | case 1:
97 | manager.setSquareSize(.verySmall)
98 | case 2:
99 | manager.setSquareSize(.small)
100 | case 3:
101 | manager.setSquareSize(.medium)
102 | case 4:
103 | manager.setSquareSize(.large)
104 | default:
105 | manager.setSquareSize(.medium)
106 | }
107 | updateStylePresetsControl()
108 | }
109 |
110 | @IBOutlet var animationSpeedControl: NSSegmentedControl!
111 | @IBAction func animationSpeedAction(_ sender: NSSegmentedControl) {
112 | switch sender.selectedSegment {
113 | case 0:
114 | manager.setAnimationSpeed(.fast)
115 | case 2:
116 | manager.setAnimationSpeed(.slow)
117 | default:
118 | manager.setAnimationSpeed(.normal)
119 | }
120 | updateStylePresetsControl()
121 | }
122 |
123 | @IBOutlet var color1Well: NSColorWell!
124 | @IBAction func color1Action(_ sender: NSColorWell) {
125 | manager.setColor(sender.color as SKColor, for: .color1)
126 | updateColorPresetsControl()
127 | }
128 |
129 | @IBOutlet var color2Well: NSColorWell!
130 | @IBAction func color2Action(_ sender: NSColorWell) {
131 | manager.setColor(sender.color as SKColor, for: .color2)
132 | updateColorPresetsControl()
133 | }
134 |
135 | @IBOutlet var color3Well: NSColorWell!
136 | @IBAction func color3Action(_ sender: NSColorWell) {
137 | manager.setColor(sender.color as SKColor, for: .color3)
138 | updateColorPresetsControl()
139 | }
140 |
141 | @IBOutlet var randomColorPresetCheck: NSButton!
142 | @IBAction func randomColorPresetAction(_ sender: NSButtonCell) {
143 | manager.setRandomColorPreset(sender.state == .on ? true : false)
144 | updateColorPresetsControl()
145 | }
146 |
147 | @IBAction func twitterAction(_: NSButton) {
148 | URLType.twitter.open()
149 | }
150 |
151 | @IBAction func gitHubAction(_: NSButton) {
152 | URLType.github.open()
153 | }
154 |
155 | @IBAction func bradAction(_: NSButton) {
156 | URLType.brad.open()
157 | }
158 |
159 | @IBAction func websiteAction(_: NSButton) {
160 | URLType.website.open()
161 | }
162 |
163 | @IBAction func closeConfigureSheet(sender _: AnyObject) {
164 | guard let window = window else { return }
165 | window.sheetParent?.endSheet(window)
166 | }
167 |
168 | // MARK: - View Setup
169 |
170 | override init() {
171 | super.init()
172 | let myBundle = Bundle(for: ConfigureSheetController.self)
173 | myBundle.loadNibNamed("ConfigureSheet", owner: self, topLevelObjects: nil)
174 |
175 | randomColorPresetCheck.toolTip = "Enable this to have a random color preset selected each time the screensaver loads."
176 |
177 | loadPresets()
178 | loadSettings()
179 | }
180 |
181 | fileprivate func loadSettings() {
182 | switch manager.appearanceMode {
183 | case .dark:
184 | appearanceControl.selectedSegment = 0
185 | case .light:
186 | appearanceControl.selectedSegment = 1
187 | }
188 |
189 | switch manager.squareSize {
190 | case .superSmall:
191 | squareSizeControl.selectedSegment = 0
192 | case .verySmall:
193 | squareSizeControl.selectedSegment = 1
194 | case .small:
195 | squareSizeControl.selectedSegment = 2
196 | case .medium:
197 | squareSizeControl.selectedSegment = 3
198 | case .large:
199 | squareSizeControl.selectedSegment = 4
200 | }
201 |
202 | switch manager.animationSpeed {
203 | case .normal:
204 | animationSpeedControl.selectedSegment = 1
205 | case .fast:
206 | animationSpeedControl.selectedSegment = 0
207 | case .slow:
208 | animationSpeedControl.selectedSegment = 2
209 | case .off:
210 | animationSpeedControl.selectedSegment = 1
211 | }
212 |
213 | color1Well.color = manager.color1
214 | color2Well.color = manager.color2
215 | color3Well.color = manager.color3
216 |
217 | randomColorPresetCheck.state = manager.randomColorPreset ? .on : .off
218 |
219 | updateStylePresetsControl()
220 | updateColorPresetsControl()
221 | }
222 |
223 | fileprivate func loadPresets() {
224 | presetsButton.removeAllItems()
225 | var presetTitles: [String] = []
226 | for preset in presets {
227 | presetTitles.append(preset.title)
228 | }
229 | presetsButton.addItems(withTitles: presetTitles)
230 | }
231 |
232 | fileprivate func updateStylePresetsControl() {
233 | if manager.animationSpeed == .fast, manager.squareSize == .small {
234 | stylePresetsButton.selectSegment(withTag: 0)
235 | } else if manager.animationSpeed == .normal, manager.squareSize == .medium {
236 | stylePresetsButton.selectSegment(withTag: 1)
237 | } else if manager.animationSpeed == .slow, manager.squareSize == .large {
238 | stylePresetsButton.selectSegment(withTag: 2)
239 | } else {
240 | stylePresetsButton.setSelected(false, forSegment: stylePresetsButton.selectedSegment)
241 | }
242 | }
243 |
244 | fileprivate func updateColorPresetsControl() {
245 | let filteredPresets = presets.filter { $0.color1 == manager.color1 && $0.color2 == manager.color2 && $0.color3 == manager.color3 }
246 | presetsButton.selectItem(withTitle: filteredPresets.first?.title ?? "Custom")
247 |
248 | presetsButton.isEnabled = !manager.randomColorPreset
249 | color1Well.isEnabled = !manager.randomColorPreset
250 | color2Well.isEnabled = !manager.randomColorPreset
251 | color3Well.isEnabled = !manager.randomColorPreset
252 | appearanceControl.isEnabled = !manager.randomColorPreset
253 | }
254 |
255 | fileprivate func loadPreset(_ preset: LifePreset) {
256 | manager.configure(with: preset)
257 | loadSettings()
258 | }
259 | }
260 |
--------------------------------------------------------------------------------
/macOS Screensaver/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | Life Saver
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | $(MARKETING_VERSION)
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 | NSHumanReadableCopyright
22 | Copyright © 2019 Brad Root. All rights reserved.
23 | NSPrincipalClass
24 | Life_Saver_Screensaver.LifeScreenSaverView
25 |
26 |
27 |
--------------------------------------------------------------------------------
/macOS Screensaver/LifeDatabase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LifeDatabase.swift
3 | // Life Saver
4 | //
5 | // Created by Brad Root on 5/21/19.
6 | // Copyright © 2019 Brad Root. All rights reserved.
7 | //
8 | // This Source Code Form is subject to the terms of the Mozilla Public
9 | // License, v. 2.0. If a copy of the MPL was not distributed with this
10 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
11 |
12 | import ScreenSaver
13 | import SpriteKit
14 |
15 | struct LifeDatabase {
16 | fileprivate enum Key {
17 | static let appearanceMode = "appearanceMode"
18 | static let squareSize = "squareSize"
19 | static let blurAmount = "blurAmount"
20 | static let animationSpeed = "animationSpeed"
21 | static let color1 = "color1"
22 | static let color2 = "color2"
23 | static let color3 = "color3"
24 | static let randomColorPreset = "randomColorPreset"
25 | static let selectedPresetTitle = "selectedPresetTitle"
26 | static let deathFade = "deathFade"
27 | static let shiftingColors = "shiftingColors"
28 | static let hasPressedMenuButton = "hasPressedMenuButton"
29 | }
30 |
31 | static var standard: ScreenSaverDefaults {
32 | guard let bundleIdentifier = Bundle(for: LifeManager.self).bundleIdentifier,
33 | let database = ScreenSaverDefaults(forModuleWithName: bundleIdentifier)
34 | else { fatalError("Failed to retrieve database") }
35 |
36 | database.register(defaults:
37 | [Key.appearanceMode: Appearance.dark.rawValue,
38 | Key.animationSpeed: AnimationSpeed.normal.rawValue,
39 | Key.squareSize: SquareSize.medium.rawValue,
40 | Key.color1: archiveData(SKColor.defaultColor1),
41 | Key.color2: archiveData(SKColor.defaultColor2),
42 | Key.color3: archiveData(SKColor.defaultColor3),
43 | Key.randomColorPreset: false,
44 | Key.deathFade: true,
45 | Key.shiftingColors: false,
46 | Key.selectedPresetTitle: "Santa Fe",
47 | Key.hasPressedMenuButton: false])
48 |
49 | return database
50 | }
51 | }
52 |
53 | extension ScreenSaverDefaults {
54 | var appearanceMode: Appearance {
55 | return Appearance(rawValue: integer(forKey: LifeDatabase.Key.appearanceMode))!
56 | }
57 |
58 | func set(appearanceMode: Appearance) {
59 | set(appearanceMode.rawValue, for: LifeDatabase.Key.appearanceMode)
60 | }
61 |
62 | var squareSize: SquareSize {
63 | return SquareSize(rawValue: integer(forKey: LifeDatabase.Key.squareSize))!
64 | }
65 |
66 | func set(squareSize: SquareSize) {
67 | set(squareSize.rawValue, for: LifeDatabase.Key.squareSize)
68 | }
69 |
70 | var animationSpeed: AnimationSpeed {
71 | return AnimationSpeed(rawValue: integer(forKey: LifeDatabase.Key.animationSpeed))!
72 | }
73 |
74 | func set(animationSpeed: AnimationSpeed) {
75 | set(animationSpeed.rawValue, for: LifeDatabase.Key.animationSpeed)
76 | }
77 |
78 | var randomColorPreset: Bool {
79 | return bool(forKey: LifeDatabase.Key.randomColorPreset)
80 | }
81 |
82 | func set(randomColorPreset: Bool) {
83 | set(randomColorPreset, for: LifeDatabase.Key.randomColorPreset)
84 | }
85 |
86 | var shiftingColors: Bool {
87 | return bool(forKey: LifeDatabase.Key.shiftingColors)
88 | }
89 |
90 | func set(shiftingColors: Bool) {
91 | set(shiftingColors, for: LifeDatabase.Key.shiftingColors)
92 | }
93 |
94 | var deathFade: Bool {
95 | return bool(forKey: LifeDatabase.Key.deathFade)
96 | }
97 |
98 | func set(deathFade: Bool) {
99 | set(deathFade, for: LifeDatabase.Key.deathFade)
100 | }
101 |
102 | var hasPressedMenuButton: Bool {
103 | return bool(forKey: LifeDatabase.Key.hasPressedMenuButton)
104 | }
105 |
106 | func set(hasPressedMenuButton: Bool) {
107 | set(hasPressedMenuButton, for: LifeDatabase.Key.hasPressedMenuButton)
108 | }
109 |
110 | var selectedPresetTitle: String {
111 | return string(forKey: LifeDatabase.Key.selectedPresetTitle) ?? ""
112 | }
113 |
114 | func set(selectedPresetTitle: String) {
115 | set(selectedPresetTitle, for: LifeDatabase.Key.selectedPresetTitle)
116 | }
117 |
118 | func getColor(_ color: Colors) -> SKColor {
119 | switch color {
120 | case .color1:
121 | return unarchiveColor(data(forKey: LifeDatabase.Key.color1)!)
122 | case .color2:
123 | return unarchiveColor(data(forKey: LifeDatabase.Key.color2)!)
124 | case .color3:
125 | return unarchiveColor(data(forKey: LifeDatabase.Key.color3)!)
126 | }
127 | }
128 |
129 | func set(_ color: SKColor, for colors: Colors) {
130 | switch colors {
131 | case .color1:
132 | set(archiveData(color), for: LifeDatabase.Key.color1)
133 | case .color2:
134 | set(archiveData(color), for: LifeDatabase.Key.color2)
135 | case .color3:
136 | set(archiveData(color), for: LifeDatabase.Key.color3)
137 | }
138 | }
139 | }
140 |
141 | private extension ScreenSaverDefaults {
142 | func set(_ object: Any, for key: String) {
143 | set(object, forKey: key)
144 | synchronize()
145 | }
146 | }
147 |
148 | private func archiveData(_ data: Any) -> Data {
149 | do {
150 | let data = try NSKeyedArchiver.archivedData(withRootObject: data, requiringSecureCoding: false)
151 | return data
152 | } catch {
153 | fatalError("Failed to archive data")
154 | }
155 | }
156 |
157 | private func unarchiveColor(_ data: Data) -> SKColor {
158 | do {
159 | let color = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? SKColor
160 | return color!
161 | } catch {
162 | fatalError("Failed to unarchive data")
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/macOS Screensaver/LifeScreenSaverView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ScreenSaverView.swift
3 | // Life Saver Screensaver
4 | //
5 | // Created by Bradley Root on 5/18/19.
6 | // Copyright © 2019 Brad Root. All rights reserved.
7 | //
8 | // This Source Code Form is subject to the terms of the Mozilla Public
9 | // License, v. 2.0. If a copy of the MPL was not distributed with this
10 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
11 |
12 | import Foundation
13 | import ScreenSaver
14 | import SpriteKit
15 |
16 | final class LifeScreenSaverView: ScreenSaverView {
17 | var spriteView: SKView?
18 |
19 | lazy var sheetController: ConfigureSheetController = ConfigureSheetController()
20 |
21 | override init?(frame: NSRect, isPreview: Bool) {
22 | super.init(frame: frame, isPreview: isPreview)
23 | animationTimeInterval = 1.0
24 | }
25 |
26 | required init?(coder: NSCoder) {
27 | super.init(coder: coder)
28 | animationTimeInterval = 1.0
29 | }
30 |
31 | override var frame: NSRect {
32 | didSet {
33 | self.spriteView?.frame = frame
34 | }
35 | }
36 |
37 | override var hasConfigureSheet: Bool {
38 | return true
39 | }
40 |
41 | override var configureSheet: NSWindow? {
42 | return sheetController.window
43 | }
44 |
45 | override func startAnimation() {
46 | if spriteView == nil {
47 | let manager = LifeManager()
48 | let spriteView = SKView(frame: frame)
49 | spriteView.ignoresSiblingOrder = true
50 | spriteView.showsFPS = false
51 | spriteView.showsNodeCount = false
52 | spriteView.preferredFramesPerSecond = 30
53 | let scene = LifeScene(size: frame.size)
54 | self.spriteView = spriteView
55 | addSubview(spriteView)
56 |
57 | if manager.randomColorPreset, let preset = colorPresets.randomElement() {
58 | manager.configure(with: preset)
59 | }
60 |
61 | scene.appearanceMode = manager.appearanceMode
62 | scene.squareSize = manager.squareSize
63 | scene.animationSpeed = manager.animationSpeed
64 | scene.aliveColors = [manager.color1, manager.color2, manager.color3]
65 |
66 | scene.isUserInteractionEnabled = false
67 |
68 | spriteView.presentScene(scene)
69 | }
70 | super.startAnimation()
71 | }
72 |
73 | override func stopAnimation() {
74 | super.stopAnimation()
75 | spriteView = nil
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/macOS/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // Life Saver
4 | //
5 | // Created by Bradley Root on 5/18/19.
6 | // Copyright © 2019 Brad Root. All rights reserved.
7 | //
8 | // This Source Code Form is subject to the terms of the Mozilla Public
9 | // License, v. 2.0. If a copy of the MPL was not distributed with this
10 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
11 |
12 | import Cocoa
13 |
14 | @NSApplicationMain
15 | class AppDelegate: NSObject, NSApplicationDelegate {
16 | func applicationDidFinishLaunching(_: Notification) {
17 | // Insert code here to initialize your application
18 | }
19 |
20 | func applicationWillTerminate(_: Notification) {
21 | // Insert code here to tear down your application
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/macOS/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIconFile
10 |
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleVersion
22 | 1
23 | LSApplicationCategoryType
24 | public.app-category.entertainment
25 | LSMinimumSystemVersion
26 | $(MACOSX_DEPLOYMENT_TARGET)
27 | NSHumanReadableCopyright
28 | Copyright © 2019 Brad Root. All rights reserved.
29 | NSMainStoryboardFile
30 | Main
31 | NSPrincipalClass
32 | NSApplication
33 |
34 |
35 |
--------------------------------------------------------------------------------
/macOS/Life_Saver.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.files.user-selected.read-only
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/macOS/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // Life Saver
4 | //
5 | // Created by Bradley Root on 5/18/19.
6 | // Copyright © 2019 Brad Root. All rights reserved.
7 | //
8 | // This Source Code Form is subject to the terms of the Mozilla Public
9 | // License, v. 2.0. If a copy of the MPL was not distributed with this
10 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
11 |
12 | import Cocoa
13 | import GameplayKit
14 | import SpriteKit
15 |
16 | class ViewController: NSViewController {
17 | @IBOutlet var skView: SKView!
18 |
19 | override func viewDidLoad() {
20 | super.viewDidLoad()
21 |
22 | let scene = LifeScene(size: view.frame.size)
23 |
24 | scene.animationSpeed = .normal
25 | scene.squareSize = .medium
26 |
27 | if let preset = colorPresets.filter({ $0.title == "Santa Fe" }).first {
28 | if let appearanceMode = preset.appearanceMode {
29 | scene.appearanceMode = appearanceMode
30 | }
31 | if let color1 = preset.color1, let color2 = preset.color2, let color3 = preset.color3 {
32 | scene.aliveColors = [color1, color2, color3]
33 | }
34 | }
35 |
36 | let skView = view as? SKView
37 | skView?.preferredFramesPerSecond = 30
38 | skView?.presentScene(scene)
39 | skView?.showsFPS = true
40 | skView?.showsDrawCount = true
41 | skView?.showsNodeCount = true
42 |
43 | skView?.ignoresSiblingOrder = true
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/tvOS/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // Life Saver
4 | //
5 | // Created by Bradley Root on 6/25/19.
6 | // Copyright © 2019 Brad Root. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 | var window: UIWindow?
14 |
15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
16 | // Override point for customization after application launch.
17 | application.isIdleTimerDisabled = true
18 | return true
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/tvOS/Assets.xcassets/Brand Assets.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "filename" : "TVA - Bottom Layer.png"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | }
12 | }
--------------------------------------------------------------------------------
/tvOS/Assets.xcassets/Brand Assets.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/TVA - Bottom Layer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amiantos/lifesaver/158ac5378c0686480e57709d25d8488b79a26eeb/tvOS/Assets.xcassets/Brand Assets.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/TVA - Bottom Layer.png
--------------------------------------------------------------------------------
/tvOS/Assets.xcassets/Brand Assets.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/tvOS/Assets.xcassets/Brand Assets.brandassets/App Icon - App Store.imagestack/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "layers" : [
3 | {
4 | "filename" : "Front.imagestacklayer"
5 | },
6 | {
7 | "filename" : "Middle.imagestacklayer"
8 | },
9 | {
10 | "filename" : "Back.imagestacklayer"
11 | }
12 | ],
13 | "info" : {
14 | "version" : 1,
15 | "author" : "xcode"
16 | }
17 | }
--------------------------------------------------------------------------------
/tvOS/Assets.xcassets/Brand Assets.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "filename" : "TVA - Top Layer.png"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | }
12 | }
--------------------------------------------------------------------------------
/tvOS/Assets.xcassets/Brand Assets.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/TVA - Top Layer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amiantos/lifesaver/158ac5378c0686480e57709d25d8488b79a26eeb/tvOS/Assets.xcassets/Brand Assets.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/TVA - Top Layer.png
--------------------------------------------------------------------------------
/tvOS/Assets.xcassets/Brand Assets.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/tvOS/Assets.xcassets/Brand Assets.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "filename" : "TVA - Middle Layer.png"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | }
12 | }
--------------------------------------------------------------------------------
/tvOS/Assets.xcassets/Brand Assets.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/TVA - Middle Layer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amiantos/lifesaver/158ac5378c0686480e57709d25d8488b79a26eeb/tvOS/Assets.xcassets/Brand Assets.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/TVA - Middle Layer.png
--------------------------------------------------------------------------------
/tvOS/Assets.xcassets/Brand Assets.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/tvOS/Assets.xcassets/Brand Assets.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "filename" : "TV - Bottom Layer.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "tv",
10 | "filename" : "TV - Bottom Layer@2x.png",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "version" : 1,
16 | "author" : "xcode"
17 | }
18 | }
--------------------------------------------------------------------------------
/tvOS/Assets.xcassets/Brand Assets.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/TV - Bottom Layer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amiantos/lifesaver/158ac5378c0686480e57709d25d8488b79a26eeb/tvOS/Assets.xcassets/Brand Assets.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/TV - Bottom Layer.png
--------------------------------------------------------------------------------
/tvOS/Assets.xcassets/Brand Assets.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/TV - Bottom Layer@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amiantos/lifesaver/158ac5378c0686480e57709d25d8488b79a26eeb/tvOS/Assets.xcassets/Brand Assets.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/TV - Bottom Layer@2x.png
--------------------------------------------------------------------------------
/tvOS/Assets.xcassets/Brand Assets.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/tvOS/Assets.xcassets/Brand Assets.brandassets/App Icon.imagestack/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "layers" : [
3 | {
4 | "filename" : "Front.imagestacklayer"
5 | },
6 | {
7 | "filename" : "Middle.imagestacklayer"
8 | },
9 | {
10 | "filename" : "Back.imagestacklayer"
11 | }
12 | ],
13 | "info" : {
14 | "version" : 1,
15 | "author" : "xcode"
16 | }
17 | }
--------------------------------------------------------------------------------
/tvOS/Assets.xcassets/Brand Assets.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "filename" : "TV - Top Layer.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "tv",
10 | "filename" : "TV - Top Layer@2x.png",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "version" : 1,
16 | "author" : "xcode"
17 | }
18 | }
--------------------------------------------------------------------------------
/tvOS/Assets.xcassets/Brand Assets.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/TV - Top Layer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amiantos/lifesaver/158ac5378c0686480e57709d25d8488b79a26eeb/tvOS/Assets.xcassets/Brand Assets.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/TV - Top Layer.png
--------------------------------------------------------------------------------
/tvOS/Assets.xcassets/Brand Assets.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/TV - Top Layer@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amiantos/lifesaver/158ac5378c0686480e57709d25d8488b79a26eeb/tvOS/Assets.xcassets/Brand Assets.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/TV - Top Layer@2x.png
--------------------------------------------------------------------------------
/tvOS/Assets.xcassets/Brand Assets.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/tvOS/Assets.xcassets/Brand Assets.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "filename" : "TV - Middle Layer.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "tv",
10 | "filename" : "TV - Middle Layer@2x.png",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "version" : 1,
16 | "author" : "xcode"
17 | }
18 | }
--------------------------------------------------------------------------------
/tvOS/Assets.xcassets/Brand Assets.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/TV - Middle Layer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amiantos/lifesaver/158ac5378c0686480e57709d25d8488b79a26eeb/tvOS/Assets.xcassets/Brand Assets.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/TV - Middle Layer.png
--------------------------------------------------------------------------------
/tvOS/Assets.xcassets/Brand Assets.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/TV - Middle Layer@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amiantos/lifesaver/158ac5378c0686480e57709d25d8488b79a26eeb/tvOS/Assets.xcassets/Brand Assets.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/TV - Middle Layer@2x.png
--------------------------------------------------------------------------------
/tvOS/Assets.xcassets/Brand Assets.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/tvOS/Assets.xcassets/Brand Assets.brandassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "assets" : [
3 | {
4 | "size" : "1280x768",
5 | "idiom" : "tv",
6 | "filename" : "App Icon - App Store.imagestack",
7 | "role" : "primary-app-icon"
8 | },
9 | {
10 | "size" : "400x240",
11 | "idiom" : "tv",
12 | "filename" : "App Icon.imagestack",
13 | "role" : "primary-app-icon"
14 | },
15 | {
16 | "size" : "2320x720",
17 | "idiom" : "tv",
18 | "filename" : "Top Shelf Image Wide.imageset",
19 | "role" : "top-shelf-image-wide"
20 | },
21 | {
22 | "size" : "1920x720",
23 | "idiom" : "tv",
24 | "filename" : "Top Shelf Image.imageset",
25 | "role" : "top-shelf-image"
26 | }
27 | ],
28 | "info" : {
29 | "version" : 1,
30 | "author" : "xcode"
31 | }
32 | }
--------------------------------------------------------------------------------
/tvOS/Assets.xcassets/Brand Assets.brandassets/Top Shelf Image Wide.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "filename" : "Top Shelf.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "tv",
10 | "filename" : "Top Shelf@2x.png",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "version" : 1,
16 | "author" : "xcode"
17 | }
18 | }
--------------------------------------------------------------------------------
/tvOS/Assets.xcassets/Brand Assets.brandassets/Top Shelf Image Wide.imageset/Top Shelf.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amiantos/lifesaver/158ac5378c0686480e57709d25d8488b79a26eeb/tvOS/Assets.xcassets/Brand Assets.brandassets/Top Shelf Image Wide.imageset/Top Shelf.png
--------------------------------------------------------------------------------
/tvOS/Assets.xcassets/Brand Assets.brandassets/Top Shelf Image Wide.imageset/Top Shelf@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amiantos/lifesaver/158ac5378c0686480e57709d25d8488b79a26eeb/tvOS/Assets.xcassets/Brand Assets.brandassets/Top Shelf Image Wide.imageset/Top Shelf@2x.png
--------------------------------------------------------------------------------
/tvOS/Assets.xcassets/Brand Assets.brandassets/Top Shelf Image.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "tv",
9 | "scale" : "2x"
10 | }
11 | ],
12 | "info" : {
13 | "version" : 1,
14 | "author" : "xcode"
15 | }
16 | }
--------------------------------------------------------------------------------
/tvOS/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/tvOS/Assets.xcassets/LaunchImage.launchimage/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "orientation" : "landscape",
5 | "idiom" : "tv",
6 | "filename" : "LaunchImage@2x.png",
7 | "extent" : "full-screen",
8 | "minimum-system-version" : "11.0",
9 | "scale" : "2x"
10 | },
11 | {
12 | "orientation" : "landscape",
13 | "idiom" : "tv",
14 | "filename" : "LaunchImage.png",
15 | "extent" : "full-screen",
16 | "minimum-system-version" : "9.0",
17 | "scale" : "1x"
18 | }
19 | ],
20 | "info" : {
21 | "version" : 1,
22 | "author" : "xcode"
23 | }
24 | }
--------------------------------------------------------------------------------
/tvOS/Assets.xcassets/LaunchImage.launchimage/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amiantos/lifesaver/158ac5378c0686480e57709d25d8488b79a26eeb/tvOS/Assets.xcassets/LaunchImage.launchimage/LaunchImage.png
--------------------------------------------------------------------------------
/tvOS/Assets.xcassets/LaunchImage.launchimage/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amiantos/lifesaver/158ac5378c0686480e57709d25d8488b79a26eeb/tvOS/Assets.xcassets/LaunchImage.launchimage/LaunchImage@2x.png
--------------------------------------------------------------------------------
/tvOS/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | Life Saver
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIcons
12 |
13 | CFBundleIcons~ipad
14 |
15 | CFBundleIdentifier
16 | $(PRODUCT_BUNDLE_IDENTIFIER)
17 | CFBundleInfoDictionaryVersion
18 | 6.0
19 | CFBundleName
20 | $(PRODUCT_NAME)
21 | CFBundlePackageType
22 | APPL
23 | CFBundleShortVersionString
24 | 1.0
25 | CFBundleVersion
26 | 3
27 | LSRequiresIPhoneOS
28 |
29 | UIMainStoryboardFile
30 | Main
31 | UIRequiredDeviceCapabilities
32 |
33 | arm64
34 |
35 | UIUserInterfaceStyle
36 | Dark
37 |
38 |
39 |
--------------------------------------------------------------------------------
/tvOS/LifeViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LifeViewController.swift
3 | // Life Saver
4 | //
5 | // Created by Bradley Root on 6/25/19.
6 | // Copyright © 2019 Brad Root. All rights reserved.
7 | //
8 |
9 | import GameplayKit
10 | import SpriteKit
11 | import UIKit
12 |
13 | enum UIState {
14 | case colorPresets
15 | case mainMenu
16 | case allClosed
17 | }
18 |
19 | class LifeViewController: UIViewController, MenuTableDelegate {
20 | var scene: LifeScene?
21 | let manager = LifeManager()
22 | var state: UIState = .allClosed
23 |
24 | @IBOutlet var colorPresetsTableView: UITableView!
25 | @IBOutlet var colorPresetsView: UIVisualEffectView!
26 | @IBOutlet var mainMenuView: UIVisualEffectView!
27 |
28 | @IBOutlet var mainMenuLeadingConstraint: NSLayoutConstraint!
29 | @IBOutlet var colorMenuTrailingConstraint: NSLayoutConstraint!
30 |
31 | @IBOutlet var menuHintToast: UIVisualEffectView!
32 | @IBOutlet var menuHintToastConstraint: NSLayoutConstraint!
33 | var menuHintBounceAnimation: UIViewPropertyAnimator?
34 | @IBOutlet var colorMenuCloseToast: UIVisualEffectView!
35 | @IBOutlet var mainMenuCloseToast: UIVisualEffectView!
36 |
37 | @IBOutlet var kludgeButton: UIButton!
38 | @IBOutlet var initialOverlayView: UIView!
39 |
40 | let tableViewSource: [Int: [LifePreset]] = [0: settingsPresets, 1: colorPresets]
41 |
42 | var menuTableViewController: MenuTableViewController?
43 | var pressedMenuButtonRecognizer: UITapGestureRecognizer?
44 |
45 | var propertyAnimators: [UIViewPropertyAnimator] = []
46 |
47 | // MARK: - View Lifecycle
48 |
49 | override func viewDidLoad() {
50 | super.viewDidLoad()
51 | view.backgroundColor = .black
52 |
53 | setupView()
54 |
55 | setupPresetMenu()
56 |
57 | setupGestureRecognizers()
58 |
59 | if !manager.hasPressedMenuButton {
60 | bounceOrHideMenuHintToast(reverse: false)
61 | } else {
62 | menuHintToast.alpha = 0
63 | }
64 |
65 | createScene()
66 |
67 | hideInitialOverlay()
68 | }
69 |
70 | override func viewDidAppear(_ animated: Bool) {
71 | super.viewDidAppear(animated)
72 | view.isUserInteractionEnabled = true
73 | }
74 |
75 | override var preferredFocusEnvironments: [UIFocusEnvironment] {
76 | if state == .colorPresets {
77 | return [colorPresetsView]
78 | } else {
79 | return [mainMenuView]
80 | }
81 | }
82 |
83 | override func prepare(for segue: UIStoryboardSegue, sender _: Any?) {
84 | if segue.identifier == "menuEmbedSegue" {
85 | if let tableViewController = segue.destination as? MenuTableViewController {
86 | menuTableViewController = tableViewController
87 | menuTableViewController?.manager = manager
88 | menuTableViewController?.delegate = self
89 | manager.settingsDelegate = tableViewController
90 | }
91 | }
92 | }
93 |
94 | // MARK: - UI Interactions
95 |
96 | fileprivate func setupGestureRecognizers() {
97 | let swipeFromRight = UISwipeGestureRecognizer(target: self, action: #selector(didSwipeLeft))
98 | swipeFromRight.direction = .left
99 | swipeFromRight.allowedTouchTypes = [NSNumber(value: UITouch.TouchType.indirect.rawValue)]
100 | view.addGestureRecognizer(swipeFromRight)
101 |
102 | let swipeFromLeft = UISwipeGestureRecognizer(target: self, action: #selector(didSwipeRight))
103 | swipeFromLeft.direction = .right
104 | swipeFromLeft.allowedTouchTypes = [NSNumber(value: UITouch.TouchType.indirect.rawValue)]
105 | view.addGestureRecognizer(swipeFromLeft)
106 |
107 | pressedMenuButtonRecognizer = UITapGestureRecognizer(target: self, action: #selector(didPressMenuButton))
108 | pressedMenuButtonRecognizer!.allowedPressTypes = [NSNumber(value: UIPress.PressType.menu.rawValue)]
109 | view.addGestureRecognizer(pressedMenuButtonRecognizer!)
110 | }
111 |
112 | @objc func didPressMenuButton(gesture _: UIGestureRecognizer) {
113 | showMainMenu()
114 | }
115 |
116 | @objc func didSwipeLeft(gesture _: UIGestureRecognizer) {
117 | if state == .mainMenu {
118 | hideAllMenus()
119 | }
120 |
121 | if state == .allClosed {
122 | showColorPresets()
123 | }
124 | }
125 |
126 | @objc func didSwipeRight(gesture _: UIGestureRecognizer) {
127 | if state == .colorPresets {
128 | hideAllMenus()
129 | }
130 |
131 | if state == .allClosed {
132 | showMainMenu()
133 | }
134 | }
135 |
136 | func showColorPresets() {
137 | cancelRunningAnimators()
138 | DispatchQueue.main.async {
139 | self.colorMenuTrailingConstraint.constant = 0
140 | self.mainMenuLeadingConstraint.constant = -self.mainMenuView.frame.width
141 | self.state = .colorPresets
142 | self.pressedMenuButtonRecognizer?.isEnabled = true
143 |
144 | let propertyAnimator = UIViewPropertyAnimator(duration: 1, dampingRatio: 1, animations: {
145 | self.view.layoutIfNeeded()
146 | self.colorMenuCloseToast.alpha = 1
147 | self.mainMenuCloseToast.alpha = 0
148 | })
149 | propertyAnimator.addCompletion { _ in
150 | self.kludgeButton.alpha = 0
151 | self.setNeedsFocusUpdate()
152 | self.updateFocusIfNeeded()
153 | }
154 | self.propertyAnimators.append(propertyAnimator)
155 | propertyAnimator.startAnimation()
156 | }
157 | }
158 |
159 | func showMainMenu() {
160 | cancelRunningAnimators()
161 | if !manager.hasPressedMenuButton {
162 | manager.setHasPressedMenuButton(true)
163 | }
164 | DispatchQueue.main.async {
165 | self.colorMenuTrailingConstraint.constant = -self.colorPresetsView.frame.width
166 | self.mainMenuLeadingConstraint.constant = 0
167 | self.state = .mainMenu
168 | self.pressedMenuButtonRecognizer?.isEnabled = false
169 |
170 | let propertyAnimator = UIViewPropertyAnimator(duration: 1, dampingRatio: 1, animations: {
171 | self.view.layoutIfNeeded()
172 | self.colorMenuCloseToast.alpha = 0
173 | self.mainMenuCloseToast.alpha = 1
174 | })
175 | propertyAnimator.addCompletion { _ in
176 | self.kludgeButton.alpha = 0
177 | self.setNeedsFocusUpdate()
178 | self.updateFocusIfNeeded()
179 | }
180 | self.propertyAnimators.append(propertyAnimator)
181 | propertyAnimator.startAnimation()
182 | }
183 | }
184 |
185 | func hideAllMenus() {
186 | cancelRunningAnimators()
187 | DispatchQueue.main.async {
188 | self.colorMenuTrailingConstraint.constant = -self.colorPresetsView.frame.width
189 | self.mainMenuLeadingConstraint.constant = -self.mainMenuView.frame.width
190 | self.state = .allClosed
191 | self.pressedMenuButtonRecognizer?.isEnabled = true
192 |
193 | let propertyAnimator = UIViewPropertyAnimator(duration: 1, dampingRatio: 1, animations: {
194 | self.view.layoutIfNeeded()
195 | self.colorMenuCloseToast.alpha = 0
196 | self.mainMenuCloseToast.alpha = 0
197 | })
198 | propertyAnimator.addCompletion { _ in
199 | self.kludgeButton.alpha = 1
200 | self.setNeedsFocusUpdate()
201 | self.updateFocusIfNeeded()
202 | }
203 | self.propertyAnimators.append(propertyAnimator)
204 | propertyAnimator.startAnimation()
205 | }
206 | }
207 |
208 | func cancelRunningAnimators() {
209 | propertyAnimators.forEach {
210 | $0.pauseAnimation()
211 | $0.stopAnimation(true)
212 | }
213 | propertyAnimators.removeAll()
214 | }
215 |
216 | // MARK: - View Setup
217 |
218 | fileprivate func setupView() {
219 | menuHintToast.layer.cornerRadius = menuHintToast.frame.height / 2
220 | menuHintToast.clipsToBounds = true
221 | menuHintToast.alpha = 1
222 |
223 | colorMenuCloseToast.layer.cornerRadius = colorMenuCloseToast.frame.height / 2
224 | colorMenuCloseToast.clipsToBounds = true
225 | colorMenuCloseToast.alpha = 0
226 | colorMenuTrailingConstraint.constant = -colorPresetsView.frame.width
227 |
228 | mainMenuCloseToast.layer.cornerRadius = colorMenuCloseToast.frame.height / 2
229 | mainMenuCloseToast.clipsToBounds = true
230 | mainMenuCloseToast.alpha = 0
231 | mainMenuLeadingConstraint.constant = -mainMenuView.frame.width
232 |
233 | updateViewConstraints()
234 |
235 | colorPresetsTableView.delegate = self
236 | colorPresetsTableView.dataSource = self
237 | }
238 |
239 | fileprivate func createScene() {
240 | if let view = self.view as? SKView {
241 | scene = LifeScene(size: view.bounds.size)
242 | scene!.scaleMode = .aspectFit
243 |
244 | scene!.manager = manager
245 |
246 | view.ignoresSiblingOrder = true
247 | view.preferredFramesPerSecond = 60
248 | view.presentScene(scene)
249 | }
250 | }
251 |
252 | fileprivate func setupPresetMenu() {
253 | let selectedPresetTitle = manager.selectedPresetTitle == "" ? "Default" : manager.selectedPresetTitle
254 | var filteredPresets = colorPresets.filter { $0.title == selectedPresetTitle }
255 | if filteredPresets.isEmpty {
256 | filteredPresets = settingsPresets.filter { $0.title == selectedPresetTitle }
257 | }
258 | if let selectedPreset = filteredPresets.first {
259 | var index = colorPresets.firstIndex(where: { $0.title == selectedPreset.title })
260 | var section = 1
261 | if index == nil {
262 | section = 0
263 | index = settingsPresets.firstIndex(where: { $0.title == selectedPreset.title })
264 | }
265 | colorPresetsTableView.selectRow(
266 | at: IndexPath(
267 | row: index ?? colorPresets.count - 1,
268 | section: section
269 | ),
270 | animated: false,
271 | scrollPosition: .top
272 | )
273 | }
274 | }
275 |
276 | fileprivate func hideInitialOverlay() {
277 | let fadeAction = UIViewPropertyAnimator(duration: 2, curve: .easeInOut) {
278 | self.initialOverlayView.alpha = 0
279 | }
280 | fadeAction.addCompletion { state in
281 | switch state {
282 | case .end:
283 | self.initialOverlayView.removeFromSuperview()
284 | self.setNeedsFocusUpdate()
285 | default:
286 | return
287 | }
288 | }
289 | fadeAction.startAnimation(afterDelay: 1)
290 | }
291 |
292 | fileprivate func bounceOrHideMenuHintToast(reverse: Bool) {
293 | // TODO: - This code is problematic (infinitely repeats unnecessarily) and should be broken apart.
294 | if !manager.hasPressedMenuButton {
295 | menuHintToastConstraint.constant = reverse ? 35 : 15
296 | menuHintBounceAnimation = UIViewPropertyAnimator(duration: 1.5, curve: .easeInOut, animations: nil)
297 | } else {
298 | menuHintBounceAnimation?.stopAnimation(true)
299 | menuHintToastConstraint.constant = 35
300 | menuHintBounceAnimation = UIViewPropertyAnimator(duration: 1, curve: .easeIn, animations: nil)
301 | menuHintBounceAnimation?.addAnimations {
302 | self.menuHintToast.alpha = 0
303 | }
304 | }
305 | menuHintBounceAnimation?.addAnimations {
306 | self.view.layoutIfNeeded()
307 | }
308 | menuHintBounceAnimation?.addCompletion { state in
309 | if state == .end {
310 | self.bounceOrHideMenuHintToast(reverse: !reverse)
311 | }
312 | }
313 | menuHintBounceAnimation?.startAnimation()
314 | }
315 | }
316 |
317 | // MARK: - Preset Table View
318 |
319 | extension LifeViewController: UITableViewDelegate, UITableViewDataSource {
320 | func numberOfSections(in _: UITableView) -> Int {
321 | return 2
322 | }
323 |
324 | func tableView(_: UITableView, titleForHeaderInSection section: Int) -> String? {
325 | if section == 1 {
326 | return "Color Presets"
327 | } else {
328 | return "Presets"
329 | }
330 | }
331 |
332 | func tableView(_: UITableView, numberOfRowsInSection section: Int) -> Int {
333 | if section == 1 {
334 | return colorPresets.count - 1
335 | } else {
336 | return settingsPresets.count
337 | }
338 | }
339 |
340 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
341 | if let cell = tableView.dequeueReusableCell(withIdentifier: "presetCell", for: indexPath) as? ColorPresetTableViewCell,
342 | let preset = tableViewSource[indexPath.section]?[indexPath.row] {
343 | cell.titleLabel.text = preset.title
344 | return cell
345 | }
346 |
347 | return UITableViewCell()
348 | }
349 |
350 | func tableView(_: UITableView, didSelectRowAt indexPath: IndexPath) {
351 | if let preset = tableViewSource[indexPath.section]?[indexPath.row] {
352 | manager.configure(with: preset)
353 | }
354 | }
355 | }
356 |
--------------------------------------------------------------------------------
/tvOS/Menus/ColorPresetTableViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ColorPresetTableViewCell.swift
3 | // Life Saver tvOS
4 | //
5 | // Created by Bradley Root on 6/25/19.
6 | // Copyright © 2019 Brad Root. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class ColorPresetTableViewCell: UITableViewCell {
12 | @IBOutlet var titleLabel: UILabel!
13 |
14 | override func awakeFromNib() {
15 | super.awakeFromNib()
16 | // Initialization code
17 | }
18 |
19 | override func setSelected(_ selected: Bool, animated: Bool) {
20 | super.setSelected(selected, animated: animated)
21 |
22 | // Configure the view for the selected state
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/tvOS/Menus/MenuTableViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MenuTableViewController.swift
3 | // Life Saver tvOS
4 | //
5 | // Created by Bradley Root on 6/26/19.
6 | // Copyright © 2019 Brad Root. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | protocol MenuTableDelegate: AnyObject {
12 | func showColorPresets()
13 | }
14 |
15 | class MenuTableViewController: UITableViewController, LifeManagerDelegate {
16 | @IBOutlet var squareSizeCell: UITableViewCell!
17 | @IBOutlet var speedCell: UITableViewCell!
18 | @IBOutlet var deathFadeCell: UITableViewCell!
19 | @IBOutlet var randomPresetColorCell: UITableViewCell!
20 | @IBOutlet var showColorPresetsCell: UITableViewCell!
21 | @IBOutlet var aboutCell: UITableViewCell!
22 |
23 | weak var manager: LifeManager?
24 | weak var delegate: MenuTableDelegate?
25 |
26 | override func viewDidLoad() {
27 | super.viewDidLoad()
28 |
29 | updatedSettings()
30 | }
31 |
32 | func updatedSettings() {
33 | if let manager = manager {
34 | switch manager.squareSize {
35 | case .superSmall:
36 | squareSizeCell.detailTextLabel?.text = "XX Small"
37 | case .verySmall:
38 | squareSizeCell.detailTextLabel?.text = "Tiny"
39 | case .small:
40 | squareSizeCell.detailTextLabel?.text = "Small"
41 | case .medium:
42 | squareSizeCell.detailTextLabel?.text = "Medium"
43 | case .large:
44 | squareSizeCell.detailTextLabel?.text = "Large"
45 | }
46 |
47 | switch manager.animationSpeed {
48 | case .normal:
49 | speedCell.detailTextLabel?.text = "Normal"
50 | case .fast:
51 | speedCell.detailTextLabel?.text = "Fast"
52 | case .slow:
53 | speedCell.detailTextLabel?.text = "Slow"
54 | case .off:
55 | speedCell.detailTextLabel?.text = "Instant"
56 | }
57 |
58 | let randomColorPresetTitle = manager.shiftingColors ? "On" : "Off"
59 | randomPresetColorCell.detailTextLabel?.text = randomColorPresetTitle
60 |
61 | let deathFadeTitle = manager.deathFade ? "On" : "Off"
62 | deathFadeCell.detailTextLabel?.text = deathFadeTitle
63 | }
64 | }
65 |
66 | override func tableView(_: UITableView, didSelectRowAt indexPath: IndexPath) {
67 | print((indexPath.section, indexPath.row))
68 | switch (indexPath.section, indexPath.row) {
69 | case (0, 0):
70 | showSquareSizePicker()
71 | case (0, 1):
72 | showSpeedPicker()
73 | case (0, 2):
74 | showDeathFadePicker()
75 | case (0, 3):
76 | showColorShiftingPicker()
77 | case (0, 4):
78 | delegate?.showColorPresets()
79 | case (1, 0):
80 | showAboutPage()
81 | default:
82 | return
83 | }
84 | }
85 |
86 | fileprivate func showColorShiftingPicker() {
87 | let alert = UIAlertController(
88 | title: "Shifting Color",
89 | message: "When this is enabled, the colors of newly born squares will be slightly mutated, leading to a color shift over time.",
90 | preferredStyle: .actionSheet
91 | )
92 |
93 | let onAction = UIAlertAction(title: "On", style: .default) { _ in
94 | self.manager?.setShiftingColors(true)
95 | }
96 | alert.addAction(onAction)
97 |
98 | let offAction = UIAlertAction(title: "Off", style: .default) { _ in
99 | self.manager?.setShiftingColors(false)
100 | }
101 | alert.addAction(offAction)
102 |
103 | let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
104 | alert.addAction(cancelAction)
105 |
106 | present(alert, animated: true, completion: nil)
107 | }
108 |
109 | fileprivate func showAboutPage() {
110 | let alert = UIAlertController(title: "About Life Saver", message: """
111 | Life Saver is an artistic implementation of the Game of Life, a cellular automaton created by mathematician John Conway in 1972.
112 |
113 | Life Saver is open source software, which means you can see how it works and use it to make your own versions or modifications.
114 |
115 | For more information, visit https://amiantos.net/lifesaver
116 | """, preferredStyle: .alert)
117 |
118 | let cancelAction = UIAlertAction(title: "Close", style: .cancel, handler: nil)
119 | alert.addAction(cancelAction)
120 |
121 | present(alert, animated: true, completion: nil)
122 | }
123 |
124 | fileprivate func showSpeedPicker() {
125 | let alert = UIAlertController(
126 | title: "Animation Speed",
127 | message: """
128 | This governs how quickly change animations occur. \
129 | Slower speeds lead to more abstract, shifting colors. \
130 | Faster speeds make the simulation easier to observe.
131 | """,
132 | preferredStyle: .actionSheet
133 | )
134 |
135 | let defaultAction = UIAlertAction(title: "Normal", style: .default) { _ in
136 | self.manager?.setAnimationSpeed(.normal)
137 | }
138 | alert.addAction(defaultAction)
139 |
140 | let fastAction = UIAlertAction(title: "Fast", style: .default) { _ in
141 | self.manager?.setAnimationSpeed(.fast)
142 | }
143 | alert.addAction(fastAction)
144 |
145 | let offAction = UIAlertAction(title: "Instant", style: .default) { _ in
146 | self.manager?.setAnimationSpeed(.off)
147 | }
148 | alert.addAction(offAction)
149 |
150 | let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
151 | alert.addAction(cancelAction)
152 | present(alert, animated: true, completion: nil)
153 | }
154 |
155 | fileprivate func showDeathFadePicker() {
156 | let alert = UIAlertController(
157 | title: "Death Fade",
158 | message: """
159 | With death fade turned on, when a cell dies, it will fade into the background, \
160 | and eventually fade out completely. With death fade turned off, the cell color \
161 | will persist on the screen until it comes back to life as another color.
162 | """,
163 | preferredStyle: .actionSheet
164 | )
165 |
166 | let onAction = UIAlertAction(title: "On", style: .default) { _ in
167 | self.manager?.setDeathFade(true)
168 | }
169 | alert.addAction(onAction)
170 |
171 | let offAction = UIAlertAction(title: "Off", style: .default) { _ in
172 | self.manager?.setDeathFade(false)
173 | }
174 | alert.addAction(offAction)
175 |
176 | let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
177 | alert.addAction(cancelAction)
178 |
179 | present(alert, animated: true, completion: nil)
180 | }
181 |
182 | fileprivate func showSquareSizePicker() {
183 | // Square Size Picker
184 | let alert = UIAlertController(
185 | title: "Square Size",
186 | message: """
187 | This governs the size of the squares on screen. \
188 | Larger squares are more abstract, while smaller squares allow you to see the simulation easier.
189 | """,
190 | preferredStyle: .actionSheet
191 | )
192 | let xSmallAction = UIAlertAction(title: "Tiny", style: .default) { _ in
193 | self.manager?.setSquareSize(.verySmall)
194 | }
195 | alert.addAction(xSmallAction)
196 |
197 | let smallAction = UIAlertAction(title: "Small", style: .default) { _ in
198 | self.manager?.setSquareSize(.small)
199 | }
200 | alert.addAction(smallAction)
201 |
202 | let mediumAction = UIAlertAction(title: "Medium", style: .default) { _ in
203 | self.manager?.setSquareSize(.medium)
204 | }
205 | alert.addAction(mediumAction)
206 |
207 | let largeAction = UIAlertAction(title: "Large", style: .default) { _ in
208 | self.manager?.setSquareSize(.large)
209 | }
210 | alert.addAction(largeAction)
211 |
212 | let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
213 | alert.addAction(cancelAction)
214 |
215 | present(alert, animated: true, completion: nil)
216 | }
217 | }
218 |
--------------------------------------------------------------------------------