Xcode // Add SindreSorhus's Preferences module.
This commit is contained in:
parent
94de281aca
commit
597ec55fc3
|
@ -0,0 +1,103 @@
|
|||
// Copyright (c) 2018 and onwards Sindre Sorhus (MIT License).
|
||||
/*
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import SwiftUI
|
||||
|
||||
@available(macOS 10.15, *)
|
||||
extension Preferences {
|
||||
/**
|
||||
Function builder for `Preferences` components used in order to restrict types of child views to be of type `Section`.
|
||||
*/
|
||||
@resultBuilder
|
||||
public struct SectionBuilder {
|
||||
public static func buildBlock(_ sections: Section...) -> [Section] {
|
||||
sections
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
A view which holds `Preferences.Section` views and does all the alignment magic similar to `NSGridView` from AppKit.
|
||||
*/
|
||||
public struct Container: View {
|
||||
private let sectionBuilder: () -> [Section]
|
||||
private let contentWidth: Double
|
||||
private let minimumLabelWidth: Double
|
||||
@State private var maximumLabelWidth = 0.0
|
||||
|
||||
/**
|
||||
Creates an instance of container component, which handles layout of stacked `Preferences.Section` views.
|
||||
|
||||
Custom alignment requires content width to be specified beforehand.
|
||||
|
||||
- Parameters:
|
||||
- contentWidth: A fixed width of the container's content (excluding paddings).
|
||||
- minimumLabelWidth: A minimum width for labels within this container. By default, it will fit to the largest label.
|
||||
- builder: A view builder that creates `Preferences.Section`'s of this container.
|
||||
*/
|
||||
public init(
|
||||
contentWidth: Double,
|
||||
minimumLabelWidth: Double = 0,
|
||||
@SectionBuilder builder: @escaping () -> [Section]
|
||||
) {
|
||||
self.sectionBuilder = builder
|
||||
self.contentWidth = contentWidth
|
||||
self.minimumLabelWidth = minimumLabelWidth
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
let sections = sectionBuilder()
|
||||
|
||||
return VStack(alignment: .preferenceSectionLabel) {
|
||||
ForEach(0..<sections.count, id: \.self) { index in
|
||||
viewForSection(sections, index: index)
|
||||
}
|
||||
}
|
||||
.modifier(Section.LabelWidthModifier(maximumWidth: $maximumLabelWidth))
|
||||
.frame(width: CGFloat(contentWidth), alignment: .leading)
|
||||
.padding(.vertical, 20)
|
||||
.padding(.horizontal, 30)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func viewForSection(_ sections: [Section], index: Int) -> some View {
|
||||
sections[index]
|
||||
if index != sections.count - 1 && sections[index].bottomDivider {
|
||||
Divider()
|
||||
// Strangely doesn't work without width being specified. Probably because of custom alignment.
|
||||
.frame(width: CGFloat(contentWidth), height: 20)
|
||||
.alignmentGuide(.preferenceSectionLabel) {
|
||||
$0[.leading] + CGFloat(max(minimumLabelWidth, maximumLabelWidth))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Extension with custom alignment guide for section title labels.
|
||||
@available(macOS 10.15, *)
|
||||
extension HorizontalAlignment {
|
||||
private enum PreferenceSectionLabelAlignment: AlignmentID {
|
||||
static func defaultValue(in context: ViewDimensions) -> CGFloat {
|
||||
context[HorizontalAlignment.leading]
|
||||
}
|
||||
}
|
||||
|
||||
static let preferenceSectionLabel = HorizontalAlignment(PreferenceSectionLabelAlignment.self)
|
||||
}
|
|
@ -0,0 +1,155 @@
|
|||
// Copyright (c) 2018 and onwards Sindre Sorhus (MIT License).
|
||||
/*
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
|
||||
struct Localization {
|
||||
enum Identifier {
|
||||
case preferences
|
||||
case preferencesEllipsized
|
||||
}
|
||||
|
||||
private static let localizedStrings: [Identifier: [String: String]] = [
|
||||
.preferences: [
|
||||
"ar": "تفضيلات",
|
||||
"ca": "Preferències",
|
||||
"cs": "Předvolby",
|
||||
"da": "Indstillinger",
|
||||
"de": "Einstellungen",
|
||||
"el": "Προτιμήσεις",
|
||||
"en": "Preferences",
|
||||
"en-AU": "Preferences",
|
||||
"en-GB": "Preferences",
|
||||
"es": "Preferencias",
|
||||
"es-419": "Preferencias",
|
||||
"fi": "Asetukset",
|
||||
"fr": "Préférences",
|
||||
"fr-CA": "Préférences",
|
||||
"he": "העדפות",
|
||||
"hi": "प्राथमिकता",
|
||||
"hr": "Postavke",
|
||||
"hu": "Beállítások",
|
||||
"id": "Preferensi",
|
||||
"it": "Preferenze",
|
||||
"ja": "環境設定",
|
||||
"ko": "환경설정",
|
||||
"ms": "Keutamaan",
|
||||
"nl": "Voorkeuren",
|
||||
"no": "Valg",
|
||||
"pl": "Preferencje",
|
||||
"pt": "Preferências",
|
||||
"pt-PT": "Preferências",
|
||||
"ro": "Preferințe",
|
||||
"ru": "Настройки",
|
||||
"sk": "Nastavenia",
|
||||
"sv": "Inställningar",
|
||||
"th": "การตั้งค่า",
|
||||
"tr": "Tercihler",
|
||||
"uk": "Параметри",
|
||||
"vi": "Tùy chọn",
|
||||
"zh-CN": "偏好设置",
|
||||
"zh-HK": "偏好設定",
|
||||
"zh-TW": "偏好設定",
|
||||
],
|
||||
.preferencesEllipsized: [
|
||||
"ar": "تفضيلات…",
|
||||
"ca": "Preferències…",
|
||||
"cs": "Předvolby…",
|
||||
"da": "Indstillinger…",
|
||||
"de": "Einstellungen…",
|
||||
"el": "Προτιμήσεις…",
|
||||
"en": "Preferences…",
|
||||
"en-AU": "Preferences…",
|
||||
"en-GB": "Preferences…",
|
||||
"es": "Preferencias…",
|
||||
"es-419": "Preferencias…",
|
||||
"fi": "Asetukset…",
|
||||
"fr": "Préférences…",
|
||||
"fr-CA": "Préférences…",
|
||||
"he": "העדפות…",
|
||||
"hi": "प्राथमिकता…",
|
||||
"hr": "Postavke…",
|
||||
"hu": "Beállítások…",
|
||||
"id": "Preferensi…",
|
||||
"it": "Preferenze…",
|
||||
"ja": "環境設定…",
|
||||
"ko": "환경설정...",
|
||||
"ms": "Keutamaan…",
|
||||
"nl": "Voorkeuren…",
|
||||
"no": "Valg…",
|
||||
"pl": "Preferencje…",
|
||||
"pt": "Preferências…",
|
||||
"pt-PT": "Preferências…",
|
||||
"ro": "Preferințe…",
|
||||
"ru": "Настройки…",
|
||||
"sk": "Nastavenia…",
|
||||
"sv": "Inställningar…",
|
||||
"th": "การตั้งค่า…",
|
||||
"tr": "Tercihler…",
|
||||
"uk": "Параметри…",
|
||||
"vi": "Tùy chọn…",
|
||||
"zh-CN": "偏好设置…",
|
||||
"zh-HK": "偏好設定⋯",
|
||||
"zh-TW": "偏好設定⋯",
|
||||
],
|
||||
]
|
||||
|
||||
/**
|
||||
Returns the localized version of the given string.
|
||||
|
||||
- Parameter identifier: Identifier of the string to localize.
|
||||
|
||||
- Note: If the system's locale can't be determined, the English localization of the string will be returned.
|
||||
*/
|
||||
static subscript(identifier: Identifier) -> String {
|
||||
// Force-unwrapped since all of the involved code is under our control.
|
||||
let localizedDict = Localization.localizedStrings[identifier]!
|
||||
let defaultLocalizedString = localizedDict["en"]!
|
||||
|
||||
// Iterate through all user-preferred languages until we find one that has a valid language code.
|
||||
let preferredLocale =
|
||||
Locale.preferredLanguages
|
||||
.lazy
|
||||
.map { Locale(identifier: $0) }
|
||||
.first { $0.languageCode != nil }
|
||||
?? .current
|
||||
|
||||
guard let languageCode = preferredLocale.languageCode else {
|
||||
return defaultLocalizedString
|
||||
}
|
||||
|
||||
// Chinese is the only language where different region codes result in different translations.
|
||||
if languageCode == "zh" {
|
||||
let regionCode = preferredLocale.regionCode ?? ""
|
||||
if regionCode == "HK" || regionCode == "TW" {
|
||||
return localizedDict["\(languageCode)-\(regionCode)"]!
|
||||
} else {
|
||||
// Fall back to "regular" zh-CN if neither the HK or TW region codes are found.
|
||||
return localizedDict["\(languageCode)-CN"]!
|
||||
}
|
||||
} else {
|
||||
if let localizedString = localizedDict[languageCode] {
|
||||
return localizedString
|
||||
}
|
||||
}
|
||||
|
||||
return defaultLocalizedString
|
||||
}
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
// Copyright (c) 2018 and onwards Sindre Sorhus (MIT License).
|
||||
/*
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import SwiftUI
|
||||
|
||||
/// Represents a type that can be converted to `PreferencePane`.
|
||||
///
|
||||
/// Acts as type-eraser for `Preferences.Pane<T>`.
|
||||
public protocol PreferencePaneConvertible {
|
||||
/**
|
||||
Convert `self` to equivalent `PreferencePane`.
|
||||
*/
|
||||
func asPreferencePane() -> PreferencePane
|
||||
}
|
||||
|
||||
@available(macOS 10.15, *)
|
||||
extension Preferences {
|
||||
/**
|
||||
Create a SwiftUI-based preference pane.
|
||||
|
||||
SwiftUI equivalent of the `PreferencePane` protocol.
|
||||
*/
|
||||
public struct Pane<Content: View>: View, PreferencePaneConvertible {
|
||||
let identifier: PaneIdentifier
|
||||
let title: String
|
||||
let toolbarIcon: NSImage
|
||||
let content: Content
|
||||
|
||||
public init(
|
||||
identifier: PaneIdentifier,
|
||||
title: String,
|
||||
toolbarIcon: NSImage,
|
||||
contentView: () -> Content
|
||||
) {
|
||||
self.identifier = identifier
|
||||
self.title = title
|
||||
self.toolbarIcon = toolbarIcon
|
||||
self.content = contentView()
|
||||
}
|
||||
|
||||
public var body: some View { content }
|
||||
|
||||
public func asPreferencePane() -> PreferencePane {
|
||||
PaneHostingController(pane: self)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Hosting controller enabling `Preferences.Pane` to be used alongside AppKit `NSViewController`'s.
|
||||
*/
|
||||
public final class PaneHostingController<Content: View>: NSHostingController<Content>, PreferencePane {
|
||||
public let preferencePaneIdentifier: PaneIdentifier
|
||||
public let preferencePaneTitle: String
|
||||
public let toolbarItemIcon: NSImage
|
||||
|
||||
init(
|
||||
identifier: PaneIdentifier,
|
||||
title: String,
|
||||
toolbarIcon: NSImage,
|
||||
content: Content
|
||||
) {
|
||||
self.preferencePaneIdentifier = identifier
|
||||
self.preferencePaneTitle = title
|
||||
self.toolbarItemIcon = toolbarIcon
|
||||
super.init(rootView: content)
|
||||
}
|
||||
|
||||
public convenience init(pane: Pane<Content>) {
|
||||
self.init(
|
||||
identifier: pane.identifier,
|
||||
title: pane.title,
|
||||
toolbarIcon: pane.toolbarIcon,
|
||||
content: pane.content
|
||||
)
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
@objc
|
||||
dynamic required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@available(macOS 10.15, *)
|
||||
extension View {
|
||||
/**
|
||||
Applies font and color for a label used for describing a preference.
|
||||
*/
|
||||
public func preferenceDescription() -> some View {
|
||||
font(.system(size: 11.0))
|
||||
// TODO: Use `.foregroundStyle` when targeting macOS 12.
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
// Copyright (c) 2018 and onwards Sindre Sorhus (MIT License).
|
||||
/*
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import Cocoa
|
||||
|
||||
extension Preferences {
|
||||
public struct PaneIdentifier: Hashable, RawRepresentable, Codable {
|
||||
public let rawValue: String
|
||||
|
||||
public init(rawValue: String) {
|
||||
self.rawValue = rawValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public protocol PreferencePane: NSViewController {
|
||||
var preferencePaneIdentifier: Preferences.PaneIdentifier { get }
|
||||
var preferencePaneTitle: String { get }
|
||||
var toolbarItemIcon: NSImage { get }
|
||||
}
|
||||
|
||||
extension PreferencePane {
|
||||
public var toolbarItemIdentifier: NSToolbarItem.Identifier {
|
||||
preferencePaneIdentifier.toolbarItemIdentifier
|
||||
}
|
||||
|
||||
public var toolbarItemIcon: NSImage { .empty }
|
||||
}
|
||||
|
||||
extension Preferences.PaneIdentifier {
|
||||
public init(_ rawValue: String) {
|
||||
self.init(rawValue: rawValue)
|
||||
}
|
||||
|
||||
public init(fromToolbarItemIdentifier itemIdentifier: NSToolbarItem.Identifier) {
|
||||
self.init(rawValue: itemIdentifier.rawValue)
|
||||
}
|
||||
|
||||
public var toolbarItemIdentifier: NSToolbarItem.Identifier {
|
||||
NSToolbarItem.Identifier(rawValue)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
// Copyright (c) 2018 and onwards Sindre Sorhus (MIT License).
|
||||
/*
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/// The namespace for this package.
|
||||
public enum Preferences {}
|
|
@ -0,0 +1,28 @@
|
|||
// Copyright (c) 2018 and onwards Sindre Sorhus (MIT License).
|
||||
/*
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import Cocoa
|
||||
|
||||
extension Preferences {
|
||||
public enum Style {
|
||||
case toolbarItems
|
||||
case segmentedControl
|
||||
}
|
||||
}
|
35
Source/3rdParty/SindreSorhus/Preferences/PreferencesStyleController.swift
vendored
Executable file
35
Source/3rdParty/SindreSorhus/Preferences/PreferencesStyleController.swift
vendored
Executable file
|
@ -0,0 +1,35 @@
|
|||
// Copyright (c) 2018 and onwards Sindre Sorhus (MIT License).
|
||||
/*
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import Cocoa
|
||||
|
||||
protocol PreferencesStyleController: AnyObject {
|
||||
var delegate: PreferencesStyleControllerDelegate? { get set }
|
||||
var isKeepingWindowCentered: Bool { get }
|
||||
|
||||
func toolbarItemIdentifiers() -> [NSToolbarItem.Identifier]
|
||||
func toolbarItem(preferenceIdentifier: Preferences.PaneIdentifier) -> NSToolbarItem?
|
||||
func selectTab(index: Int)
|
||||
}
|
||||
|
||||
protocol PreferencesStyleControllerDelegate: AnyObject {
|
||||
func activateTab(preferenceIdentifier: Preferences.PaneIdentifier, animated: Bool)
|
||||
func activateTab(index: Int, animated: Bool)
|
||||
}
|
258
Source/3rdParty/SindreSorhus/Preferences/PreferencesTabViewController.swift
vendored
Executable file
258
Source/3rdParty/SindreSorhus/Preferences/PreferencesTabViewController.swift
vendored
Executable file
|
@ -0,0 +1,258 @@
|
|||
// Copyright (c) 2018 and onwards Sindre Sorhus (MIT License).
|
||||
/*
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import Cocoa
|
||||
|
||||
final class PreferencesTabViewController: NSViewController, PreferencesStyleControllerDelegate {
|
||||
private var activeTab: Int?
|
||||
private var preferencePanes = [PreferencePane]()
|
||||
private var style: Preferences.Style?
|
||||
internal var preferencePanesCount: Int { preferencePanes.count }
|
||||
private var preferencesStyleController: PreferencesStyleController!
|
||||
private var isKeepingWindowCentered: Bool { preferencesStyleController.isKeepingWindowCentered }
|
||||
|
||||
private var toolbarItemIdentifiers: [NSToolbarItem.Identifier] {
|
||||
preferencesStyleController?.toolbarItemIdentifiers() ?? []
|
||||
}
|
||||
|
||||
var window: NSWindow! { view.window }
|
||||
|
||||
var isAnimated = true
|
||||
|
||||
var activeViewController: NSViewController? {
|
||||
guard let activeTab = activeTab else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return preferencePanes[activeTab]
|
||||
}
|
||||
|
||||
override func loadView() {
|
||||
view = NSView()
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
}
|
||||
|
||||
func configure(preferencePanes: [PreferencePane], style: Preferences.Style) {
|
||||
self.preferencePanes = preferencePanes
|
||||
self.style = style
|
||||
children = preferencePanes
|
||||
|
||||
let toolbar = NSToolbar(identifier: "PreferencesToolbar")
|
||||
toolbar.allowsUserCustomization = false
|
||||
toolbar.displayMode = .iconAndLabel
|
||||
toolbar.showsBaselineSeparator = true
|
||||
toolbar.delegate = self
|
||||
|
||||
switch style {
|
||||
case .segmentedControl:
|
||||
preferencesStyleController = SegmentedControlStyleViewController(preferencePanes: preferencePanes)
|
||||
case .toolbarItems:
|
||||
preferencesStyleController = ToolbarItemStyleViewController(
|
||||
preferencePanes: preferencePanes,
|
||||
toolbar: toolbar,
|
||||
centerToolbarItems: false
|
||||
)
|
||||
}
|
||||
preferencesStyleController.delegate = self
|
||||
|
||||
// Called last so that `preferencesStyleController` can be asked for items.
|
||||
window.toolbar = toolbar
|
||||
}
|
||||
|
||||
func activateTab(preferencePane: PreferencePane, animated: Bool) {
|
||||
activateTab(preferenceIdentifier: preferencePane.preferencePaneIdentifier, animated: animated)
|
||||
}
|
||||
|
||||
func activateTab(preferenceIdentifier: Preferences.PaneIdentifier, animated: Bool) {
|
||||
guard let index = (preferencePanes.firstIndex { $0.preferencePaneIdentifier == preferenceIdentifier }) else {
|
||||
return activateTab(index: 0, animated: animated)
|
||||
}
|
||||
|
||||
activateTab(index: index, animated: animated)
|
||||
}
|
||||
|
||||
func activateTab(index: Int, animated: Bool) {
|
||||
defer {
|
||||
activeTab = index
|
||||
preferencesStyleController.selectTab(index: index)
|
||||
updateWindowTitle(tabIndex: index)
|
||||
}
|
||||
|
||||
if activeTab == nil {
|
||||
immediatelyDisplayTab(index: index)
|
||||
} else {
|
||||
guard index != activeTab else {
|
||||
return
|
||||
}
|
||||
|
||||
animateTabTransition(index: index, animated: animated)
|
||||
}
|
||||
}
|
||||
|
||||
func restoreInitialTab() {
|
||||
if activeTab == nil {
|
||||
activateTab(index: 0, animated: false)
|
||||
}
|
||||
}
|
||||
|
||||
private func updateWindowTitle(tabIndex: Int) {
|
||||
window.title = {
|
||||
if preferencePanes.count > 1 {
|
||||
return preferencePanes[tabIndex].preferencePaneTitle
|
||||
} else {
|
||||
let preferences = Localization[.preferences]
|
||||
let appName = Bundle.main.appName
|
||||
return "\(appName) \(preferences)"
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
/// Cached constraints that pin `childViewController` views to the content view.
|
||||
private var activeChildViewConstraints = [NSLayoutConstraint]()
|
||||
|
||||
private func immediatelyDisplayTab(index: Int) {
|
||||
let toViewController = preferencePanes[index]
|
||||
view.addSubview(toViewController.view)
|
||||
activeChildViewConstraints = toViewController.view.constrainToSuperviewBounds()
|
||||
setWindowFrame(for: toViewController, animated: false)
|
||||
}
|
||||
|
||||
private func animateTabTransition(index: Int, animated: Bool) {
|
||||
guard let activeTab = activeTab else {
|
||||
assertionFailure(
|
||||
"animateTabTransition called before a tab was displayed; transition only works from one tab to another")
|
||||
immediatelyDisplayTab(index: index)
|
||||
return
|
||||
}
|
||||
|
||||
let fromViewController = preferencePanes[activeTab]
|
||||
let toViewController = preferencePanes[index]
|
||||
|
||||
// View controller animations only work on macOS 10.14 and newer.
|
||||
let options: NSViewController.TransitionOptions
|
||||
if #available(macOS 10.14, *) {
|
||||
options = animated && isAnimated ? [.crossfade] : []
|
||||
} else {
|
||||
options = []
|
||||
}
|
||||
|
||||
view.removeConstraints(activeChildViewConstraints)
|
||||
|
||||
transition(
|
||||
from: fromViewController,
|
||||
to: toViewController,
|
||||
options: options
|
||||
) { [self] in
|
||||
activeChildViewConstraints = toViewController.view.constrainToSuperviewBounds()
|
||||
}
|
||||
}
|
||||
|
||||
override func transition(
|
||||
from fromViewController: NSViewController,
|
||||
to toViewController: NSViewController,
|
||||
options: NSViewController.TransitionOptions = [],
|
||||
completionHandler completion: (() -> Void)? = nil
|
||||
) {
|
||||
let isAnimated =
|
||||
options
|
||||
.intersection([
|
||||
.crossfade,
|
||||
.slideUp,
|
||||
.slideDown,
|
||||
.slideForward,
|
||||
.slideBackward,
|
||||
.slideLeft,
|
||||
.slideRight,
|
||||
])
|
||||
.isEmpty == false
|
||||
|
||||
if isAnimated {
|
||||
NSAnimationContext.runAnimationGroup(
|
||||
{ context in
|
||||
context.allowsImplicitAnimation = true
|
||||
context.duration = 0.25
|
||||
context.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
|
||||
setWindowFrame(for: toViewController, animated: true)
|
||||
|
||||
super.transition(
|
||||
from: fromViewController,
|
||||
to: toViewController,
|
||||
options: options,
|
||||
completionHandler: completion
|
||||
)
|
||||
}, completionHandler: nil)
|
||||
} else {
|
||||
super.transition(
|
||||
from: fromViewController,
|
||||
to: toViewController,
|
||||
options: options,
|
||||
completionHandler: completion
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private func setWindowFrame(for viewController: NSViewController, animated: Bool = false) {
|
||||
guard let window = window else {
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
let contentSize = viewController.view.fittingSize
|
||||
|
||||
let newWindowSize = window.frameRect(forContentRect: CGRect(origin: .zero, size: contentSize)).size
|
||||
var frame = window.frame
|
||||
frame.origin.y += frame.height - newWindowSize.height
|
||||
frame.size = newWindowSize
|
||||
|
||||
if isKeepingWindowCentered {
|
||||
let horizontalDiff = (window.frame.width - newWindowSize.width) / 2
|
||||
frame.origin.x += horizontalDiff
|
||||
}
|
||||
|
||||
let animatableWindow = animated ? window.animator() : window
|
||||
animatableWindow.setFrame(frame, display: false)
|
||||
}
|
||||
}
|
||||
|
||||
extension PreferencesTabViewController: NSToolbarDelegate {
|
||||
func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
|
||||
toolbarItemIdentifiers
|
||||
}
|
||||
|
||||
func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
|
||||
toolbarItemIdentifiers
|
||||
}
|
||||
|
||||
func toolbarSelectableItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
|
||||
style == .segmentedControl ? [] : toolbarItemIdentifiers
|
||||
}
|
||||
|
||||
public func toolbar(
|
||||
_ toolbar: NSToolbar,
|
||||
itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier,
|
||||
willBeInsertedIntoToolbar flag: Bool
|
||||
) -> NSToolbarItem? {
|
||||
if itemIdentifier == .flexibleSpace {
|
||||
return nil
|
||||
}
|
||||
|
||||
return preferencesStyleController.toolbarItem(
|
||||
preferenceIdentifier: Preferences.PaneIdentifier(fromToolbarItemIdentifier: itemIdentifier))
|
||||
}
|
||||
}
|
188
Source/3rdParty/SindreSorhus/Preferences/PreferencesWindowController.swift
vendored
Executable file
188
Source/3rdParty/SindreSorhus/Preferences/PreferencesWindowController.swift
vendored
Executable file
|
@ -0,0 +1,188 @@
|
|||
// Copyright (c) 2018 and onwards Sindre Sorhus (MIT License).
|
||||
/*
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import Cocoa
|
||||
|
||||
extension NSWindow.FrameAutosaveName {
|
||||
static let preferences: NSWindow.FrameAutosaveName = "com.sindresorhus.Preferences.FrameAutosaveName"
|
||||
}
|
||||
|
||||
public final class PreferencesWindowController: NSWindowController {
|
||||
private let tabViewController = PreferencesTabViewController()
|
||||
|
||||
public var isAnimated: Bool {
|
||||
get { tabViewController.isAnimated }
|
||||
set {
|
||||
tabViewController.isAnimated = newValue
|
||||
}
|
||||
}
|
||||
|
||||
public var hidesToolbarForSingleItem: Bool {
|
||||
didSet {
|
||||
updateToolbarVisibility()
|
||||
}
|
||||
}
|
||||
|
||||
private func updateToolbarVisibility() {
|
||||
window?.toolbar?.isVisible =
|
||||
(hidesToolbarForSingleItem == false)
|
||||
|| (tabViewController.preferencePanesCount > 1)
|
||||
}
|
||||
|
||||
public init(
|
||||
preferencePanes: [PreferencePane],
|
||||
style: Preferences.Style = .toolbarItems,
|
||||
animated: Bool = true,
|
||||
hidesToolbarForSingleItem: Bool = true
|
||||
) {
|
||||
precondition(!preferencePanes.isEmpty, "You need to set at least one view controller")
|
||||
|
||||
let window = UserInteractionPausableWindow(
|
||||
contentRect: preferencePanes[0].view.bounds,
|
||||
styleMask: [
|
||||
.titled,
|
||||
.closable,
|
||||
],
|
||||
backing: .buffered,
|
||||
defer: true
|
||||
)
|
||||
self.hidesToolbarForSingleItem = hidesToolbarForSingleItem
|
||||
super.init(window: window)
|
||||
|
||||
window.contentViewController = tabViewController
|
||||
|
||||
window.titleVisibility = {
|
||||
switch style {
|
||||
case .toolbarItems:
|
||||
return .visible
|
||||
case .segmentedControl:
|
||||
return preferencePanes.count <= 1 ? .visible : .hidden
|
||||
}
|
||||
}()
|
||||
|
||||
if #available(macOS 11.0, *), style == .toolbarItems {
|
||||
window.toolbarStyle = .preference
|
||||
}
|
||||
|
||||
tabViewController.isAnimated = animated
|
||||
tabViewController.configure(preferencePanes: preferencePanes, style: style)
|
||||
updateToolbarVisibility()
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
override public init(window: NSWindow?) {
|
||||
fatalError("init(window:) is not supported, use init(preferences:style:animated:)")
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
public required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) is not supported, use init(preferences:style:animated:)")
|
||||
}
|
||||
|
||||
/**
|
||||
Show the preferences window and brings it to front.
|
||||
|
||||
If you pass a `Preferences.PaneIdentifier`, the window will activate the corresponding tab.
|
||||
|
||||
- Parameter preferencePane: Identifier of the preference pane to display, or `nil` to show the tab that was open when the user last closed the window.
|
||||
|
||||
- Note: Unless you need to open a specific pane, prefer not to pass a parameter at all or `nil`.
|
||||
|
||||
- See `close()` to close the window again.
|
||||
- See `showWindow(_:)` to show the window without the convenience of activating the app.
|
||||
*/
|
||||
public func show(preferencePane preferenceIdentifier: Preferences.PaneIdentifier? = nil) {
|
||||
if let preferenceIdentifier = preferenceIdentifier {
|
||||
tabViewController.activateTab(preferenceIdentifier: preferenceIdentifier, animated: false)
|
||||
} else {
|
||||
tabViewController.restoreInitialTab()
|
||||
}
|
||||
|
||||
showWindow(self)
|
||||
restoreWindowPosition()
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
}
|
||||
|
||||
private func restoreWindowPosition() {
|
||||
guard
|
||||
let window = window,
|
||||
let screenContainingWindow = window.screen
|
||||
else {
|
||||
return
|
||||
}
|
||||
|
||||
window.setFrameOrigin(
|
||||
CGPoint(
|
||||
x: screenContainingWindow.visibleFrame.midX - window.frame.width / 2,
|
||||
y: screenContainingWindow.visibleFrame.midY - window.frame.height / 2
|
||||
))
|
||||
window.setFrameUsingName(.preferences)
|
||||
window.setFrameAutosaveName(.preferences)
|
||||
}
|
||||
}
|
||||
|
||||
extension PreferencesWindowController {
|
||||
/// Returns the active pane if it responds to the given action.
|
||||
override public func supplementalTarget(forAction action: Selector, sender: Any?) -> Any? {
|
||||
if let target = super.supplementalTarget(forAction: action, sender: sender) {
|
||||
return target
|
||||
}
|
||||
|
||||
guard let activeViewController = tabViewController.activeViewController else {
|
||||
return nil
|
||||
}
|
||||
|
||||
if let target = NSApp.target(forAction: action, to: activeViewController, from: sender) as? NSResponder,
|
||||
target.responds(to: action)
|
||||
{
|
||||
return target
|
||||
}
|
||||
|
||||
if let target = activeViewController.supplementalTarget(forAction: action, sender: sender) as? NSResponder,
|
||||
target.responds(to: action)
|
||||
{
|
||||
return target
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@available(macOS 10.15, *)
|
||||
extension PreferencesWindowController {
|
||||
/**
|
||||
Create a preferences window from only SwiftUI-based preference panes.
|
||||
*/
|
||||
public convenience init(
|
||||
panes: [PreferencePaneConvertible],
|
||||
style: Preferences.Style = .toolbarItems,
|
||||
animated: Bool = true,
|
||||
hidesToolbarForSingleItem: Bool = true
|
||||
) {
|
||||
let preferencePanes = panes.map { $0.asPreferencePane() }
|
||||
|
||||
self.init(
|
||||
preferencePanes: preferencePanes,
|
||||
style: style,
|
||||
animated: animated,
|
||||
hidesToolbarForSingleItem: hidesToolbarForSingleItem
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
// Copyright (c) 2018 and onwards Sindre Sorhus (MIT License).
|
||||
/*
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import SwiftUI
|
||||
|
||||
@available(macOS 10.15, *)
|
||||
extension Preferences {
|
||||
/**
|
||||
Represents a section with right-aligned title and optional bottom divider.
|
||||
*/
|
||||
@available(macOS 10.15, *)
|
||||
public struct Section: View {
|
||||
/**
|
||||
Preference key holding max width of section labels.
|
||||
*/
|
||||
private struct LabelWidthPreferenceKey: PreferenceKey {
|
||||
typealias Value = Double
|
||||
|
||||
static var defaultValue = 0.0
|
||||
|
||||
static func reduce(value: inout Double, nextValue: () -> Double) {
|
||||
let next = nextValue()
|
||||
value = next > value ? next : value
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Convenience overlay for finding a label's dimensions using `GeometryReader`.
|
||||
*/
|
||||
private struct LabelOverlay: View {
|
||||
var body: some View {
|
||||
GeometryReader { geometry in
|
||||
Color.clear
|
||||
.preference(key: LabelWidthPreferenceKey.self, value: Double(geometry.size.width))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Convenience modifier for applying `LabelWidthPreferenceKey`.
|
||||
*/
|
||||
struct LabelWidthModifier: ViewModifier {
|
||||
@Binding var maximumWidth: Double
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
content
|
||||
.onPreferenceChange(LabelWidthPreferenceKey.self) { newMaximumWidth in
|
||||
maximumWidth = Double(newMaximumWidth)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public let label: AnyView
|
||||
public let content: AnyView
|
||||
public let bottomDivider: Bool
|
||||
public let verticalAlignment: VerticalAlignment
|
||||
|
||||
/**
|
||||
A section is responsible for controlling a single preference.
|
||||
|
||||
- Parameters:
|
||||
- bottomDivider: Whether to place a `Divider` after the section content. Default is `false`.
|
||||
- verticalAlignement: The vertical alignment of the section content.
|
||||
- label: A view describing preference handled by this section.
|
||||
- content: A content view.
|
||||
*/
|
||||
public init<Label: View, Content: View>(
|
||||
bottomDivider: Bool = false,
|
||||
verticalAlignment: VerticalAlignment = .firstTextBaseline,
|
||||
label: @escaping () -> Label,
|
||||
@ViewBuilder content: @escaping () -> Content
|
||||
) {
|
||||
self.label = label()
|
||||
.overlay(LabelOverlay())
|
||||
.eraseToAnyView() // TODO: Remove use of `AnyView`.
|
||||
self.bottomDivider = bottomDivider
|
||||
self.verticalAlignment = verticalAlignment
|
||||
let stack = VStack(alignment: .leading) { content() }
|
||||
self.content = stack.eraseToAnyView()
|
||||
}
|
||||
|
||||
/**
|
||||
Creates instance of section, responsible for controling single preference with `Text` as a `Label`.
|
||||
|
||||
- Parameters:
|
||||
- title: A string describing preference handled by this section.
|
||||
- bottomDivider: Whether to place a `Divider` after the section content. Default is `false`.
|
||||
- verticalAlignement: The vertical alignment of the section content.
|
||||
- content: A content view.
|
||||
*/
|
||||
public init<Content: View>(
|
||||
title: String,
|
||||
bottomDivider: Bool = false,
|
||||
verticalAlignment: VerticalAlignment = .firstTextBaseline,
|
||||
@ViewBuilder content: @escaping () -> Content
|
||||
) {
|
||||
let textLabel = {
|
||||
Text(title)
|
||||
.font(.system(size: 13.0))
|
||||
.overlay(LabelOverlay())
|
||||
.eraseToAnyView()
|
||||
}
|
||||
|
||||
self.init(
|
||||
bottomDivider: bottomDivider,
|
||||
verticalAlignment: verticalAlignment,
|
||||
label: textLabel,
|
||||
content: content
|
||||
)
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
HStack(alignment: verticalAlignment) {
|
||||
label
|
||||
.alignmentGuide(.preferenceSectionLabel) { $0[.trailing] }
|
||||
content
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
159
Source/3rdParty/SindreSorhus/Preferences/SegmentedControlStyleViewController.swift
vendored
Executable file
159
Source/3rdParty/SindreSorhus/Preferences/SegmentedControlStyleViewController.swift
vendored
Executable file
|
@ -0,0 +1,159 @@
|
|||
// Copyright (c) 2018 and onwards Sindre Sorhus (MIT License).
|
||||
/*
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import Cocoa
|
||||
|
||||
extension NSToolbarItem.Identifier {
|
||||
static let toolbarSegmentedControlItem = Self("toolbarSegmentedControlItem")
|
||||
}
|
||||
|
||||
extension NSUserInterfaceItemIdentifier {
|
||||
static let toolbarSegmentedControl = Self("toolbarSegmentedControl")
|
||||
}
|
||||
|
||||
final class SegmentedControlStyleViewController: NSViewController, PreferencesStyleController {
|
||||
var segmentedControl: NSSegmentedControl! {
|
||||
get { view as? NSSegmentedControl }
|
||||
set {
|
||||
view = newValue
|
||||
}
|
||||
}
|
||||
|
||||
var isKeepingWindowCentered: Bool { true }
|
||||
|
||||
weak var delegate: PreferencesStyleControllerDelegate?
|
||||
|
||||
private var preferencePanes: [PreferencePane]!
|
||||
|
||||
required init(preferencePanes: [PreferencePane]) {
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
self.preferencePanes = preferencePanes
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func loadView() {
|
||||
view = createSegmentedControl(preferencePanes: preferencePanes)
|
||||
}
|
||||
|
||||
fileprivate func createSegmentedControl(preferencePanes: [PreferencePane]) -> NSSegmentedControl {
|
||||
let segmentedControl = NSSegmentedControl()
|
||||
segmentedControl.segmentCount = preferencePanes.count
|
||||
segmentedControl.segmentStyle = .texturedSquare
|
||||
segmentedControl.target = self
|
||||
segmentedControl.action = #selector(segmentedControlAction)
|
||||
segmentedControl.identifier = .toolbarSegmentedControl
|
||||
|
||||
if let cell = segmentedControl.cell as? NSSegmentedCell {
|
||||
cell.controlSize = .regular
|
||||
cell.trackingMode = .selectOne
|
||||
}
|
||||
|
||||
let segmentSize: CGSize = {
|
||||
let insets = CGSize(width: 36, height: 12)
|
||||
var maxSize = CGSize.zero
|
||||
|
||||
for preferencePane in preferencePanes {
|
||||
let title = preferencePane.preferencePaneTitle
|
||||
let titleSize = title.size(
|
||||
withAttributes: [
|
||||
.font: NSFont.systemFont(ofSize: NSFont.systemFontSize(for: .regular))
|
||||
]
|
||||
)
|
||||
|
||||
maxSize = CGSize(
|
||||
width: max(titleSize.width, maxSize.width),
|
||||
height: max(titleSize.height, maxSize.height)
|
||||
)
|
||||
}
|
||||
|
||||
return CGSize(
|
||||
width: maxSize.width + insets.width,
|
||||
height: maxSize.height + insets.height
|
||||
)
|
||||
}()
|
||||
|
||||
let segmentBorderWidth = CGFloat(preferencePanes.count) + 1
|
||||
let segmentWidth = segmentSize.width * CGFloat(preferencePanes.count) + segmentBorderWidth
|
||||
let segmentHeight = segmentSize.height
|
||||
segmentedControl.frame = CGRect(x: 0, y: 0, width: segmentWidth, height: segmentHeight)
|
||||
|
||||
for (index, preferencePane) in preferencePanes.enumerated() {
|
||||
segmentedControl.setLabel(preferencePane.preferencePaneTitle, forSegment: index)
|
||||
segmentedControl.setWidth(segmentSize.width, forSegment: index)
|
||||
if let cell = segmentedControl.cell as? NSSegmentedCell {
|
||||
cell.setTag(index, forSegment: index)
|
||||
}
|
||||
}
|
||||
|
||||
return segmentedControl
|
||||
}
|
||||
|
||||
@IBAction private func segmentedControlAction(_ control: NSSegmentedControl) {
|
||||
delegate?.activateTab(index: control.selectedSegment, animated: true)
|
||||
}
|
||||
|
||||
func selectTab(index: Int) {
|
||||
segmentedControl.selectedSegment = index
|
||||
}
|
||||
|
||||
func toolbarItemIdentifiers() -> [NSToolbarItem.Identifier] {
|
||||
[
|
||||
.flexibleSpace,
|
||||
.toolbarSegmentedControlItem,
|
||||
.flexibleSpace,
|
||||
]
|
||||
}
|
||||
|
||||
func toolbarItem(preferenceIdentifier: Preferences.PaneIdentifier) -> NSToolbarItem? {
|
||||
let toolbarItemIdentifier = preferenceIdentifier.toolbarItemIdentifier
|
||||
precondition(toolbarItemIdentifier == .toolbarSegmentedControlItem)
|
||||
|
||||
// When the segments outgrow the window, we need to provide a group of
|
||||
// NSToolbarItems with custom menu item labels and action handling for the
|
||||
// context menu that pops up at the right edge of the window.
|
||||
let toolbarItemGroup = NSToolbarItemGroup(itemIdentifier: toolbarItemIdentifier)
|
||||
toolbarItemGroup.view = segmentedControl
|
||||
toolbarItemGroup.subitems = preferencePanes.enumerated().map { index, preferenceable -> NSToolbarItem in
|
||||
let item = NSToolbarItem(itemIdentifier: .init("segment-\(preferenceable.preferencePaneTitle)"))
|
||||
item.label = preferenceable.preferencePaneTitle
|
||||
|
||||
let menuItem = NSMenuItem(
|
||||
title: preferenceable.preferencePaneTitle,
|
||||
action: #selector(segmentedControlMenuAction),
|
||||
keyEquivalent: ""
|
||||
)
|
||||
menuItem.tag = index
|
||||
menuItem.target = self
|
||||
item.menuFormRepresentation = menuItem
|
||||
|
||||
return item
|
||||
}
|
||||
|
||||
return toolbarItemGroup
|
||||
}
|
||||
|
||||
@IBAction private func segmentedControlMenuAction(_ menuItem: NSMenuItem) {
|
||||
delegate?.activateTab(index: menuItem.tag, animated: true)
|
||||
}
|
||||
}
|
77
Source/3rdParty/SindreSorhus/Preferences/ToolbarItemStyleViewController.swift
vendored
Executable file
77
Source/3rdParty/SindreSorhus/Preferences/ToolbarItemStyleViewController.swift
vendored
Executable file
|
@ -0,0 +1,77 @@
|
|||
// Copyright (c) 2018 and onwards Sindre Sorhus (MIT License).
|
||||
/*
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import Cocoa
|
||||
|
||||
final class ToolbarItemStyleViewController: NSObject, PreferencesStyleController {
|
||||
let toolbar: NSToolbar
|
||||
let centerToolbarItems: Bool
|
||||
let preferencePanes: [PreferencePane]
|
||||
var isKeepingWindowCentered: Bool { centerToolbarItems }
|
||||
weak var delegate: PreferencesStyleControllerDelegate?
|
||||
|
||||
init(preferencePanes: [PreferencePane], toolbar: NSToolbar, centerToolbarItems: Bool) {
|
||||
self.preferencePanes = preferencePanes
|
||||
self.toolbar = toolbar
|
||||
self.centerToolbarItems = centerToolbarItems
|
||||
}
|
||||
|
||||
func toolbarItemIdentifiers() -> [NSToolbarItem.Identifier] {
|
||||
var toolbarItemIdentifiers = [NSToolbarItem.Identifier]()
|
||||
|
||||
if centerToolbarItems {
|
||||
toolbarItemIdentifiers.append(.flexibleSpace)
|
||||
}
|
||||
|
||||
for preferencePane in preferencePanes {
|
||||
toolbarItemIdentifiers.append(preferencePane.toolbarItemIdentifier)
|
||||
}
|
||||
|
||||
if centerToolbarItems {
|
||||
toolbarItemIdentifiers.append(.flexibleSpace)
|
||||
}
|
||||
|
||||
return toolbarItemIdentifiers
|
||||
}
|
||||
|
||||
func toolbarItem(preferenceIdentifier: Preferences.PaneIdentifier) -> NSToolbarItem? {
|
||||
guard let preference = (preferencePanes.first { $0.preferencePaneIdentifier == preferenceIdentifier }) else {
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
let toolbarItem = NSToolbarItem(itemIdentifier: preferenceIdentifier.toolbarItemIdentifier)
|
||||
toolbarItem.label = preference.preferencePaneTitle
|
||||
toolbarItem.image = preference.toolbarItemIcon
|
||||
toolbarItem.target = self
|
||||
toolbarItem.action = #selector(toolbarItemSelected)
|
||||
return toolbarItem
|
||||
}
|
||||
|
||||
@IBAction private func toolbarItemSelected(_ toolbarItem: NSToolbarItem) {
|
||||
delegate?.activateTab(
|
||||
preferenceIdentifier: Preferences.PaneIdentifier(fromToolbarItemIdentifier: toolbarItem.itemIdentifier),
|
||||
animated: true
|
||||
)
|
||||
}
|
||||
|
||||
func selectTab(index: Int) {
|
||||
toolbar.selectedItemIdentifier = preferencePanes[index].toolbarItemIdentifier
|
||||
}
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
// Copyright (c) 2018 and onwards Sindre Sorhus (MIT License).
|
||||
/*
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import Cocoa
|
||||
import SwiftUI
|
||||
|
||||
extension NSImage {
|
||||
static var empty: NSImage { NSImage(size: .zero) }
|
||||
}
|
||||
|
||||
extension NSView {
|
||||
@discardableResult
|
||||
func constrainToSuperviewBounds() -> [NSLayoutConstraint] {
|
||||
guard let superview = superview else {
|
||||
preconditionFailure("superview has to be set first")
|
||||
}
|
||||
|
||||
var result = [NSLayoutConstraint]()
|
||||
result.append(
|
||||
contentsOf: NSLayoutConstraint.constraints(
|
||||
withVisualFormat: "H:|-0-[subview]-0-|", options: .directionLeadingToTrailing, metrics: nil,
|
||||
views: ["subview": self]))
|
||||
result.append(
|
||||
contentsOf: NSLayoutConstraint.constraints(
|
||||
withVisualFormat: "V:|-0-[subview]-0-|", options: .directionLeadingToTrailing, metrics: nil,
|
||||
views: ["subview": self]))
|
||||
translatesAutoresizingMaskIntoConstraints = false
|
||||
superview.addConstraints(result)
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
extension NSEvent {
|
||||
/// Events triggered by user interaction.
|
||||
static let userInteractionEvents: [NSEvent.EventType] = {
|
||||
var events: [NSEvent.EventType] = [
|
||||
.leftMouseDown,
|
||||
.leftMouseUp,
|
||||
.rightMouseDown,
|
||||
.rightMouseUp,
|
||||
.leftMouseDragged,
|
||||
.rightMouseDragged,
|
||||
.keyDown,
|
||||
.keyUp,
|
||||
.scrollWheel,
|
||||
.tabletPoint,
|
||||
.otherMouseDown,
|
||||
.otherMouseUp,
|
||||
.otherMouseDragged,
|
||||
.gesture,
|
||||
.magnify,
|
||||
.swipe,
|
||||
.rotate,
|
||||
.beginGesture,
|
||||
.endGesture,
|
||||
.smartMagnify,
|
||||
.quickLook,
|
||||
.directTouch,
|
||||
]
|
||||
|
||||
if #available(macOS 10.10.3, *) {
|
||||
events.append(.pressure)
|
||||
}
|
||||
|
||||
return events
|
||||
}()
|
||||
|
||||
/// Whether the event was triggered by user interaction.
|
||||
var isUserInteraction: Bool { NSEvent.userInteractionEvents.contains(type) }
|
||||
}
|
||||
|
||||
extension Bundle {
|
||||
var appName: String {
|
||||
string(forInfoDictionaryKey: "CFBundleDisplayName")
|
||||
?? string(forInfoDictionaryKey: "CFBundleName")
|
||||
?? string(forInfoDictionaryKey: "CFBundleExecutable")
|
||||
?? "<Unknown App Name>"
|
||||
}
|
||||
|
||||
private func string(forInfoDictionaryKey key: String) -> String? {
|
||||
// `object(forInfoDictionaryKey:)` prefers localized info dictionary over the regular one automatically
|
||||
object(forInfoDictionaryKey: key) as? String
|
||||
}
|
||||
}
|
||||
|
||||
/// A window that allows you to disable all user interactions via `isUserInteractionEnabled`.
|
||||
///
|
||||
/// Used to avoid breaking animations when the user clicks too fast. Disable user interactions during animations and you're set.
|
||||
class UserInteractionPausableWindow: NSWindow { // swiftlint:disable:this final_class
|
||||
var isUserInteractionEnabled = true
|
||||
|
||||
override func sendEvent(_ event: NSEvent) {
|
||||
guard isUserInteractionEnabled || !event.isUserInteraction else {
|
||||
return
|
||||
}
|
||||
|
||||
super.sendEvent(event)
|
||||
}
|
||||
|
||||
override func responds(to selector: Selector!) -> Bool {
|
||||
// Deactivate toolbar interactions from the Main Menu.
|
||||
if selector == #selector(NSWindow.toggleToolbarShown(_:)) {
|
||||
return false
|
||||
}
|
||||
|
||||
return super.responds(to: selector)
|
||||
}
|
||||
}
|
||||
|
||||
@available(macOS 10.15, *)
|
||||
extension View {
|
||||
/**
|
||||
Equivalent to `.eraseToAnyPublisher()` from the Combine framework.
|
||||
*/
|
||||
func eraseToAnyView() -> AnyView {
|
||||
AnyView(self)
|
||||
}
|
||||
}
|
|
@ -30,6 +30,19 @@
|
|||
5B707CEC27D9F4870099EF99 /* OpenCC in Frameworks */ = {isa = PBXBuildFile; productRef = 5B707CEB27D9F4870099EF99 /* OpenCC */; };
|
||||
5B73FB5E27B2BE1300E9BF49 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5B73FB6027B2BE1300E9BF49 /* InfoPlist.strings */; };
|
||||
5B7BC4B027AFFBE800F66C24 /* frmPrefWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5B7BC4AE27AFFBE800F66C24 /* frmPrefWindow.xib */; };
|
||||
5BA9FD2327FEF39C002DE248 /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD1627FEF39C002DE248 /* Utilities.swift */; };
|
||||
5BA9FD2427FEF39C002DE248 /* Pane.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD1727FEF39C002DE248 /* Pane.swift */; };
|
||||
5BA9FD2527FEF39C002DE248 /* Localization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD1827FEF39C002DE248 /* Localization.swift */; };
|
||||
5BA9FD2627FEF39C002DE248 /* PreferencesStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD1927FEF39C002DE248 /* PreferencesStyle.swift */; };
|
||||
5BA9FD2727FEF39C002DE248 /* PreferencePane.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD1A27FEF39C002DE248 /* PreferencePane.swift */; };
|
||||
5BA9FD2827FEF39C002DE248 /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD1B27FEF39C002DE248 /* Preferences.swift */; };
|
||||
5BA9FD2927FEF39C002DE248 /* SegmentedControlStyleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD1C27FEF39C002DE248 /* SegmentedControlStyleViewController.swift */; };
|
||||
5BA9FD2A27FEF39C002DE248 /* ToolbarItemStyleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD1D27FEF39C002DE248 /* ToolbarItemStyleViewController.swift */; };
|
||||
5BA9FD2B27FEF39C002DE248 /* Container.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD1E27FEF39C002DE248 /* Container.swift */; };
|
||||
5BA9FD2C27FEF39C002DE248 /* PreferencesStyleController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD1F27FEF39C002DE248 /* PreferencesStyleController.swift */; };
|
||||
5BA9FD2D27FEF39C002DE248 /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD2027FEF39C002DE248 /* PreferencesWindowController.swift */; };
|
||||
5BA9FD2E27FEF39C002DE248 /* Section.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD2127FEF39C002DE248 /* Section.swift */; };
|
||||
5BA9FD2F27FEF39C002DE248 /* PreferencesTabViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD2227FEF39C002DE248 /* PreferencesTabViewController.swift */; };
|
||||
5BAD0CD527D701F6003D127F /* vChewingKeyLayout.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 5B30F11227BA568800484E24 /* vChewingKeyLayout.bundle */; };
|
||||
5BB802DA27FABA8300CF1C19 /* ctlInputMethod_Menu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BB802D927FABA8300CF1C19 /* ctlInputMethod_Menu.swift */; };
|
||||
5BBBB75F27AED54C0023B93A /* Beep.m4a in Resources */ = {isa = PBXBuildFile; fileRef = 5BBBB75D27AED54C0023B93A /* Beep.m4a */; };
|
||||
|
@ -189,6 +202,19 @@
|
|||
5B7BC4AF27AFFBE800F66C24 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/frmPrefWindow.xib; sourceTree = "<group>"; };
|
||||
5B7BC4B227AFFC0B00F66C24 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/frmPrefWindow.strings; sourceTree = "<group>"; };
|
||||
5B8F43ED27C9BC220069AC27 /* SymbolLM.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SymbolLM.h; sourceTree = "<group>"; };
|
||||
5BA9FD1627FEF39C002DE248 /* Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Utilities.swift; sourceTree = "<group>"; };
|
||||
5BA9FD1727FEF39C002DE248 /* Pane.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Pane.swift; sourceTree = "<group>"; };
|
||||
5BA9FD1827FEF39C002DE248 /* Localization.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Localization.swift; sourceTree = "<group>"; };
|
||||
5BA9FD1927FEF39C002DE248 /* PreferencesStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesStyle.swift; sourceTree = "<group>"; };
|
||||
5BA9FD1A27FEF39C002DE248 /* PreferencePane.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencePane.swift; sourceTree = "<group>"; };
|
||||
5BA9FD1B27FEF39C002DE248 /* Preferences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = "<group>"; };
|
||||
5BA9FD1C27FEF39C002DE248 /* SegmentedControlStyleViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SegmentedControlStyleViewController.swift; sourceTree = "<group>"; };
|
||||
5BA9FD1D27FEF39C002DE248 /* ToolbarItemStyleViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ToolbarItemStyleViewController.swift; sourceTree = "<group>"; };
|
||||
5BA9FD1E27FEF39C002DE248 /* Container.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Container.swift; sourceTree = "<group>"; };
|
||||
5BA9FD1F27FEF39C002DE248 /* PreferencesStyleController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesStyleController.swift; sourceTree = "<group>"; };
|
||||
5BA9FD2027FEF39C002DE248 /* PreferencesWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesWindowController.swift; sourceTree = "<group>"; };
|
||||
5BA9FD2127FEF39C002DE248 /* Section.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Section.swift; sourceTree = "<group>"; };
|
||||
5BA9FD2227FEF39C002DE248 /* PreferencesTabViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesTabViewController.swift; sourceTree = "<group>"; };
|
||||
5BB802D927FABA8300CF1C19 /* ctlInputMethod_Menu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ctlInputMethod_Menu.swift; sourceTree = "<group>"; };
|
||||
5BBBB75D27AED54C0023B93A /* Beep.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = Beep.m4a; sourceTree = "<group>"; };
|
||||
5BBBB75E27AED54C0023B93A /* Fart.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = Fart.m4a; sourceTree = "<group>"; };
|
||||
|
@ -353,6 +379,7 @@
|
|||
children = (
|
||||
5B707CE627D9F43E0099EF99 /* OpenCCBridge */,
|
||||
5B62A30227AE733500A19448 /* OVMandarin */,
|
||||
5BA9FCEA27FED652002DE248 /* SindreSorhus */,
|
||||
);
|
||||
path = 3rdParty;
|
||||
sourceTree = "<group>";
|
||||
|
@ -564,6 +591,35 @@
|
|||
path = OpenCCBridge;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
5BA9FCEA27FED652002DE248 /* SindreSorhus */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5BA9FD1527FEF39C002DE248 /* Preferences */,
|
||||
);
|
||||
name = SindreSorhus;
|
||||
path = Source/3rdParty/SindreSorhus;
|
||||
sourceTree = SOURCE_ROOT;
|
||||
};
|
||||
5BA9FD1527FEF39C002DE248 /* Preferences */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5BA9FD1E27FEF39C002DE248 /* Container.swift */,
|
||||
5BA9FD1827FEF39C002DE248 /* Localization.swift */,
|
||||
5BA9FD1727FEF39C002DE248 /* Pane.swift */,
|
||||
5BA9FD1A27FEF39C002DE248 /* PreferencePane.swift */,
|
||||
5BA9FD1B27FEF39C002DE248 /* Preferences.swift */,
|
||||
5BA9FD1927FEF39C002DE248 /* PreferencesStyle.swift */,
|
||||
5BA9FD1F27FEF39C002DE248 /* PreferencesStyleController.swift */,
|
||||
5BA9FD2227FEF39C002DE248 /* PreferencesTabViewController.swift */,
|
||||
5BA9FD2027FEF39C002DE248 /* PreferencesWindowController.swift */,
|
||||
5BA9FD2127FEF39C002DE248 /* Section.swift */,
|
||||
5BA9FD1C27FEF39C002DE248 /* SegmentedControlStyleViewController.swift */,
|
||||
5BA9FD1D27FEF39C002DE248 /* ToolbarItemStyleViewController.swift */,
|
||||
5BA9FD1627FEF39C002DE248 /* Utilities.swift */,
|
||||
);
|
||||
path = Preferences;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
5BBBB75C27AED54C0023B93A /* SoundFiles */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -970,7 +1026,9 @@
|
|||
D461B792279DAC010070E734 /* InputState.swift in Sources */,
|
||||
5B62A33D27AE7CC100A19448 /* ctlAboutWindow.swift in Sources */,
|
||||
D47B92C027972AD100458394 /* main.swift in Sources */,
|
||||
5BA9FD2527FEF39C002DE248 /* Localization.swift in Sources */,
|
||||
D44FB74D2792189A003C80A6 /* PhraseReplacementMap.mm in Sources */,
|
||||
5BA9FD2E27FEF39C002DE248 /* Section.swift in Sources */,
|
||||
D4A13D5A27A59F0B003BE359 /* ctlInputMethod.swift in Sources */,
|
||||
D4E569DC27A34D0E00AC2CEF /* KeyHandler.mm in Sources */,
|
||||
D47F7DD0278C0897002F9DD7 /* ctlNonModalAlertWindow.swift in Sources */,
|
||||
|
@ -978,16 +1036,26 @@
|
|||
5BE78BE027B38804005EA1BE /* LMConsolidator.mm in Sources */,
|
||||
D456576E279E4F7B00DF6BC9 /* KeyParser.swift in Sources */,
|
||||
D47F7DCE278BFB57002F9DD7 /* ctlPrefWindow.swift in Sources */,
|
||||
5BA9FD2927FEF39C002DE248 /* SegmentedControlStyleViewController.swift in Sources */,
|
||||
D47D73AC27A6CAE600255A50 /* AssociatedPhrases.mm in Sources */,
|
||||
5B62A34A27AE7CD900A19448 /* NotifierController.swift in Sources */,
|
||||
5BA9FD2827FEF39C002DE248 /* Preferences.swift in Sources */,
|
||||
5BA9FD2D27FEF39C002DE248 /* PreferencesWindowController.swift in Sources */,
|
||||
5BA9FD2B27FEF39C002DE248 /* Container.swift in Sources */,
|
||||
5BA9FD2427FEF39C002DE248 /* Pane.swift in Sources */,
|
||||
5B11328927B94CFB00E58451 /* AppleKeyboardConverter.swift in Sources */,
|
||||
D41355DB278E6D17005E5CBD /* LMInstantiator.mm in Sources */,
|
||||
5B62A32927AE77D100A19448 /* FSEventStreamHelper.swift in Sources */,
|
||||
D47F7DD3278C1263002F9DD7 /* UserOverrideModel.cpp in Sources */,
|
||||
5B62A33627AE795800A19448 /* mgrPrefs.swift in Sources */,
|
||||
5B62A33827AE79CD00A19448 /* NSStringUtils.swift in Sources */,
|
||||
5BA9FD2327FEF39C002DE248 /* Utilities.swift in Sources */,
|
||||
5B62A33227AE792F00A19448 /* InputSourceHelper.swift in Sources */,
|
||||
5BA9FD2C27FEF39C002DE248 /* PreferencesStyleController.swift in Sources */,
|
||||
5BA9FD2A27FEF39C002DE248 /* ToolbarItemStyleViewController.swift in Sources */,
|
||||
5BA9FD2F27FEF39C002DE248 /* PreferencesTabViewController.swift in Sources */,
|
||||
5B5E535227EF261400C6AA1E /* IME.swift in Sources */,
|
||||
5BA9FD2627FEF39C002DE248 /* PreferencesStyle.swift in Sources */,
|
||||
5B62A34927AE7CD900A19448 /* TooltipController.swift in Sources */,
|
||||
6A0D4F4515FC0EB100ABF4B3 /* Mandarin.cpp in Sources */,
|
||||
5B62A34827AE7CD900A19448 /* ctlCandidateVertical.swift in Sources */,
|
||||
|
@ -999,6 +1067,7 @@
|
|||
5BB802DA27FABA8300CF1C19 /* ctlInputMethod_Menu.swift in Sources */,
|
||||
D41355DE278EA3ED005E5CBD /* UserPhrasesLM.mm in Sources */,
|
||||
6ACC3D3F27914F2400F1B140 /* KeyValueBlobReader.cpp in Sources */,
|
||||
5BA9FD2727FEF39C002DE248 /* PreferencePane.swift in Sources */,
|
||||
D41355D8278D74B5005E5CBD /* mgrLangModel.mm in Sources */,
|
||||
5BDC1CFA27FDF1310052C2B9 /* apiUpdate.swift in Sources */,
|
||||
);
|
||||
|
|
Loading…
Reference in New Issue