├── .gitattributes
├── .github
└── FUNDING.yml
├── .gitignore
├── .gitmodules
├── LiveSplitKit
├── LiveSplitKit.xcodeproj
│ ├── project.pbxproj
│ └── xcshareddata
│ │ └── xcschemes
│ │ ├── LiveSplitKit.xcscheme
│ │ └── LiveSplitKitTests.xcscheme
├── LiveSplitKit
│ ├── CLiveSplitCore
│ │ └── livesplit_core.h
│ ├── Color.swift
│ ├── Font+NSFont.swift
│ ├── Font.swift
│ ├── LSKit.swift
│ ├── LiveSplitCore Extensions.swift
│ ├── LiveSplitCore.swift
│ ├── LiveSplitCore
│ │ └── liblivesplit_core.a
│ ├── LiveSplitKit.docc
│ │ └── LiveSplitKit.md
│ ├── LiveSplitKit.h
│ ├── RunEditorState.swift
│ ├── TimeComparison.swift
│ └── module.map
└── LiveSplitKitTests
│ ├── LSCoreTesting.swift
│ └── LiveSplitKitTests.swift
├── Odyssey.split.zip
├── Podfile
├── Podfile.lock
├── Programming-Notes.md
├── README.md
├── SplitsIOKit
├── .gitignore
├── .swiftpm
│ └── xcode
│ │ ├── package.xcworkspace
│ │ └── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ └── xcschemes
│ │ └── SplitsIOKit.xcscheme
├── Package.swift
├── README.md
├── Sources
│ └── SplitsIOKit
│ │ ├── SRL.swift
│ │ ├── SpeedrunCom
│ │ ├── Game.swift
│ │ ├── Run.swift
│ │ └── SpeedrunCom.swift
│ │ ├── SplitsIOAuth.swift
│ │ ├── SplitsIOExchangeFormat.swift
│ │ └── SplitsIOKit.swift
└── Tests
│ ├── LinuxMain.swift
│ └── SplitsIOKitTests
│ ├── SpeedrunComTests.swift
│ ├── SplitsIOKitTests.swift
│ └── XCTestManifests.swift
├── Splitter.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── swiftpm
│ │ └── Package.resolved
└── xcshareddata
│ └── xcschemes
│ ├── Splitter - Release.xcscheme
│ ├── SplitterTests.xcscheme
│ └── SplitterUITests.xcscheme
├── Splitter.xcworkspace
├── contents.xcworkspacedata
└── xcshareddata
│ ├── IDEWorkspaceChecks.plist
│ ├── WorkspaceSettings.xcsettings
│ └── xcschemes
│ └── Splitter.xcscheme
├── Splitter
├── AppDelegate.swift
├── Assets.xcassets
│ ├── AppIcon.appiconset
│ │ ├── Contents.json
│ │ ├── icon_128x128.png
│ │ ├── icon_128x128@2x.png
│ │ ├── icon_16x16.png
│ │ ├── icon_16x16@2x.png
│ │ ├── icon_256x256.png
│ │ ├── icon_256x256@2x.png
│ │ ├── icon_32x32.png
│ │ ├── icon_32x32@2x.png
│ │ ├── icon_512x512.png
│ │ └── icon_512x512@2x.png
│ ├── Contents.json
│ ├── CurrentSplitColor.colorset
│ │ └── Contents.json
│ ├── Document Icon
│ │ ├── Contents.json
│ │ ├── docBG.iconset
│ │ │ ├── icon_128x128.png
│ │ │ ├── icon_128x128@2x.png
│ │ │ ├── icon_16x16.png
│ │ │ ├── icon_16x16@2x.png
│ │ │ ├── icon_256x256.png
│ │ │ ├── icon_256x256@2x.png
│ │ │ ├── icon_32x32.png
│ │ │ ├── icon_32x32@2x.png
│ │ │ ├── icon_512x512.png
│ │ │ └── icon_512x512@2x.png
│ │ ├── livesplit.iconset
│ │ │ ├── icon_128x128.png
│ │ │ ├── icon_128x128@2x.png
│ │ │ ├── icon_16x16.png
│ │ │ ├── icon_16x16@2x.png
│ │ │ ├── icon_256x256.png
│ │ │ ├── icon_256x256@2x.png
│ │ │ ├── icon_32x32.png
│ │ │ ├── icon_32x32@2x.png
│ │ │ ├── icon_512x512.png
│ │ │ └── icon_512x512@2x.png
│ │ ├── splitDocIcon.iconset
│ │ │ ├── icon_128x128.png
│ │ │ ├── icon_128x128@2x.png
│ │ │ ├── icon_16x16.png
│ │ │ ├── icon_16x16@2x.png
│ │ │ ├── icon_256x256.png
│ │ │ ├── icon_256x256@2x.png
│ │ │ ├── icon_32x32.png
│ │ │ ├── icon_32x32@2x.png
│ │ │ ├── icon_512x512.png
│ │ │ └── icon_512x512@2x.png
│ │ └── splitsioDoc.iconset
│ │ │ ├── icon_128x128.png
│ │ │ ├── icon_128x128@2x.png
│ │ │ ├── icon_16x16.png
│ │ │ ├── icon_16x16@2x.png
│ │ │ ├── icon_256x256.png
│ │ │ ├── icon_256x256@2x.png
│ │ │ ├── icon_32x32.png
│ │ │ ├── icon_32x32@2x.png
│ │ │ ├── icon_512x512.png
│ │ │ └── icon_512x512@2x.png
│ ├── Game Controller.imageset
│ │ ├── Contents.json
│ │ └── Gamepad 2.png
│ ├── Hotkeys.imageset
│ │ ├── Contents.json
│ │ └── Icon.png
│ ├── accentColor.colorset
│ │ └── Contents.json
│ ├── chevron.down.1.png
│ ├── chevron.down.2.png
│ ├── chevron.down.png
│ ├── chevron.up.1.png
│ ├── chevron.up.2.png
│ ├── chevron.up.png
│ ├── downarrow.imageset
│ │ ├── Contents.json
│ │ └── downarrow.png
│ ├── flag.imageset
│ │ ├── Contents.json
│ │ └── flagsmaller.png
│ ├── folder.imageset
│ │ ├── Contents.json
│ │ └── folder.pdf
│ ├── gearshape.imageset
│ │ ├── Contents.json
│ │ └── gearshape.pdf
│ ├── info.imageset
│ │ ├── Contents.json
│ │ └── info.png
│ ├── lines.imageset
│ │ ├── Contents.json
│ │ ├── List@1x.png
│ │ └── List@2x.png
│ ├── menuBarIcon.imageset
│ │ ├── Contents.json
│ │ └── Splitter Glyph 2.svg
│ ├── pencil.imageset
│ │ ├── Contents.json
│ │ └── PencilIcon.png
│ ├── plus.app.imageset
│ │ ├── Contents.json
│ │ └── plus.app.pdf
│ ├── splitsio.imageset
│ │ ├── Contents.json
│ │ └── logo-578e4861acb4fde546fe2a2f1cc2d750da727b0eefef55e876aa40c6cbc4ebd8.png
│ ├── square.and.arrow.down.fill.imageset
│ │ ├── Contents.json
│ │ └── square.and.arrow.down.fill.pdf
│ ├── stop.imageset
│ │ ├── Contents.json
│ │ ├── stop.1.png
│ │ ├── stop.2.png
│ │ └── stop.png
│ ├── stopSquare.imageset
│ │ ├── Contents.json
│ │ ├── stop.1.png
│ │ ├── stop.2.png
│ │ └── stop.png
│ └── trash.imageset
│ │ ├── Contents.json
│ │ └── trash.4.png
├── Credits.html
├── Data
│ ├── CommonPaths.swift
│ ├── Documents
│ │ ├── Common
│ │ │ ├── SplitterDoc.swift
│ │ │ └── SplitterDocBundle.swift
│ │ ├── Native
│ │ │ ├── Document.swift
│ │ │ ├── SplitterAppearance.swift
│ │ │ └── runInfo.swift
│ │ ├── Other Formats
│ │ │ ├── LiveSplit
│ │ │ │ ├── LiveSplit.swift
│ │ │ │ └── lss.swift
│ │ │ └── SplitsIO
│ │ │ │ └── SplitsIODoc.swift
│ │ └── SplitterDocumentController.swift
│ ├── Time Conversion.swift
│ └── TimeSplit.swift
├── Extensions.swift
├── Info.plist
├── IntentHandling.swift
├── Intents.intentdefinition
├── LiveSplit Core
│ └── liblivesplit_core.a
├── Prefs
│ ├── DebugPrefsViewController.swift
│ ├── DebugPrefsViewController.xib
│ ├── GeneralPrefsViewController.swift
│ ├── GeneralPrefsViewController.xib
│ ├── Hotkeys
│ │ ├── AdvancedHotkeyViewController.swift
│ │ ├── AdvancedHotkeyViewController.xib
│ │ ├── DefaultHotkeys.swift
│ │ ├── GlobalKeybindPrefs.swift
│ │ ├── HotkeysViewController.swift
│ │ ├── HotkeysViewController.xib
│ │ ├── KeybindHandlers.swift
│ │ └── Storage.swift
│ ├── PrefPanes.swift
│ └── SettingsKeys.swift
├── SetupForUITesting.swift
├── Splitter-Bridging-Header.h
├── Splitter.entitlements
├── SplitterDocLSS.icns
├── SplitterDocSplitsio.icns
├── StatusBarController.swift
├── Super Mario 3D Land Any%.json
├── Super Mario Odyssey Any%.json
├── TimerCore
│ ├── CodableLayout.swift
│ ├── FontManager.swift
│ ├── Fontable.swift
│ ├── SplitterRun.swift
│ ├── SplitterTimer.swift
│ └── ZippyFontDecoding.swift
├── UI
│ ├── Controls
│ │ ├── EditableSegmentIconView.swift
│ │ └── SplitterColorWell.swift
│ ├── Import
│ │ ├── CategoryViewController.swift
│ │ ├── DownloadView.swift
│ │ ├── DownloadWindow.storyboard
│ │ └── DownloadWindowController.swift
│ ├── LoadableNib.swift
│ ├── LoadingViewController.swift
│ ├── LoadingViewController.xib
│ ├── MenuItemConstants.swift
│ ├── NSViewExtensions.swift
│ ├── NibLoadable.swift
│ ├── Splits Editor
│ │ ├── CustomComparisonList.swift
│ │ ├── CustomComparisonList.xib
│ │ ├── SplitsEditorOutlineView.swift
│ │ ├── SplitsEditorTextField.swift
│ │ ├── SplitsEditorViewController.swift
│ │ └── SplitsEditorViewController.xib
│ ├── Splits.ioAccount
│ │ ├── AccountButton
│ │ │ ├── AccountButtonView.swift
│ │ │ └── AccountButtonView.xib
│ │ ├── AccountProfileViewController
│ │ │ ├── AccountProfileView.swift
│ │ │ └── AccountProfileView.xib
│ │ ├── AccountViewController.swift
│ │ └── Login
│ │ │ ├── LoginView.swift
│ │ │ └── LoginView.xib
│ ├── SplitsIOUpload.swift
│ ├── TouchBarDelegate.swift
│ ├── View Controller
│ │ ├── Base.lproj
│ │ │ └── Main.storyboard
│ │ ├── Colors.swift
│ │ ├── Components
│ │ │ ├── KeyValueComponent
│ │ │ │ ├── KeyValueComponent.swift
│ │ │ │ └── KeyValueComponent.xib
│ │ │ ├── OptionsRow
│ │ │ │ ├── OptionsRow.swift
│ │ │ │ └── OptionsRow.xib
│ │ │ ├── PrevNextRow
│ │ │ │ ├── PrevNextRow.swift
│ │ │ │ └── PrevNextRow.xib
│ │ │ ├── SegmentComponent
│ │ │ │ └── SegmentComponent.swift
│ │ │ ├── Shared
│ │ │ │ ├── ComponentState.swift
│ │ │ │ ├── SplitterComponent.swift
│ │ │ │ └── SplitterComponentType.swift
│ │ │ ├── SplitsComponent
│ │ │ │ ├── SplitTableColumnIdentifiers.swift
│ │ │ │ ├── SplitsComponent.swift
│ │ │ │ ├── SplitsComponent.xib
│ │ │ │ ├── SplitsComponentAdvancedOptions.swift
│ │ │ │ ├── SplitsComponentOptions.swift
│ │ │ │ ├── SplitterTableDelegate.swift
│ │ │ │ ├── SplitterTableView.swift
│ │ │ │ └── VerticalCenteredTextFieldCell.swift
│ │ │ ├── StartRow
│ │ │ │ ├── StartRow.swift
│ │ │ │ └── StartRow.xib
│ │ │ ├── TimeRow
│ │ │ │ ├── TimeRow.swift
│ │ │ │ ├── TimeRow.xib
│ │ │ │ └── TimeRowOptions.swift
│ │ │ └── TitleComponent
│ │ │ │ ├── TitleComponent.swift
│ │ │ │ └── TitleComponent.xib
│ │ ├── Controls
│ │ │ ├── Button Actions.swift
│ │ │ ├── Icon Actions.swift
│ │ │ ├── Menu Items.swift
│ │ │ ├── StepperWithNumberField.swift
│ │ │ └── Themeing
│ │ │ │ ├── MetadataImage.swift
│ │ │ │ ├── Themeable.swift
│ │ │ │ ├── ThemedButton.swift
│ │ │ │ ├── ThemedImage.swift
│ │ │ │ ├── ThemedPopUpButton.swift
│ │ │ │ └── ThemedTextField.swift
│ │ ├── DraggingStackView.swift
│ │ ├── Options Windows
│ │ │ ├── InfoOptionsViewController.swift
│ │ │ └── Layout Editor
│ │ │ │ ├── ColorsView
│ │ │ │ ├── ColorsView.swift
│ │ │ │ └── ColorsView.xib
│ │ │ │ ├── ComponentOptions.swift
│ │ │ │ ├── ComponentOptionsFontStack.swift
│ │ │ │ ├── DragIndicator.swift
│ │ │ │ ├── FontPopUpButton.swift
│ │ │ │ ├── GeneralLayoutSettingsViewController.swift
│ │ │ │ ├── LayoutEditorListCell.swift
│ │ │ │ ├── LayoutEditorListCell.xib
│ │ │ │ ├── LayoutEditorViewController.swift
│ │ │ │ └── WindowSettings
│ │ │ │ ├── WindowSettingViewController.swift
│ │ │ │ └── WindowSettingViewController.xib
│ │ ├── SplitsIOUpload.swift
│ │ ├── Timer Functions.swift
│ │ ├── UISettings.swift
│ │ ├── ViewController.swift
│ │ ├── WelcomeWindowController.swift
│ │ └── Windows
│ │ │ ├── BlankWindowController.swift
│ │ │ ├── MainWindow.swift
│ │ │ ├── MainWindowController.swift
│ │ │ ├── QuickAlert.swift
│ │ │ └── blankWindow.xib
│ └── WelcomeView.swift
└── UpdateController.swift
├── SplitterTests
├── Info.plist
├── Odyssey.json
├── Odyssey.split
│ ├── appearance.json
│ ├── gameIcon.png
│ ├── runInfo.json
│ └── segIcons
│ │ ├── 0.png
│ │ ├── 1.png
│ │ ├── 10.png
│ │ ├── 11.png
│ │ ├── 12.png
│ │ ├── 13.png
│ │ ├── 14.png
│ │ ├── 2.png
│ │ ├── 3.png
│ │ ├── 4.png
│ │ ├── 5.png
│ │ ├── 6.png
│ │ ├── 7.png
│ │ ├── 8.png
│ │ └── 9.png
├── Odyssey2 copy.json
├── Odyssey2.json
├── SplitterTests.swift
├── TestAttempts.json
├── TestPlan.xctestplan
├── Untitled2.json
└── Untitled3.json
├── SplitterUITests
├── FileTests.swift
├── Info.plist
├── LayoutEditorTests.swift
├── SplitterUITests.swift
└── UITest Sample Files
│ └── LongList.split
│ ├── appearance.json
│ ├── layout.lsl
│ ├── runInfo.json
│ └── splits.lss
├── appcenter-post-clone.sh
├── appdmg.json
├── package-lock.json
└── package.json
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.pbxproj merge=union
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: Splitter # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: michael_berk
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: ['https://mberk.com/donate/'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.gitignore.io/api/swift
3 | # Edit at https://www.gitignore.io/?templates=swift
4 |
5 | ### Swift ###
6 | # Xcode
7 | #
8 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
9 |
10 | ## Build generated
11 | build/
12 | DerivedData/
13 | archive/
14 |
15 | ## Various settings
16 | *.pbxuser
17 | !default.pbxuser
18 | *.mode1v3
19 | !default.mode1v3
20 | *.mode2v3
21 | !default.mode2v3
22 | *.perspectivev3
23 | !default.perspectivev3
24 | xcuserdata/
25 |
26 | ## Other
27 | *.moved-aside
28 | *.xccheckout
29 | *.xcscmblueprint
30 | *.sh
31 |
32 | ## Obj-C/Swift specific
33 | *.hmap
34 | *.ipa
35 | *.dSYM.zip
36 | *.dSYM
37 |
38 | ## Playgrounds
39 | timeline.xctimeline
40 | playground.xcworkspace
41 | *.lfs
42 | *.lss
43 | *.split
44 | robobot.json
45 | appdmg.json
46 | *.app
47 | *.dmg
48 | /SplitterTests/*.json
49 |
50 | Pods/CocoaPodsKeys/
51 |
52 | # Swift Package Manager
53 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
54 | Packages/
55 | # Package.pins
56 | Package.resolved
57 | .build/
58 | # Add this line if you want to avoid checking in Xcode SPM integration.
59 | # .swiftpm/xcode
60 |
61 | # CocoaPods
62 | # We recommend against adding the Pods directory to your .gitignore. However
63 | # you should judge for yourself, the pros and cons are mentioned at:
64 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
65 | Pods/*
66 | # Add this line if you want to avoid checking in source code from the Xcode workspace
67 | # *.xcworkspace
68 |
69 | # Carthage
70 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
71 | # Carthage/Checkouts
72 |
73 | Carthage/Build
74 |
75 | # Accio dependency management
76 | Dependencies/
77 | .accio/
78 |
79 | rSplitterTest.sh
80 | releaseSplitter.sh
81 |
82 | # fastlane
83 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
84 | # screenshots whenever they are needed.
85 | # For more information about the recommended setup visit:
86 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
87 |
88 | fastlane/report.xml
89 | fastlane/Preview.html
90 | fastlane/screenshots/**/*.png
91 | fastlane/test_output
92 |
93 | # Code Injection
94 | # After new code Injection tools there's a generated folder /iOSInjectionProject
95 | # https://github.com/johnno1962/injectionforxcode
96 |
97 | iOSInjectionProject/
98 |
99 | # End of https://www.gitignore.io/api/swift
100 | /Pods/CocoaPodsKeys/SplitterKeys.h
101 | /Pods/CocoaPodsKeys/SplitterKeys.m
102 | /ExportOptions.plist
103 | /GithubExportOptions.plist
104 | /NotarizationUUIDs.log
105 | /archive.xcarchive/
106 | Package.resolved
107 | archiveLog.txt
108 | Config.xcconfig
109 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "Preferences"]
2 | path = Preferences
3 | url = https://github.com/MichaelJBerk/Preferences.git
4 | [submodule "FontPopUp"]
5 | path = FontPopUp
6 | url = https://github.com/MichaelJBerk/FontPopUp
7 |
--------------------------------------------------------------------------------
/LiveSplitKit/LiveSplitKit.xcodeproj/xcshareddata/xcschemes/LiveSplitKit.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
42 |
43 |
53 |
54 |
60 |
61 |
67 |
68 |
69 |
70 |
72 |
73 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/LiveSplitKit/LiveSplitKit.xcodeproj/xcshareddata/xcschemes/LiveSplitKitTests.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
14 |
15 |
17 |
23 |
24 |
25 |
26 |
27 |
37 |
38 |
44 |
45 |
47 |
48 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/LiveSplitKit/LiveSplitKit/Color.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSColorExtension.swift
3 | // LiveSplitKit
4 | //
5 | // Created by Michael Berk on 1/21/22.
6 | //
7 |
8 | import AppKit
9 |
10 | extension NSColor {
11 | convenience init(_ color: [Double]) {
12 | let floats = color.map({CGFloat($0)})
13 | self.init(red: floats[0], green: floats[1], blue: floats[2], alpha: floats[3])
14 | }
15 | func toDouble() -> [Double] {
16 | let converted = self.usingColorSpace(.sRGB)
17 | let floats = [converted!.redComponent, converted!.greenComponent, converted!.blueComponent, converted!.alphaComponent]
18 | return floats.map({Double($0)})
19 | }
20 | convenience init(_ color: [Float]) {
21 | let floats = color.map({CGFloat($0)})
22 | self.init(red: floats[0], green: floats[1], blue: floats[2], alpha: floats[3])
23 | }
24 | func toFloat() -> [Float] {
25 | let converted = self.usingColorSpace(.sRGB)
26 | let floats = [converted!.redComponent, converted!.greenComponent, converted!.blueComponent, converted!.alphaComponent]
27 | return floats.map({Float($0)})
28 | }
29 | }
30 |
31 | extension NSColor {
32 | static func from(hex: String) -> NSColor {
33 | let hexVal = hex.replacingOccurrences(of: "#", with: "")
34 | let hex = UInt32(hexVal, radix: 16)!
35 | let a = CGFloat((hex >> 24) & 0xFF) / 255.0
36 | let r = CGFloat((hex >> 16) & 0xFF) / 255.0
37 | let g = CGFloat((hex >> 8) & 0xFF) / 255.0
38 | let b = CGFloat((hex) & 0xFF) / 255.0
39 |
40 | let color = NSColor(red: r, green: g, blue: b, alpha: a)
41 |
42 | return color
43 | }
44 | }
45 |
46 | extension SettingValue {
47 | public static func fromNSColor(_ color: NSColor) -> SettingValue {
48 | let floats = color.toFloat()
49 | return .fromColor(floats[0], floats[1], floats[2], floats[3])
50 | }
51 | public static func fromAlternatingNSColor(_ color1: NSColor, _ color2: NSColor) -> SettingValue {
52 | let floats1 = color1.toFloat()
53 | let floats2 = color2.toFloat()
54 | return .fromAlternatingGradient(floats1[0], floats1[1], floats1[2], floats1[3], floats2[0], floats2[1], floats2[2], floats2[3])
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/LiveSplitKit/LiveSplitKit/Font+NSFont.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Font+NSFont.swift
3 | // LiveSplitKit
4 | //
5 | // Created by Michael Berk on 6/8/23.
6 | //
7 |
8 | import Foundation
9 | #if canImport(Cocoa)
10 | import Cocoa
11 | extension LiveSplitFont.Weight {
12 | /**
13 | Representation of the font's weight that can be used in an NSFontDescriptor
14 |
15 | This value represents the font's weight using units that can be used with NSFontDescriptor.
16 |
17 | ```swift
18 | let fontAttributes: [NSFontDescriptor.AttributeName: Any] = [
19 | .family: fontName,
20 | .traits: [NSFontDescriptor.TraitKey.weight: LiveSplitFont.Weight.medium]
21 | ]
22 | let fontDescriptor = NSFontDescriptor(fontAttributes: fontAttributes)
23 | let font = NSFont(descriptor: fontDescriptor, size: NSFont.systemFontSize)!
24 | ```
25 | */
26 | public var nsFontWeight: CGFloat {
27 | let black = NSFont.Weight.black.rawValue
28 | let light = NSFont.Weight.light.rawValue
29 | let regular = NSFont.Weight.regular.rawValue
30 |
31 | switch cssWeight {
32 | case 100:
33 | return NSFont.Weight.ultraLight.rawValue
34 | case 200:
35 | return NSFont.Weight.thin.rawValue
36 | case 300:
37 | return NSFont.Weight.light.rawValue
38 | case 350:
39 | return light + (regular - light)/2
40 | case 400:
41 | return NSFont.Weight.regular.rawValue
42 | case 500:
43 | return NSFont.Weight.medium.rawValue
44 | case 600:
45 | return NSFont.Weight.semibold.rawValue
46 | case 700:
47 | return NSFont.Weight.bold.rawValue
48 | case 800:
49 | return NSFont.Weight.heavy.rawValue
50 | case 900:
51 | return black
52 | case 950:
53 | return black + (1 - black)/2
54 | default:
55 | return NSFont.Weight.regular.rawValue
56 | }
57 | }
58 | }
59 |
60 |
61 | public extension LiveSplitFont {
62 | ///Create an NSFont based on the LiveSplitFont
63 | func toNSFont(size: CGFloat = NSFont.systemFontSize) -> NSFont? {
64 | var fontSlant = 0.0
65 | if style == .italic {
66 | fontSlant = 1
67 | }
68 | //Cannot add stretch since there's no equivalent in NSFont.
69 | let med = 0.23000000417232513
70 | let num = round(weight.nsFontWeight * 10)/10
71 | let descriptor = NSFontDescriptor(fontAttributes: [
72 | .family: family,
73 | .traits: [
74 | .slant: fontSlant,
75 | NSFontDescriptor.TraitKey.weight: num,
76 | ]
77 | ])
78 | let font = NSFont(descriptor: descriptor, size: size)
79 | return font
80 | }
81 | }
82 | #endif
83 |
--------------------------------------------------------------------------------
/LiveSplitKit/LiveSplitKit/LSKit.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LSKit.swift
3 | // LiveSplitKit
4 | //
5 | // Created by Michael Berk on 1/21/22.
6 | //
7 |
8 | import Foundation
9 |
10 | public func updateLayoutState(_ layout: Layout, timer: LSTimer) -> String {
11 | let state = layout.state(timer)
12 | let refMutState = LayoutStateRefMut(ptr: state.ptr)
13 | let json = layout.updateStateAsJson(refMutState, timer)
14 | return json
15 | }
16 |
17 | class CThing {
18 | func hey() {
19 | print("hello, worldb")
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/LiveSplitKit/LiveSplitKit/LiveSplitCore/liblivesplit_core.a:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/LiveSplitKit/LiveSplitKit/LiveSplitCore/liblivesplit_core.a
--------------------------------------------------------------------------------
/LiveSplitKit/LiveSplitKit/LiveSplitKit.docc/LiveSplitKit.md:
--------------------------------------------------------------------------------
1 | # ``LiveSplitKit``
2 |
3 | Summary
4 |
5 | ## Overview
6 |
7 | Text
8 |
9 | ## Topics
10 |
11 | ### Group
12 |
13 | - ``Symbol``
--------------------------------------------------------------------------------
/LiveSplitKit/LiveSplitKit/LiveSplitKit.h:
--------------------------------------------------------------------------------
1 | //
2 | // LiveSplitKit.h
3 | // LiveSplitKit
4 | //
5 | // Created by Michael Berk on 1/21/22.
6 | //
7 |
8 | #import
9 |
10 | //! Project version number for LiveSplitKit.
11 | FOUNDATION_EXPORT double LiveSplitKitVersionNumber;
12 |
13 | //! Project version string for LiveSplitKit.
14 | FOUNDATION_EXPORT const unsigned char LiveSplitKitVersionString[];
15 |
16 | // In this header, you should import all the public headers of your framework using statements like #import
17 |
18 |
19 | #import "livesplit_core.h"
20 |
--------------------------------------------------------------------------------
/LiveSplitKit/LiveSplitKit/module.map:
--------------------------------------------------------------------------------
1 |
2 |
3 | framework module LiveSplitKit {
4 | umbrella header "LiveSplitKit.h"
5 | export *
6 | module * { export * }
7 |
8 | link "CLiveSplitCore"
9 | // use "CLiveSplitCore"
10 | }
11 |
12 | framework module CLiveSplitCore [extern_c] {
13 | header "livesplit_core.h"
14 | link "livesplit_core"
15 | link framework "Carbon"
16 | link framework "CoreFoundation"
17 | link framework "CoreGraphics"
18 | // export *
19 | }
20 |
--------------------------------------------------------------------------------
/LiveSplitKit/LiveSplitKitTests/LSCoreTesting.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LSCoreTesting.swift
3 | // SplitterTests
4 | //
5 | // Created by Michael Berk on 3/24/22.
6 | // Copyright © 2022 Michael Berk. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import LiveSplitKit
11 |
12 | class LSCoreTesting: XCTestCase {
13 |
14 | var run: Run!
15 | var layout: Layout!
16 |
17 | override func setUpWithError() throws {
18 | // Put setup code here. This method is called before the invocation of each test method in the class.
19 | run = Run()
20 | let segment = Segment("")
21 | run.pushSegment(segment)
22 | layout = Layout.defaultLayout()
23 |
24 | }
25 |
26 | override func tearDownWithError() throws {
27 | // Put teardown code here. This method is called after the invocation of each test method in the class.
28 | }
29 |
30 | ///Test that the methods for getting/setting timing method work
31 | func testTimingMethod() {
32 | guard let le = LayoutEditor(layout) else {return}
33 | le.select(1)
34 | let method1 = le.getTimingMethod(for: 0)
35 | XCTAssert(method1 == nil)
36 | le.setColumn(0, timingMethod: .realTime)
37 | let method2 = le.getTimingMethod(for: 0)
38 | XCTAssert(method2 == .realTime)
39 | le.setColumn(0, timingMethod: .gameTime)
40 | let method3 = le.getTimingMethod(for: 0)
41 | XCTAssert(method3 == .gameTime)
42 | }
43 |
44 | func testGetFont() {
45 | guard let le = LayoutEditor(layout) else {return}
46 | let font = LiveSplitFont(family: "times", style: .normal, weight: .normal, stretch: .normal)
47 | guard let setVal = SettingValue.fromFont(font) else {return}
48 | le.setGeneralSettingsValue(3, setVal)
49 | layout = le.close()
50 |
51 | let timer = LSTimer(run)
52 | let jState = layout.stateAsJson(timer!)
53 |
54 | let pasteboard = NSPasteboard.general
55 | pasteboard.declareTypes([.string], owner: nil)
56 | pasteboard.setString(jState, forType: .string)
57 |
58 | /*
59 | 1: Custom Timer Font
60 | 2: Custom Times Font
61 | 3: Custom Text Font
62 | */
63 | // SettingValue.fromFont(<#T##String#>, <#T##String#>, <#T##String#>, <#T##String#>)
64 | // let font = le.state().fieldValue(false, 3)
65 | // let json = font.asJson()
66 | // print(json)
67 | // print("hey")
68 | // le.setGeneralSettingsValue(<#T##size_t#>, T##SettingValue)
69 | }
70 |
71 | func testExample() throws {
72 | // This is an example of a functional test case.
73 | // Use XCTAssert and related functions to verify your tests produce the correct results.
74 | // Any test you write for XCTest can be annotated as throws and async.
75 | // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error.
76 | // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards.
77 | }
78 |
79 | func testPerformanceExample() throws {
80 | // This is an example of a performance test case.
81 | self.measure {
82 | // Put the code you want to measure the time of here.
83 | }
84 | }
85 |
86 | }
87 |
--------------------------------------------------------------------------------
/LiveSplitKit/LiveSplitKitTests/LiveSplitKitTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LiveSplitKitTests.swift
3 | // LiveSplitKitTests
4 | //
5 | // Created by Michael Berk on 1/21/22.
6 | //
7 |
8 | import XCTest
9 | @testable import LiveSplitKit
10 |
11 | class LiveSplitKitTests: XCTestCase {
12 |
13 | override func setUpWithError() throws {
14 | // Put setup code here. This method is called before the invocation of each test method in the class.
15 | }
16 |
17 | override func tearDownWithError() throws {
18 | // Put teardown code here. This method is called after the invocation of each test method in the class.
19 | }
20 |
21 | func testExample() throws {
22 | // This is an example of a functional test case.
23 | // Use XCTAssert and related functions to verify your tests produce the correct results.
24 | // Any test you write for XCTest can be annotated as throws and async.
25 | // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error.
26 | // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards.
27 | }
28 |
29 | func testPerformanceExample() throws {
30 | // This is an example of a performance test case.
31 | self.measure {
32 | // Put the code you want to measure the time of here.
33 | }
34 | }
35 |
36 | func testColor() {
37 | // let run = Run()
38 | // run.pushSegment(.init("Hello"))
39 | // let timer = LSTimer(run)!
40 | // let layout = Layout.defaultLayout()
41 | // let state = layout.state(timer)
42 | let color = "#8000CC36"
43 | let c = NSColor.from(hex: color)
44 |
45 |
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/Odyssey.split.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Odyssey.split.zip
--------------------------------------------------------------------------------
/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment the next line to define a global platform for your project
2 | platform :osx, '10.15'
3 | use_frameworks!
4 | inhibit_all_warnings!
5 |
6 | def splitter_pods
7 | pod 'MASShortcut'
8 | pod 'ZippyJSON'
9 | end
10 |
11 | target 'Splitter' do
12 | splitter_pods
13 |
14 | target 'SplitterTests' do
15 | splitter_pods
16 | end
17 |
18 | target 'SplitterUITests' do
19 | splitter_pods
20 | end
21 |
22 | post_install do |installer|
23 | installer.generated_projects.each do |project|
24 | project.targets.each do |target|
25 | target.build_configurations.each do |config|
26 | config.build_settings['MACOSX_DEPLOYMENT_TARGET'] = '10.15'
27 | end
28 | end
29 | puts "Fix MASShortcut"
30 | end
31 | end
32 |
33 | plugin 'cocoapods-keys', {
34 | :project => "splitter_pods",
35 | :keys => [
36 | "SPLIT_SIO_SECRET",
37 | "SPLIT_SIO_CLIENT"
38 | ]
39 | }
40 |
41 | end
--------------------------------------------------------------------------------
/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - JJLISO8601DateFormatter (0.1.5)
3 | - Keys (1.0.1)
4 | - MASShortcut (2.4.0)
5 | - ZippyJSON (1.2.10):
6 | - JJLISO8601DateFormatter (= 0.1.5)
7 | - ZippyJSONCFamily (= 1.2.9)
8 | - ZippyJSONCFamily (1.2.9)
9 |
10 | DEPENDENCIES:
11 | - Keys (from `Pods/CocoaPodsKeys`)
12 | - MASShortcut
13 | - ZippyJSON
14 |
15 | SPEC REPOS:
16 | trunk:
17 | - JJLISO8601DateFormatter
18 | - MASShortcut
19 | - ZippyJSON
20 | - ZippyJSONCFamily
21 |
22 | EXTERNAL SOURCES:
23 | Keys:
24 | :path: Pods/CocoaPodsKeys
25 |
26 | SPEC CHECKSUMS:
27 | JJLISO8601DateFormatter: 36bc0fc8fbd77440fe8b33e0281a4c57170781e4
28 | Keys: a576f4c9c1c641ca913a959a9c62ed3f215a8de9
29 | MASShortcut: d9e4909e878661cc42877cc9d6efbe638273ab57
30 | ZippyJSON: 565d1cbf09c1fac147ae6323356b04caaf9e1397
31 | ZippyJSONCFamily: a6fdeeb750d4891d68b8324dd90d87111f314388
32 |
33 | PODFILE CHECKSUM: 3c11cb38b1a54edac2985aaaa16d5a4793454bf8
34 |
35 | COCOAPODS: 1.15.2
36 |
--------------------------------------------------------------------------------
/Programming-Notes.md:
--------------------------------------------------------------------------------
1 | # Programming Notes
2 |
3 | This document is a list of general guidelines for working on Splitter.
4 |
5 |
6 | ## SF Symbols
7 | - Use of SF Symbols is encouraged, but remember that macOS ≤ 10.15 doesn't support it, so there needs to be a fallback for that operating system
8 | - When using a new SF Symbol, always check if the symbol name changed in a previous version, and fall back to it if necessary.
9 | - DON'T force-unwrap an SF Symbol unless you're sure that the symbol name is the same on all supported versions of macOS
10 | - For example, `line.3.horizontal` was `line.horizontal.3` in macOS Big Sur. Since I had used the name `line.3.horizontal` force-unwrapped, the app would crash on Big Sur. (commit 69abc858dc6393a01e61b6732837258e17aec43c)
11 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Splitter
2 |
3 | 
4 |
5 | A native Speedrunning timer for macOS
6 |
7 | **[FAQ](https://github.com/MichaelJBerk/Splitter/wiki/FAQ)**
8 |
9 | **[Tips](https://github.com/MichaelJBerk/Splitter/wiki/Tips)**
10 |
11 | **[Tip Jar](https://mberk.com/donate/)**
12 |
13 | ## Download
14 |
15 | ### Stable
16 |
17 | The latest stable release of Splitter is available on the Mac App Store:
18 |
19 | [Download Latest Stable Release](https://apps.apple.com/us/app/splitter-speedrun-timer/id1502505482?ls=1)
20 |
21 | ## Beta Testing
22 |
23 | If you're a _"thrill seeker"_, want to help test the next version of Splitter, or just like living on the edge, you can try out the latest beta release, which is based on the `dev` branch
24 |
25 | (https://github.com/MichaelJBerk/Splitter/releases)
26 |
27 | ## Discussion
28 |
29 | You can suggest features, report issues, and contribute to the overall discussion about Splitter on the official [Discord Server](https://discord.gg/S6zCHYq).
30 |
31 | ## Supported Formats
32 |
33 | [Info about Splitter's native file format](https://github.com/MichaelJBerk/Splitter/wiki/.Split-Format)
34 |
35 | In addition to the Splitter-native `.split` format, the app can edit and save the following:
36 | - LiveSplit (`.lss`)
37 | - Splits.io (`.json`)
38 |
39 | Note that some features (such as appearance and color settings) are only saved to a `.split` file. As such, if you don't want to have to set them every time you open the file, it's recommended to save the file as `.split` (by clicking "File" -> "Save as..." in the menubar). If you need to need to share it with someone who isn't using Splitter, just open the `.split` file, and save it in whatever other format works best.
40 |
41 | ## Building Splitter
42 |
43 | If you want to build Splitter yourself:
44 | - Install CocoaPods if you don't already have it installed
45 | - Clone this repo
46 | - run `pod install` in the project's root directory
47 | - You'll be prompted to set the secret and client keys for Splits.io, but you can press the Return key to leave them blank. See [cocoapods-keys](https://github.com/orta/cocoapods-keys?tab=readme-ov-file) for more info.
48 | - Open Splitter.xcworkspace in Xcode
49 |
50 |
51 | ### Acknowledgements
52 | [LiveSplit Core](https://github.com/LiveSplit/livesplit-core)
53 |
54 | [Splits.io](https://splits.io/)
55 |
56 | [Files by John Sundell](https://github.com/JohnSundell/Files)
57 |
58 | [MASShortcut by Vadim Shpakovski](https://github.com/shpakovski/MASShortcut)
59 |
60 | [Preferences by Sindre Sorhus](https://github.com/sindresorhus/Preferences)
61 |
62 | [Sparkle by Sparkle Project](https://github.com/sparkle-project/Sparkle)
63 |
64 | [Cocoapods](https://cocoapods.org)
65 |
--------------------------------------------------------------------------------
/SplitsIOKit/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 |
--------------------------------------------------------------------------------
/SplitsIOKit/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/SplitsIOKit/.swiftpm/xcode/xcshareddata/xcschemes/SplitsIOKit.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
42 |
43 |
53 |
54 |
60 |
61 |
67 |
68 |
69 |
70 |
72 |
73 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/SplitsIOKit/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.2
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "SplitsIOKit",
8 | platforms: [.macOS(SupportedPlatform.MacOSVersion.v10_14)],
9 | products: [
10 | // Products define the executables and libraries a package produces, and make them visible to other packages.
11 | .library(
12 | name: "SplitsIOKit",
13 | targets: ["SplitsIOKit"]),
14 | ],
15 | dependencies: [
16 | .package(url: "https://github.com/SwiftyJSON/SwiftyJSON.git", .upToNextMajor(from: "5.0.0")),
17 | .package(url: "https://github.com/Alamofire/Alamofire.git", .upToNextMajor(from: "5.2.0")),
18 | .package(url: "https://github.com/pitiphong-p/URLQueryItemEncoder.git", .upToNextMajor(from: "0.2.0")),
19 | .package(name: "Files", url: "https://github.com/JohnSundell/Files", from: "4.0.0"),
20 | .package(name: "OAuth2", url: "https://github.com/p2/OAuth2.git", .upToNextMajor(from: "5.2.0"))
21 |
22 | // Dependencies declare other packages that this package depends on.
23 | // .package(url: /* package url */, from: "1.0.0"),
24 | ],
25 | targets: [
26 | // Targets are the basic building blocks of a package. A target can define a module or a test suite.
27 | // Targets can depend on other targets in this package, and on products in packages this package depends on.
28 | .target(
29 | name: "SplitsIOKit",
30 | dependencies: ["SwiftyJSON", "Alamofire", "Files", "OAuth2", "URLQueryItemEncoder"]),
31 | .testTarget(
32 | name: "SplitsIOKitTests",
33 | dependencies: ["SplitsIOKit", "Files"]),
34 | ]
35 | )
36 |
--------------------------------------------------------------------------------
/SplitsIOKit/README.md:
--------------------------------------------------------------------------------
1 | # SplitsIOKit
2 |
3 | A Swift library for interacting with Splits.io
4 |
--------------------------------------------------------------------------------
/SplitsIOKit/Sources/SplitsIOKit/SRL.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Michael Berk on 8/11/20.
6 | //
7 |
8 | import Foundation
9 |
10 | public struct SRLRun: Decodable {
11 | let count: String
12 | let races: [SRLRace]
13 | }
14 |
15 | // MARK: - SRLRace
16 | public struct SRLRace: Decodable {
17 | let id: String
18 | let game: SRLGame
19 | let goal: String
20 | let time, state: Int
21 | let statetext, filename: String
22 | let numentrants: Int
23 | var entrants: [String: SRLEntrant]
24 |
25 | private enum CodingKeys: String, CodingKey {
26 | case id
27 | case game
28 | case goal
29 | case time
30 | case state
31 | case statetext
32 | case filename
33 | case numentrants
34 | case entrants
35 | }
36 | public init(from decoder: Decoder) throws {
37 | let container = try decoder.container(keyedBy: CodingKeys.self)
38 | id = try container.decode(String.self, forKey: .id)
39 | game = try container.decode(SRLGame.self, forKey: .game)
40 | goal = try container.decode(String.self, forKey: .goal)
41 | time = try container.decode(Int.self, forKey: .time)
42 | state = try container.decode(Int.self, forKey: .state)
43 | statetext = try container.decode(String.self, forKey: .statetext)
44 | filename = try container.decode(String.self, forKey: .filename)
45 | numentrants = try container.decode(Int.self, forKey: .numentrants)
46 | entrants = [String:SRLEntrant]()
47 |
48 | let subContainer = try container.nestedContainer(keyedBy: GenericCodingKeys.self, forKey: .entrants)
49 | for key in subContainer.allKeys {
50 | entrants[key.stringValue] = try subContainer.decode(SRLEntrant.self, forKey: key)
51 | }
52 |
53 |
54 | }
55 | }
56 |
57 | public struct SRLEntrant: Codable {
58 | let displayname: String
59 | let place, time: Int
60 | let message: String?
61 | let statetext, twitch, trueskill: String
62 | }
63 |
64 | // MARK: - SRLGame
65 | public struct SRLGame: Codable {
66 | let id: Int
67 | let name, abbrev: String
68 | let popularity, popularityrank: Double
69 | }
70 |
71 | public struct GenericCodingKeys: CodingKey {
72 | public var intValue: Int?
73 | public var stringValue: String
74 |
75 | public init?(intValue: Int) { self.intValue = intValue; self.stringValue = "\(intValue)" }
76 | public init?(stringValue: String) { self.stringValue = stringValue }
77 |
78 | public static func makeKey(name: String) -> GenericCodingKeys {
79 | return GenericCodingKeys(stringValue: name)!
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/SplitsIOKit/Sources/SplitsIOKit/SplitsIOAuth.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Michael Berk on 8/11/20.
6 | //
7 |
8 | import Foundation
9 | import Cocoa
10 | import OAuth2
11 | /// Class for handling authentication with Splits.io
12 | ///
13 | /// To use this, you'll need to set up a client and secret on Splits.io.
14 | public class SplitsIOAuth {
15 | let oAuth2: OAuth2CodeGrant
16 |
17 | /// Initalize SplitsIOAuth. Requires developer setup from Splits.io
18 | /// - Parameters:
19 | /// - client: Client ID
20 | /// - secret: Secret ID
21 | /// - redirects: Array of URI redirects for the application
22 | /// - url: Requires https
23 | public init(client: String, secret: String, redirects: String, url: String) {
24 | oAuth2 = OAuth2CodeGrant(settings: [
25 | "client_id": "\(client)",
26 | "client_secret": "\(secret)",
27 | "authorize_uri": "\(url)/oauth/authorize",
28 | "token_uri": "\(url)/oauth/token",
29 | "redirect_uris": [redirects],
30 | "scope": "upload_run delete_run manage_race",
31 | "keychain": "true",
32 | ] as OAuth2JSON)
33 | }
34 |
35 | public func authenticate(completion: @escaping() -> ()) throws {
36 | oAuth2.logger = OAuth2DebugLogger(.trace)
37 | do {
38 | let url = try oAuth2.authorizeURL()
39 | try oAuth2.authorizer.openAuthorizeURLInBrowser(url)
40 | oAuth2.didAuthorizeOrFail = { authParams, error in
41 | NotificationCenter.default.post(name: .splitsIOLogin, object: nil)
42 | completion()
43 |
44 | }
45 | } catch {
46 | print("authURL error:", error)
47 | }
48 | }
49 | public func logout(completion: @escaping() -> ()) {
50 | oAuth2.forgetTokens()
51 | NotificationCenter.default.post(name: .splitsIOLogout, object: nil)
52 | completion()
53 |
54 | }
55 |
56 |
57 | }
58 |
59 | extension Notification.Name {
60 | public static let splitsIOLogin = Notification.Name(rawValue: "splitsIOLogin")
61 | public static let splitsIOLogout = Notification.Name(rawValue: "splitsIOLogout")
62 | }
63 |
--------------------------------------------------------------------------------
/SplitsIOKit/Tests/LinuxMain.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | import SplitsIOKitTests
4 |
5 | var tests = [XCTestCaseEntry]()
6 | tests += SplitsIOKitTests.allTests()
7 | XCTMain(tests)
8 |
--------------------------------------------------------------------------------
/SplitsIOKit/Tests/SplitsIOKitTests/SpeedrunComTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SpeedrunComTests.swift
3 | //
4 | //
5 | // Created by Michael Berk on 11/16/21.
6 | //
7 |
8 | import XCTest
9 | import Files
10 |
11 | @testable import SplitsIOKit
12 |
13 | final class SpeedrunComTests: XCTestCase {
14 |
15 | func testSearch() {
16 | let expectation = XCTestExpectation(description: "Complete Search")
17 | Speedruncom().searchSpeedruncom(for: "Super Mario Odyssey", completion: { games, error in
18 | XCTAssertFalse(games == nil)
19 | print(games?.compactMap({$0.names.international}))
20 | expectation.fulfill()
21 | })
22 | wait(for: [expectation], timeout: 10.0)
23 | }
24 |
25 | func testGetRuns() {
26 | let smsGameID = "v1pxjz68"
27 | let expectation = XCTestExpectation(description: "Get Game")
28 | Speedruncom().getRuns(query: .init(game: smsGameID, max: 1, offset: 0), completion: {runs, error in
29 | if let error = error {
30 | print("Error\n\(error)")
31 | XCTFail("GetRuns Failed")
32 | return
33 | }
34 | XCTAssertFalse(runs == nil)
35 | print(runs!.compactMap {$0.category})
36 | expectation.fulfill()
37 | })
38 | wait(for: [expectation], timeout: 10.0)
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/SplitsIOKit/Tests/SplitsIOKitTests/SplitsIOKitTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | import Files
3 |
4 | @testable import SplitsIOKit
5 |
6 | final class SplitsIOKitTests: XCTestCase {
7 |
8 | func testSearch() {
9 | let expectation = XCTestExpectation(description: "Complete Search")
10 | SplitsIOKit().searchSplitsIO(for: "Psychonauts", completion: { games in
11 | XCTAssertFalse(games == nil)
12 | expectation.fulfill()
13 | })
14 | wait(for: [expectation], timeout: 10.0)
15 | }
16 |
17 | @available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *)
18 | func testAsyncSearch() async {
19 | let games = await SplitsIOKit().searchSplitsIO(for: "Psychonauts")
20 | XCTAssertFalse(games == nil)
21 | }
22 |
23 | func testGetRun() {
24 | let expectation = XCTestExpectation(description: "Find run")
25 | SplitsIOKit().getRun(runID: "5yj6", completion: { run in
26 | expectation.fulfill()
27 | })
28 | wait(for: [expectation], timeout: 10.0)
29 | }
30 | func testGetLivesplitRun() {
31 | let expectation = XCTestExpectation(description: "Find run")
32 | SplitsIOKit().getRunAsLivesplit(runID: "5yj6", completion: { run in
33 | let rFile = try? File(path: run!)
34 | try? rFile?.delete()
35 | expectation.fulfill()
36 | })
37 | wait(for: [expectation], timeout: 10.0)
38 | }
39 | func testGetRunsFromCat() {
40 | let expectation = XCTestExpectation(description: "get runs from cat")
41 | SplitsIOKit().getRunFromCat(categoryID: "4075", completion: { runs in
42 | expectation.fulfill()
43 |
44 | })
45 | wait(for: [expectation], timeout: 10.0)
46 | }
47 | func testGetCategories() {
48 | let expectation = XCTestExpectation(description: "Get categories")
49 | SplitsIOKit().getCategories(for: "yi", completion: { cats in
50 | expectation.fulfill()
51 |
52 | })
53 | wait(for: [expectation], timeout: 10.0)
54 | }
55 | func testSRL() {
56 | let expectation = XCTestExpectation(description: "get runs from SRL")
57 | SplitsIOKit().getLatestSRLRaces(completion: { SRL in
58 | expectation.fulfill()
59 | print(SRL.count)
60 |
61 | })
62 | wait(for: [expectation], timeout: 10.0)
63 | }
64 | func testGetRunner() {
65 | let expectation = XCTestExpectation(description: "get lphantom")
66 | SplitsIOKit().getRunner(name: "lphantom") {runner in
67 | if let runner = runner {
68 | expectation.fulfill()
69 | print(runner.displayName)
70 | }
71 | }
72 | wait(for: [expectation], timeout: 10.0)
73 | }
74 | func testGetGamesFromRunner() {
75 | let expectation = XCTestExpectation(description: "get lphantom games")
76 | SplitsIOKit().getGamesFromRunner(runnerName: "lphantom", completion: { games in
77 | XCTAssertFalse(games == nil)
78 | expectation.fulfill()
79 |
80 | })
81 | wait(for: [expectation], timeout: 10.0)
82 |
83 | }
84 | // func testGetGamesFromSpedruncom() {
85 | // let expectation = XCTestExpectation(description: "get lphantom games")
86 | // Speedruncom().searchSpeedruncom(for: "Super Mario Odyssey", completion: { games, _ in in
87 | // XCTAssertFalse(games == nil)
88 | // expectation.fulfill()
89 | //
90 | // })
91 | // wait(for: [expectation], timeout: 10.0)
92 | // }
93 |
94 |
95 |
96 | }
97 |
--------------------------------------------------------------------------------
/SplitsIOKit/Tests/SplitsIOKitTests/XCTestManifests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | #if !canImport(ObjectiveC)
4 | public func allTests() -> [XCTestCaseEntry] {
5 | return [
6 | testCase(SplitsIOKitTests.allTests),
7 | ]
8 | }
9 | #endif
10 |
--------------------------------------------------------------------------------
/Splitter.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Splitter.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Splitter.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "object": {
3 | "pins": [
4 | {
5 | "package": "Files",
6 | "repositoryURL": "https://github.com/JohnSundell/Files",
7 | "state": {
8 | "branch": null,
9 | "revision": "22fe84797d499ffca911ccd896b34efaf06a50b9",
10 | "version": "4.1.1"
11 | }
12 | },
13 | {
14 | "package": "Preferences",
15 | "repositoryURL": "https://github.com/sindresorhus/Preferences",
16 | "state": {
17 | "branch": null,
18 | "revision": "73664ff9f9faeb40f4b8f2c02d21b4486c82cce9",
19 | "version": "2.0.0"
20 | }
21 | },
22 | {
23 | "package": "SwiftyJSON",
24 | "repositoryURL": "https://github.com/SwiftyJSON/SwiftyJSON.git",
25 | "state": {
26 | "branch": null,
27 | "revision": "2b6054efa051565954e1d2b9da831680026cd768",
28 | "version": "5.0.0"
29 | }
30 | }
31 | ]
32 | },
33 | "version": 1
34 | }
35 |
--------------------------------------------------------------------------------
/Splitter.xcodeproj/xcshareddata/xcschemes/Splitter - Release.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
43 |
45 |
51 |
52 |
53 |
54 |
60 |
62 |
68 |
69 |
70 |
71 |
73 |
74 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/Splitter.xcodeproj/xcshareddata/xcschemes/SplitterTests.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
10 |
11 |
16 |
17 |
19 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
43 |
44 |
45 |
46 |
52 |
53 |
55 |
56 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/Splitter.xcodeproj/xcshareddata/xcschemes/SplitterUITests.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
10 |
11 |
16 |
17 |
19 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
43 |
44 |
45 |
46 |
52 |
53 |
55 |
56 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/Splitter.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Splitter.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Splitter.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "icon_16x16.png",
5 | "scale" : "1x",
6 | "idiom" : "mac",
7 | "size" : "16x16"
8 | },
9 | {
10 | "idiom" : "mac",
11 | "filename" : "icon_16x16@2x.png",
12 | "size" : "16x16",
13 | "scale" : "2x"
14 | },
15 | {
16 | "idiom" : "mac",
17 | "filename" : "icon_32x32.png",
18 | "size" : "32x32",
19 | "scale" : "1x"
20 | },
21 | {
22 | "idiom" : "mac",
23 | "size" : "32x32",
24 | "filename" : "icon_32x32@2x.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "filename" : "icon_128x128.png",
29 | "idiom" : "mac",
30 | "size" : "128x128",
31 | "scale" : "1x"
32 | },
33 | {
34 | "scale" : "2x",
35 | "size" : "128x128",
36 | "idiom" : "mac",
37 | "filename" : "icon_128x128@2x.png"
38 | },
39 | {
40 | "size" : "256x256",
41 | "idiom" : "mac",
42 | "filename" : "icon_256x256.png",
43 | "scale" : "1x"
44 | },
45 | {
46 | "filename" : "icon_256x256@2x.png",
47 | "idiom" : "mac",
48 | "scale" : "2x",
49 | "size" : "256x256"
50 | },
51 | {
52 | "filename" : "icon_512x512.png",
53 | "idiom" : "mac",
54 | "size" : "512x512",
55 | "scale" : "1x"
56 | },
57 | {
58 | "size" : "512x512",
59 | "filename" : "icon_512x512@2x.png",
60 | "scale" : "2x",
61 | "idiom" : "mac"
62 | }
63 | ],
64 | "info" : {
65 | "version" : 1,
66 | "author" : "xcode"
67 | }
68 | }
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/AppIcon.appiconset/icon_128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/AppIcon.appiconset/icon_128x128.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/AppIcon.appiconset/icon_16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/AppIcon.appiconset/icon_16x16.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/AppIcon.appiconset/icon_256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/AppIcon.appiconset/icon_256x256.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/AppIcon.appiconset/icon_32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/AppIcon.appiconset/icon_32x32.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/AppIcon.appiconset/icon_512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/AppIcon.appiconset/icon_512x512.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/CurrentSplitColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | },
6 | "colors" : [
7 | {
8 | "idiom" : "universal",
9 | "color" : {
10 | "color-space" : "srgb",
11 | "components" : {
12 | "red" : "91",
13 | "alpha" : "1.000",
14 | "blue" : "181",
15 | "green" : "74"
16 | }
17 | }
18 | }
19 | ]
20 | }
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/Document Icon/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/Document Icon/docBG.iconset/icon_128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/Document Icon/docBG.iconset/icon_128x128.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/Document Icon/docBG.iconset/icon_128x128@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/Document Icon/docBG.iconset/icon_128x128@2x.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/Document Icon/docBG.iconset/icon_16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/Document Icon/docBG.iconset/icon_16x16.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/Document Icon/docBG.iconset/icon_16x16@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/Document Icon/docBG.iconset/icon_16x16@2x.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/Document Icon/docBG.iconset/icon_256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/Document Icon/docBG.iconset/icon_256x256.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/Document Icon/docBG.iconset/icon_256x256@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/Document Icon/docBG.iconset/icon_256x256@2x.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/Document Icon/docBG.iconset/icon_32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/Document Icon/docBG.iconset/icon_32x32.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/Document Icon/docBG.iconset/icon_32x32@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/Document Icon/docBG.iconset/icon_32x32@2x.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/Document Icon/docBG.iconset/icon_512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/Document Icon/docBG.iconset/icon_512x512.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/Document Icon/docBG.iconset/icon_512x512@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/Document Icon/docBG.iconset/icon_512x512@2x.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/Document Icon/livesplit.iconset/icon_128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/Document Icon/livesplit.iconset/icon_128x128.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/Document Icon/livesplit.iconset/icon_128x128@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/Document Icon/livesplit.iconset/icon_128x128@2x.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/Document Icon/livesplit.iconset/icon_16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/Document Icon/livesplit.iconset/icon_16x16.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/Document Icon/livesplit.iconset/icon_16x16@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/Document Icon/livesplit.iconset/icon_16x16@2x.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/Document Icon/livesplit.iconset/icon_256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/Document Icon/livesplit.iconset/icon_256x256.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/Document Icon/livesplit.iconset/icon_256x256@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/Document Icon/livesplit.iconset/icon_256x256@2x.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/Document Icon/livesplit.iconset/icon_32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/Document Icon/livesplit.iconset/icon_32x32.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/Document Icon/livesplit.iconset/icon_32x32@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/Document Icon/livesplit.iconset/icon_32x32@2x.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/Document Icon/livesplit.iconset/icon_512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/Document Icon/livesplit.iconset/icon_512x512.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/Document Icon/livesplit.iconset/icon_512x512@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/Document Icon/livesplit.iconset/icon_512x512@2x.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/Document Icon/splitDocIcon.iconset/icon_128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/Document Icon/splitDocIcon.iconset/icon_128x128.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/Document Icon/splitDocIcon.iconset/icon_128x128@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/Document Icon/splitDocIcon.iconset/icon_128x128@2x.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/Document Icon/splitDocIcon.iconset/icon_16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/Document Icon/splitDocIcon.iconset/icon_16x16.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/Document Icon/splitDocIcon.iconset/icon_16x16@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/Document Icon/splitDocIcon.iconset/icon_16x16@2x.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/Document Icon/splitDocIcon.iconset/icon_256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/Document Icon/splitDocIcon.iconset/icon_256x256.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/Document Icon/splitDocIcon.iconset/icon_256x256@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/Document Icon/splitDocIcon.iconset/icon_256x256@2x.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/Document Icon/splitDocIcon.iconset/icon_32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/Document Icon/splitDocIcon.iconset/icon_32x32.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/Document Icon/splitDocIcon.iconset/icon_32x32@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/Document Icon/splitDocIcon.iconset/icon_32x32@2x.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/Document Icon/splitDocIcon.iconset/icon_512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/Document Icon/splitDocIcon.iconset/icon_512x512.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/Document Icon/splitDocIcon.iconset/icon_512x512@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/Document Icon/splitDocIcon.iconset/icon_512x512@2x.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/Document Icon/splitsioDoc.iconset/icon_128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/Document Icon/splitsioDoc.iconset/icon_128x128.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/Document Icon/splitsioDoc.iconset/icon_128x128@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/Document Icon/splitsioDoc.iconset/icon_128x128@2x.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/Document Icon/splitsioDoc.iconset/icon_16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/Document Icon/splitsioDoc.iconset/icon_16x16.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/Document Icon/splitsioDoc.iconset/icon_16x16@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/Document Icon/splitsioDoc.iconset/icon_16x16@2x.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/Document Icon/splitsioDoc.iconset/icon_256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/Document Icon/splitsioDoc.iconset/icon_256x256.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/Document Icon/splitsioDoc.iconset/icon_256x256@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/Document Icon/splitsioDoc.iconset/icon_256x256@2x.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/Document Icon/splitsioDoc.iconset/icon_32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/Document Icon/splitsioDoc.iconset/icon_32x32.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/Document Icon/splitsioDoc.iconset/icon_32x32@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/Document Icon/splitsioDoc.iconset/icon_32x32@2x.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/Document Icon/splitsioDoc.iconset/icon_512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/Document Icon/splitsioDoc.iconset/icon_512x512.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/Document Icon/splitsioDoc.iconset/icon_512x512@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/Document Icon/splitsioDoc.iconset/icon_512x512@2x.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/Game Controller.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "Gamepad 2.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/Game Controller.imageset/Gamepad 2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/Game Controller.imageset/Gamepad 2.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/Hotkeys.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "Icon.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | },
21 | "properties" : {
22 | "template-rendering-intent" : "template"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/Hotkeys.imageset/Icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/Hotkeys.imageset/Icon.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/accentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.710",
9 | "green" : "0.290",
10 | "red" : "0.357"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/chevron.down.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/chevron.down.1.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/chevron.down.2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/chevron.down.2.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/chevron.down.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/chevron.down.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/chevron.up.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/chevron.up.1.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/chevron.up.2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/chevron.up.2.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/chevron.up.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/chevron.up.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/downarrow.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "downarrow.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | },
21 | "properties" : {
22 | "preserves-vector-representation" : true,
23 | "template-rendering-intent" : "template"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/downarrow.imageset/downarrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/downarrow.imageset/downarrow.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/flag.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "flagsmaller.png",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | },
12 | "properties" : {
13 | "preserves-vector-representation" : true,
14 | "template-rendering-intent" : "template"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/flag.imageset/flagsmaller.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/flag.imageset/flagsmaller.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/folder.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "folder.pdf",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | },
21 | "properties" : {
22 | "preserves-vector-representation" : true,
23 | "template-rendering-intent" : "template"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/folder.imageset/folder.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/folder.imageset/folder.pdf
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/gearshape.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "gearshape.pdf",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | },
21 | "properties" : {
22 | "preserves-vector-representation" : true,
23 | "template-rendering-intent" : "template"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/gearshape.imageset/gearshape.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/gearshape.imageset/gearshape.pdf
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/info.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "info.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | },
21 | "properties" : {
22 | "template-rendering-intent" : "template"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/info.imageset/info.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/info.imageset/info.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/lines.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "List@1x.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "List@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "author" : "xcode",
20 | "version" : 1
21 | },
22 | "properties" : {
23 | "template-rendering-intent" : "template"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/lines.imageset/List@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/lines.imageset/List@1x.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/lines.imageset/List@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/lines.imageset/List@2x.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/menuBarIcon.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Splitter Glyph 2.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | },
12 | "properties" : {
13 | "auto-scaling" : "auto",
14 | "preserves-vector-representation" : true,
15 | "template-rendering-intent" : "template"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/pencil.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "PencilIcon.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | },
21 | "properties" : {
22 | "preserves-vector-representation" : true,
23 | "template-rendering-intent" : "template"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/pencil.imageset/PencilIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/pencil.imageset/PencilIcon.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/plus.app.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "plus.app.pdf",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | },
21 | "properties" : {
22 | "preserves-vector-representation" : true,
23 | "template-rendering-intent" : "template"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/plus.app.imageset/plus.app.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/plus.app.imageset/plus.app.pdf
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/splitsio.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "logo-578e4861acb4fde546fe2a2f1cc2d750da727b0eefef55e876aa40c6cbc4ebd8.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | },
21 | "properties" : {
22 | "template-rendering-intent" : "original"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/splitsio.imageset/logo-578e4861acb4fde546fe2a2f1cc2d750da727b0eefef55e876aa40c6cbc4ebd8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/splitsio.imageset/logo-578e4861acb4fde546fe2a2f1cc2d750da727b0eefef55e876aa40c6cbc4ebd8.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/square.and.arrow.down.fill.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "square.and.arrow.down.fill.pdf",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | },
21 | "properties" : {
22 | "preserves-vector-representation" : true,
23 | "template-rendering-intent" : "template"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/square.and.arrow.down.fill.imageset/square.and.arrow.down.fill.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/square.and.arrow.down.fill.imageset/square.and.arrow.down.fill.pdf
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/stop.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "stop.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "stop.1.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "stop.2.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | },
23 | "properties" : {
24 | "preserves-vector-representation" : true,
25 | "template-rendering-intent" : "template"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/stop.imageset/stop.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/stop.imageset/stop.1.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/stop.imageset/stop.2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/stop.imageset/stop.2.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/stop.imageset/stop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/stop.imageset/stop.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/stopSquare.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "stop.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "stop.1.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "stop.2.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/stopSquare.imageset/stop.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/stopSquare.imageset/stop.1.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/stopSquare.imageset/stop.2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/stopSquare.imageset/stop.2.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/stopSquare.imageset/stop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/stopSquare.imageset/stop.png
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/trash.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "trash.4.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | },
21 | "properties" : {
22 | "preserves-vector-representation" : true,
23 | "template-rendering-intent" : "template"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Splitter/Assets.xcassets/trash.imageset/trash.4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/Assets.xcassets/trash.imageset/trash.4.png
--------------------------------------------------------------------------------
/Splitter/Credits.html:
--------------------------------------------------------------------------------
1 |
6 |
7 | Icons made by Freepik from www.flaticon.com
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/Splitter/Data/CommonPaths.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CommonPaths.swift
3 | // Splitter
4 | //
5 | // Created by Michael Berk on 2/12/20.
6 | // Copyright © 2020 Michael Berk. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Cocoa
11 |
12 | enum CommonPaths {
13 | static let appSupport = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!.appendingPathComponent("/Splitter")
14 | static let keybinds = appSupport.appendingPathComponent("/splitter.splitkeys")
15 | }
16 |
17 |
--------------------------------------------------------------------------------
/Splitter/Data/Documents/Common/SplitterDocBundle.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SplitterDocBundle.swift
3 | // Splitter
4 | //
5 | // Created by Michael Berk on 2/12/20.
6 | // Copyright © 2020 Michael Berk. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import Files
11 |
12 |
13 | ///Class for Splitter's Document Bundles
14 | class SplitterDocBundle: SplitterDoc {
15 |
16 | override internal var file: File? {
17 | return nil
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/Splitter/Data/Documents/Other Formats/LiveSplit/LiveSplit.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LiveSplit.swift
3 | // Splitter
4 | //
5 | // Created by Michael Berk on 2/5/20.
6 | // Copyright © 2020 Michael Berk. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Cocoa
11 | import Files
12 | import LiveSplitKit
13 |
14 | extension ViewController {
15 | func loadLS(url: URL, asTemplate: Bool = false) {
16 | if let run = Run.parseFile(path: url.path, loadFiles: true) {
17 | self.run = SplitterRun (run: run, isNewRun: false)
18 | if asTemplate {
19 | self.run.timer.resetHistories()
20 | }
21 | self.run.updateLayoutState()
22 | }
23 | }
24 | }
25 |
26 |
27 | extension NSImage {
28 | //TODO: Use this when saving to LiveSplit
29 | /** Turns an NSImage into the format necessary to be used with LiveSplitCore
30 | - Parameters:
31 | - withImage: Function that takes the data and passes it to LiveSplitCore
32 |
33 | In LiveSplitCore, methods dealing with images take two parameters: a pointer (`UnsafeMutableRawPointer?`) and the image's length (`Int`).
34 |
35 | `toLSImage` provides an easy way to deal with this in Swift, using an existing `NSImage`.
36 |
37 | Let's say you have `editor` - a `RunEditor`, and want to set the game's icon - an `NSImage` called `gameIcon`. You would use this method like so:
38 | ```swift
39 | gameIcon.toLSImage { pointer, len in
40 | editor.setGameIcon(pointer, len)
41 | }
42 | ```
43 | */
44 | func toLSImage(_ withImage: (UnsafeMutableRawPointer?, Int) -> ()) {
45 | if var giData = tiffRepresentation {
46 | let giLen = giData.count
47 | giData.withUnsafeMutableBytes( { bytes in
48 | let umbp = bytes.baseAddress
49 | withImage(umbp, giLen)
50 | })
51 | }
52 | }
53 |
54 | static func parseImageFromLiveSplit(ptr: UnsafeMutableRawPointer?, len: size_t) -> NSImage? {
55 | if let p = ptr {
56 | let data = Data(bytes: p, count: Int(len))
57 | if let img = NSImage(data: data) {
58 | return img
59 | }
60 | }
61 | return nil
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/Splitter/Data/Documents/Other Formats/LiveSplit/lss.swift:
--------------------------------------------------------------------------------
1 | //
2 | // lss.swift
3 | // Splitter
4 | //
5 | // Created by Michael Berk on 2/10/20.
6 | // Copyright © 2020 Michael Berk. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import Files
11 |
12 | class lss: SplitterDoc {
13 |
14 | /*
15 | override var windowNibName: String? {
16 | // Override returning the nib file name of the document
17 | // If you need to use a subclass of NSWindowController or if your document supports multiple NSWindowControllers, you should remove this method and override -makeWindowControllers instead.
18 | return "lss"
19 | }
20 | */
21 |
22 | override func windowControllerDidLoadNib(_ aController: NSWindowController) {
23 | super.windowControllerDidLoadNib(aController)
24 |
25 | // Add any code here that needs to be executed once the windowController has loaded the document's window.
26 | }
27 |
28 | var tempURL: URL?
29 | private var urlToLoad: URL? {
30 | if let url = tempURL {
31 | return url
32 | }
33 | else if let url = fileURL {
34 | return url
35 | }
36 | return nil
37 | }
38 | var template: Bool = false
39 |
40 | override func makeWindowControllers() {
41 | let load = loadViewController()
42 | let vc = load.vc
43 | if let url = urlToLoad {
44 | vc.loadLS(url: url, asTemplate: template)
45 | }
46 | vc.setupVC()
47 | }
48 |
49 | override func save(to url: URL, ofType typeName: String, for saveOperation: NSDocument.SaveOperationType, delegate: Any?, didSave didSaveSelector: Selector?, contextInfo: UnsafeMutableRawPointer?) {
50 | determineSave(to: url, ofType: typeName, for: saveOperation, delegate: delegate, didSave: didSaveSelector, contextInfo: contextInfo)
51 | }
52 |
53 | override func close() {
54 | super.close()
55 | if let tempURL = tempURL {
56 | let tempFile = try? File(path: tempURL.path)
57 | try? tempFile?.delete()
58 | }
59 |
60 | }
61 |
62 | override func data(ofType typeName: String) throws -> Data {
63 |
64 | throw NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil)
65 | }
66 |
67 | override func read(from data: Data, ofType typeName: String) throws {
68 | // Insert code here to read your document from the given data of the specified type, throwing an error in case of failure.
69 | // Alternatively, you could remove this method and override read(from:ofType:) instead. If you do, you should also override isEntireFileLoaded to return false if the contents are lazily loaded.
70 |
71 | }
72 |
73 | }
74 |
--------------------------------------------------------------------------------
/Splitter/Data/Time Conversion.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Time Conversion.swift
3 | // Splitter
4 | //
5 | // Created by Michael Berk on 3/3/20.
6 | // Copyright © 2020 Michael Berk. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /// Parse RFC 3339 date string to NSDate
12 | ///
13 | /// :param: rfc3339DateTimeString string with format "yyyy-MM-ddTHH:mm:ssZ"
14 | /// :returns: NSDate, or nil if string cannot be parsed
15 | public func dateForRFC3339DateTimeString(rfc3339DateTimeString: String) -> Date? {
16 | let formatter = getThreadLocalRFC3339DateFormatter()
17 | return formatter.date(from: rfc3339DateTimeString)
18 | }
19 |
20 | /// Generate RFC 3339 date string for an NSDate
21 | ///
22 | /// :param: date NSDate
23 | /// :returns: String
24 | public func rfc3339DateTimeStringForDate(date: Date) -> String {
25 | let formatter = getThreadLocalRFC3339DateFormatter()
26 | return formatter.string(from: date)
27 | }
28 |
29 | // Date formatters are not thread-safe, so use a thread-local instance
30 | private func getThreadLocalRFC3339DateFormatter() -> DateFormatter {
31 | return cachedThreadLocalObjectWithKey(key: "net.kristopherjohnson.getThreadLocalRFC3339DateFormatter") {
32 | let en_US_POSIX = Locale(identifier: "en_US_POSIX")
33 | let rfc3339DateFormatter = DateFormatter()
34 | rfc3339DateFormatter.locale = en_US_POSIX
35 | rfc3339DateFormatter.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ssXXX"
36 | rfc3339DateFormatter.timeZone = TimeZone(secondsFromGMT: 0)
37 | return rfc3339DateFormatter
38 | }
39 | }
40 |
41 | /// Return a thread-local object, creating it if it has not already been created
42 | ///
43 | /// :param: create closure that will be invoked to create the object
44 | /// :returns: object of type T
45 | private func cachedThreadLocalObjectWithKey(key: String, create: () -> T) -> T {
46 | let threadDictionary = Thread.current.threadDictionary
47 | if let cachedObject = threadDictionary[key] as! T? {
48 | return cachedObject
49 | }
50 | else {
51 | let newObject = create()
52 | threadDictionary[key] = newObject
53 | return newObject
54 | }
55 | // If we don't have a thread-local dictionary for some reason,
56 | // then just call create() on every call
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/Splitter/Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Extensions.swift
3 | // Splitter
4 | //
5 | // Created by Michael Berk on 4/28/21.
6 | // Copyright © 2021 Michael Berk. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | extension NSImage {
12 |
13 | static func isDiff(_ lhs: NSImage?, _ rhs: NSImage?) -> Bool {
14 | if lhs?.tiffRepresentation != rhs?.tiffRepresentation || (lhs != nil && rhs == nil) || (lhs == nil && rhs != nil) {
15 | return true
16 | }
17 | return false
18 | }
19 | }
20 |
21 | extension NSGridView {
22 | // open override var isFlipped: Bool {
23 | // return true
24 | // }
25 | }
26 | extension NSVisualEffectView {
27 | open override var isFlipped: Bool {
28 | return true
29 | }
30 | }
31 | extension NSMenuItem {
32 | convenience init(title: String, action: Selector, keyEquivalent: String, representedObject: Any?) {
33 | self.init(title: title, action: action, keyEquivalent: keyEquivalent)
34 | self.representedObject = representedObject
35 | }
36 | }
37 | extension NSView {
38 | ///Toggles whether the view is hidden`
39 | func hide() {
40 | self.isHidden.toggle()
41 | }
42 | }
43 | extension NSAppearance {
44 | var isDark: Bool {
45 | if name.rawValue.contains("Dark") {
46 | return true
47 | }
48 | return false
49 | }
50 | }
51 | extension NSImage {
52 | static var gameControllerIcon: NSImage {
53 | let icon = #imageLiteral(resourceName: "Game Controller")
54 | icon.isTemplate = true
55 | return icon
56 | }
57 | }
58 |
59 | extension NSControl.StateValue {
60 |
61 | func toBool() -> Bool {
62 | if self == .on {
63 | return true
64 | } else if self == .off {
65 | return false
66 | }
67 | return false
68 | }
69 |
70 | init (bool: Bool) {
71 | if bool {
72 | self = .on
73 | } else {
74 | self = .off
75 | }
76 | }
77 |
78 | }
79 |
--------------------------------------------------------------------------------
/Splitter/LiveSplit Core/liblivesplit_core.a:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/LiveSplit Core/liblivesplit_core.a
--------------------------------------------------------------------------------
/Splitter/Prefs/DebugPrefsViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DebugPrefsViewController.swift
3 | // Splitter
4 | //
5 | // Created by Michael Berk on 7/26/20.
6 | // Copyright © 2020 Michael Berk. All rights reserved.
7 | //
8 | #if DEBUG
9 | import Cocoa
10 | import Preferences
11 |
12 | class DebugPrefsViewController: NSViewController, PreferencePane {
13 | var preferencePaneIdentifier: Preferences.PaneIdentifier = .debug
14 |
15 | var preferencePaneTitle: String = "Debug"
16 | var toolbarItemIcon: NSImage {
17 | if #available(macOS 11.0, *), let img = NSImage(systemSymbolName: "hammer", accessibilityDescription: nil) {
18 | return img
19 | } else {
20 | return NSImage(named: NSImage.mobileMeName)!
21 | }
22 | }
23 |
24 | override var nibName: NSNib.Name? { "DebugPrefsViewController" }
25 | @IBOutlet var splitsIOURLTextField: NSTextField!
26 | @IBOutlet var splitsIOClientIDField: NSTextField!
27 | @IBOutlet var splitsIOSecretIDField: NSTextField!
28 | @IBOutlet var placeholderSIOCheck: NSButton!
29 |
30 |
31 | override func viewDidLoad() {
32 | super.viewDidLoad()
33 | // Do view setup here.
34 | preferredContentSize = NSSize(width: self.view.frame.size.width, height: self.view.frame.size.height)
35 | placeholderSIOCheck.state = .init(bool: Settings.placeholderSIO)
36 |
37 | }
38 | override func viewDidAppear() {
39 | super.viewDidAppear()
40 | splitsIOURLTextField.stringValue = Settings.splitsIOURL.absoluteString
41 |
42 | }
43 |
44 | @IBAction func editURLTextField(_ sender: NSTextField) {
45 | if let url = URL(string: sender.stringValue) {
46 | Settings.splitsIOURL = url
47 | }
48 | }
49 |
50 | @IBAction func editClientTextField(_ sender: NSTextField) {
51 | if sender.stringValue == "" {
52 | Settings.splitsIOClientOverride = nil
53 | } else {
54 | Settings.splitsIOClientOverride = sender.stringValue
55 | }
56 | }
57 |
58 | @IBAction func editSecretTextField(_ sender: NSTextField) {
59 | if sender.stringValue == "" {
60 | Settings.splitsIOSecretOverride = nil
61 | } else {
62 | Settings.splitsIOSecretOverride = sender.stringValue
63 | }
64 | }
65 |
66 | @IBAction func sioInfoButtonClick(_ sender: NSButton) {
67 | let alert = NSAlert()
68 | alert.informativeText = "The following is persisted in the DB, and is what will be used for auth on next startup:\n\n\n\n\n\n\n\n\n\n\n\nURL: \(Settings.splitsIOURL) \nClientID: \(AppDelegate.splitsioclient)\nSecret: \(AppDelegate.splitsiosecret)"
69 | alert.beginSheetModal(for: self.view.window!)
70 | }
71 |
72 | @IBAction func togglePlaceholderSIO(_ sender: NSButton) {
73 | Settings.placeholderSIO = sender.state.toBool()
74 | }
75 |
76 | }
77 | #endif
78 |
--------------------------------------------------------------------------------
/Splitter/Prefs/GeneralPrefsViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GeneralPrefsViewController.swift
3 | // Splitter
4 | //
5 | // Created by Michael Berk on 12/13/21.
6 | // Copyright © 2021 Michael Berk. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import Preferences
11 |
12 | class GeneralPrefsViewController: NSViewController, PreferencePane {
13 |
14 | @IBOutlet var menuBarSwitch: NSSwitch!
15 | @IBOutlet var resetWarningsButton: NSButton!
16 |
17 | @IBAction func resetWarningsClicked(_ sender: Any?) {
18 | for warning in Warning.allCases {
19 | Settings.setWarning(warning, suppresed: false)
20 | }
21 | }
22 |
23 | @IBAction func setAppMode(_ sender: NSSwitch) {
24 | if sender.state == .on {
25 | StatusBarController.setMenuBarMode(true)
26 | NSApp.activate(ignoringOtherApps: true)
27 | } else {
28 | StatusBarController.setMenuBarMode(false)
29 | }
30 |
31 | }
32 | @objc func popover(_ sender: Any) {
33 | NSApp.activate(ignoringOtherApps: true)
34 | }
35 | var preferencePaneTitle: String = "General"
36 |
37 | var preferencePaneIdentifier: Preferences.PaneIdentifier = .general
38 |
39 | override var nibName: NSNib.Name? { "GeneralPrefsViewController" }
40 |
41 | var toolbarItemIcon: NSImage {
42 | if #available(macOS 11.0, *), let img = NSImage(systemSymbolName: "gearshape", accessibilityDescription: nil) {
43 | return img
44 | } else {
45 | return NSImage(named: NSImage.preferencesGeneralName)!
46 | }
47 | }
48 |
49 | override func viewDidLoad() {
50 | super.viewDidLoad()
51 | // Do view setup here.
52 | preferredContentSize = NSSize(width: self.view.frame.size.width, height: self.view.frame.size.height)
53 | NotificationCenter.default.addObserver(forName: .menuBarModeChanged, object: nil, queue: nil, using: { _ in
54 | self.setMenuBarToggle()
55 | })
56 | setMenuBarToggle()
57 | }
58 |
59 | func setMenuBarToggle() {
60 | if Settings.menuBarMode {
61 | menuBarSwitch.state = .on
62 | } else {
63 | menuBarSwitch.state = .off
64 | }
65 | }
66 |
67 | }
68 |
69 | extension AppDelegate {
70 | @objc func statusAction(_ sender: Any?) {
71 | NSApp.activate(ignoringOtherApps: true)
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/Splitter/Prefs/Hotkeys/GlobalKeybindPrefs.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GlobalKeybindPrefs.swift
3 | // Splitter
4 | //
5 | // Created by Michael Berk on 1/13/20.
6 | // Uses code from https://dev.to/mitchartemis/creating-a-global-configurable-shortcut-for-macos-apps-in-swift-25e9
7 | // Copyright © 2020 Michael Berk. All rights reserved.
8 | //
9 |
10 | import Foundation
11 |
12 | class GlobalKeybindPreferences: Codable, CustomStringConvertible {
13 | // let function : Bool
14 | let control : Bool
15 | let command : Bool
16 | let shift : Bool
17 | let option : Bool
18 | let capsLock : Bool
19 | let carbonFlags : UInt32
20 | let characters : String?
21 | let keyCode : UInt32
22 |
23 | init (control: Bool, command: Bool, shift: Bool, option: Bool, capsLock: Bool, carbonFlags: UInt32, characters: String?, keyCode: UInt32) {
24 | self.control = control
25 | self.command = command
26 | self.shift = shift
27 | self.option = option
28 | self.capsLock = capsLock
29 | self.carbonFlags = carbonFlags
30 | self.characters = characters
31 | self.keyCode = keyCode
32 | }
33 |
34 | var description: String {
35 |
36 | //the reason why I have to make a copy of self.function is explained later on
37 | // var function = self.function
38 |
39 |
40 |
41 | var stringBuilder = ""
42 |
43 | if self.control {
44 | stringBuilder += modifierChars.control.rawValue
45 | }
46 | if self.option {
47 | stringBuilder += modifierChars.option.rawValue
48 | }
49 | if self.shift {
50 | stringBuilder += modifierChars.shift.rawValue
51 | }
52 | if self.command {
53 | stringBuilder += modifierChars.command.rawValue
54 | }
55 |
56 | if self.capsLock {
57 | stringBuilder += "⇪"
58 | }
59 |
60 | if var characters = self.characters {
61 | let intKC = Int(keyCode)
62 | switch intKC {
63 | case 36:
64 | characters = "⏎"
65 | case 48:
66 | characters = "⇥"
67 | case 49:
68 | characters = "␣"
69 | // function = false
70 | case 51:
71 | characters = "⌫"
72 | case 115:
73 | characters = "↖"
74 | case 116:
75 | characters = "⇞"
76 | case 117:
77 | characters = "⌦"
78 | // function = false
79 | case 119:
80 | characters = "↘"
81 | case 121:
82 | characters = "⇟"
83 |
84 | //For some reason, when pressing an arrow key, it thinks that fn is being pressed, so we correct it. It's actually not possible to do fn+[arrow key] because it turns into one of the pgUp/pgDown/home/end keys listed above, so this shouldn't cause false positives.
85 | case 123:
86 | characters = "←"
87 | // function = false
88 | case 124:
89 | characters = "→"
90 | // function = false
91 | case 125:
92 | characters = "↓"
93 | // function = false
94 | case 126:
95 | characters = "↑"
96 | // function = false
97 | default: break
98 | }
99 |
100 | stringBuilder += characters.uppercased()
101 |
102 | }
103 |
104 | // if function {
105 | // stringBuilder = "Fn" + stringBuilder
106 | // }
107 | return "\(stringBuilder)"
108 | }
109 | }
110 |
111 | enum modifierChars: String {
112 | case command = "⌘"
113 | case option = "⌥"
114 | case control = "⌃"
115 | case shift = "⇧"
116 | }
117 |
--------------------------------------------------------------------------------
/Splitter/Prefs/Hotkeys/KeybindHandlers.swift:
--------------------------------------------------------------------------------
1 | //
2 | // KeybindHandlers.swift
3 | // Splitter
4 | //
5 | // Created by Michael Berk on 2/16/20.
6 | // Copyright © 2020 Michael Berk. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | ///Stuff for handling keybind actions
12 | extension AppDelegate {
13 | ///Brings the current window to the front. Intended for use with keybinds.
14 | func frontHandler() {
15 | NSApplication.shared.orderedWindows.forEach({ (window) in
16 | if let mainWindow = window as? MainWindow {
17 | mainWindow.hidesOnDeactivate = false
18 | NSApplication.shared.activate(ignoringOtherApps: true)
19 | mainWindow.makeKeyAndOrderFront(nil)
20 | mainWindow.makeKey()
21 |
22 | }
23 | })
24 | }
25 |
26 | //TODO: Document why this exists
27 | func startSplitHandler() {
28 | if let vc = viewController {
29 | vc.startSplitTimer()
30 | }
31 | }
32 |
33 | func pauseHandler() {
34 | if let vc = viewController {
35 | vc.pauseResumeTimer()
36 | }
37 | }
38 |
39 | func prevHandler() {
40 | if let vc = viewController {
41 | vc.run.timer.previousSplit()
42 | }
43 | }
44 | func skipHandler() {
45 | if let vc = viewController {
46 | vc.run.timer.skipSplit()
47 | }
48 | }
49 | func stopHandler() {
50 | if let vc = viewController {
51 | vc.cancelRun()
52 | }
53 | }
54 | func cancelRunHandler() {
55 | if let vc = viewController {
56 | vc.cancelRun()
57 | }
58 | }
59 |
60 | func showInfoHandler() {
61 | if let vc = viewController {
62 | vc.displayInfoPopover(self)
63 | }
64 | }
65 | func showLayoutEditorHandler() {
66 | if let vc = viewController {
67 | vc.showLayoutEditor()
68 | }
69 | }
70 | func showSplitsEditorHandler() {
71 | if let vc = viewController {
72 | vc.displaySplitsEditor()
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/Splitter/Prefs/PrefPanes.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PrefPanes.swift
3 | // Splitter
4 | //
5 | // Created by Michael Berk on 1/1/20.
6 | // Copyright © 2020 Michael Berk. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import Preferences
11 |
12 | extension Preferences.PaneIdentifier {
13 | static let general = Self("general")
14 | static let advanced = Self("advanced")
15 | static let hotkeys = Self("hotkeys")
16 | static let debug = Self("debug")
17 | static let splitsIO = Self("splitsIO")
18 | }
19 |
--------------------------------------------------------------------------------
/Splitter/SetupForUITesting.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SetupForUITesting.swift
3 | // Splitter
4 | //
5 | // Created by Michael Berk on 10/25/22.
6 | // Copyright © 2022 Michael Berk. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | extension AppDelegate {
11 |
12 | var isUITesting: Bool {
13 | return CommandLine.arguments.contains("-uiTesting")
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Splitter/Splitter-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | //
2 | // Splitter-Bridging-Header.h
3 | // Splitter
4 | //
5 | // Created by Michael Berk on 2/6/20.
6 | // Copyright © 2020 Michael Berk. All rights reserved.
7 | //
8 |
9 | #ifndef Splitter_Bridging_Header_h
10 | #define Splitter_Bridging_Header_h
11 | //#import "LiveSplit Core/livesplit_core.h"
12 | #import
13 | #import
14 |
15 | #endif /* Splitter_Bridging_Header_h */
16 |
--------------------------------------------------------------------------------
/Splitter/Splitter.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.cs.allow-unsigned-executable-memory
8 |
9 | com.apple.security.cs.disable-library-validation
10 |
11 | com.apple.security.files.user-selected.read-write
12 |
13 | com.apple.security.network.client
14 |
15 | keychain-access-groups
16 |
17 | $(AppIdentifierPrefix)com.mibe.Splitter
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/Splitter/SplitterDocLSS.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/SplitterDocLSS.icns
--------------------------------------------------------------------------------
/Splitter/SplitterDocSplitsio.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/Splitter/SplitterDocSplitsio.icns
--------------------------------------------------------------------------------
/Splitter/StatusBarController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StatusBarController.swift
3 | // Splitter
4 | //
5 | // Created by Michael Berk on 12/13/21.
6 | // Copyright © 2021 Michael Berk. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | class StatusBarController: NSObject {
12 |
13 | var statusItem: NSStatusItem!
14 | var statusMenu: NSMenu!
15 |
16 | var quitItem: NSMenuItem {
17 | let quit = NSMenuItem(title: "Quit", action: #selector(quitMenuItem(_:)), keyEquivalent: "q")
18 | quit.keyEquivalentModifierMask = [.command]
19 | return quit
20 | }
21 |
22 | var turnOffOverlayItem = NSMenuItem(title: "Turn off Overlay Mode", action: #selector(turnOffOverlayAction(_:)), keyEquivalent: "")
23 |
24 | func setupItem() {
25 | let item = NSStatusBar.system.statusItem(withLength: NSStatusItem.squareLength)
26 | statusMenu = NSMenu(title: "PopMenu")
27 | self.statusItem = item
28 | if let button = item.button {
29 | let img = NSImage(named: "menuBarIcon")
30 | button.image = img!
31 | button.imageScaling = .scaleProportionallyDown
32 | button.target = self
33 | button.action = #selector(clickItem(_:))
34 | item.button?.sendAction(on: [.leftMouseUp, .rightMouseUp])
35 | }
36 |
37 |
38 | statusMenu.delegate = self
39 | let menuItems = [quitItem, .separator(), turnOffOverlayItem, .separator()]
40 | for item in menuItems {
41 | item.target = self
42 | statusMenu.addItem(item)
43 | }
44 | let mainMenu = NSApp.mainMenu!
45 | for item in mainMenu.items {
46 | let copy = item.copy() as! NSMenuItem
47 | statusMenu.addItem(copy)
48 | }
49 |
50 |
51 |
52 | NotificationCenter.default.addObserver(forName: .menuBarModeChanged, object: nil, queue: nil, using: { _ in
53 | self.toggleVisible()
54 | })
55 | Self.setMenuBarMode(Settings.menuBarMode)
56 | }
57 |
58 | func toggleVisible() {
59 | if Settings.menuBarMode {
60 | statusItem.isVisible = true
61 | } else {
62 | statusItem.isVisible = false
63 | }
64 | }
65 |
66 | @objc func quitMenuItem(_ sender: Any?) {
67 | NSApp.terminate(nil)
68 | }
69 | @objc func turnOffOverlayAction(_ sender: Any?) {
70 | Self.setMenuBarMode(false)
71 | }
72 |
73 | @objc func clickItem(_ sender: Any?) {
74 | let event = NSApp.currentEvent!
75 | if event.type == .rightMouseUp {
76 | statusItem.menu = statusMenu
77 | statusItem.button?.performClick(nil)
78 | } else {
79 | NSApp.activate(ignoringOtherApps: true)
80 | //Always 1 window - status item
81 | AppDelegate.shared?.newWindowIfNone()
82 | }
83 | }
84 |
85 | static func setMenuBarMode(_ bool: Bool) {
86 | Settings.menuBarMode = bool
87 | let pol: NSApplication.ActivationPolicy = bool ? .accessory : .regular
88 | NSApp.setActivationPolicy(pol)
89 | }
90 | }
91 |
92 | extension StatusBarController: NSMenuDelegate {
93 | func menuDidClose(_ menu: NSMenu) {
94 | statusItem.menu = nil
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/Splitter/TimerCore/Fontable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Fontable.swift
3 | // Splitter
4 | //
5 | // Created by Michael Berk on 12/22/22.
6 | // Copyright © 2022 Michael Berk. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | protocol Fontable {
12 | func setFont()
13 | func setFontObserver()
14 | var run: SplitterRun! {get}
15 | ///Initial font size for the control
16 | ///
17 | ///- NOTE: The default implementation does not set this. The `NSControl` implemention _does_ set this when ``setFont()-75x2j`` is run.
18 | var defaultFontSize: CGFloat? {get set}
19 | ///Option to opt in/out of Fontable behaviors
20 | var fontable: Bool {get set}
21 | ///Determines if the control responds to Splitter's font size adjustment
22 | var fixedFontSize: Bool {get}
23 | }
24 |
25 | extension Fontable {
26 | func setFontObserver() {
27 | NotificationCenter.default.addObserver(forName: .fontChanged, object: run, queue: nil, using: { notification in
28 | if fontable {
29 | setFont()
30 | }
31 | })
32 | }
33 | var fixedFontSize: Bool {
34 | return false
35 | }
36 | }
37 |
38 | //Need separate extension for each; I tried making it into a single protocol, but it wouldn't work.
39 |
40 | extension NSCell {
41 | func setFont(run: SplitterRun) {
42 | if let csize = self.font?.pointSize {
43 | let font = run.fontManager.getTextFont(fixedFontSize: false, defaultSize: csize)
44 | self.font = font
45 | }
46 | }
47 | }
48 |
49 | extension Fontable where Self: NSCell {
50 | func setFont() {
51 | self.setFont(run: run)
52 | }
53 | }
54 |
55 | extension Fontable where Self: NSControl {
56 | func setFont() {
57 | self.setFont(run: run)
58 | }
59 | }
60 |
61 | extension NSControl {
62 | @objc func setFont(run: SplitterRun) {
63 | if var control = self as? Fontable {
64 | if control.defaultFontSize == nil {
65 | if let size = self.font?.pointSize {
66 | control.defaultFontSize = size
67 | } else {
68 | control.defaultFontSize = 12
69 | }
70 | }
71 | let defaultSize = control.defaultFontSize!
72 | self.font = run.fontManager.getTextFont(fixedFontSize: control.fixedFontSize, defaultSize: defaultSize)
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/Splitter/TimerCore/ZippyFontDecoding.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ZippyFontDecoding.swift
3 | // Splitter
4 | //
5 | // Created by Michael Berk on 4/11/22.
6 | // Copyright © 2022 Michael Berk. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import LiveSplitKit
11 | import ZippyJSON
12 |
13 | fileprivate struct FontWrapper: Codable {
14 | var Font: LiveSplitFont
15 | }
16 |
17 | public extension SettingValueRef {
18 | func toFont() -> LiveSplitFont {
19 | let value = self.asJson()
20 | let data = value.data(using: .utf8)!
21 | let decoded = try! ZippyJSONDecoder().decode(FontWrapper.self, from: data)
22 | return decoded.Font
23 |
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/Splitter/UI/Controls/EditableSegmentIconView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EditableSegmentIconView.swift
3 | // Splitter
4 | //
5 | // Created by Michael Berk on 1/26/20.
6 | // Copyright © 2020 Michael Berk. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | class EditableSegmentIconView: ThemedImage {
12 | var delegate: EditableSegmentIconViewDelegate!
13 | var row: Int!
14 |
15 | required init?(coder: NSCoder) {
16 | super.init(coder: coder)
17 | setup()
18 | }
19 | override init(frame frameRect: NSRect) {
20 | super.init(frame: frameRect)
21 | setup()
22 | }
23 |
24 | func setup() {
25 | self.target = self
26 | self.action = #selector(imageChanged(_:))
27 | }
28 |
29 | func setPlaceholderImage() {
30 | if self.image == nil {
31 | self.image = .gameControllerIcon
32 | }
33 | }
34 |
35 | @objc func imageChanged(_ sender: EditableSegmentIconView) {
36 | let image = sender.image
37 | delegate.iconPicked(image, for: row)
38 | setPlaceholderImage()
39 | }
40 |
41 | ///Used in right-click menu
42 | @IBAction func removeImage(_ sender: Any?) {
43 | image = nil
44 | }
45 | ///Used in right-click menu
46 | @IBAction func chooseImageMenuItem(_ sender: Any?) {
47 | setImage()
48 | }
49 | }
50 | extension EditableSegmentIconView {
51 |
52 | override func mouseDown(with event: NSEvent) {
53 | if event.type == .leftMouseDown, event.clickCount > 1 {
54 | setImage()
55 | }
56 | }
57 | func pictureFileDialog() -> NSOpenPanel{
58 | let dialog = NSOpenPanel();
59 | dialog.title = "Choose an image file"
60 | dialog.showsResizeIndicator = true
61 | dialog.showsHiddenFiles = false
62 | dialog.canChooseDirectories = false
63 | dialog.canCreateDirectories = false
64 | dialog.allowsMultipleSelection = false
65 | dialog.allowedFileTypes = ["png"]
66 | return dialog
67 | }
68 |
69 | /// Prompts the user to select an image for the split icon
70 | func setImage() {
71 | let dialog = pictureFileDialog()
72 |
73 | let response = dialog.runModal()
74 | if response == .OK {
75 | let result = dialog.url
76 |
77 | if (result != nil) {
78 | let imageFile = try? Data(contentsOf: result!)
79 | let myImage = NSImage(data: imageFile!)
80 | delegate.iconPicked(myImage, for: row)
81 | }
82 | }
83 | }
84 | }
85 | protocol EditableSegmentIconViewDelegate {
86 | func iconPicked(_ icon: NSImage?, for row: Int)
87 | }
88 |
--------------------------------------------------------------------------------
/Splitter/UI/Controls/SplitterColorWell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SplitterColorWell.swift
3 | // Splitter
4 | //
5 | // Created by Michael Berk on 6/30/21.
6 | // Copyright © 2021 Michael Berk. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | @IBDesignable
11 | ///A control that displays a color value and lets the user change that color value.
12 | class SplitterColorWell: NSColorWell {
13 | var customAction: (SplitterColorWell) -> () = {_ in}
14 |
15 | @objc func colorWellAction(_ sender: SplitterColorWell) {
16 | customAction(sender)
17 | }
18 |
19 | convenience init(action: @escaping (SplitterColorWell) -> ()) {
20 | self.init()
21 | self.target = self
22 | self.action = #selector(colorWellAction(_:))
23 | self.customAction = action
24 | }
25 |
26 | @IBInspectable var allowsOpacity: Bool = true
27 |
28 | ///Place to save the existing shared `showsAlpha` setting
29 | private var sharedOpacity: Bool?
30 |
31 | override func activate(_ exclusive: Bool) {
32 | if allowsOpacity {
33 | NSColorPanel.shared.showsAlpha = true
34 | } else {
35 | NSColorPanel.shared.showsAlpha = false
36 | }
37 | super.activate(exclusive)
38 | }
39 | override func deactivate() {
40 | if let shared = sharedOpacity {
41 | NSColorPanel.shared.showsAlpha = shared
42 | }
43 | super.deactivate()
44 | }
45 | }
46 |
47 |
--------------------------------------------------------------------------------
/Splitter/UI/Import/DownloadWindowController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DownloadWindowController.swift
3 | // Splitter
4 | //
5 | // Created by Michael Berk on 8/2/20.
6 | // Copyright © 2020 Michael Berk. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import SplitsIOKit
11 | class DownloadWindowController: NSWindowController, NSWindowDelegate {
12 | static var windowOpened: Bool = false
13 | @IBOutlet weak var searchField: NSSearchField!
14 | @IBOutlet weak var accountButton: AccountButtonView!
15 | var account: SplitsIORunner? = Settings.splitsIOUser {
16 | didSet {
17 | accountButton.account = self.account
18 | accountButton.setAccountLabel()
19 | accountButton.setAccountImage()
20 | }
21 | }
22 |
23 |
24 | override func windowDidLoad() {
25 | super.windowDidLoad()
26 | window?.delegate = self
27 | if let vc = window?.contentViewController as? DownloadViewController {
28 | vc.sField = searchField
29 | }
30 | if let _ = account { accountButton.reloadData() }
31 | NotificationCenter.default.addObserver(forName: .splitsIOLogin, object: nil, queue: nil, using: {_ in
32 | self.getUser()
33 | })
34 | NotificationCenter.default.addObserver(forName: .splitsIOLogout, object: nil, queue: nil, using: { notification in
35 |
36 | Settings.splitsIOUser = nil
37 | self.account = nil
38 | })
39 |
40 | }
41 | func windowWillClose(_ notification: Notification) {
42 | Self.windowOpened = false
43 | }
44 | override func windowWillLoad() {
45 | super.windowWillLoad()
46 | Self.windowOpened = true
47 | }
48 |
49 |
50 | func getUser() {
51 | try? SplitsIOKit.shared.getCurrentUser(completion: { runner in
52 | self.account = runner
53 |
54 | })
55 | }
56 |
57 |
58 |
59 | }
60 |
--------------------------------------------------------------------------------
/Splitter/UI/LoadingViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LoadingViewController.swift
3 | // Splitter
4 | //
5 | // Created by Michael Berk on 8/18/20.
6 | // Copyright © 2020 Michael Berk. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | class LoadingViewController: NSViewController, LoadableNib {
12 | @IBOutlet weak var contentView: NSView!
13 |
14 | @IBOutlet weak var imageView: NSImageView!
15 | @IBOutlet weak var labelView: NSTextField!
16 | @IBOutlet weak var loadingBar: NSProgressIndicator!
17 |
18 |
19 | override func viewWillAppear() {
20 | super.viewWillAppear()
21 | self.imageView.image = NSImage(named: NSImage.applicationIconName)
22 | loadingBar.startAnimation(nil)
23 |
24 | }
25 | override func viewDidLoad() {
26 | super.viewDidLoad()
27 |
28 | // Do view setup here.
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/Splitter/UI/NSViewExtensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSViewExtensions.swift
3 | // Splitter
4 | //
5 | // Created by Michael Berk on 7/28/20.
6 | // Copyright © 2020 Michael Berk. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | extension NSView {
12 | /// Returns the first constraint with the given identifier, if available.
13 | ///
14 | /// - Parameter identifier: The constraint identifier.
15 | func constraintWithIdentifier(_ identifier: String) -> NSLayoutConstraint? {
16 |
17 | return self.constraints.first { $0.identifier == identifier }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Splitter/UI/NibLoadable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NibLoadable.swift
3 | // Splitter
4 | //
5 | // Created by Michael Berk on 6/25/21.
6 | // Copyright © 2021 Michael Berk. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | ///The newer, better Nib loading protocol
12 | protocol NibLoadable {
13 | static var nibName: String? { get }
14 | static func createFromNib(in bundle: Bundle) -> Self?
15 | }
16 |
17 | extension NibLoadable where Self: NSView {
18 |
19 | static var nibName: String? {
20 | return String(describing: self)
21 | }
22 |
23 | static func createFromNib(in bundle: Bundle = Bundle.main) -> Self? {
24 | guard let nibName = nibName else { return nil }
25 | var topLevelArray: NSArray? = nil
26 | bundle.loadNibNamed(NSNib.Name(nibName), owner: self, topLevelObjects: &topLevelArray)
27 | guard let results = topLevelArray else { return nil }
28 | let views = Array(results).filter { $0 is Self }
29 | return views.last as? Self
30 | }
31 | }
32 |
33 | extension NibLoadable where Self: NSViewController {
34 |
35 | static var nibName: String? {
36 | return String(describing: self)
37 | }
38 |
39 | static func createFromNib(in bundle: Bundle = Bundle.main) -> Self? {
40 | guard let nibName = nibName else { return nil }
41 | var topLevelArray: NSArray? = nil
42 | bundle.loadNibNamed(NSNib.Name(nibName), owner: self, topLevelObjects: &topLevelArray)
43 | guard let results = topLevelArray else { return nil }
44 | let views = Array(results).filter { $0 is Self }
45 | return views.last as? Self
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Splitter/UI/Splits Editor/SplitsEditorOutlineView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SplitsEditorOutlineView.swift
3 | // Splitter
4 | //
5 | // Created by Michael Berk on 6/29/21.
6 | // Copyright © 2021 Michael Berk. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import LiveSplitKit
11 |
12 | class SplitsEditorOutlineView: NSOutlineView {
13 | var editor: RunEditor!
14 | var editorState: RunEditorState {
15 | editor.getState()
16 | }
17 |
18 | override func selectRowIndexes(_ indexes: IndexSet, byExtendingSelection extend: Bool) {
19 | for row in 0.. Bool {
69 | editorState.segments?[row].selected.bool() ?? false
70 | }
71 |
72 | var splitsDelegate: SplitsEditorOutlineViewDelegate {
73 | delegate as! SplitsEditorOutlineViewDelegate
74 | }
75 |
76 | ///Used to make the segment icons selectable, and so that clicking a text field both selects the row and begins editing
77 | override func validateProposedFirstResponder(_ responder: NSResponder, for event: NSEvent?) -> Bool {
78 | if let view = responder as? NSView,
79 | let sup = view.superview {
80 | let row = self.row(for: sup)
81 |
82 | //Make sure that the text field handles selecting the row and then becomes first responder
83 | if view is SplitsEditorTextField {
84 | return true
85 | }
86 | if row > -1, selectedRowIndexes.contains(row) {
87 | return true
88 | }
89 | }
90 | return super.validateProposedFirstResponder(responder, for: event)
91 | }
92 | }
93 |
94 | ///This protocol doesn't do anything special right now, but I made it at one point, and it could save time in the future, so it's here.
95 | @objc
96 | protocol SplitsEditorOutlineViewDelegate: NSOutlineViewDelegate {}
97 |
--------------------------------------------------------------------------------
/Splitter/UI/Splits Editor/SplitsEditorTextField.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SplitsEditorTextField.swift
3 | // Splitter
4 | //
5 | // Created by Michael Berk on 6/30/21.
6 | // Copyright © 2021 Michael Berk. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | class TableViewTextField: NSTextField {
12 | var column: NSUserInterfaceItemIdentifier!
13 |
14 | var row: Int!
15 |
16 | override func validateProposedFirstResponder(_ responder: NSResponder, for event: NSEvent?) -> Bool {
17 | return true
18 | }
19 | }
20 |
21 | class SplitsEditorTextField: TableViewTextField {
22 |
23 | var outlineView: SplitsEditorOutlineView!
24 | ///Previous of the text field before the user edited it
25 | ///
26 | ///This is used to prevent changing text in LiveSplit if text didn't change, in ``SplitsEditorViewController/controlTextDidEndEditing(_:)``
27 | var previousValue: String!
28 |
29 | ///Make sure that clicking text field selects the segment in the segment editor
30 | override func becomeFirstResponder() -> Bool {
31 | let sup = super.becomeFirstResponder()
32 | if !outlineView.selectedRowIndexes.contains(row) {
33 | outlineView.selectRowIndexes(.init(integer: row), byExtendingSelection: false)
34 | }
35 | return sup
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Splitter/UI/Splits.ioAccount/AccountButton/AccountButtonView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AccountButtonViewController.swift
3 | // Splitter
4 | //
5 | // Created by Michael Berk on 8/11/20.
6 | // Copyright © 2020 Michael Berk. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import SplitsIOKit
11 |
12 | @IBDesignable
13 | class AccountButtonView: NSView, LoadableNib {
14 | @IBOutlet var contentView: NSView!
15 |
16 | @IBOutlet var accountLabel: NSTextField!
17 | @IBOutlet var accountIcon: NSImageView!
18 | var account: SplitsIORunner? = Settings.splitsIOUser {
19 | didSet {
20 | Settings.splitsIOUser = account
21 | setAccountLabel()
22 | setAccountImage()
23 | }
24 | }
25 | func reloadData() {
26 | setAccountLabel()
27 | setAccountImage()
28 | }
29 | func setAccountLabel() {
30 | #if DEBUG
31 | if Settings.placeholderSIO {
32 | accountLabel.stringValue = "Splitter"
33 | return
34 | }
35 | #endif
36 | if let account = account {
37 | accountLabel.stringValue = account.displayName
38 | } else {
39 | accountLabel.stringValue = "Sign In"
40 | }
41 | }
42 |
43 |
44 | func setAccountImage() {
45 | if let account = account, let avatarURL = URL(string: account.avatar) {
46 | let avatar = NSImage(byReferencing: avatarURL)
47 | accountIcon.image = avatar
48 | } else {
49 | accountIcon.image = NSImage(named: NSImage.userName)
50 | }
51 | }
52 |
53 | override func draw(_ dirtyRect: NSRect) {
54 | super.draw(dirtyRect)
55 |
56 | }
57 | required init?(coder: NSCoder) {
58 | super.init(coder: coder)
59 | loadViewFromNib()
60 | let click = NSClickGestureRecognizer(target: self, action: #selector(clicked(_:)))
61 | self.addGestureRecognizer(click)
62 | //For some reason, I need to manually do this so it doesn't show the placeholder when the view first appears if you're already logged in
63 |
64 | account = Settings.splitsIOUser
65 | }
66 |
67 |
68 |
69 | @objc func clicked(_ sender: Any) {
70 | let aView = AccountViewController()
71 | let pop = NSPopover()
72 | pop.contentViewController = aView
73 | pop.behavior = .semitransient
74 | // pop.contentSize = aView.currentView?.view.frame.size
75 | pop.show(relativeTo: accountLabel.frame, of: self, preferredEdge: .minY)
76 |
77 | NotificationCenter.default.addObserver(forName: .splitsIOLogout, object: nil, queue: nil, using: { _ in
78 | pop.close()
79 | })
80 | }
81 |
82 | }
83 |
--------------------------------------------------------------------------------
/Splitter/UI/Splits.ioAccount/AccountProfileViewController/AccountProfileView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AccountViewController.swift
3 | // Splitter
4 | //
5 | // Created by Michael Berk on 8/12/20.
6 | // Copyright © 2020 Michael Berk. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import SplitsIOKit
11 |
12 |
13 | class AccountProfileView: NSViewController, AccountSubview {
14 | var accountController: AccountViewController!
15 |
16 | @IBOutlet var contentView: NSView!
17 |
18 | @IBOutlet weak var avatarView: NSImageView!
19 | @IBOutlet weak var usernameField: NSTextField!
20 | @IBOutlet weak var twitchUsernameLabel: NSTextField!
21 | @IBOutlet weak var twitchUsernameField: NSTextField!
22 |
23 | @IBOutlet weak var logoutButton: NSButton!
24 |
25 | @IBAction func logoutButtonClick(_ sender: NSButton) {
26 | logout()
27 | }
28 | override var view: NSView {
29 | get {
30 | return contentView
31 | }
32 | set {
33 | contentView = newValue
34 | }
35 | }
36 |
37 |
38 | func setAccount(account: SplitsIORunner) {
39 | usernameField.stringValue = account.displayName
40 | let hasTwitch = account.twitchName != nil
41 | twitchUsernameField.isHidden = !hasTwitch
42 | twitchUsernameLabel.isHidden = !hasTwitch
43 | twitchUsernameField.stringValue = account.twitchName ?? ""
44 | if let avatarURL = URL(string: account.avatar) {
45 | let avatar = NSImage(byReferencing: avatarURL)
46 | avatarView.image = avatar
47 | } else {
48 | avatarView.image = #imageLiteral(resourceName: "splitsio")
49 | }
50 | }
51 | func logout() {
52 | accountController.logout()
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Splitter/UI/Splits.ioAccount/Login/LoginView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LoginViewController.swift
3 | // Splitter
4 | //
5 | // Created by Michael Berk on 8/11/20.
6 | // Copyright © 2020 Michael Berk. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import SplitsIOKit
11 |
12 | class LoginView: NSViewController, AccountSubview {
13 | var accountController: AccountViewController!
14 |
15 | override var view: NSView {
16 | get {
17 | return contentView
18 | }
19 | set {
20 | contentView = newValue
21 | }
22 | }
23 |
24 | @IBOutlet weak var loginButton: NSButton!
25 |
26 | @IBOutlet var contentView: NSView!
27 |
28 | @IBAction func loginButtonClick(_ sender: NSButton) {
29 | login()
30 | }
31 |
32 | func login() {
33 | accountController.login()
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Splitter/UI/View Controller/Components/KeyValueComponent/KeyValueComponent.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SumOfBestComponent.swift
3 | // Splitter
4 | //
5 | // Created by Michael Berk on 4/20/21.
6 | // Copyright © 2021 Michael Berk. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | class KeyValueComponent: NSStackView, SplitterComponent, NibLoadable {
12 | var run: SplitterRun!
13 | var customSpacing: CGFloat? = nil
14 | var key: KeyValueComponentType!
15 | private var componentIndex: Int!
16 | private var refreshUITimer = Timer()
17 | internal override func awakeAfter(using coder: NSCoder) -> Any? {
18 | return instantiateView() // You need to add this line to load view
19 | }
20 |
21 | static func instantiateView(with run: SplitterRun, _ viewController: ViewController, type: KeyValueComponentType, layoutIndex: Int) -> KeyValueComponent {
22 | let row: KeyValueComponent = KeyValueComponent.instantiateView()
23 | row.viewController = viewController
24 | row.run = run
25 | row.key = type
26 | row.componentIndex = layoutIndex
27 | row.initialization()
28 | return row
29 | }
30 |
31 | private func initialization() {
32 | textField.run = run
33 | keyField.run = run
34 | run.updateFunctions.append(updateUI)
35 | self.updateFields()
36 | }
37 |
38 | func updateUI() {
39 | if !self.isHidden {
40 | self.updateFields()
41 | }
42 | }
43 |
44 | func updateFields() {
45 | let state = run.getLayoutState()
46 | let kvs = state.componentAsKeyValue(componentIndex)
47 | keyField.stringValue = kvs.key()
48 | textField.stringValue = kvs.value()
49 | }
50 |
51 | var viewController: ViewController!
52 |
53 | var isSelected: Bool = false {
54 | didSet {
55 | didSetSelected()
56 | }
57 | }
58 |
59 | @IBOutlet var textField: ThemedTextField!
60 | @IBOutlet var keyField: ThemedTextField!
61 |
62 |
63 | }
64 |
65 | enum KeyValueComponentType: String, Codable, CaseIterable {
66 | case sumOfBest = "Sum of Best Segments"
67 | case previousSegment = "Previous Segment"
68 | case totalPlaytime = "Total Playtime"
69 | case currentSegment = "Segment Time"
70 | case possibleTimeSave = "Possible Time Save"
71 |
72 | var componentType: SplitterComponentType{
73 | switch self {
74 | case .sumOfBest:
75 | return .sumOfBest
76 | case .previousSegment:
77 | return .previousSegment
78 | case .totalPlaytime:
79 | return .totalPlaytime
80 | case .currentSegment:
81 | return .segment
82 | case .possibleTimeSave:
83 | return .possibleTimeSave
84 | }
85 | }
86 |
87 | init(from decoder: Decoder) throws {
88 | let str: String = try decoder.decodeSingleValue()
89 | if str == "Sum of Best Segments" {
90 | self = .sumOfBest
91 | return
92 | }
93 | if str == "Previous Segment" || str == "Live Segment" {
94 | self = .previousSegment
95 | return
96 | }
97 | if str == "Current Segment" {
98 | self = .currentSegment
99 | return
100 | }
101 | if str == "Possible Time Save" {
102 | self = .possibleTimeSave
103 | return
104 | }
105 | throw DecodingError.typeMismatch(Self.Type.self, .init(codingPath: decoder.codingPath, debugDescription: "Can't decode"))
106 |
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/Splitter/UI/View Controller/Components/PrevNextRow/PrevNextRow.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PrevNextRow.swift
3 | // Splitter
4 | //
5 | // Created by Michael Berk on 2/10/21.
6 | // Copyright © 2021 Michael Berk. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | @IBDesignable
12 | class PrevNextRow: NSStackView, NibLoadable, SplitterComponent {
13 | var run: SplitterRun!
14 |
15 | var customSpacing: CGFloat? = nil
16 |
17 | static func instantiateView(run: SplitterRun, viewController: ViewController) -> PrevNextRow {
18 | let row: PrevNextRow = PrevNextRow.instantiateView()
19 | row.run = run
20 | row.viewController = viewController
21 | row.initialization()
22 | return row
23 | }
24 |
25 | internal override func awakeAfter(using coder: NSCoder) -> Any? {
26 | return instantiateView() // You need to add this line to load view
27 | }
28 |
29 | internal override func awakeFromNib() {
30 | super.awakeFromNib()
31 | }
32 | private func initialization() {
33 | self.prevButton.run = self.run
34 | self.nextButton.run = self.run
35 | NotificationCenter.default.addObserver(forName: .timerStateChanged, object: run.timer, queue: nil, using: { notification in
36 | guard let timerState = notification.userInfo?["timerState"] as? TimerState else {return}
37 | var buttonsEnabled = false
38 | if timerState == .running {
39 | buttonsEnabled = true
40 | }
41 | self.prevButton.isEnabled = buttonsEnabled
42 | self.nextButton.isEnabled = buttonsEnabled
43 | self.nextButton.baseTitle = self.run.nextButtonTitle
44 | })
45 | NotificationCenter.default.addObserver(forName: .splitChanged, object: run.timer, queue: nil, using: { notification in
46 | self.nextButton.baseTitle = self.run.nextButtonTitle
47 | })
48 | }
49 |
50 |
51 |
52 | @IBOutlet var prevButton: ThemedButton!
53 | @IBOutlet var nextButton: ThemedButton!
54 | @IBOutlet var contentView: NSView!
55 |
56 | var viewController: ViewController!
57 |
58 | @IBAction func prevButtonClick(_ sender: NSButton?) {
59 | run.timer.previousSplit()
60 | }
61 | @IBAction func nextButtonClick(_ sender: NSButton?) {
62 | run.timer.splitOrStart()
63 | }
64 | var isSelected = false {
65 | didSet {
66 | self.didSetSelected()
67 | }
68 | }
69 |
70 |
71 | }
72 |
--------------------------------------------------------------------------------
/Splitter/UI/View Controller/Components/SegmentComponent/SegmentComponent.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SegmentComponent.swift
3 | // Splitter
4 | //
5 | // Created by Michael Berk on 7/14/21.
6 | // Copyright © 2021 Michael Berk. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
--------------------------------------------------------------------------------
/Splitter/UI/View Controller/Components/Shared/ComponentState.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ComponentState.swift
3 | // Splitter
4 | //
5 | // Created by Michael Berk on 7/5/21.
6 | // Copyright © 2021 Michael Berk. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | ///Codable representation of a component's state
12 | ///
13 | ///This is used mostly for the `appearance.json` file to include the list of components, in order.
14 | ///``ComponentState`` can be used to store certain state information that is not supported in LiveSplitCore, or where it's too difficult to.
15 | ///To save/load state, see ``ComponentState/properties``
16 | struct ComponentState: Codable {
17 | var type: SplitterComponentType
18 |
19 | //TODO: Finish Docs for this
20 | ///Contains the state information for the component
21 | ///
22 | ///Use this to save component state information to `appearance.json`, or to load component state information from it.
23 | ///
24 | ///Each value has a key and value
25 | var properties: [String: Codable]
26 |
27 | init(type: SplitterComponentType, properties: [String: Codable]) {
28 | self.type = type
29 | self.properties = properties
30 | }
31 |
32 | init(from decoder: Decoder) throws {
33 | self.type = try decoder.decode("type")
34 | self.properties = try decoder.decode("properties", as: [String: JSONAny].self)
35 | }
36 | func encode(to encoder: Encoder) throws {
37 | try encoder.encode(type, for: "type")
38 | let propMap: [String: JSONAny] = try properties.mapValues({
39 | let e = try $0.encoded()
40 | return try e.decoded(as: JSONAny.self, using: JSONDecoder())
41 |
42 | })
43 | try encoder.encode(propMap, for: "properties")
44 | }
45 |
46 | /// Get the value of a state property
47 | /// - Parameters:
48 | /// - key: The key for which to get the value for
49 | /// - type: The type of value to return
50 | /// - Returns: If the state contains a property of the given type with the given key, it will return the value of that property. Otherwise, it returns `nil`.
51 | func getProperty(with key: String, of type: PropType.Type) -> PropType? where PropType: Decodable {
52 | return (properties[key] as? JSONAny)?.value as? PropType
53 |
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/Splitter/UI/View Controller/Components/Shared/SplitterComponentType.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SplitterComponentType.swift
3 | // Splitter
4 | //
5 | // Created by Michael Berk on 7/5/21.
6 | // Copyright © 2021 Michael Berk. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | enum SplitterComponentType: Int, Codable, CaseIterable {
11 | case title
12 | case splits
13 | case tableOptions
14 | case time
15 | case start
16 | case prevNext
17 | case sumOfBest
18 | case previousSegment
19 | case totalPlaytime
20 | case segment
21 | case possibleTimeSave
22 |
23 | var displayTitle: String {
24 | switch self {
25 | case .title:
26 | return "Title"
27 | case .splits:
28 | return "Splits"
29 | case .tableOptions:
30 | return "Options Row"
31 | case .time:
32 | return "Time"
33 | case .start:
34 | return "Start/Delete Buttons"
35 | case .prevNext:
36 | return "Prev/Next Buttons"
37 | case .sumOfBest:
38 | return "Sum of Best"
39 | case .previousSegment:
40 | return "Previous Segment"
41 | case .totalPlaytime:
42 | return "Total Playtime"
43 | case .segment:
44 | return "Current Segment"
45 | case .possibleTimeSave:
46 | return "Possible Time Save"
47 | }
48 |
49 | }
50 |
51 | var componentType: SplitterComponent.Type {
52 | switch self {
53 | case .title:
54 | return TitleComponent.self
55 | case .splits:
56 | return SplitsComponent.self
57 | case .tableOptions:
58 | return OptionsRow.self
59 | case .time:
60 | return TimeRow.self
61 | case .start:
62 | return StartRow.self
63 | case .prevNext:
64 | return PrevNextRow.self
65 | case .sumOfBest:
66 | return KeyValueComponent.self
67 | case .previousSegment:
68 | return KeyValueComponent.self
69 | case .totalPlaytime:
70 | return KeyValueComponent.self
71 | case .segment:
72 | return KeyValueComponent.self
73 | case .possibleTimeSave:
74 | return KeyValueComponent.self
75 | }
76 | }
77 | static func FromType(_ type: SplitterComponent) -> SplitterComponentType? {
78 | //Switch won't work here, and I have no idea why
79 | if type is TitleComponent {
80 | return .title
81 | } else if type is SplitsComponent {
82 | return .splits
83 |
84 | } else if type is OptionsRow {
85 | return .tableOptions
86 | } else if type is TimeRow {
87 | return .time
88 | } else if type is StartRow {
89 | return .start
90 | } else if type is PrevNextRow {
91 | return .prevNext
92 | } else if let kvc = type as? KeyValueComponent {
93 | switch kvc.key {
94 | case .sumOfBest:
95 | return .sumOfBest
96 | case .previousSegment:
97 | return .previousSegment
98 | case .totalPlaytime:
99 | return .totalPlaytime
100 | case .currentSegment:
101 | return .segment
102 | case .possibleTimeSave:
103 | return .possibleTimeSave
104 | default:
105 | return nil
106 | }
107 | }
108 | return nil
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/Splitter/UI/View Controller/Components/SplitsComponent/SplitTableColumnIdentifiers.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SplitTableViewColumnIdentifiers.swift
3 | // Splitter
4 | //
5 | // Created by Michael Berk on 1/26/20.
6 | // Copyright © 2020 Michael Berk. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Cocoa
11 |
12 | public enum STVColumnID {
13 | static let imageColumn = NSUserInterfaceItemIdentifier("ImageColumn")
14 | static let splitTitleColumn = NSUserInterfaceItemIdentifier("SplitTitle")
15 | static let differenceColumn = NSUserInterfaceItemIdentifier("Difference")
16 | static let currentSplitColumn = NSUserInterfaceItemIdentifier("CurrentSplit")
17 | static let bestSplitColumn = NSUserInterfaceItemIdentifier("B")
18 | static let previousSplitColumn = NSUserInterfaceItemIdentifier("PreviousSplit")
19 |
20 | }
21 | //TODO: Change how column width is saved. It's currently saved in runInfo, where it finds the column with the given ID and sets the width. Problem is, if the user has removed that column, it could crash.
22 | public var colIds: [String: NSUserInterfaceItemIdentifier] = [
23 | "Icon": STVColumnID.imageColumn,
24 | "Title": STVColumnID.splitTitleColumn,
25 | "Difference": STVColumnID.differenceColumn,
26 | "Time": STVColumnID.currentSplitColumn,
27 | "Personal Best": STVColumnID.bestSplitColumn,
28 | "Previous Split": STVColumnID.previousSplitColumn
29 | ]
30 |
--------------------------------------------------------------------------------
/Splitter/UI/View Controller/Components/SplitsComponent/VerticalCenteredTextFieldCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // VerticalCenteredTextFieldCell.swift
3 | // Splitter
4 | //
5 | // Created by Michael Berk on 6/1/23.
6 | // Copyright © 2023 Michael Berk. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | ///A Text Field Cell that vertically centers its contents
12 | ///
13 | ///Used for the Splits component
14 | class VerticalCenteredTextFieldCell: NSTextFieldCell {
15 | override func titleRect(forBounds rect: NSRect) -> NSRect {
16 | var titleRect = super.titleRect(forBounds: rect)
17 |
18 | let minimumHeight = self.cellSize(forBounds: rect).height
19 | titleRect.origin.y += (titleRect.height - minimumHeight) / 2
20 | titleRect.size.height = minimumHeight
21 |
22 | return titleRect
23 | }
24 |
25 | override func drawInterior(withFrame cellFrame: NSRect, in controlView: NSView) {
26 | super.drawInterior(withFrame: titleRect(forBounds: cellFrame), in: controlView)
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Splitter/UI/View Controller/Components/StartRow/StartRow.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StartRow.swift
3 | // Splitter
4 | //
5 | // Created by Michael Berk on 3/22/21.
6 | // Copyright © 2021 Michael Berk. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | @IBDesignable
11 | class StartRow: NSStackView, NibLoadable, SplitterComponent {
12 | var run: SplitterRun!
13 |
14 | var customSpacing: CGFloat? = nil
15 |
16 | static func instantiateView(run: SplitterRun, viewController: ViewController) -> StartRow {
17 | let startRow: StartRow = StartRow.instantiateView()
18 | startRow.run = run
19 | startRow.viewController = viewController
20 | startRow.initialization()
21 | return startRow
22 | }
23 |
24 | internal override func awakeAfter(using coder: NSCoder) -> Any? {
25 | return instantiateView() // You need to add this line to load view
26 | }
27 |
28 | internal override func awakeFromNib() {
29 | super.awakeFromNib()
30 | }
31 |
32 | func initialization() {
33 | startButton.run = run
34 | stopButton.run = run
35 | stopButton.image = nil
36 | if #available(macOS 11.0, *) {
37 | stopButton.image = NSImage(systemSymbolName: "stop.circle.fill", accessibilityDescription: nil)
38 | } else {
39 | stopButton.image = NSImage(named: "stop")
40 | }
41 |
42 | if startButton.acceptsFirstResponder {
43 | startButton.window?.makeFirstResponder(startButton)
44 | }
45 | NotificationCenter.default.addObserver(forName: .timerStateChanged, object: run.timer, queue: nil, using: { notification in
46 | guard let timerState = notification.userInfo?["timerState"] as? TimerState else {return}
47 | switch timerState {
48 | case .paused:
49 | self.startButton.baseTitle = "Resume"
50 | case .running:
51 | self.startButton.baseTitle = "Pause"
52 | case .stopped:
53 | self.startButton.baseTitle = "Start"
54 | }
55 | self.setVisibleButtons()
56 | })
57 | }
58 |
59 | func setVisibleButtons() {
60 | stopButton.isHidden = shouldStopButtonBeHidden
61 | }
62 |
63 |
64 | @IBOutlet var startButton: ThemedButton!
65 | @IBOutlet var stopButton: ThemedButton!
66 |
67 | var viewController: ViewController!
68 |
69 |
70 | var isSelected = false {
71 | didSet {
72 | self.didSetSelected()
73 | }
74 | }
75 |
76 | @IBAction func startButtonClick(_ sender: Any) {
77 | viewController?.toggleTimer()
78 | }
79 | @IBAction func stopButtonClick(_ sender: Any) {
80 | viewController?.cancelRun()
81 | }
82 |
83 | var shouldTrashCanBeHidden: Bool {
84 | switch run.timer.timerState {
85 | case .stopped:
86 | return false
87 | default:
88 | return true
89 | }
90 | }
91 |
92 | var shouldStopButtonBeHidden: Bool {
93 | stopButton.isEnabled = true
94 | switch run.timer.timerState {
95 | case .stopped:
96 | return true
97 | default:
98 | return false
99 | }
100 | }
101 |
102 |
103 | }
104 |
--------------------------------------------------------------------------------
/Splitter/UI/View Controller/Components/TimeRow/TimeRowOptions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TimeRowOptions.swift
3 | // Splitter
4 | //
5 | // Created by Michael Berk on 6/4/23.
6 | // Copyright © 2023 Michael Berk. All rights reserved.
7 | //
8 | import AppKit
9 | import LiveSplitKit
10 |
11 | class TimeRowOptionsController: NSObject {
12 |
13 | init(timeRow: TimeRow) {
14 | self.timeRow = timeRow
15 | super.init()
16 | NotificationCenter.default.addObserver(forName: .fontChanged, object: self.timeRow.run, queue: nil, using: { _ in
17 | // self.fontStack.font = self.timeRow.run.codableLayout.timerFont
18 | })
19 | }
20 |
21 | func fontChanged(to newFont: LiveSplitFont?) {
22 | self.timeRow.run.fontManager.setTimerFont(to: newFont)
23 | }
24 |
25 | var timeRow: TimeRow!
26 | var fontStack: ComponentOptionsFontStack!
27 |
28 | var showAttemptsLabelButton: ComponentOptionsButton {
29 | ComponentOptionsButton(checkboxWithTitle: "Show Attempts Label", clickAction: { button in
30 | let oldValue = self.timeRow.showAttemptsLabel
31 | self.timeRow.undoableSetting(actionName: "Set Show Attempts Label", oldValue: oldValue, newValue: !oldValue, edit: { comp, value in
32 | comp.showAttemptsLabel = value
33 | button.state = .init(bool: value)
34 | })
35 | })
36 | }
37 |
38 | var optionsView: NSView! {
39 | let d = timeRow.defaultComponentOptions() as! ComponentOptionsVstack
40 | let showAttemptsLabelButton = self.showAttemptsLabelButton
41 | //Can't move this outside of class since it needs the label button to exist
42 | let showAttemptsButton = ComponentOptionsButton(checkboxWithTitle: "Show Attempts", clickAction: {button in
43 | let oldValue = self.timeRow.showAttempts
44 | self.timeRow.undoableSetting(actionName: "Set Show Attempts", oldValue: oldValue, newValue: !oldValue, edit: {comp, value in
45 | comp.showAttempts = value
46 | button.state = .init(bool: value)
47 | showAttemptsLabelButton.isEnabled = value
48 | })
49 | })
50 | showAttemptsButton.state = .init(bool: timeRow.showAttempts)
51 |
52 | showAttemptsLabelButton.state = .init(bool: timeRow.showAttemptsLabel)
53 | showAttemptsLabelButton.sizeToFit()
54 | showAttemptsLabelButton.isEnabled = timeRow.showAttempts
55 | let bv = NSView(frame: NSRect(x: 0, y: 0, width: 20, height: showAttemptsLabelButton.frame.height))
56 | NSLayoutConstraint.activate([
57 | bv.widthAnchor.constraint(equalToConstant: 20)
58 | ])
59 | bv.setContentHuggingPriority(.required, for: .horizontal)
60 | showAttemptsLabelButton.setContentHuggingPriority(.defaultLow, for: .horizontal)
61 | let attemptsLabelButtonStack = NSStackView(views: [bv, showAttemptsLabelButton])
62 | attemptsLabelButtonStack.distribution = .equalSpacing
63 | attemptsLabelButtonStack.spacing = 0
64 | attemptsLabelButtonStack.alignment = .leading
65 | attemptsLabelButtonStack.orientation = .horizontal
66 |
67 | d.addArrangedSubview(showAttemptsButton)
68 | d.addArrangedSubview(attemptsLabelButtonStack)
69 |
70 | d.addSeparator()
71 |
72 | fontStack = ComponentOptionsFontStack(title: "Timer Font", fontSize: nil, helpText: "Font used to display the current time.", font: self.timeRow.run.codableLayout.timerFont, onFontChange: fontChanged(to:))
73 | d.addArrangedSubview(fontStack)
74 | return d
75 | }
76 |
77 | }
78 | extension TimeRow {
79 | var optionsView: NSView! {
80 | optionsController.optionsView
81 | }
82 | }
83 |
84 |
85 |
--------------------------------------------------------------------------------
/Splitter/UI/View Controller/Controls/Button Actions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Button Actions.swift
3 | // Splitter
4 | //
5 | // Created by Michael Berk on 2/4/20.
6 | // Copyright © 2020 Michael Berk. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Cocoa
11 |
--------------------------------------------------------------------------------
/Splitter/UI/View Controller/Controls/Icon Actions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Icon Actions.swift
3 | // Splitter
4 | //
5 | // Created by Michael Berk on 2/4/20.
6 | // Copyright © 2020 Michael Berk. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Cocoa
11 |
12 | extension ViewController {
13 | // MARK: - Icon Actions
14 |
15 | //TODO: Move This to MetadataImage
16 | /// Prompts the user to select a game icon image
17 | ///
18 | ///Used in right-click menu
19 | @IBAction func pictureButtonPressed(_ sender: Any) {
20 | let dialog = pictureFileDialog()
21 |
22 | dialog.beginSheetModal(for: view.window!) { (response) in
23 | if response == .OK {
24 | let result = dialog.url // Pathname of the file
25 |
26 | if (result != nil) {
27 | let imageFile = try? Data(contentsOf: result!)
28 |
29 | let myImage = NSImage(data: imageFile!)
30 |
31 | self.run.gameIcon = myImage
32 | self.gameIconButton.image = self.run.gameIcon
33 |
34 | }
35 | }
36 | }
37 | }
38 |
39 | func pictureFileDialog() -> NSOpenPanel{
40 | let dialog = NSOpenPanel();
41 | dialog.title = "Choose an image file"
42 | dialog.showsResizeIndicator = true
43 | dialog.showsHiddenFiles = false
44 | dialog.canChooseDirectories = false
45 | dialog.canCreateDirectories = false
46 | dialog.allowsMultipleSelection = false
47 | dialog.allowedFileTypes = ["png"]
48 | return dialog
49 | }
50 | ///Used in right-click menu for game icon
51 | @IBAction func removeGameIconMenuItem(sender: Any?) {
52 | removeGameIcon(sender: sender)
53 | }
54 | ///Used in right-click menu for game icon
55 | func removeGameIcon(sender: Any?) {
56 | gameIconButton.image = .gameControllerIcon
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/Splitter/UI/View Controller/Controls/StepperWithNumberField.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StepperWithNumberField.swift
3 | // Splitter
4 | //
5 | // Created by Michael Berk on 12/14/23.
6 | // Copyright © 2023 Michael Berk. All rights reserved.
7 | //
8 |
9 | import AppKit
10 |
11 | class StepperWithNumberField: NSStackView, NSTextFieldDelegate {
12 | //Based on https://stackoverflow.com/questions/70778911/how-do-i-define-an-action-for-a-custom-nscontrol-in-code
13 |
14 | var numberField: NSTextField!
15 | var stepper: NSStepper!
16 |
17 | var floatValue: Float {
18 |
19 | get {
20 | return stepper.floatValue
21 | }
22 | set {
23 | stepper.floatValue = newValue
24 | numberField.floatValue = newValue
25 | }
26 | }
27 |
28 | convenience init(minValue: Float, maxValue: Float) {
29 |
30 | let numberField = NSTextField()
31 | numberField.autoresizingMask = [.width, .height]
32 | numberField.floatValue = 100
33 | numberField.isEditable = true
34 | numberField.alignment = .right
35 |
36 | let formatter = NumberFormatter()
37 | formatter.allowsFloats = true
38 | formatter.minimum = minValue as NSNumber
39 | formatter.maximum = maxValue as NSNumber
40 |
41 | numberField.formatter = formatter
42 | let stepper = NSStepper()
43 | stepper.minValue = Double(minValue)
44 | stepper.maxValue = Double(maxValue)
45 | stepper.increment = 1
46 | stepper.valueWraps = false
47 |
48 | self.init(views: [numberField, stepper])
49 | self.orientation = .horizontal
50 | self.numberField = numberField
51 | self.stepper = stepper
52 |
53 | self.numberField.delegate = self
54 | self.stepper.target = self
55 | self.stepper.action = #selector(self.stepperClicked)
56 | }
57 |
58 | required init?(coder: NSCoder) {
59 | fatalError("init(coder:) has not been implemented")
60 | }
61 |
62 | override init(frame frameRect: NSRect) {
63 | super.init(frame: frameRect)
64 | }
65 |
66 | func controlTextDidEndEditing(_ notification: Notification) {
67 | if let nobj = notification.object as? NSTextField {
68 | stepper.floatValue = nobj.floatValue
69 | self.handler?(self)
70 | }
71 | }
72 |
73 | public typealias Handler = (StepperWithNumberField) -> Void
74 | var handler: Handler?
75 |
76 | @objc func stepperClicked(sender: NSStepper) {
77 | numberField.floatValue = stepper.floatValue
78 | self.handler?(self)
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/Splitter/UI/View Controller/Controls/Themeing/MetadataImage.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MetadataImage.swift
3 | // Splitter
4 | //
5 | // Created by Michael Berk on 7/7/21.
6 | // Copyright © 2021 Michael Berk. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | ///Game Icon
12 | class MetadataImage: ThemedImage {
13 | override var themeable: Bool {
14 | get {
15 | if run.gameIcon == nil {
16 | return true
17 | }
18 | return false
19 | }
20 | set {}
21 | }
22 |
23 | required init?(coder: NSCoder) {
24 | super.init(coder: coder)
25 | setup()
26 | }
27 | override init(frame frameRect: NSRect) {
28 | super.init(frame: frameRect)
29 | setup()
30 | }
31 | func setup() {
32 | self.target = self
33 | self.action = #selector(imageChanged(_:))
34 | NotificationCenter.default.addObserver(forName: .gameIconEdited, object: nil, queue: nil, using: { notification in
35 | self.image = self.run.gameIcon
36 | self.setPlaceholderImage()
37 | self.setColor()
38 | })
39 | }
40 | override func setColor() {
41 | if themeable {
42 | self.contentTintColor = run.textColor
43 | }
44 | }
45 |
46 | func setPlaceholderImage() {
47 | if self.image == nil {
48 | self.image = .gameControllerIcon
49 | }
50 | }
51 |
52 | @objc func imageChanged(_ sender: Any?) {
53 | run.gameIcon = self.image
54 | setPlaceholderImage()
55 | }
56 |
57 | var allowsUpdate = true
58 |
59 | }
60 | extension MetadataImage {
61 | override func mouseDown(with event: NSEvent) {
62 | if event.clickCount > 1 {
63 | self.setImage()
64 | }
65 | }
66 | func pictureFileDialog() -> NSOpenPanel{
67 | let dialog = NSOpenPanel();
68 | dialog.title = "Choose an image file"
69 | dialog.showsResizeIndicator = true
70 | dialog.showsHiddenFiles = false
71 | dialog.canChooseDirectories = false
72 | dialog.canCreateDirectories = false
73 | dialog.allowsMultipleSelection = false
74 | dialog.allowedFileTypes = ["png"]
75 | return dialog
76 | }
77 |
78 | func setImage() {
79 | let dialog = pictureFileDialog()
80 |
81 | let response = dialog.runModal()
82 | if response == .OK {
83 | let result = dialog.url
84 |
85 | if (result != nil) {
86 | let imageFile = try? Data(contentsOf: result!)
87 |
88 | let myImage = NSImage(data: imageFile!)
89 |
90 | self.image = myImage
91 | self.imageChanged(nil)
92 | }
93 | }
94 | }
95 | }
96 |
97 |
--------------------------------------------------------------------------------
/Splitter/UI/View Controller/Controls/Themeing/Themeable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Themeable.swift
3 | // Splitter
4 | //
5 | // Created by Michael Berk on 7/7/21.
6 | // Copyright © 2021 Michael Berk. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | protocol Themeable {
12 | func setColor()
13 | func setColorObserver()
14 | var run: SplitterRun! {get}
15 | var themeable: Bool {get set}
16 | }
17 | extension Themeable {
18 | //TODO: What was this used for?
19 | static var runKey: String {
20 | return "run"
21 | }
22 | func setColorObserver() {
23 | NotificationCenter.default.addObserver(forName: .updateComponentColors, object: run, queue: nil, using: { notification in
24 | if themeable {
25 | setColor()
26 | }
27 | })
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Splitter/UI/View Controller/Controls/Themeing/ThemedButton.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ThemedButton.swift
3 | // Splitter
4 | //
5 | // Created by Michael Berk on 4/8/21.
6 | // Copyright © 2021 Michael Berk. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | class ThemedButton: NSButton, Themeable, Fontable {
12 |
13 | var run: SplitterRun!
14 | var defaultFontSize: CGFloat?
15 | var fixedFontSize: Bool {
16 | return true
17 | }
18 |
19 | override init(frame frameRect: NSRect) {
20 | super.init(frame: frameRect)
21 | setColorObserver()
22 | setFontObserver()
23 | }
24 | required init?(coder: NSCoder) {
25 | super.init(coder: coder)
26 | setColorObserver()
27 | setFontObserver()
28 | }
29 |
30 | func setColor() {
31 | var newImage = self.image
32 | newImage?.isTemplate = true
33 | newImage = newImage?.image(with: run.textColor)
34 | self.image = newImage
35 | self.contentTintColor = run.textColor
36 | self.attributedTitle = NSAttributedString(string: self.title, attributes: [.foregroundColor: run.textColor])
37 | self.appearance = NSAppearance(named: .darkAqua)
38 | self.shadow = .none
39 | }
40 |
41 | var themeable: Bool = true
42 | var fontable: Bool = true
43 |
44 | override func draw(_ dirtyRect: NSRect) {
45 | super.draw(dirtyRect)
46 |
47 | // Drawing code here.
48 | }
49 | override func mouseDown(with event: NSEvent) {
50 | window?.makeFirstResponder(nil)
51 | super.mouseDown(with: event)
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/Splitter/UI/View Controller/Controls/Themeing/ThemedImage.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ThemedImage.swift
3 | // Splitter
4 | //
5 | // Created by Michael Berk on 7/7/21.
6 | // Copyright © 2021 Michael Berk. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | class ThemedImage: NSImageView, Themeable {
12 | @IBInspectable var themeable: Bool = true
13 | var run: SplitterRun!
14 | override init(frame frameRect: NSRect) {
15 | super.init(frame: frameRect)
16 | setColorObserver()
17 | }
18 |
19 | required init?(coder: NSCoder) {
20 | super.init(coder: coder)
21 | setColorObserver()
22 | }
23 | func setColor() {
24 | if let image = self.image, image.isTemplate {
25 | self.contentTintColor = run.textColor
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Splitter/UI/View Controller/Controls/Themeing/ThemedPopUpButton.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ThemedPopUpButton.swift
3 | // Splitter
4 | //
5 | // Created by Michael Berk on 4/8/21.
6 | // Copyright © 2021 Michael Berk. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | class ThemedPopUpButton: NSPopUpButton, Themeable {
12 | var run: SplitterRun!
13 | var themeable: Bool = true
14 |
15 | override init(frame frameRect: NSRect) {
16 | super.init(frame: frameRect)
17 | setColorObserver()
18 |
19 | }
20 | required init?(coder: NSCoder) {
21 | super.init(coder: coder)
22 | setColorObserver()
23 | }
24 |
25 | func setColor() {
26 |
27 | self.contentTintColor = run.textColor
28 | //not used now, but could be useful in the future for popup buttons with titles instead of images
29 | self.attributedTitle = NSAttributedString(string: self.title, attributes: [.foregroundColor: run.textColor])
30 | if let menu = menu {
31 | setColors(for: menu, color: run.textColor)
32 | }
33 | self.appearance = NSAppearance(named: .darkAqua)
34 | }
35 | func setColors(for menu: NSMenu, color: NSColor) {
36 | for item in menu.items {
37 | if var newImage = item.image {
38 | newImage.isTemplate = true
39 | newImage = newImage.image(with: color)
40 | item.image = newImage
41 | }
42 | if item.hasSubmenu, let submenu = item.submenu {
43 | setColors(for: submenu, color: color)
44 | }
45 |
46 | }
47 | }
48 |
49 | override func draw(_ dirtyRect: NSRect) {
50 | super.draw(dirtyRect)
51 |
52 | // Drawing code here.
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/Splitter/UI/View Controller/Controls/Themeing/ThemedTextField.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ThemedTextField.swift
3 | // Splitter
4 | //
5 | // Created by Michael Berk on 4/8/21.
6 | // Copyright © 2021 Michael Berk. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | class ThemedTextField: NSTextField, Themeable, Fontable {
12 |
13 | var run: SplitterRun!
14 | var defaultFontSize: CGFloat?
15 |
16 | @IBInspectable var fontable: Bool = true
17 | @IBInspectable var themeable: Bool = true
18 | @IBInspectable var fixedFontSize: Bool = false
19 |
20 | override init(frame frameRect: NSRect) {
21 | super.init(frame: frameRect)
22 | setColorObserver()
23 | setFontObserver()
24 | }
25 |
26 | required init?(coder: NSCoder) {
27 | super.init(coder: coder)
28 | setColorObserver()
29 | setFontObserver()
30 | }
31 |
32 | func setColor() {
33 | self.textColor = run.textColor
34 | }
35 |
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/Splitter/UI/View Controller/Options Windows/Layout Editor/ComponentOptions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ComponentOptions.swift
3 | // Splitter
4 | //
5 | // Created by Michael Berk on 7/5/21.
6 | // Copyright © 2021 Michael Berk. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import LiveSplitKit
11 |
12 | class ComponentOptionsButton: NSButton {
13 | var clickAction: (NSButton) -> () = {_ in}
14 | override func mouseDown(with event: NSEvent) {
15 | super.mouseDown(with: event)
16 | if isEnabled {
17 | clickAction(self)
18 | }
19 | }
20 |
21 | convenience init (checkboxWithTitle title: String, clickAction: @escaping (NSButton) -> ()) {
22 | self.init(checkboxWithTitle: title, target: nil, action: nil)
23 | self.clickAction = clickAction
24 | }
25 | convenience init (title: String, clickAction: @escaping (NSButton) -> ()) {
26 | self.init(title: title, target: nil, action: nil)
27 | self.clickAction = clickAction
28 | }
29 | convenience init(image: NSImage, clickAction: @escaping (NSButton) -> ()) {
30 | self.init(image: image, target: nil, action: nil)
31 | self.clickAction = clickAction
32 | }
33 | }
34 |
35 | class ComponentPopUpButton: NSPopUpButton {
36 | var customAction: (NSPopUpButton) -> () = {_ in}
37 | @objc func selectAction(_ sender: NSPopUpButton) {
38 | customAction(sender)
39 | }
40 |
41 | convenience init(title: String, selectAction: @escaping (NSPopUpButton) -> ()) {
42 | self.init(title: title, target: nil, action: #selector(selectAction(_:)))
43 | self.target = self
44 | self.customAction = selectAction
45 | }
46 |
47 | }
48 |
49 | class ComponentOptionsVstack: NSStackView {
50 | override init(frame frameRect: NSRect) {
51 | super.init(frame: frameRect)
52 | self.orientation = .vertical
53 | self.alignment = .leading
54 | }
55 |
56 | required init?(coder: NSCoder) {
57 | fatalError("init(coder:) has not been implemented")
58 | }
59 |
60 | override var isFlipped: Bool {
61 | return true
62 | }
63 |
64 | var onFontChanged: ((_ sender: NSFontManager?) -> Void) = {_ in}
65 | var fontPanelModes: NSFontPanel.ModeMask = [.standardModes]
66 |
67 | @discardableResult func addSeparator() -> NSView {
68 | let separatorView = NSView()
69 | separatorView.wantsLayer = true
70 | separatorView.layer?.backgroundColor = NSColor.separatorColor.cgColor
71 | self.addArrangedSubview(separatorView)
72 | NSLayoutConstraint.activate([
73 | separatorView.heightAnchor.constraint(equalToConstant: 1)
74 | ])
75 | return separatorView
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/Splitter/UI/View Controller/Options Windows/Layout Editor/DragIndicator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DragIndicator.swift
3 | // Splitter
4 | //
5 | // Created by Michael Berk on 10/21/22.
6 | // Copyright © 2022 Michael Berk. All rights reserved.
7 | //
8 |
9 | import AppKit
10 |
11 | @IBDesignable
12 | class DragIndicator: NSView {
13 |
14 | @IBInspectable var alwaysShowIcon: Bool = false {
15 | didSet {
16 | imageView.isHidden = !alwaysShowIcon
17 | }
18 | }
19 |
20 | var imageView: NSImageView!
21 |
22 | override init(frame frameRect: NSRect) {
23 | super.init(frame: frameRect)
24 | setup()
25 | }
26 | func setup() {
27 | var image: NSImage
28 |
29 | if #available(macOS 11.0, *) {
30 | //macOS ≥ 12 has a different name for the symbol, so we need to use the proper name on macOS 11
31 | var imageName = "line.3.horizontal"
32 | if ProcessInfo.processInfo.operatingSystemVersion.majorVersion == 11 {
33 | imageName = "line.horizontal.3"
34 | }
35 | image = NSImage(systemSymbolName: imageName, accessibilityDescription: nil)!
36 | } else {
37 | image = NSImage(named: "lines")!
38 | }
39 | let imageView = NSImageView(image: image)
40 | self.imageView = imageView
41 |
42 | self.addSubview(imageView)
43 | let trackingArea = NSTrackingArea(rect: bounds, options: [.activeInKeyWindow, .mouseEnteredAndExited], owner: self)
44 | addTrackingArea(trackingArea)
45 | self.imageView.isHidden = !alwaysShowIcon
46 | }
47 |
48 | override func layout() {
49 | super.layout()
50 | if let imageView {
51 | let x = (self.frame.width - 16) * 0.5
52 | let y = (self.frame.height - 9) * 0.5
53 | let f = NSRect(x: x, y: y, width: 16, height: 9)
54 | imageView.frame = f
55 | }
56 | }
57 |
58 | required init?(coder: NSCoder) {
59 | super.init(coder: coder)
60 | setup()
61 | }
62 |
63 | override func mouseEntered(with event: NSEvent) {
64 | super.mouseEntered(with: event)
65 | if !alwaysShowIcon {
66 | self.imageView.isHidden = false
67 | }
68 | }
69 |
70 | override func mouseExited(with event: NSEvent) {
71 | super.mouseExited(with: event)
72 | if !alwaysShowIcon {
73 | self.imageView.isHidden = true
74 | }
75 | }
76 |
77 | override func mouseUp(with event: NSEvent) {
78 | super.mouseUp(with: event)
79 | if !alwaysShowIcon {
80 | self.imageView.isHidden = true
81 | }
82 | }
83 |
84 | }
85 |
--------------------------------------------------------------------------------
/Splitter/UI/View Controller/Options Windows/Layout Editor/LayoutEditorListCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LayoutEditorListCell.swift
3 | // Splitter
4 | //
5 | // Created by Michael Berk on 10/27/22.
6 | // Copyright © 2022 Michael Berk. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | class LayoutEditorListCell: NSTableCellView {
12 |
13 | @IBOutlet private var dragIndicator: DragIndicator?
14 |
15 | override init(frame frameRect: NSRect) {
16 | super.init(frame: frameRect)
17 | setup()
18 | }
19 |
20 | required init?(coder: NSCoder) {
21 | super.init(coder: coder)
22 | setup()
23 | }
24 | func setup() {
25 | ///See the documentation on ``layoutEditorItemDragged`` for more info on why I call this
26 | NotificationCenter.default.addObserver(forName: .layoutEditorItemDragged, object: nil, queue: nil, using: { notification in
27 | if self.dragIndicator?.alwaysShowIcon == false {
28 | self.dragIndicator?.imageView.isHidden = true
29 | }
30 | })
31 | }
32 |
33 | override func draw(_ dirtyRect: NSRect) {
34 | super.draw(dirtyRect)
35 |
36 | // Drawing code here.
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/Splitter/UI/View Controller/UISettings.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UISettings.swift
3 | // Splitter
4 | //
5 | // Created by Michael Berk on 1/7/20.
6 | // Copyright © 2020 Michael Berk. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Cocoa
11 |
12 | extension ViewController {
13 |
14 | var showHideTitleBarItemText: String {
15 | get {
16 | if titleBarHidden {
17 | return "Show Title Bar"
18 | } else {
19 | return "Hide Title Bar"
20 | }
21 | }
22 | }
23 | ///Sets the window to stay on top, depending on the current setting
24 | func setFloatingWindow() {
25 | if !Settings.menuBarMode {
26 | let id = menuIdentifiers.windowMenu.windowFloat
27 | if let menuItem = NSApp.mainMenu?.item(withIdentifier: id) {
28 | if windowFloat {
29 | view.window?.level = .floating
30 | menuItem.state = .on
31 | } else {
32 | view.window?.level = .normal
33 | menuItem.state = .off
34 | }
35 | }
36 | }
37 | }
38 |
39 | ///Shows or hides the title bar, depending on the current setting
40 | func showHideTitleBar() {
41 | if titleBarHidden {
42 |
43 | view.window?.styleMask.insert(.fullSizeContentView)
44 | view.window?.standardWindowButton(.closeButton)?.isHidden = true
45 | view.window?.standardWindowButton(.miniaturizeButton)?.isHidden = true
46 | view.window?.standardWindowButton(.zoomButton)?.isHidden = true
47 | view.window?.standardWindowButton(.documentIconButton)?.isHidden = true
48 | view.window?.titleVisibility = .hidden
49 | } else {
50 | view.window?.standardWindowButton(.closeButton)?.isHidden = false
51 | view.window?.standardWindowButton(.miniaturizeButton)?.isHidden = false
52 | view.window?.standardWindowButton(.zoomButton)?.isHidden = false
53 | view.window?.standardWindowButton(.documentIconButton)?.isHidden = false
54 | view.window?.titleVisibility = .visible
55 | view.window?.styleMask.remove(.fullSizeContentView)
56 | }
57 | let showHideTitleBarItem = NSApp.mainMenu?.item(withIdentifier: menuIdentifiers.appearanceMenu.hideTitleBar)
58 | showHideTitleBarItem?.title = showHideTitleBarItemText
59 | }
60 | func showHideTitle() {
61 | if hideTitle {
62 | view.window?.titleVisibility = .hidden
63 | } else {
64 | view.window?.titleVisibility = .visible
65 | }
66 | }
67 |
68 |
69 | @IBAction func showHideTitleBarMenuItem(_ sender: Any? ) {
70 | titleBarHidden.toggle()
71 | showHideTitleBar()
72 | }
73 |
74 | var showHideButtonsText: String {
75 | get {
76 | if buttonHidden {
77 | return "Show Buttons"
78 | } else {
79 | return "Hide Buttons"
80 | }
81 | }
82 | }
83 | }
84 |
85 | extension NSWindow {
86 | var titlebarHeight: CGFloat {
87 | if let windowFrameHeight = contentView?.frame.height {
88 | let contentLayoutRectHeight = contentLayoutRect.height
89 | let fullSizeContentViewNoContentAreaHeight = windowFrameHeight - contentLayoutRectHeight
90 | return fullSizeContentViewNoContentAreaHeight
91 | }
92 | return 0
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/Splitter/UI/View Controller/WelcomeWindowController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WelcomeWindowController.swift
3 | // Splitter
4 | //
5 | // Created by Michael Berk on 7/27/20.
6 | // Copyright © 2020 Michael Berk. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | class WelcomeWindowController: NSWindowController {
12 |
13 | override func windowDidLoad() {
14 | super.windowDidLoad()
15 | //Need to close the window controller so that `NSApp.windows` removes the welcome window when closed
16 | NotificationCenter.default.addObserver(forName: NSWindow.willCloseNotification, object: self.window, queue: nil, using: { _ in
17 | self.close()
18 | })
19 | // Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
20 |
21 | }
22 |
23 |
24 |
25 |
26 | }
27 |
28 |
--------------------------------------------------------------------------------
/Splitter/UI/View Controller/Windows/BlankWindowController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BlankWindowController.swift
3 | // Splitter
4 | //
5 | // Created by Michael Berk on 1/13/20.
6 | // Copyright © 2020 Michael Berk. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | class BlankWindowController: NSWindowController {
12 |
13 | override func windowDidLoad() {
14 | super.windowDidLoad()
15 |
16 | // Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/Splitter/UI/View Controller/Windows/MainWindow.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MainWindow.swift
3 | // Splitter
4 | //
5 | // Created by Michael Berk on 1/13/20.
6 | // Copyright © 2020 Michael Berk. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | ///This class is needed for the Hotkeys to work. I don't remeber why at the moment.
12 | class MainWindow: NSWindow {
13 |
14 | var observer: Any!
15 | override init(contentRect: NSRect, styleMask style: NSWindow.StyleMask, backing backingStoreType: NSWindow.BackingStoreType, defer flag: Bool) {
16 | super.init(contentRect: contentRect, styleMask: style, backing: backingStoreType, defer: flag)
17 | observer = NSWorkspace.shared.notificationCenter.addObserver(forName: NSWorkspace.activeSpaceDidChangeNotification, object: nil, queue: nil, using: { _ in
18 | if Settings.menuBarMode {
19 | self.orderFront(self)
20 | }
21 | })
22 | }
23 |
24 | override func setTitleWithRepresentedFilename(_ filename: String) {
25 | super.setTitleWithRepresentedFilename(filename)
26 |
27 | }
28 |
29 | //need to override `close` and remove the observer or the window will reappear after changing spaces in Overlay Mode
30 | override func close() {
31 | super.close()
32 | NSWorkspace.shared.notificationCenter.removeObserver(observer!, name: NSWorkspace.activeSpaceDidChangeNotification, object: nil)
33 | }
34 | }
35 |
36 |
37 |
--------------------------------------------------------------------------------
/Splitter/UI/View Controller/Windows/MainWindowController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MainWindowController.swift
3 | // Splitter
4 | //
5 | // Created by Michael Berk on 12/22/19.
6 | // Copyright © 2019 Michael Berk. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | class MainWindowController: NSWindowController {
12 |
13 | override func windowDidLoad() {
14 | super.windowDidLoad()
15 | NotificationCenter.default.addObserver(forName: .menuBarModeChanged, object: nil, queue: nil, using: { _ in
16 | self.updateFromMenuBarMode()
17 | })
18 | updateFromMenuBarMode()
19 | }
20 |
21 | func updateFromMenuBarMode() {
22 | if Settings.menuBarMode {
23 | window?.level = .statusBar
24 | window?.collectionBehavior = .canJoinAllSpaces
25 | } else {
26 | window?.collectionBehavior = .fullScreenAuxiliary
27 | }
28 |
29 |
30 | }
31 |
32 |
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/Splitter/UI/View Controller/Windows/QuickAlert.swift:
--------------------------------------------------------------------------------
1 | //
2 | // QuickAlert.swift
3 | // Splitter
4 | //
5 | // Created by Michael Berk on 1/31/20.
6 | // Copyright © 2020 Michael Berk. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Cocoa
11 |
12 | class QuickAlert {
13 |
14 | let alert: NSAlert
15 |
16 | init(message: String, image: NSImage?) {
17 | alert = NSAlert()
18 | alert.messageText = message
19 | if let img = image {
20 | alert.icon = img
21 | }
22 | alert.alertStyle = .informational
23 | alert.addButton(withTitle: "OK")
24 | }
25 |
26 | func show() {
27 | alert.runModal()
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Splitter/UI/View Controller/Windows/blankWindow.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/Splitter/UpdateController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UpdaterStuff.swift
3 | // Splitter
4 | //
5 | // Created by Michael Berk on 2/14/23.
6 | // Copyright © 2023 Michael Berk. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Defaults
11 | import BasicUpdater
12 |
13 | class UpdateController:NSObject {
14 | static let shared = UpdateController()
15 |
16 | let updater = Updater(projectURL: URL(string: "https://github.com/michaeljberk/Splitter"), shouldUpdateTo: { release in
17 | let regexStr = #"[\d .]*-\d*"#
18 | let tag = release.tagName
19 | guard let tagVerRange = tag.range(of: regexStr, options: .regularExpression) else {return false}
20 | let tagVer = tag[tagVerRange]
21 | let tagVerSplit = tagVer.split(separator: "-")
22 | guard tagVerSplit.count > 1 else {return false}
23 | let newBuildStr = tagVerSplit[1]
24 | guard let newBuildNum = Int(newBuildStr) else {return false}
25 | let currentBuildStr = Bundle.main.infoDictionary?["CFBundleVersion"] as! String
26 | let currentBuildNum = Int(currentBuildStr)!
27 | return newBuildNum > currentBuildNum
28 | })
29 |
30 | @objc func checkForUpdates() {
31 | updater.checkForUpdates()
32 | }
33 |
34 | func addUpdateCommand() {
35 | let settingsItem = NSApp.mainMenu!.item(withIdentifier: menuIdentifiers.appMenu.settingsMenuItem)!
36 | let appMenu = NSApp.mainMenu!.items[0].submenu!
37 | let settingsIndex = appMenu.index(of: settingsItem)
38 | let updateItem = NSMenuItem(title: "Check for Updates...", action: #selector(checkForUpdates), keyEquivalent: "")
39 | updateItem.target = self
40 | appMenu.insertItem(updateItem, at: settingsIndex + 1)
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/SplitterTests/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 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 6.1
19 | CFBundleVersion
20 | 328
21 |
22 |
23 |
--------------------------------------------------------------------------------
/SplitterTests/Odyssey.json:
--------------------------------------------------------------------------------
1 | {"segments":[{"name":"Cappy","endedAt":{"gametimeMS":70,"realtimeMS":70},"bestDuration":{"gametimeMS":70,"realtimeMS":70}},{"name":"Cascade","endedAt":{"gametimeMS":2032,"realtimeMS":2032},"bestDuration":{"gametimeMS":2032,"realtimeMS":2032}},{"name":"Sand","endedAt":{"gametimeMS":4022,"realtimeMS":4022},"bestDuration":{"gametimeMS":4022,"realtimeMS":4022}},{"name":"Lake","endedAt":{"gametimeMS":5044,"realtimeMS":5044},"bestDuration":{"gametimeMS":5044,"realtimeMS":5044}},{"name":"Wooded","endedAt":{"gametimeMS":6070,"realtimeMS":6070},"bestDuration":{"gametimeMS":6070,"realtimeMS":6070}},{"name":"Cloud","endedAt":{"gametimeMS":7054,"realtimeMS":7054},"bestDuration":{"gametimeMS":7054,"realtimeMS":7054}},{"name":"Lost","endedAt":{"gametimeMS":8072,"realtimeMS":8072},"bestDuration":{"gametimeMS":8072,"realtimeMS":8072}},{"name":"Metro (Night)","endedAt":{"gametimeMS":13045,"realtimeMS":13045},"bestDuration":{"gametimeMS":13045,"realtimeMS":13045}},{"name":"Metro (Day)","endedAt":{"gametimeMS":14060,"realtimeMS":14060},"bestDuration":{"gametimeMS":14060,"realtimeMS":14060}},{"name":"Seaside","endedAt":{"gametimeMS":15021,"realtimeMS":15021},"bestDuration":{"gametimeMS":15021,"realtimeMS":15021}},{"name":"Snow","endedAt":{"gametimeMS":15070,"realtimeMS":15070},"bestDuration":{"gametimeMS":15070,"realtimeMS":15070}},{"name":"Luncheon","endedAt":{"gametimeMS":16003,"realtimeMS":16003},"bestDuration":{"gametimeMS":16003,"realtimeMS":16003}},{"name":"Ruined","endedAt":{"gametimeMS":16032,"realtimeMS":16032},"bestDuration":{"gametimeMS":16032,"realtimeMS":16032}},{"name":"Bowsers","endedAt":{"gametimeMS":16064,"realtimeMS":16064},"bestDuration":{"gametimeMS":16064,"realtimeMS":16064}},{"name":"Moon","endedAt":{"gametimeMS":17000,"realtimeMS":17000},"bestDuration":{"gametimeMS":17000,"realtimeMS":17000}}],"schemaVersion":"v1.0.1","timer":{"website":"https:\/\/mberk.com\/splitter","shortname":"Splitter","longname":"Splitter","version":"v1.0 (44)"},"game":{"longname":"Super Mario Odyssey"},"category":{"longname":"Any %"}}
--------------------------------------------------------------------------------
/SplitterTests/Odyssey.split/appearance.json:
--------------------------------------------------------------------------------
1 | {"hideButtons":true,"tableColor":{"red":0.20000000298023224,"alpha":1,"blue":0.20000000298023224,"green":0.20000000298023224},"keepOnTop":false,"roundTo":0,"columnSizes":{"Icon":32,"Previous Split":152,"Difference":99.5,"Time":90,"Title":80,"Personal Best":85},"bgColor":{"red":0.10000000149011612,"alpha":1,"blue":0.10000000149011612,"green":0.10000000149011612},"textColor":{"red":1,"alpha":1,"blue":1,"green":1},"windowHeight":805,"hideColumns":{"Icon":false,"Previous Split":false,"Difference":false,"Time":false,"Title":false,"Personal Best":false},"hideTitlebar":false,"diffsLongerColor":{"red":1,"alpha":1,"blue":0,"green":0},"diffsShorterColor":{"red":0,"alpha":1,"blue":0,"green":1},"windowWidth":734,"selectColor":{"red":0.35686275362968445,"alpha":1,"blue":0.70980393886566162,"green":0.29019609093666077}}
--------------------------------------------------------------------------------
/SplitterTests/Odyssey.split/gameIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/SplitterTests/Odyssey.split/gameIcon.png
--------------------------------------------------------------------------------
/SplitterTests/Odyssey.split/runInfo.json:
--------------------------------------------------------------------------------
1 | {"id":"47476FFC-0044-4A04-B8B6-CBEB2D104646","category":"Any %","gameVersion":"3.1","version":"3.1","compareTo":0,"gameRegion":"USA","segments":[{"previousPersonalBestTime":"00:00:01.41","personalBestTime":"00:00:00.70","name":"Cappy","previousTime":"00:00:00.99","currentTime":"00:00:01.22"},{"previousPersonalBestTime":"00:00:00.00","personalBestTime":"00:00:02.32","name":"Cascade","previousTime":"00:00:00.00","currentTime":"00:00:02.32"},{"previousPersonalBestTime":"00:00:00.00","personalBestTime":"00:00:04.22","name":"Sand","previousTime":"00:00:00.00","currentTime":"00:00:04.22"},{"previousPersonalBestTime":"00:00:00.00","personalBestTime":"00:00:05.44","name":"Lake","previousTime":"00:00:00.00","currentTime":"00:00:05.44"},{"previousPersonalBestTime":"00:00:00.00","personalBestTime":"00:00:06.70","name":"Wooded","previousTime":"00:00:00.00","currentTime":"00:00:06.70"},{"previousPersonalBestTime":"00:00:00.00","personalBestTime":"00:00:07.54","name":"Cloud","previousTime":"00:00:00.00","currentTime":"00:00:07.54"},{"previousPersonalBestTime":"00:00:00.00","personalBestTime":"00:00:08.72","name":"Lost","previousTime":"00:00:00.00","currentTime":"00:00:08.72"},{"previousPersonalBestTime":"00:00:00.00","personalBestTime":"00:00:13.45","name":"Metro (Night)","previousTime":"00:00:00.00","currentTime":"00:00:13.45"},{"previousPersonalBestTime":"00:00:00.00","personalBestTime":"00:00:14.60","name":"Metro (Day)","previousTime":"00:00:00.00","currentTime":"00:00:14.60"},{"previousPersonalBestTime":"00:00:00.00","personalBestTime":"00:00:15.21","name":"Seaside","previousTime":"00:00:00.00","currentTime":"00:00:15.21"},{"previousPersonalBestTime":"00:00:00.00","personalBestTime":"00:00:15.70","name":"Snow","previousTime":"00:00:00.00","currentTime":"00:00:15.70"},{"previousPersonalBestTime":"00:00:00.00","personalBestTime":"00:00:16.03","name":"Luncheon","previousTime":"00:00:00.00","currentTime":"00:00:16.03"},{"previousPersonalBestTime":"00:00:00.00","personalBestTime":"00:00:16.32","name":"Ruined","previousTime":"00:00:00.00","currentTime":"00:00:16.32"},{"previousPersonalBestTime":"00:00:00.00","personalBestTime":"00:00:16.64","name":"Bowsers","previousTime":"00:00:00.00","currentTime":"00:00:16.64"},{"previousPersonalBestTime":"00:00:00.00","personalBestTime":"00:00:17.00","name":"Moon","previousTime":"00:00:00.00","currentTime":"01:00:17.00"}],"title":"Super Mario Odyssey","attempts":3,"platform":"Switch","endTime":"2020-03-08T05:20:43Z","startTime":"2020-03-08T05:20:42Z","build":"170"}
--------------------------------------------------------------------------------
/SplitterTests/Odyssey.split/segIcons/0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/SplitterTests/Odyssey.split/segIcons/0.png
--------------------------------------------------------------------------------
/SplitterTests/Odyssey.split/segIcons/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/SplitterTests/Odyssey.split/segIcons/1.png
--------------------------------------------------------------------------------
/SplitterTests/Odyssey.split/segIcons/10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/SplitterTests/Odyssey.split/segIcons/10.png
--------------------------------------------------------------------------------
/SplitterTests/Odyssey.split/segIcons/11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/SplitterTests/Odyssey.split/segIcons/11.png
--------------------------------------------------------------------------------
/SplitterTests/Odyssey.split/segIcons/12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/SplitterTests/Odyssey.split/segIcons/12.png
--------------------------------------------------------------------------------
/SplitterTests/Odyssey.split/segIcons/13.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/SplitterTests/Odyssey.split/segIcons/13.png
--------------------------------------------------------------------------------
/SplitterTests/Odyssey.split/segIcons/14.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/SplitterTests/Odyssey.split/segIcons/14.png
--------------------------------------------------------------------------------
/SplitterTests/Odyssey.split/segIcons/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/SplitterTests/Odyssey.split/segIcons/2.png
--------------------------------------------------------------------------------
/SplitterTests/Odyssey.split/segIcons/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/SplitterTests/Odyssey.split/segIcons/3.png
--------------------------------------------------------------------------------
/SplitterTests/Odyssey.split/segIcons/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/SplitterTests/Odyssey.split/segIcons/4.png
--------------------------------------------------------------------------------
/SplitterTests/Odyssey.split/segIcons/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/SplitterTests/Odyssey.split/segIcons/5.png
--------------------------------------------------------------------------------
/SplitterTests/Odyssey.split/segIcons/6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/SplitterTests/Odyssey.split/segIcons/6.png
--------------------------------------------------------------------------------
/SplitterTests/Odyssey.split/segIcons/7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/SplitterTests/Odyssey.split/segIcons/7.png
--------------------------------------------------------------------------------
/SplitterTests/Odyssey.split/segIcons/8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/SplitterTests/Odyssey.split/segIcons/8.png
--------------------------------------------------------------------------------
/SplitterTests/Odyssey.split/segIcons/9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MichaelJBerk/Splitter/5899aabffd0884694827c94827bae2c6e68a3265/SplitterTests/Odyssey.split/segIcons/9.png
--------------------------------------------------------------------------------
/SplitterTests/Odyssey2 copy.json:
--------------------------------------------------------------------------------
1 | {"segments":[{"name":"Cappy","endedAt":{"gametimeMS":70,"realtimeMS":70},"bestDuration":{"gametimeMS":70,"realtimeMS":70}},{"name":"Cascade","endedAt":{"gametimeMS":232,"realtimeMS":232},"bestDuration":{"gametimeMS":232,"realtimeMS":232}},{"name":"Sand","endedAt":{"gametimeMS":422,"realtimeMS":422},"bestDuration":{"gametimeMS":422,"realtimeMS":422}},{"name":"Lake","endedAt":{"gametimeMS":544,"realtimeMS":544},"bestDuration":{"gametimeMS":544,"realtimeMS":544}},{"name":"Wooded","endedAt":{"gametimeMS":670,"realtimeMS":670},"bestDuration":{"gametimeMS":670,"realtimeMS":670}},{"name":"Cloud","endedAt":{"gametimeMS":754,"realtimeMS":754},"bestDuration":{"gametimeMS":754,"realtimeMS":754}},{"name":"Lost","endedAt":{"gametimeMS":872,"realtimeMS":872},"bestDuration":{"gametimeMS":872,"realtimeMS":872}},{"name":"Metro (Night)","endedAt":{"gametimeMS":1345,"realtimeMS":1345},"bestDuration":{"gametimeMS":1345,"realtimeMS":1345}},{"name":"Metro (Day)","endedAt":{"gametimeMS":1460,"realtimeMS":1460},"bestDuration":{"gametimeMS":1460,"realtimeMS":1460}},{"name":"Seaside","endedAt":{"gametimeMS":1521,"realtimeMS":1521},"bestDuration":{"gametimeMS":1521,"realtimeMS":1521}},{"name":"Snow","endedAt":{"gametimeMS":1570,"realtimeMS":1570},"bestDuration":{"gametimeMS":1570,"realtimeMS":1570}},{"name":"Luncheon","endedAt":{"gametimeMS":1603,"realtimeMS":1603},"bestDuration":{"gametimeMS":1603,"realtimeMS":1603}},{"name":"Ruined","endedAt":{"gametimeMS":1632,"realtimeMS":1632},"bestDuration":{"gametimeMS":1632,"realtimeMS":1632}},{"name":"Bowsers","endedAt":{"gametimeMS":1664,"realtimeMS":1664},"bestDuration":{"gametimeMS":1664,"realtimeMS":1664}},{"name":"Moon","endedAt":{"gametimeMS":1700,"realtimeMS":1700},"bestDuration":{"gametimeMS":1700,"realtimeMS":1700}}],"additionalProperties":false,"_schemaVersion":"v1.0.1","timer":{"website":"https:\/\/mberk.com\/splitter","shortname":"Splitter","longname":"Splitter","version":"v1.0 (61)"},"game":{"longname":"Super Mario Odyssey"},"category":{"longname":"Any %"}}
--------------------------------------------------------------------------------
/SplitterTests/Odyssey2.json:
--------------------------------------------------------------------------------
1 | {"segments":[{"name":"Cappy","endedAt":{"gametimeMS":70,"realtimeMS":70},"bestDuration":{"gametimeMS":70,"realtimeMS":70}},{"name":"Cascade","endedAt":{"gametimeMS":232,"realtimeMS":232},"bestDuration":{"gametimeMS":232,"realtimeMS":232}},{"name":"Sand","endedAt":{"gametimeMS":422,"realtimeMS":422},"bestDuration":{"gametimeMS":422,"realtimeMS":422}},{"name":"Lake","endedAt":{"gametimeMS":544,"realtimeMS":544},"bestDuration":{"gametimeMS":544,"realtimeMS":544}},{"name":"Wooded","endedAt":{"gametimeMS":670,"realtimeMS":670},"bestDuration":{"gametimeMS":670,"realtimeMS":670}},{"name":"Cloud","endedAt":{"gametimeMS":754,"realtimeMS":754},"bestDuration":{"gametimeMS":754,"realtimeMS":754}},{"name":"Lost","endedAt":{"gametimeMS":872,"realtimeMS":872},"bestDuration":{"gametimeMS":872,"realtimeMS":872}},{"name":"Metro (Night)","endedAt":{"gametimeMS":1345,"realtimeMS":1345},"bestDuration":{"gametimeMS":1345,"realtimeMS":1345}},{"name":"Metro (Day)","endedAt":{"gametimeMS":1460,"realtimeMS":1460},"bestDuration":{"gametimeMS":1460,"realtimeMS":1460}},{"name":"Seaside","endedAt":{"gametimeMS":1521,"realtimeMS":1521},"bestDuration":{"gametimeMS":1521,"realtimeMS":1521}},{"name":"Snow","endedAt":{"gametimeMS":1570,"realtimeMS":1570},"bestDuration":{"gametimeMS":1570,"realtimeMS":1570}},{"name":"Luncheon","endedAt":{"gametimeMS":1603,"realtimeMS":1603},"bestDuration":{"gametimeMS":1603,"realtimeMS":1603}},{"name":"Ruined","endedAt":{"gametimeMS":1632,"realtimeMS":1632},"bestDuration":{"gametimeMS":1632,"realtimeMS":1632}},{"name":"Bowsers","endedAt":{"gametimeMS":1664,"realtimeMS":1664},"bestDuration":{"gametimeMS":1664,"realtimeMS":1664}},{"name":"Moon","endedAt":{"gametimeMS":1700,"realtimeMS":1700},"bestDuration":{"gametimeMS":1700,"realtimeMS":1700}}],"schemaVersion":"v1.0.1","timer":{"website":"https:\/\/mberk.com\/splitter","shortname":"Splitter","longname":"Splitter","version":"v1.0 (61)"},"game":{"longname":"Super Mario Odyssey"},"category":{"longname":"Any %"}}
--------------------------------------------------------------------------------
/SplitterTests/SplitterTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SplitterTests.swift
3 | // SplitterTests
4 | //
5 | // Created by Michael Berk on 7/14/20.
6 | // Copyright © 2020 Michael Berk. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import Splitter
11 | import Files
12 | import SwiftyJSON
13 |
14 | class SplitterTests: XCTestCase {
15 |
16 | //Path to the SplitterTests folder
17 | fileprivate let testPath = URL(fileURLWithPath: #file).pathComponents.dropLast().joined(separator: "/").dropFirst() //.pathComponents
18 | //.prefix(while: { $0 != "Tests" }).joined(separator: "/").dropFirst()
19 |
20 | var viewController: ViewController?
21 |
22 | override func setUp() {
23 | // Put setup code here. This method is called before the invocation of each test method in the class.
24 | self.viewController = ViewController()
25 | }
26 | // func testGlobalHotkeysSetup() {
27 | // let globalHotkeySetting = Settings.enableGlobalHotkeys
28 | // if let app = NSApp.delegate as? AppDelegate {
29 | // let hotkey1Paused = app.keybinds[0].hotkey?.isPaused
30 | // XCTAssert((hotkey1Paused != globalHotkeySetting))
31 | // }
32 | // }
33 |
34 |
35 | func testOpenSplit() {
36 | let path = String(testPath + "/Odyssey.split")
37 | let url = URL(fileURLWithPath: path)
38 | let doc = try? Document(contentsOf: url, ofType: "Split file")
39 | let dc = NSDocumentController.shared
40 | dc.addDocument(doc!)
41 | }
42 |
43 | func testOpenSplitFromExternalVolume() {
44 | if let blueS = try? Folder(path: "/Volumes/Blue Shell") {
45 |
46 | let path = "/Volumes/Blue Shell/Odyssey.split"
47 | let url = URL(fileURLWithPath: path)
48 | let doc = try? Document(contentsOf: url, ofType: "Split File")
49 | let dc = NSDocumentController.shared
50 | dc.addDocument(doc!)
51 | }
52 | // dc.addDocument(doc!)
53 |
54 |
55 | }
56 |
57 | func testImportSplitsIO() {
58 | let path = String(testPath + "/Odyssey.json")
59 | if let jsonFile = try? File(path: path), let data = try? jsonFile.read() {
60 | let sDoc = SplitsIODoc()
61 | try? sDoc.read(from: data, ofType: "Splits.io File")
62 | sDoc.makeWindowControllers()
63 | }
64 | }
65 |
66 |
67 | func testImportLiveSplit() {
68 | //TODO: New LiveSplit import test
69 | }
70 |
71 |
72 |
73 | override func tearDown() {
74 | // Put teardown code here. This method is called after the invocation of each test method in the class.
75 | }
76 |
77 | func testExample() {
78 | // This is an example of a functional test case.
79 | // Use XCTAssert and related functions to verify your tests produce the correct results.
80 | }
81 |
82 | func testPerformanceExample() {
83 | // This is an example of a performance test case.
84 | measure {
85 | // Put the code you want to measure the time of here.
86 | }
87 | }
88 |
89 | }
90 |
--------------------------------------------------------------------------------
/SplitterTests/TestAttempts.json:
--------------------------------------------------------------------------------
1 | {"segments":[{"name":"1","endedAt":{"gametimeMS":0,"realtimeMS":0},"bestDuration":{"gametimeMS":0,"realtimeMS":0}}],"schemaVersion":"v1.0.1","timer":{"website":"https:\/\/mberk.com\/splitter","shortname":"Splitter","longname":"Splitter","version":"v1.0 (55)"},"game":{"longname":"as"},"category":{"longname":""}}
--------------------------------------------------------------------------------
/SplitterTests/TestPlan.xctestplan:
--------------------------------------------------------------------------------
1 | {
2 | "configurations" : [
3 | {
4 | "id" : "D38F000E-4FF6-447B-8A7F-5935169DB9A5",
5 | "name" : "Configuration 1",
6 | "options" : {
7 |
8 | }
9 | }
10 | ],
11 | "defaultOptions" : {
12 |
13 | },
14 | "testTargets" : [
15 | {
16 | "target" : {
17 | "containerPath" : "container:Splitter.xcodeproj",
18 | "identifier" : "4A71054824BE267300FA9127",
19 | "name" : "SplitterTests"
20 | }
21 | },
22 | {
23 | "target" : {
24 | "containerPath" : "container:Splitter.xcodeproj",
25 | "identifier" : "4AF9E8BE24CE448B00DFD5BE",
26 | "name" : "SplitterUITests"
27 | }
28 | }
29 | ],
30 | "version" : 1
31 | }
32 |
--------------------------------------------------------------------------------
/SplitterTests/Untitled2.json:
--------------------------------------------------------------------------------
1 | {"segments":[{"name":"s1","endedAt":{"gametimeMS":20,"realtimeMS":20},"bestDuration":{"gametimeMS":20,"realtimeMS":20}}],"_schemaVersion":"v1.0.1","timer":{"website":"https:\/\/mberk.com\/splitter","shortname":"Splitter","longname":"Splitter","version":"v1.0 (61)"},"game":{"longname":"thing"},"category":{"longname":""}}
--------------------------------------------------------------------------------
/SplitterTests/Untitled3.json:
--------------------------------------------------------------------------------
1 | {"segments":[{"name":"1","endedAt":{"gametimeMS":0,"realtimeMS":0},"bestDuration":{"gametimeMS":0,"realtimeMS":0}}],"schemaVersion":"v1.0.1","timer":{"website":"https:\/\/mberk.com\/splitter","shortname":"Splitter","longname":"Splitter","version":"v1.0 (55)"},"game":{"longname":"adasdas"},"category":{"longname":""}}
--------------------------------------------------------------------------------
/SplitterUITests/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 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 6.1
19 | CFBundleVersion
20 | 328
21 |
22 |
23 |
--------------------------------------------------------------------------------
/SplitterUITests/SplitterUITests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SplitterUITests.swift
3 | // SplitterUITests
4 | //
5 | // Created by Michael Berk on 3/23/20.
6 | // Copyright © 2020 Michael Berk. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import Splitter
11 |
12 | class SplitterUITests: XCTestCase {
13 |
14 | var app: XCUIApplication!
15 |
16 | override func setUp() {
17 | // Put setup code here. This method is called before the invocation of each test method in the class.
18 |
19 | app = XCUIApplication()
20 | app.launch()
21 |
22 |
23 |
24 | // In UI tests it is usually best to stop immediately when a failure occurs.
25 | continueAfterFailure = false
26 |
27 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
28 | }
29 |
30 | override func tearDown() {
31 | // Put teardown code here. This method is called after the invocation of each test method in the class.
32 | }
33 |
34 | func testLaunchPerformance() {
35 | if #available(macOS 10.15, iOS 13.0, tvOS 13.0, *) {
36 | // This measures how long it takes to launch your application.
37 | measure(metrics: [XCTOSSignpostMetric.applicationLaunch]) {
38 | XCUIApplication().launch()
39 | }
40 | }
41 | }
42 |
43 | }
44 |
45 |
--------------------------------------------------------------------------------
/SplitterUITests/UITest Sample Files/LongList.split/appearance.json:
--------------------------------------------------------------------------------
1 | {"components":[{"type":0,"properties":{"isHidden":false,"afterSpacing":5,"showInfoButton":true}},{"type":1,"properties":{"isHidden":false,"hideTitleColumn":false,"hideHScroll":true,"columnWidths":["32.0","314.5","100.0","100.0"],"hideIconColumn":false,"hideVScroll":true,"showHeader":true,"afterSpacing":7}},{"type":2,"properties":{"isHidden":false,"showLabel":true,"afterSpacing":5}},{"type":3,"properties":{"showAttempts":true,"showAttemptsLabel":true,"isHidden":false,"afterSpacing":5}},{"type":4,"properties":{"isHidden":false,"afterSpacing":5}},{"type":5,"properties":{"afterSpacing":5,"isHidden":false}},{"type":6,"properties":{"isHidden":true,"afterSpacing":5}},{"type":7,"properties":{"isHidden":true,"afterSpacing":5}},{"type":8,"properties":{"afterSpacing":5,"isHidden":true}},{"type":9,"properties":{"isHidden":true,"afterSpacing":5}},{"type":10,"properties":{"afterSpacing":5,"isHidden":true}}],"keepOnTop":false,"windowHeight":656,"hideTitlebar":false,"selectColor":{"red":0.35686275362968445,"alpha":1,"blue":0.70980393886566162,"green":0.29019609093666077},"windowWidth":431,"hideTitle":false,"hideButtons":false}
--------------------------------------------------------------------------------
/SplitterUITests/UITest Sample Files/LongList.split/layout.lsl:
--------------------------------------------------------------------------------
1 | {"components":[{"Title":{"background":{"Vertical":[[1.0,1.0,1.0,0.13],[1.0,1.0,1.0,0.0]]},"text_color":null,"show_game_name":true,"show_category_name":true,"show_finished_runs_count":false,"show_attempt_count":true,"text_alignment":"Auto","display_as_single_line":false,"display_game_icon":true,"show_region":false,"show_platform":false,"show_variables":true}},{"Splits":{"background":{"Alternating":[[0.2,0.2,0.2,1.0],[0.2,0.2,0.2,1.0]]},"visual_split_count":0,"split_preview_count":1,"show_thin_separators":true,"separator_last_split":true,"always_show_last_split":true,"fill_with_blank_space":true,"display_two_rows":false,"current_split_gradient":{"Vertical":[[0.2,0.4509804,0.95686275,1.0],[0.08235294,0.20784314,0.45490196,1.0]]},"split_time_accuracy":"Seconds","segment_time_accuracy":"Hundredths","delta_time_accuracy":"Tenths","delta_drop_decimals":true,"show_column_labels":true,"columns":[{"name":"Time","start_with":"ComparisonTime","update_with":"SplitTime","update_trigger":"OnEndingSegment","comparison_override":null,"timing_method":null},{"name":"+/−","start_with":"Empty","update_with":"Delta","update_trigger":"Contextual","comparison_override":null,"timing_method":null}]}},{"Timer":{"background":"Transparent","timing_method":null,"height":60,"color_override":null,"show_gradient":true,"digits_format":"SingleDigitSeconds","accuracy":"Hundredths","is_segment_timer":false}},{"PreviousSegment":{"background":{"Vertical":[[1.0,1.0,1.0,0.06],[1.0,1.0,1.0,0.005]]},"comparison_override":null,"display_two_rows":false,"label_color":null,"drop_decimals":true,"accuracy":"Tenths","show_possible_time_save":false}},{"SumOfBest":{"background":{"Vertical":[[1.0,1.0,1.0,0.06],[1.0,1.0,1.0,0.005]]},"display_two_rows":false,"label_color":null,"value_color":null,"accuracy":"Seconds"}},{"TotalPlaytime":{"background":{"Vertical":[[1.0,1.0,1.0,0.06],[1.0,1.0,1.0,0.005]]},"display_two_rows":false,"show_days":true,"label_color":null,"value_color":null}},{"SegmentTime":{"background":{"Vertical":[[1.0,1.0,1.0,0.06],[1.0,1.0,1.0,0.005]]},"comparison_override":null,"display_two_rows":false,"label_color":null,"value_color":null,"accuracy":"Hundredths"}},{"PossibleTimeSave":{"background":{"Vertical":[[1.0,1.0,1.0,0.06],[1.0,1.0,1.0,0.005]]},"comparison_override":null,"display_two_rows":false,"total_possible_time_save":false,"label_color":null,"value_color":null,"accuracy":"Hundredths"}}],"general":{"direction":"Vertical","timer_font":null,"times_font":null,"text_font":null,"background":{"Plain":[0.1,0.1,0.1,1.0]},"best_segment_color":[1.0,0.8333334,0.0,1.0],"ahead_gaining_time_color":[0.0,0.8,0.21333352,1.0],"ahead_losing_time_color":[0.38000003,0.82000005,0.49733347,1.0],"behind_gaining_time_color":[0.82000005,0.38000003,0.38000003,1.0],"behind_losing_time_color":[0.8,0.0,0.0,1.0],"not_running_color":[0.67,0.67,0.67,1.0],"personal_best_color":[0.08000004,0.64733326,1.0,1.0],"paused_color":[0.48,0.48,0.48,1.0],"thin_separators_color":[1.0,1.0,1.0,0.09],"separators_color":[1.0,1.0,1.0,0.35],"text_color":[1.0,1.0,1.0,1.0]}}
--------------------------------------------------------------------------------
/SplitterUITests/UITest Sample Files/LongList.split/runInfo.json:
--------------------------------------------------------------------------------
1 | {"version":"5.0","build":"285","id":"2794AA4D-2404-42B6-87B3-6625078306E7"}
--------------------------------------------------------------------------------
/SplitterUITests/UITest Sample Files/LongList.split/splits.lss:
--------------------------------------------------------------------------------
1 | Personal Best00:00:00.00000000123456789101112
--------------------------------------------------------------------------------
/appcenter-post-clone.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | CUR_COCOAPODS_VER=`sed -n -e 's/^COCOAPODS: \([0-9.]*\)/\1/p' Podfile.lock`
3 | ENV_COCOAPODS_VER=`pod --version`
4 |
5 | # check if not the same version, reinstall cocoapods version to current project's
6 | if [ $CUR_COCOAPODS_VER != $ENV_COCOAPODS_VER ];
7 | then
8 | echo "Uninstalling all CocoaPods versions"
9 | sudo gem uninstall cocoapods --all --executables
10 | echo "Installing CocoaPods version $CUR_COCOAPODS_VER"
11 | sudo gem install cocoapods -v $CUR_COCOAPODS_VER
12 | else
13 | echo "CocoaPods version is suitable for the project"
14 | fi;
--------------------------------------------------------------------------------
/appdmg.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Splitter",
3 | "contents": [
4 | { "x": 448, "y": 344, "type": "link", "path": "/Applications" },
5 | { "x": 192, "y": 344, "type": "file", "path": "Splitter.app" }
6 | ]
7 | }
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Splitter",
3 | "lockfileVersion": 3,
4 | "requires": true,
5 | "packages": {}
6 | }
7 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "create-dmg": "^7.0.0"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------