SSPreferences // Update to the upstream version (Mar. 5, 2023).
This commit is contained in:
parent
8541c3b9e6
commit
5fa96c7f64
|
@ -1,10 +1,10 @@
|
|||
// swift-tools-version:5.3
|
||||
// swift-tools-version:5.5
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "SSPreferences",
|
||||
platforms: [
|
||||
.macOS(.v10_11),
|
||||
.macOS(.v10_13),
|
||||
],
|
||||
products: [
|
||||
.library(
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
// (c) 2018 and onwards Sindre Sorhus (MIT License).
|
||||
// ====================
|
||||
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
|
||||
|
||||
/// The namespace for this package.
|
||||
public enum SSPreferences {}
|
|
@ -5,9 +5,9 @@
|
|||
import SwiftUI
|
||||
|
||||
@available(macOS 10.15, *)
|
||||
public extension SSPreferences {
|
||||
public extension Settings {
|
||||
/**
|
||||
Function builder for `Preferences` components used in order to restrict types of child views to be of type `Section`.
|
||||
Function builder for `Settings` components used in order to restrict types of child views to be of type `Section`.
|
||||
*/
|
||||
@resultBuilder
|
||||
enum SectionBuilder {
|
||||
|
@ -17,7 +17,7 @@ public extension SSPreferences {
|
|||
}
|
||||
|
||||
/**
|
||||
A view which holds `Preferences.Section` views and does all the alignment magic similar to `NSGridView` from AppKit.
|
||||
A view which holds `Settings.Section` views and does all the alignment magic similar to `NSGridView` from AppKit.
|
||||
*/
|
||||
struct Container: View {
|
||||
private let sectionBuilder: () -> [Section]
|
||||
|
@ -26,14 +26,14 @@ public extension SSPreferences {
|
|||
@State private var maximumLabelWidth = 0.0
|
||||
|
||||
/**
|
||||
Creates an instance of container component, which handles layout of stacked `Preferences.Section` views.
|
||||
Creates an instance of container component, which handles layout of stacked `Settings.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.
|
||||
- builder: A view builder that creates `Settings.Section`'s of this container.
|
||||
*/
|
||||
public init(
|
||||
contentWidth: Double,
|
||||
|
@ -47,21 +47,10 @@ public extension SSPreferences {
|
|||
|
||||
public var body: some View {
|
||||
let sections = sectionBuilder()
|
||||
let labelWidth = max(minimumLabelWidth, maximumLabelWidth)
|
||||
|
||||
return VStack(alignment: .preferenceSectionLabel) {
|
||||
return VStack(alignment: .settingsSectionLabel) {
|
||||
ForEach(0 ..< sections.count, id: \.self) { index in
|
||||
if sections[index].label != nil {
|
||||
sections[index].bodyLimited(rightPaneWidth: contentWidth - labelWidth)
|
||||
} else {
|
||||
sections[index]
|
||||
.alignmentGuide(.preferenceSectionLabel) { $0[.leading] + labelWidth }
|
||||
}
|
||||
if sections[index].bottomDivider, index < sections.count - 1 {
|
||||
Divider()
|
||||
.frame(height: 10)
|
||||
.alignmentGuide(.preferenceSectionLabel) { $0[.leading] + labelWidth }
|
||||
}
|
||||
viewForSection(sections, index: index)
|
||||
}
|
||||
}
|
||||
.modifier(Section.LabelWidthModifier(maximumWidth: $maximumLabelWidth))
|
||||
|
@ -69,17 +58,31 @@ public extension SSPreferences {
|
|||
.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: contentWidth, height: 20)
|
||||
.alignmentGuide(.settingsSectionLabel) { $0[.leading] + max(minimumLabelWidth, maximumLabelWidth) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Extension with custom alignment guide for section title labels.
|
||||
/**
|
||||
Extension with custom alignment guide for section title labels.
|
||||
*/
|
||||
@available(macOS 10.15, *)
|
||||
extension HorizontalAlignment {
|
||||
private enum PreferenceSectionLabelAlignment: AlignmentID {
|
||||
private enum SettingsSectionLabelAlignment: AlignmentID {
|
||||
// swiftlint:disable:next no_cgfloat
|
||||
static func defaultValue(in context: ViewDimensions) -> CGFloat {
|
||||
context[HorizontalAlignment.leading]
|
||||
}
|
||||
}
|
||||
|
||||
static let preferenceSectionLabel = HorizontalAlignment(PreferenceSectionLabelAlignment.self)
|
||||
static let settingsSectionLabel = HorizontalAlignment(SettingsSectionLabelAlignment.self)
|
||||
}
|
|
@ -7,7 +7,7 @@ import Foundation
|
|||
struct Localization {
|
||||
enum Identifier {
|
||||
case preferences
|
||||
case preferencesEllipsized
|
||||
case settings
|
||||
}
|
||||
|
||||
private static let localizedStrings: [Identifier: [String: String]] = [
|
||||
|
@ -52,46 +52,46 @@ struct Localization {
|
|||
"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": "偏好設定⋯",
|
||||
.settings: [
|
||||
"ar": "الإعدادات",
|
||||
"ca": "Configuració",
|
||||
"cs": "Nastavení",
|
||||
"da": "Indstillinger",
|
||||
"de": "Einstellungen",
|
||||
"el": "Ρυθμίσεις",
|
||||
"en": "Settings",
|
||||
"en-AU": "Settings",
|
||||
"en-GB": "Settings",
|
||||
"es": "Ajustes",
|
||||
"es-419": "Ajustes",
|
||||
"fi": "Asetukset",
|
||||
"fr": "Réglages",
|
||||
"fr-CA": "Réglages",
|
||||
"he": "הגדרות",
|
||||
"hi": "समायोजन",
|
||||
"hr": "Postavke",
|
||||
"hu": "Beállítások",
|
||||
"id": "Pengaturan",
|
||||
"it": "Impostazioni",
|
||||
"ja": "設定",
|
||||
"ko": "설정",
|
||||
"ms": "Tetapan",
|
||||
"nl": "Instellingen",
|
||||
"no": "Innstillinger",
|
||||
"pl": "Ustawienia",
|
||||
"pt": "Ajustes",
|
||||
"pt-PT": "Definições",
|
||||
"ro": "Configurări",
|
||||
"ru": "Настройки",
|
||||
"sk": "Nastavenia",
|
||||
"sv": "Inställningar",
|
||||
"th": "ค่าติดตั้ง",
|
||||
"tr": "Ayarlar",
|
||||
"uk": "Параметри",
|
||||
"vi": "Cài đặt",
|
||||
"zh-CN": "设置",
|
||||
"zh-HK": "設定",
|
||||
"zh-TW": "設定",
|
||||
],
|
||||
]
|
||||
|
||||
|
@ -104,12 +104,12 @@ struct Localization {
|
|||
*/
|
||||
static subscript(identifier: Identifier) -> String {
|
||||
// Force-unwrapped since all of the involved code is under our control.
|
||||
let localizedDict = Self.localizedStrings[identifier]!
|
||||
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
|
||||
let preferredLocale = Locale.preferredLanguages
|
||||
// TODO: Use `.firstNonNil()` here when available.
|
||||
.lazy
|
||||
.map { Locale(identifier: $0) }
|
||||
.first { $0.languageCode != nil }
|
|
@ -4,24 +4,26 @@
|
|||
|
||||
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`.
|
||||
/**
|
||||
Represents a type that can be converted to `SettingsPane`.
|
||||
|
||||
Acts as type-eraser for `Settings.Pane<T>`.
|
||||
*/
|
||||
func asPreferencePane() -> PreferencePane
|
||||
public protocol SettingsPaneConvertible {
|
||||
/**
|
||||
Convert `self` to equivalent `SettingsPane`.
|
||||
*/
|
||||
func asPreferencePane() -> SettingsPane
|
||||
}
|
||||
|
||||
@available(macOS 10.15, *)
|
||||
public extension SSPreferences {
|
||||
public extension Settings {
|
||||
/**
|
||||
Create a SwiftUI-based preference pane.
|
||||
Create a SwiftUI-based settings pane.
|
||||
|
||||
SwiftUI equivalent of the `PreferencePane` protocol.
|
||||
SwiftUI equivalent of the `SettingsPane` protocol.
|
||||
*/
|
||||
struct Pane<Content: View>: View, PreferencePaneConvertible {
|
||||
struct Pane<Content: View>: View, SettingsPaneConvertible {
|
||||
let identifier: PaneIdentifier
|
||||
let title: String
|
||||
let toolbarIcon: NSImage
|
||||
|
@ -41,15 +43,15 @@ public extension SSPreferences {
|
|||
|
||||
public var body: some View { content }
|
||||
|
||||
public func asPreferencePane() -> PreferencePane {
|
||||
public func asPreferencePane() -> SettingsPane {
|
||||
PaneHostingController(pane: self)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Hosting controller enabling `Preferences.Pane` to be used alongside AppKit `NSViewController`'s.
|
||||
Hosting controller enabling `Settings.Pane` to be used alongside AppKit `NSViewController`'s.
|
||||
*/
|
||||
final class PaneHostingController<Content: View>: NSHostingController<Content>, PreferencePane {
|
||||
final class PaneHostingController<Content: View>: NSHostingController<Content>, SettingsPane {
|
||||
public let preferencePaneIdentifier: PaneIdentifier
|
||||
public let preferencePaneTitle: String
|
||||
public let toolbarItemIcon: NSImage
|
||||
|
@ -86,12 +88,11 @@ public extension SSPreferences {
|
|||
@available(macOS 10.15, *)
|
||||
public extension View {
|
||||
/**
|
||||
Applies font and color for a label used for describing a preference.
|
||||
Applies font and color for a label used for describing a setting.
|
||||
*/
|
||||
func preferenceDescription() -> some View {
|
||||
font(.system(size: 11.0))
|
||||
// TODO: Use `.foregroundStyle` when targeting macOS 12.
|
||||
.foregroundColor(.secondary)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
}
|
|
@ -5,7 +5,7 @@
|
|||
import SwiftUI
|
||||
|
||||
@available(macOS 10.15, *)
|
||||
public extension SSPreferences {
|
||||
public extension Settings {
|
||||
/**
|
||||
Represents a section with right-aligned title and optional bottom divider.
|
||||
*/
|
||||
|
@ -32,7 +32,10 @@ public extension SSPreferences {
|
|||
var body: some View {
|
||||
GeometryReader { geometry in
|
||||
Color.clear
|
||||
.preference(key: LabelWidthPreferenceKey.self, value: Double(geometry.size.width))
|
||||
.preference(
|
||||
key: LabelWidthPreferenceKey.self,
|
||||
value: geometry.size.width
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -46,53 +49,30 @@ public extension SSPreferences {
|
|||
func body(content: Content) -> some View {
|
||||
content
|
||||
.onPreferenceChange(LabelWidthPreferenceKey.self) { newMaximumWidth in
|
||||
maximumWidth = Double(newMaximumWidth)
|
||||
maximumWidth = newMaximumWidth
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public private(set) var label: AnyView?
|
||||
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 without Label.
|
||||
A section is responsible for controlling a single setting.
|
||||
|
||||
- Parameters:
|
||||
- bottomDivider: Whether to place a `Divider` after the section content. Default is `false`.
|
||||
- verticalAlignement: The vertical alignment of the section content.
|
||||
- verticalAlignment:
|
||||
- label: A view describing preference handled by this section.
|
||||
- label: A view describing the setting handled by this section.
|
||||
- content: A content view.
|
||||
*/
|
||||
public init<Content: View>(
|
||||
public init(
|
||||
bottomDivider: Bool = false,
|
||||
verticalAlignment: VerticalAlignment = .firstTextBaseline,
|
||||
@ViewBuilder content: @escaping () -> Content
|
||||
) {
|
||||
label = nil
|
||||
self.bottomDivider = bottomDivider
|
||||
self.verticalAlignment = verticalAlignment
|
||||
let stack = VStack(alignment: .leading) { content() }
|
||||
self.content = stack.eraseToAnyView()
|
||||
}
|
||||
|
||||
/**
|
||||
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.
|
||||
- verticalAlignment:
|
||||
- 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
|
||||
label: @escaping () -> some View,
|
||||
@ViewBuilder content: @escaping () -> some View
|
||||
) {
|
||||
self.label = label()
|
||||
.overlay(LabelOverlay())
|
||||
|
@ -104,57 +84,42 @@ public extension SSPreferences {
|
|||
}
|
||||
|
||||
/**
|
||||
Creates instance of section, responsible for controling single preference with `Text` as a `Label`.
|
||||
Creates instance of section, responsible for controling a single setting with `Text` as a `Label`.
|
||||
|
||||
- Parameters:
|
||||
- title: A string describing preference handled by this section.
|
||||
- title: A string describing the setting 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.
|
||||
- verticalAlignment:
|
||||
- content: A content view.
|
||||
*/
|
||||
public init<Content: View>(
|
||||
title: String? = nil,
|
||||
public init(
|
||||
title: String,
|
||||
bottomDivider: Bool = false,
|
||||
verticalAlignment: VerticalAlignment = .firstTextBaseline,
|
||||
@ViewBuilder content: @escaping () -> Content
|
||||
@ViewBuilder content: @escaping () -> some View
|
||||
) {
|
||||
if let title = title {
|
||||
let textLabel = {
|
||||
Text(title)
|
||||
.font(.system(size: 13.0))
|
||||
.overlay(LabelOverlay())
|
||||
.eraseToAnyView()
|
||||
}
|
||||
|
||||
self.init(
|
||||
bottomDivider: bottomDivider,
|
||||
verticalAlignment: verticalAlignment,
|
||||
label: textLabel,
|
||||
content: content
|
||||
)
|
||||
return
|
||||
}
|
||||
self.init(
|
||||
bottomDivider: bottomDivider,
|
||||
verticalAlignment: verticalAlignment,
|
||||
content: content
|
||||
)
|
||||
}
|
||||
|
||||
public func bodyLimited(rightPaneWidth: CGFloat? = nil) -> some View {
|
||||
HStack(alignment: verticalAlignment) {
|
||||
if let label = label {
|
||||
label.alignmentGuide(.preferenceSectionLabel) { $0[.trailing] }
|
||||
}
|
||||
HStack {
|
||||
content
|
||||
Spacer()
|
||||
}.frame(maxWidth: rightPaneWidth)
|
||||
}
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
bodyLimited()
|
||||
HStack(alignment: verticalAlignment) {
|
||||
label
|
||||
.alignmentGuide(.settingsSectionLabel) { $0[.trailing] }
|
||||
content
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,7 +12,7 @@ extension NSUserInterfaceItemIdentifier {
|
|||
static let toolbarSegmentedControl = Self("toolbarSegmentedControl")
|
||||
}
|
||||
|
||||
final class SegmentedControlStyleViewController: NSViewController, PreferencesStyleController {
|
||||
final class SegmentedControlStyleViewController: NSViewController, SettingsStyleController {
|
||||
var segmentedControl: NSSegmentedControl! {
|
||||
get { view as? NSSegmentedControl }
|
||||
set {
|
||||
|
@ -22,13 +22,13 @@ final class SegmentedControlStyleViewController: NSViewController, PreferencesSt
|
|||
|
||||
var isKeepingWindowCentered: Bool { true }
|
||||
|
||||
weak var delegate: PreferencesStyleControllerDelegate?
|
||||
weak var delegate: SettingsStyleControllerDelegate?
|
||||
|
||||
private var preferencePanes: [PreferencePane]!
|
||||
private var panes: [SettingsPane]!
|
||||
|
||||
required init(preferencePanes: [PreferencePane]) {
|
||||
required init(panes: [SettingsPane]) {
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
self.preferencePanes = preferencePanes
|
||||
self.panes = panes
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
|
@ -37,12 +37,12 @@ final class SegmentedControlStyleViewController: NSViewController, PreferencesSt
|
|||
}
|
||||
|
||||
override func loadView() {
|
||||
view = createSegmentedControl(preferencePanes: preferencePanes)
|
||||
view = createSegmentedControl(panes: panes)
|
||||
}
|
||||
|
||||
fileprivate func createSegmentedControl(preferencePanes: [PreferencePane]) -> NSSegmentedControl {
|
||||
fileprivate func createSegmentedControl(panes: [SettingsPane]) -> NSSegmentedControl {
|
||||
let segmentedControl = NSSegmentedControl()
|
||||
segmentedControl.segmentCount = preferencePanes.count
|
||||
segmentedControl.segmentCount = panes.count
|
||||
segmentedControl.segmentStyle = .texturedSquare
|
||||
segmentedControl.target = self
|
||||
segmentedControl.action = #selector(segmentedControlAction)
|
||||
|
@ -57,8 +57,8 @@ final class SegmentedControlStyleViewController: NSViewController, PreferencesSt
|
|||
let insets = CGSize(width: 36, height: 12)
|
||||
var maxSize = CGSize.zero
|
||||
|
||||
for preferencePane in preferencePanes {
|
||||
let title = preferencePane.preferencePaneTitle
|
||||
for pane in panes {
|
||||
let title = pane.preferencePaneTitle
|
||||
let titleSize = title.size(
|
||||
withAttributes: [
|
||||
.font: NSFont.systemFont(ofSize: NSFont.systemFontSize(for: .regular)),
|
||||
|
@ -77,13 +77,13 @@ final class SegmentedControlStyleViewController: NSViewController, PreferencesSt
|
|||
)
|
||||
}()
|
||||
|
||||
let segmentBorderWidth = Double(preferencePanes.count) + 1
|
||||
let segmentWidth = segmentSize.width * Double(preferencePanes.count) + segmentBorderWidth
|
||||
let segmentBorderWidth = Double(panes.count) + 1
|
||||
let segmentWidth = segmentSize.width * Double(panes.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)
|
||||
for (index, pane) in panes.enumerated() {
|
||||
segmentedControl.setLabel(pane.preferencePaneTitle, forSegment: index)
|
||||
segmentedControl.setWidth(segmentSize.width, forSegment: index)
|
||||
if let cell = segmentedControl.cell as? NSSegmentedCell {
|
||||
cell.setTag(index, forSegment: index)
|
||||
|
@ -109,8 +109,8 @@ final class SegmentedControlStyleViewController: NSViewController, PreferencesSt
|
|||
]
|
||||
}
|
||||
|
||||
func toolbarItem(preferenceIdentifier: SSPreferences.PaneIdentifier) -> NSToolbarItem? {
|
||||
let toolbarItemIdentifier = preferenceIdentifier.toolbarItemIdentifier
|
||||
func toolbarItem(paneIdentifier: Settings.PaneIdentifier) -> NSToolbarItem? {
|
||||
let toolbarItemIdentifier = paneIdentifier.toolbarItemIdentifier
|
||||
precondition(toolbarItemIdentifier == .toolbarSegmentedControlItem)
|
||||
|
||||
// When the segments outgrow the window, we need to provide a group of
|
||||
|
@ -118,12 +118,12 @@ final class SegmentedControlStyleViewController: NSViewController, PreferencesSt
|
|||
// 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
|
||||
toolbarItemGroup.subitems = panes.enumerated().map { index, settingsPane in
|
||||
let item = NSToolbarItem(itemIdentifier: .init("segment-\(settingsPane.preferencePaneTitle)"))
|
||||
item.label = settingsPane.preferencePaneTitle
|
||||
|
||||
let menuItem = NSMenuItem(
|
||||
title: preferenceable.preferencePaneTitle,
|
||||
title: settingsPane.preferencePaneTitle,
|
||||
action: #selector(segmentedControlMenuAction),
|
||||
keyEquivalent: ""
|
||||
)
|
|
@ -0,0 +1,15 @@
|
|||
// (c) 2018 and onwards Sindre Sorhus (MIT License).
|
||||
// ====================
|
||||
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
|
||||
|
||||
/**
|
||||
The namespace for this package.
|
||||
*/
|
||||
public enum Settings {}
|
||||
|
||||
// TODO: Remove in the next major version.
|
||||
// Preserve backwards compatibility.
|
||||
public typealias Preferences = Settings
|
||||
public typealias PreferencePane = SettingsPane
|
||||
public typealias PreferencePaneConvertible = SettingsPaneConvertible
|
||||
public typealias PreferencesWindowController = SettingsWindowController
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
import Cocoa
|
||||
|
||||
public extension SSPreferences {
|
||||
public extension Settings {
|
||||
struct PaneIdentifier: Hashable, RawRepresentable, Codable {
|
||||
public let rawValue: String
|
||||
|
||||
|
@ -14,13 +14,13 @@ public extension SSPreferences {
|
|||
}
|
||||
}
|
||||
|
||||
public protocol PreferencePane: NSViewController {
|
||||
var preferencePaneIdentifier: SSPreferences.PaneIdentifier { get }
|
||||
public protocol SettingsPane: NSViewController {
|
||||
var preferencePaneIdentifier: Settings.PaneIdentifier { get }
|
||||
var preferencePaneTitle: String { get }
|
||||
var toolbarItemIcon: NSImage { get }
|
||||
}
|
||||
|
||||
public extension PreferencePane {
|
||||
public extension SettingsPane {
|
||||
var toolbarItemIdentifier: NSToolbarItem.Identifier {
|
||||
preferencePaneIdentifier.toolbarItemIdentifier
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ public extension PreferencePane {
|
|||
var toolbarItemIcon: NSImage { .empty }
|
||||
}
|
||||
|
||||
public extension SSPreferences.PaneIdentifier {
|
||||
public extension Settings.PaneIdentifier {
|
||||
init(_ rawValue: String) {
|
||||
self.init(rawValue: rawValue)
|
||||
}
|
|
@ -4,16 +4,16 @@
|
|||
|
||||
import Cocoa
|
||||
|
||||
protocol PreferencesStyleController: AnyObject {
|
||||
var delegate: PreferencesStyleControllerDelegate? { get set }
|
||||
protocol SettingsStyleController: AnyObject {
|
||||
var delegate: SettingsStyleControllerDelegate? { get set }
|
||||
var isKeepingWindowCentered: Bool { get }
|
||||
|
||||
func toolbarItemIdentifiers() -> [NSToolbarItem.Identifier]
|
||||
func toolbarItem(preferenceIdentifier: SSPreferences.PaneIdentifier) -> NSToolbarItem?
|
||||
func toolbarItem(paneIdentifier: Settings.PaneIdentifier) -> NSToolbarItem?
|
||||
func selectTab(index: Int)
|
||||
}
|
||||
|
||||
protocol PreferencesStyleControllerDelegate: AnyObject {
|
||||
func activateTab(preferenceIdentifier: SSPreferences.PaneIdentifier, animated: Bool)
|
||||
protocol SettingsStyleControllerDelegate: AnyObject {
|
||||
func activateTab(paneIdentifier: Settings.PaneIdentifier, animated: Bool)
|
||||
func activateTab(index: Int, animated: Bool)
|
||||
}
|
|
@ -4,16 +4,16 @@
|
|||
|
||||
import Cocoa
|
||||
|
||||
final class PreferencesTabViewController: NSViewController, PreferencesStyleControllerDelegate {
|
||||
final class SettingsTabViewController: NSViewController, SettingsStyleControllerDelegate {
|
||||
private var activeTab: Int?
|
||||
private var preferencePanes = [PreferencePane]()
|
||||
private var style: SSPreferences.Style?
|
||||
internal var preferencePanesCount: Int { preferencePanes.count }
|
||||
private var preferencesStyleController: PreferencesStyleController!
|
||||
private var isKeepingWindowCentered: Bool { preferencesStyleController.isKeepingWindowCentered }
|
||||
private var panes = [SettingsPane]()
|
||||
private var style: Settings.Style?
|
||||
internal var settingsPanesCount: Int { panes.count }
|
||||
private var settingsStyleController: SettingsStyleController!
|
||||
private var isKeepingWindowCentered: Bool { settingsStyleController.isKeepingWindowCentered }
|
||||
|
||||
private var toolbarItemIdentifiers: [NSToolbarItem.Identifier] {
|
||||
preferencesStyleController?.toolbarItemIdentifiers() ?? []
|
||||
settingsStyleController?.toolbarItemIdentifiers() ?? []
|
||||
}
|
||||
|
||||
var window: NSWindow! { view.window }
|
||||
|
@ -21,11 +21,11 @@ final class PreferencesTabViewController: NSViewController, PreferencesStyleCont
|
|||
var isAnimated = true
|
||||
|
||||
var activeViewController: NSViewController? {
|
||||
guard let activeTab = activeTab else {
|
||||
guard let activeTab else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return preferencePanes[activeTab]
|
||||
return panes[activeTab]
|
||||
}
|
||||
|
||||
override func loadView() {
|
||||
|
@ -33,12 +33,12 @@ final class PreferencesTabViewController: NSViewController, PreferencesStyleCont
|
|||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
}
|
||||
|
||||
func configure(preferencePanes: [PreferencePane], style: SSPreferences.Style) {
|
||||
self.preferencePanes = preferencePanes
|
||||
func configure(panes: [SettingsPane], style: Settings.Style) {
|
||||
self.panes = panes
|
||||
self.style = style
|
||||
children = preferencePanes
|
||||
children = panes
|
||||
|
||||
let toolbar = NSToolbar(identifier: "PreferencesToolbar")
|
||||
let toolbar = NSToolbar(identifier: "SettingsToolbar")
|
||||
toolbar.allowsUserCustomization = false
|
||||
toolbar.displayMode = .iconAndLabel
|
||||
toolbar.showsBaselineSeparator = true
|
||||
|
@ -46,26 +46,22 @@ final class PreferencesTabViewController: NSViewController, PreferencesStyleCont
|
|||
|
||||
switch style {
|
||||
case .segmentedControl:
|
||||
preferencesStyleController = SegmentedControlStyleViewController(preferencePanes: preferencePanes)
|
||||
settingsStyleController = SegmentedControlStyleViewController(panes: panes)
|
||||
case .toolbarItems:
|
||||
preferencesStyleController = ToolbarItemStyleViewController(
|
||||
preferencePanes: preferencePanes,
|
||||
settingsStyleController = ToolbarItemStyleViewController(
|
||||
panes: panes,
|
||||
toolbar: toolbar,
|
||||
centerToolbarItems: false
|
||||
)
|
||||
}
|
||||
preferencesStyleController.delegate = self
|
||||
settingsStyleController.delegate = self
|
||||
|
||||
// Called last so that `preferencesStyleController` can be asked for items.
|
||||
// Called last so that `settingsStyleController` can be asked for items.
|
||||
window.toolbar = toolbar
|
||||
}
|
||||
|
||||
func activateTab(preferencePane: PreferencePane, animated: Bool) {
|
||||
activateTab(preferenceIdentifier: preferencePane.preferencePaneIdentifier, animated: animated)
|
||||
}
|
||||
|
||||
func activateTab(preferenceIdentifier: SSPreferences.PaneIdentifier, animated: Bool) {
|
||||
guard let index = (preferencePanes.firstIndex { $0.preferencePaneIdentifier == preferenceIdentifier }) else {
|
||||
func activateTab(paneIdentifier: Settings.PaneIdentifier, animated: Bool) {
|
||||
guard let index = (panes.firstIndex { $0.preferencePaneIdentifier == paneIdentifier }) else {
|
||||
return activateTab(index: 0, animated: animated)
|
||||
}
|
||||
|
||||
|
@ -75,7 +71,7 @@ final class PreferencesTabViewController: NSViewController, PreferencesStyleCont
|
|||
func activateTab(index: Int, animated: Bool) {
|
||||
defer {
|
||||
activeTab = index
|
||||
preferencesStyleController.selectTab(index: index)
|
||||
settingsStyleController.selectTab(index: index)
|
||||
updateWindowTitle(tabIndex: index)
|
||||
}
|
||||
|
||||
|
@ -96,46 +92,50 @@ final class PreferencesTabViewController: NSViewController, PreferencesStyleCont
|
|||
}
|
||||
}
|
||||
|
||||
private func updateWindowTitle(tabIndex _: Int) {
|
||||
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)"
|
||||
// }
|
||||
var preferencesTitleName = NSLocalizedString("vChewing Preferences…", comment: "")
|
||||
preferencesTitleName.removeLast()
|
||||
return preferencesTitleName
|
||||
if panes.count > 1 {
|
||||
return panes[tabIndex].preferencePaneTitle
|
||||
} else {
|
||||
let settings: String
|
||||
if #available(macOS 13, *) {
|
||||
settings = Localization[.settings]
|
||||
} else {
|
||||
settings = Localization[.preferences]
|
||||
}
|
||||
|
||||
let appName = Bundle.main.appName
|
||||
return "\(appName) \(settings)"
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
/// Cached constraints that pin `childViewController` views to the content view.
|
||||
/**
|
||||
Cached constraints that pin `childViewController` views to the content view.
|
||||
*/
|
||||
private var activeChildViewConstraints = [NSLayoutConstraint]()
|
||||
|
||||
private func immediatelyDisplayTab(index: Int) {
|
||||
let toViewController = preferencePanes[index]
|
||||
let toViewController = panes[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")
|
||||
guard let 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]
|
||||
let fromViewController = panes[activeTab]
|
||||
let toViewController = panes[index]
|
||||
|
||||
// View controller animations only work on macOS 10.14 and newer.
|
||||
let options: NSViewController.TransitionOptions
|
||||
if #available(macOS 10.14, *) {
|
||||
options = animated && isAnimated ? [.slideUp] : []
|
||||
options = animated && isAnimated ? [.crossfade] : []
|
||||
} else {
|
||||
options = []
|
||||
}
|
||||
|
@ -157,9 +157,8 @@ final class PreferencesTabViewController: NSViewController, PreferencesStyleCont
|
|||
options: NSViewController.TransitionOptions = [],
|
||||
completionHandler completion: (() -> Void)? = nil
|
||||
) {
|
||||
let isAnimated =
|
||||
options
|
||||
.intersection([
|
||||
let isAnimated = options
|
||||
.isDisjoint(with: [
|
||||
.crossfade,
|
||||
.slideUp,
|
||||
.slideDown,
|
||||
|
@ -168,7 +167,6 @@ final class PreferencesTabViewController: NSViewController, PreferencesStyleCont
|
|||
.slideLeft,
|
||||
.slideRight,
|
||||
])
|
||||
.isEmpty == false
|
||||
|
||||
if isAnimated {
|
||||
NSAnimationContext.runAnimationGroup({ context in
|
||||
|
@ -195,7 +193,7 @@ final class PreferencesTabViewController: NSViewController, PreferencesStyleCont
|
|||
}
|
||||
|
||||
private func setWindowFrame(for viewController: NSViewController, animated: Bool = false) {
|
||||
guard let window = window else {
|
||||
guard let window else {
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
|
@ -216,7 +214,7 @@ final class PreferencesTabViewController: NSViewController, PreferencesStyleCont
|
|||
}
|
||||
}
|
||||
|
||||
extension PreferencesTabViewController: NSToolbarDelegate {
|
||||
extension SettingsTabViewController: NSToolbarDelegate {
|
||||
func toolbarDefaultItemIdentifiers(_: NSToolbar) -> [NSToolbarItem.Identifier] {
|
||||
toolbarItemIdentifiers
|
||||
}
|
||||
|
@ -238,7 +236,6 @@ extension PreferencesTabViewController: NSToolbarDelegate {
|
|||
return nil
|
||||
}
|
||||
|
||||
return preferencesStyleController.toolbarItem(
|
||||
preferenceIdentifier: SSPreferences.PaneIdentifier(fromToolbarItemIdentifier: itemIdentifier))
|
||||
return settingsStyleController.toolbarItem(paneIdentifier: .init(fromToolbarItemIdentifier: itemIdentifier))
|
||||
}
|
||||
}
|
|
@ -5,11 +5,11 @@
|
|||
import Cocoa
|
||||
|
||||
extension NSWindow.FrameAutosaveName {
|
||||
static let preferences: NSWindow.FrameAutosaveName = "com.sindresorhus.Preferences.FrameAutosaveName"
|
||||
static let settings: NSWindow.FrameAutosaveName = "com.sindresorhus.Preferences.FrameAutosaveName"
|
||||
}
|
||||
|
||||
public final class PreferencesWindowController: NSWindowController {
|
||||
private let tabViewController = PreferencesTabViewController()
|
||||
public final class SettingsWindowController: NSWindowController {
|
||||
private let tabViewController = SettingsTabViewController()
|
||||
|
||||
public var isAnimated: Bool {
|
||||
get { tabViewController.isAnimated }
|
||||
|
@ -25,14 +25,13 @@ public final class PreferencesWindowController: NSWindowController {
|
|||
}
|
||||
|
||||
private func updateToolbarVisibility() {
|
||||
window?.toolbar?.isVisible =
|
||||
(hidesToolbarForSingleItem == false)
|
||||
|| (tabViewController.preferencePanesCount > 1)
|
||||
window?.toolbar?.isVisible = (hidesToolbarForSingleItem == false)
|
||||
|| (tabViewController.settingsPanesCount > 1)
|
||||
}
|
||||
|
||||
public init(
|
||||
preferencePanes: [PreferencePane],
|
||||
style: SSPreferences.Style = .toolbarItems,
|
||||
preferencePanes: [SettingsPane],
|
||||
style: Settings.Style = .toolbarItems,
|
||||
animated: Bool = true,
|
||||
hidesToolbarForSingleItem: Bool = true
|
||||
) {
|
||||
|
@ -66,7 +65,7 @@ public final class PreferencesWindowController: NSWindowController {
|
|||
}
|
||||
|
||||
tabViewController.isAnimated = animated
|
||||
tabViewController.configure(preferencePanes: preferencePanes, style: style)
|
||||
tabViewController.configure(panes: preferencePanes, style: style)
|
||||
updateToolbarVisibility()
|
||||
}
|
||||
|
||||
|
@ -81,20 +80,20 @@ public final class PreferencesWindowController: NSWindowController {
|
|||
}
|
||||
|
||||
/**
|
||||
Show the preferences window and brings it to front.
|
||||
Show the settings window and brings it to front.
|
||||
|
||||
If you pass a `SSPreferences.PaneIdentifier`, the window will activate the corresponding tab.
|
||||
If you pass a `Settings.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.
|
||||
- Parameter preferencePane: Identifier of the settings 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: SSPreferences.PaneIdentifier? = nil) {
|
||||
if let preferenceIdentifier = preferenceIdentifier {
|
||||
tabViewController.activateTab(preferenceIdentifier: preferenceIdentifier, animated: false)
|
||||
public func show(preferencePane paneIdentifier: Settings.PaneIdentifier? = nil) {
|
||||
if let paneIdentifier {
|
||||
tabViewController.activateTab(paneIdentifier: paneIdentifier, animated: false)
|
||||
} else {
|
||||
tabViewController.restoreInitialTab()
|
||||
}
|
||||
|
@ -106,24 +105,25 @@ public final class PreferencesWindowController: NSWindowController {
|
|||
|
||||
private func restoreWindowPosition() {
|
||||
guard
|
||||
let window = window,
|
||||
let window,
|
||||
let screenContainingWindow = window.screen
|
||||
else {
|
||||
return
|
||||
}
|
||||
|
||||
window.setFrameOrigin(
|
||||
CGPoint(
|
||||
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)
|
||||
window.setFrameUsingName(.settings)
|
||||
window.setFrameAutosaveName(.settings)
|
||||
}
|
||||
}
|
||||
|
||||
public extension PreferencesWindowController {
|
||||
/// Returns the active pane if it responds to the given action.
|
||||
public extension SettingsWindowController {
|
||||
/**
|
||||
Returns the active pane if it responds to the given action.
|
||||
*/
|
||||
override func supplementalTarget(forAction action: Selector, sender: Any?) -> Any? {
|
||||
if let target = super.supplementalTarget(forAction: action, sender: sender) {
|
||||
return target
|
||||
|
@ -133,15 +133,11 @@ public extension PreferencesWindowController {
|
|||
return nil
|
||||
}
|
||||
|
||||
if let target = NSApp.target(forAction: action, to: activeViewController, from: sender) as? NSResponder,
|
||||
target.responds(to: action)
|
||||
{
|
||||
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)
|
||||
{
|
||||
if let target = activeViewController.supplementalTarget(forAction: action, sender: sender) as? NSResponder, target.responds(to: action) {
|
||||
return target
|
||||
}
|
||||
|
||||
|
@ -150,20 +146,18 @@ public extension PreferencesWindowController {
|
|||
}
|
||||
|
||||
@available(macOS 10.15, *)
|
||||
public extension PreferencesWindowController {
|
||||
public extension SettingsWindowController {
|
||||
/**
|
||||
Create a preferences window from only SwiftUI-based preference panes.
|
||||
Create a settings window from only SwiftUI-based settings panes.
|
||||
*/
|
||||
convenience init(
|
||||
panes: [PreferencePaneConvertible],
|
||||
style: SSPreferences.Style = .toolbarItems,
|
||||
panes: [SettingsPaneConvertible],
|
||||
style: Settings.Style = .toolbarItems,
|
||||
animated: Bool = true,
|
||||
hidesToolbarForSingleItem: Bool = true
|
||||
) {
|
||||
let preferencePanes = panes.map { $0.asPreferencePane() }
|
||||
|
||||
self.init(
|
||||
preferencePanes: preferencePanes,
|
||||
preferencePanes: panes.map { $0.asPreferencePane() },
|
||||
style: style,
|
||||
animated: animated,
|
||||
hidesToolbarForSingleItem: hidesToolbarForSingleItem
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
import Cocoa
|
||||
|
||||
public extension SSPreferences {
|
||||
public extension Settings {
|
||||
enum Style {
|
||||
case toolbarItems
|
||||
case segmentedControl
|
|
@ -4,15 +4,15 @@
|
|||
|
||||
import Cocoa
|
||||
|
||||
final class ToolbarItemStyleViewController: NSObject, PreferencesStyleController {
|
||||
final class ToolbarItemStyleViewController: NSObject, SettingsStyleController {
|
||||
let toolbar: NSToolbar
|
||||
let centerToolbarItems: Bool
|
||||
let preferencePanes: [PreferencePane]
|
||||
let panes: [SettingsPane]
|
||||
var isKeepingWindowCentered: Bool { centerToolbarItems }
|
||||
weak var delegate: PreferencesStyleControllerDelegate?
|
||||
weak var delegate: SettingsStyleControllerDelegate?
|
||||
|
||||
init(preferencePanes: [PreferencePane], toolbar: NSToolbar, centerToolbarItems: Bool) {
|
||||
self.preferencePanes = preferencePanes
|
||||
init(panes: [SettingsPane], toolbar: NSToolbar, centerToolbarItems: Bool) {
|
||||
self.panes = panes
|
||||
self.toolbar = toolbar
|
||||
self.centerToolbarItems = centerToolbarItems
|
||||
}
|
||||
|
@ -24,8 +24,8 @@ final class ToolbarItemStyleViewController: NSObject, PreferencesStyleController
|
|||
toolbarItemIdentifiers.append(.flexibleSpace)
|
||||
}
|
||||
|
||||
for preferencePane in preferencePanes {
|
||||
toolbarItemIdentifiers.append(preferencePane.toolbarItemIdentifier)
|
||||
for pane in panes {
|
||||
toolbarItemIdentifiers.append(pane.toolbarItemIdentifier)
|
||||
}
|
||||
|
||||
if centerToolbarItems {
|
||||
|
@ -35,14 +35,14 @@ final class ToolbarItemStyleViewController: NSObject, PreferencesStyleController
|
|||
return toolbarItemIdentifiers
|
||||
}
|
||||
|
||||
func toolbarItem(preferenceIdentifier: SSPreferences.PaneIdentifier) -> NSToolbarItem? {
|
||||
guard let preference = (preferencePanes.first { $0.preferencePaneIdentifier == preferenceIdentifier }) else {
|
||||
func toolbarItem(paneIdentifier: Settings.PaneIdentifier) -> NSToolbarItem? {
|
||||
guard let pane = (panes.first { $0.preferencePaneIdentifier == paneIdentifier }) else {
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
let toolbarItem = NSToolbarItem(itemIdentifier: preferenceIdentifier.toolbarItemIdentifier)
|
||||
toolbarItem.label = preference.preferencePaneTitle
|
||||
toolbarItem.image = preference.toolbarItemIcon
|
||||
let toolbarItem = NSToolbarItem(itemIdentifier: paneIdentifier.toolbarItemIdentifier)
|
||||
toolbarItem.label = pane.preferencePaneTitle
|
||||
toolbarItem.image = pane.toolbarItemIcon
|
||||
toolbarItem.target = self
|
||||
toolbarItem.action = #selector(toolbarItemSelected)
|
||||
return toolbarItem
|
||||
|
@ -50,12 +50,12 @@ final class ToolbarItemStyleViewController: NSObject, PreferencesStyleController
|
|||
|
||||
@IBAction private func toolbarItemSelected(_ toolbarItem: NSToolbarItem) {
|
||||
delegate?.activateTab(
|
||||
preferenceIdentifier: SSPreferences.PaneIdentifier(fromToolbarItemIdentifier: toolbarItem.itemIdentifier),
|
||||
paneIdentifier: .init(fromToolbarItemIdentifier: toolbarItem.itemIdentifier),
|
||||
animated: true
|
||||
)
|
||||
}
|
||||
|
||||
func selectTab(index: Int) {
|
||||
toolbar.selectedItemIdentifier = preferencePanes[index].toolbarItemIdentifier
|
||||
toolbar.selectedItemIdentifier = panes[index].toolbarItemIdentifier
|
||||
}
|
||||
}
|
|
@ -2,7 +2,6 @@
|
|||
// ====================
|
||||
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
|
||||
|
||||
import Cocoa
|
||||
import SwiftUI
|
||||
|
||||
extension NSImage {
|
||||
|
@ -12,21 +11,13 @@ extension NSImage {
|
|||
extension NSView {
|
||||
@discardableResult
|
||||
func constrainToSuperviewBounds() -> [NSLayoutConstraint] {
|
||||
guard let superview = superview else {
|
||||
guard let 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]
|
||||
))
|
||||
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)
|
||||
|
||||
|
@ -35,9 +26,10 @@ extension NSView {
|
|||
}
|
||||
|
||||
extension NSEvent {
|
||||
/// Events triggered by user interaction.
|
||||
static let userInteractionEvents: [NSEvent.EventType] = {
|
||||
var events: [NSEvent.EventType] = [
|
||||
/**
|
||||
Events triggered by user interaction.
|
||||
*/
|
||||
static let userInteractionEvents: [EventType] = [
|
||||
.leftMouseDown,
|
||||
.leftMouseUp,
|
||||
.rightMouseDown,
|
||||
|
@ -58,19 +50,15 @@ extension NSEvent {
|
|||
.beginGesture,
|
||||
.endGesture,
|
||||
.smartMagnify,
|
||||
.pressure,
|
||||
.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) }
|
||||
/**
|
||||
Whether the event was triggered by user interaction.
|
||||
*/
|
||||
var isUserInteraction: Bool { Self.userInteractionEvents.contains(type) }
|
||||
}
|
||||
|
||||
extension Bundle {
|
||||
|
@ -87,10 +75,12 @@ extension Bundle {
|
|||
}
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
/**
|
||||
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) {
|
Loading…
Reference in New Issue