├── .cocoadocs.yml ├── .gitignore ├── .travis.yml ├── KPCTabsControl.podspec ├── KPCTabsControl.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ ├── KPCTabsControl.xcscheme │ └── KPCTabsControlDemo.xcscheme ├── KPCTabsControl ├── ChromeStyle.swift ├── ChromeTheme.swift ├── CollectionType+TabsControl.swift ├── Constants.swift ├── DefaultStyle.swift ├── DefaultTheme.swift ├── Error.swift ├── Helpers.swift ├── Info.plist ├── MessageInterceptor.swift ├── NSButton+TabsControl.swift ├── NSImage+TabsControl.swift ├── Protocols.swift ├── Resources ├── SafariStyle.swift ├── SafariTheme.swift ├── SequenceType+TabsControl.swift ├── Style.swift ├── TabButton.swift ├── TabButtonCell.swift ├── TabsControl.swift ├── TabsControlCell.swift └── Theme.swift ├── KPCTabsControlDemo ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── ArrowTemplate.imageset │ │ ├── BoundariesTemplate.pdf │ │ └── Contents.json │ ├── Contents.json │ ├── Oval.imageset │ │ ├── Contents.json │ │ ├── Oval copy 2.png │ │ ├── Oval copy.png │ │ └── Oval.png │ ├── Polygon.imageset │ │ ├── Contents.json │ │ ├── Polygon copy 2.png │ │ ├── Polygon copy.png │ │ └── Polygon.png │ ├── Rectangle.imageset │ │ ├── Contents.json │ │ ├── Rectangle copy 2.png │ │ ├── Rectangle copy.png │ │ └── Rectangle.png │ ├── Spiral.imageset │ │ ├── Contents.json │ │ ├── Spiral copy 2.png │ │ ├── Spiral copy.png │ │ └── Spiral.png │ ├── Star.imageset │ │ ├── Contents.json │ │ ├── Star copy 2.png │ │ ├── Star copy.png │ │ └── Star.png │ └── Triangle.imageset │ │ ├── Contents.json │ │ ├── Triangle copy 2.png │ │ ├── Triangle copy.png │ │ └── Triangle.png ├── Base.lproj │ └── Main.storyboard ├── ColoredView.swift ├── Info.plist ├── MainMenu.xib ├── PaneViewController.swift └── ViewController.swift ├── KPCTabsControlTests ├── Info.plist └── KPCTabsControlTests.swift ├── LICENSE ├── Package.swift ├── README.md ├── Resources ├── KPCPullDownTemplate.pdf ├── KPCTabLeftTemplate.pdf ├── KPCTabPlusTemplate.pdf └── KPCTabRightTemplate.pdf ├── assets ├── 1kpcProComponents.png ├── KPCTabsControl2Demo.gif ├── KPCTabsControl2Screenshot.png ├── KPCTabsControlAuxiliaryIconMovie.gif └── KPCTabsControlScreenshot1.png └── docs ├── Classes.html ├── Classes ├── TabButton.html └── TabsControl.html ├── Enums.html ├── Enums ├── TabPosition.html ├── TabSelectionState.html └── TabWidth.html ├── Extensions.html ├── Extensions ├── NSRect.html └── Offset.html ├── Functions.html ├── Global Variables.html ├── Protocols.html ├── Protocols ├── Style.html ├── TabButtonTheme.html ├── TabsControlDataSource.html ├── TabsControlDelegate.html ├── TabsControlTheme.html ├── Theme.html └── ThemedStyle.html ├── Structs.html ├── Structs ├── BorderMask.html ├── ChromeStyle.html ├── ChromeTheme.html ├── DefaultStyle.html ├── DefaultTheme.html ├── SafariStyle.html └── SafariTheme.html ├── Typealiases.html ├── badge.svg ├── css ├── highlight.css └── jazzy.css ├── docsets ├── KPCTabsControl.docset │ └── Contents │ │ ├── Info.plist │ │ └── Resources │ │ ├── Documents │ │ ├── Classes.html │ │ ├── Classes │ │ │ ├── TabButton.html │ │ │ └── TabsControl.html │ │ ├── Enums.html │ │ ├── Enums │ │ │ ├── TabPosition.html │ │ │ ├── TabSelectionState.html │ │ │ └── TabWidth.html │ │ ├── Extensions.html │ │ ├── Extensions │ │ │ ├── NSRect.html │ │ │ └── Offset.html │ │ ├── Functions.html │ │ ├── Global Variables.html │ │ ├── Protocols.html │ │ ├── Protocols │ │ │ ├── Style.html │ │ │ ├── TabButtonTheme.html │ │ │ ├── TabsControlDataSource.html │ │ │ ├── TabsControlDelegate.html │ │ │ ├── TabsControlTheme.html │ │ │ ├── Theme.html │ │ │ └── ThemedStyle.html │ │ ├── Structs.html │ │ ├── Structs │ │ │ ├── BorderMask.html │ │ │ ├── ChromeStyle.html │ │ │ ├── ChromeTheme.html │ │ │ ├── DefaultStyle.html │ │ │ ├── DefaultTheme.html │ │ │ ├── SafariStyle.html │ │ │ └── SafariTheme.html │ │ ├── Typealiases.html │ │ ├── css │ │ │ ├── highlight.css │ │ │ └── jazzy.css │ │ ├── img │ │ │ ├── carat.png │ │ │ ├── dash.png │ │ │ └── gh.png │ │ ├── index.html │ │ ├── js │ │ │ ├── jazzy.js │ │ │ └── jquery.min.js │ │ ├── search.json │ │ └── undocumented.json │ │ └── docSet.dsidx ├── KPCTabsControl.tgz └── KPCTabsControl.xml ├── img ├── carat.png ├── dash.png └── gh.png ├── index.html ├── js ├── jazzy.js └── jquery.min.js ├── search.json └── undocumented.json /.cocoadocs.yml: -------------------------------------------------------------------------------- 1 | highlight-font: '"GT Walsheim", "gt_walsheim_regular", "Avant Garde Gothic ITCW01Dm", "Avant Garde", "Helvetica Neue", "Arial"' 2 | 3 | body: '"Helvetica Neue", "Arial", san-serif' 4 | code: '"Monaco", "Menlo", "Consolas", "Courier New", monospace' 5 | 6 | highlight-color: '#ED0015' 7 | highlight-dark-color: '#A90010' 8 | 9 | darker-color: '#C6B7B2' 10 | darker-dark-color: '#A8A8A8' 11 | 12 | background-color: '#F2F2F2' 13 | alt-link-color: '#B7233F' 14 | warning-color: '#B80E3D' 15 | 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | *.xccheckout 14 | *.moved-aside 15 | DerivedData 16 | *.hmap 17 | *.ipa 18 | *.xcuserstate 19 | 20 | # CocoaPods 21 | # 22 | # We recommend against adding the Pods directory to your .gitignore. However 23 | # you should judge for yourself, the pros and cons are mentioned at: 24 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 25 | # 26 | # Pods/ 27 | .DS_Store 28 | /.build 29 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: swift 2 | xcode_project: KPCTabsControl.xcodeproj 3 | xcode_scheme: KPCTabsControl 4 | osx_image: xcode10.2 5 | -------------------------------------------------------------------------------- /KPCTabsControl.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "KPCTabsControl" 3 | s.version = "5.0.0" 4 | s.summary = "A multi-tabs control with enhanced capabilities, and custom styles." 5 | s.homepage = "https://github.com/onekiloparsec/KPCTabsControl.git" 6 | s.license = { :type => 'MIT', :file => 'LICENSE' } 7 | s.author = { "Cédric Foellmi" => "cedric@onekilopars.ec" } 8 | s.source = { :git => "https://github.com/onekiloparsec/KPCTabsControl.git", :tag => "#{s.version}" } 9 | s.source_files = 'KPCTabsControl/*.{swift}' 10 | s.platform = :osx, '10.14' 11 | s.framework = 'QuartzCore', 'AppKit' 12 | s.resources = 'Resources/*.pdf' 13 | s.swift_version = '5' 14 | end 15 | -------------------------------------------------------------------------------- /KPCTabsControl.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /KPCTabsControl.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /KPCTabsControl.xcodeproj/xcshareddata/xcschemes/KPCTabsControl.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 51 | 52 | 53 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 76 | 77 | 83 | 84 | 85 | 86 | 92 | 93 | 99 | 100 | 101 | 102 | 104 | 105 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /KPCTabsControl.xcodeproj/xcshareddata/xcschemes/KPCTabsControlDemo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /KPCTabsControl/ChromeStyle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChromeStyle.swift 3 | // KPCTabsControl 4 | // 5 | // Created by Christian Tietze on 13/08/16. 6 | // Licensed under the MIT License (see LICENSE file) 7 | // 8 | 9 | import Cocoa 10 | 11 | public struct ChromeStyle: ThemedStyle { 12 | public let theme: Theme 13 | public let tabButtonWidth: TabWidth 14 | public let tabsControlRecommendedHeight: CGFloat = 34.0 15 | 16 | public init(theme: Theme = ChromeTheme(), tabButtonWidth: TabWidth = .flexible(min: 80, max: 180)) { 17 | self.theme = theme 18 | self.tabButtonWidth = tabButtonWidth 19 | } 20 | 21 | public func iconFrames(tabRect rect: NSRect) -> IconFrames { 22 | 23 | let paddedHeight = PaddedHeight.fromFrame(rect) 24 | let topPadding = paddedHeight.topPadding 25 | let iconHeight = paddedHeight.iconHeight 26 | let x = rect.width / 2.0 - iconHeight / 2.0 27 | 28 | // Left border is angled at 45˚, so it grows proportionally wider 29 | let iconXOffset = paddedHeight.value / 2 30 | 31 | return ( 32 | NSRect(x: iconXOffset, y: topPadding, width: iconHeight, height: iconHeight), 33 | NSRect(x: x, y: topPadding, width: iconHeight, height: iconHeight) 34 | ) 35 | } 36 | 37 | public func titleRect(title: NSAttributedString, inBounds rect: NSRect, showingIcon: Bool) -> NSRect { 38 | let paddedHeight = PaddedHeight.fromFrame(rect) 39 | // Left border is angled at 45˚, so it grows proportionally wider 40 | let iconOffset = showingIcon ? paddedHeight.iconHeight + 4 : 0.0 41 | let xOffset = paddedHeight.value / 2 + 0.5 42 | let yOffset = paddedHeight.topPadding - 2 43 | 44 | return rect 45 | .offsetBy(dx: xOffset + iconOffset, dy: yOffset) 46 | .shrinkBy(dx: 2 * xOffset + iconOffset, dy: yOffset + 2) 47 | } 48 | 49 | // TODO: Not sure what to decide about the visibility here? Same for PaddedHeight 50 | fileprivate enum Defaults { 51 | static let alignment = NSTextAlignment.left 52 | } 53 | 54 | public func titleEditorSettings() -> TitleEditorSettings { 55 | return (textColor: self.theme.tabButtonTheme.titleColor, 56 | font: self.theme.tabButtonTheme.titleFont, 57 | alignment: Defaults.alignment) 58 | } 59 | 60 | public func attributedTitle(content: String, selectionState: TabSelectionState) -> NSAttributedString { 61 | let paragraphStyle = NSMutableParagraphStyle() 62 | paragraphStyle.alignment = Defaults.alignment 63 | 64 | let activeTheme = self.theme.tabButtonTheme(fromSelectionState: selectionState) 65 | 66 | let attributes = [NSAttributedString.Key.paragraphStyle: paragraphStyle, 67 | NSAttributedString.Key.font: activeTheme.titleFont, 68 | NSAttributedString.Key.foregroundColor: activeTheme.titleColor] 69 | 70 | return NSAttributedString(string: content, attributes: attributes) 71 | } 72 | 73 | fileprivate enum PaddedHeight { 74 | 75 | case unpadded(CGFloat, originalHeight: CGFloat) 76 | case padded(CGFloat, originalHeight: CGFloat) 77 | 78 | static func fromFrame(_ frame: NSRect) -> PaddedHeight { 79 | 80 | let paddedHeight = floor(frame.height * 0.7) 81 | 82 | // Chrome tabs have lots of whitespace to the top; if that's 83 | // too cramped, don't try to achieve that effect. 84 | guard paddedHeight > 20 else { 85 | return .unpadded(frame.height - 2, originalHeight: frame.height) 86 | } 87 | 88 | return .padded(paddedHeight, originalHeight: frame.height) 89 | } 90 | 91 | var value: CGFloat { 92 | switch self { 93 | case .unpadded(let height, _): return height 94 | case .padded(let height, _): return height 95 | } 96 | } 97 | 98 | var bottomPadding: CGFloat { 99 | return CGFloat(4) 100 | } 101 | 102 | var topPadding: CGFloat { 103 | switch self { 104 | case .unpadded: return bottomPadding + 2 105 | case let .padded(height, originalHeight): 106 | /// Visual distance to top TabsControl border. 107 | let tabMargin = originalHeight - height 108 | return tabMargin + bottomPadding 109 | } 110 | } 111 | 112 | var originalHeight: CGFloat { 113 | switch self { 114 | case .unpadded(_, let originalHeight): return originalHeight 115 | case .padded(_, let originalHeight): return originalHeight 116 | } 117 | } 118 | 119 | var iconHeight: CGFloat { 120 | return self.originalHeight - self.topPadding - self.bottomPadding 121 | } 122 | } 123 | 124 | public func drawTabButtonBezel(frame: NSRect, position: TabPosition, isSelected: Bool) { 125 | 126 | let height: CGFloat = PaddedHeight.fromFrame(frame).value 127 | let xOffset = height / 2.0 128 | let curve = CGFloat(4) 129 | 130 | let lowerLeft = frame.origin + Offset(y: frame.height) 131 | let upperLeft = lowerLeft + Offset(x: xOffset, y: -height - 0.5) 132 | let lowerRight = lowerLeft + Offset(x: frame.width - 1) 133 | let upperRight = lowerRight + Offset(x: -xOffset, y: -height - 0.5) 134 | 135 | // Let's build tab path 136 | let path = NSBezierPath() 137 | 138 | // Lower left point. 139 | path.move(to: lowerLeft) 140 | 141 | // Before aligning to the top, make a slight curve. 142 | let leftRisingFromPoint = lowerLeft + Offset(x: curve) 143 | let leftRisingToPoint = lowerLeft + Offset(x: curve, y: -curve) 144 | path.appendArc(from: leftRisingFromPoint, to: leftRisingToPoint, radius: curve) 145 | 146 | // Before reaching the top, stop at the point of the coming curve 147 | let leftToppingPoint = upperLeft + Offset(x: -curve, y: curve) 148 | path.line(to: leftToppingPoint) 149 | 150 | // Curve to the top! 151 | let leftToppingFromPoint = upperLeft + Offset(x: -curve) 152 | path.appendArc(from: leftToppingFromPoint, to: upperLeft, radius: curve) 153 | 154 | // Line to the upper right 155 | path.line(to: upperRight) 156 | 157 | // Before aligning to fall down, make a slight curve 158 | let rightFallingFromPoint = upperRight + Offset(x: curve) 159 | let rightFallingToPoint = upperRight + Offset(x: curve, y: curve) 160 | path.appendArc(from: rightFallingFromPoint, to: rightFallingToPoint, radius: curve) 161 | 162 | // Before reaching the bottom right, stop at the point of the coming curve 163 | let rightBottomingPoint = lowerRight + Offset(x: -curve, y: -curve) 164 | path.line(to: rightBottomingPoint) 165 | 166 | // Curve to the bottom 167 | let rightBottomingFromPoint = lowerRight + Offset(x: -curve) 168 | path.appendArc(from: rightBottomingFromPoint, to: lowerRight, radius: curve) 169 | 170 | path.lineWidth = 1 171 | 172 | let activeTheme = isSelected ? self.theme.selectedTabButtonTheme : self.theme.tabButtonTheme 173 | activeTheme.backgroundColor.setFill() 174 | path.fill() 175 | 176 | activeTheme.borderColor.setStroke() 177 | path.stroke() 178 | 179 | if !isSelected { 180 | self.drawBottomBorder(frame: frame) 181 | } 182 | } 183 | 184 | public func drawTabsControlBezel(frame: NSRect) { 185 | self.theme.tabsControlTheme.backgroundColor.setFill() 186 | frame.fill() 187 | self.drawBottomBorder(frame: frame) 188 | } 189 | 190 | fileprivate func drawBottomBorder(frame: NSRect) { 191 | let bottomBorder = NSRect(origin: frame.origin + Offset(y: frame.height - 1), 192 | size: NSSize(width: frame.width, height: 1)) 193 | 194 | self.theme.tabsControlTheme.borderColor.setFill() 195 | bottomBorder.fill() 196 | } 197 | 198 | public func tabButtonOffset(position: TabPosition) -> Offset { 199 | switch position { 200 | case .first: return NSPoint() 201 | case .middle, .last: return NSPoint(x: -10, y: 0) 202 | } 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /KPCTabsControl/ChromeTheme.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChromeTheme.swift 3 | // KPCTabsControl 4 | // 5 | // Created by Cédric Foellmi on 14/08/16. 6 | // Licensed under the MIT License (see LICENSE file) 7 | // 8 | 9 | import Cocoa 10 | 11 | public struct ChromeTheme: Theme { 12 | 13 | public init() { } 14 | 15 | public let tabButtonTheme: TabButtonTheme = DefaultTabButtonTheme() 16 | public let selectedTabButtonTheme: TabButtonTheme = SelectedTabButtonTheme(base: DefaultTabButtonTheme()) 17 | public let unselectableTabButtonTheme: TabButtonTheme = UnselectableTabButtonTheme(base: DefaultTabButtonTheme()) 18 | public let tabsControlTheme: TabsControlTheme = DefaultTabsControlTheme() 19 | 20 | fileprivate static var sharedBorderColor: NSColor { return NSColor(calibratedWhite: 152/256.0, alpha: 1.0) } 21 | fileprivate static var sharedBackgroundColor: NSColor { return NSColor(calibratedWhite: 216/256.0, alpha: 1.0) } 22 | 23 | fileprivate struct DefaultTabButtonTheme: KPCTabsControl.TabButtonTheme { 24 | 25 | var backgroundColor: NSColor { return ChromeTheme.sharedBackgroundColor } 26 | var borderColor: NSColor { return ChromeTheme.sharedBorderColor } 27 | var titleColor: NSColor { return NSColor.controlTextColor } 28 | var titleFont: NSFont { return NSFont.systemFont(ofSize: 14) } 29 | } 30 | 31 | fileprivate struct SelectedTabButtonTheme: KPCTabsControl.TabButtonTheme { 32 | 33 | let base: DefaultTabButtonTheme 34 | 35 | var backgroundColor: NSColor { return NSColor(calibratedWhite: 245/256.0, alpha: 1.0) } 36 | var borderColor: NSColor { return base.borderColor } 37 | var titleColor: NSColor { return base.titleColor } 38 | var titleFont: NSFont { return base.titleFont } 39 | } 40 | 41 | fileprivate struct UnselectableTabButtonTheme: KPCTabsControl.TabButtonTheme { 42 | let base: DefaultTabButtonTheme 43 | 44 | var backgroundColor: NSColor { return base.backgroundColor } 45 | var borderColor: NSColor { return base.borderColor } 46 | var titleColor: NSColor { return NSColor.lightGray } 47 | var titleFont: NSFont { return base.titleFont } 48 | } 49 | 50 | fileprivate struct DefaultTabsControlTheme: KPCTabsControl.TabsControlTheme { 51 | 52 | var borderColor: NSColor { return ChromeTheme.sharedBorderColor } 53 | var backgroundColor: NSColor { return ChromeTheme.sharedBackgroundColor } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /KPCTabsControl/CollectionType+TabsControl.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CollectionType+TabsControl.swift 3 | // KPCTabsControl 4 | // 5 | // Created by Christian Tietze on 15/08/16. 6 | // Licensed under the MIT License (see LICENSE file) 7 | // 8 | 9 | import Foundation 10 | 11 | extension Collection { 12 | internal subscript (safe index: Self.Index) -> Self.Iterator.Element? { 13 | return index < endIndex ? self[index] : nil 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /KPCTabsControl/Constants.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Constants.swift 3 | // KPCTabsControl 4 | // 5 | // Created by Cédric Foellmi on 15/07/16. 6 | // Licensed under the MIT License (see LICENSE file) 7 | // 8 | 9 | import Foundation 10 | 11 | /// The name of the notification upon the selection of a new tab. 12 | public let TabsControlSelectionDidChangeNotification = "TabsControlSelectionDidChangeNotification" 13 | 14 | /** 15 | The position of a tab button inside the control. Used in the Style. 16 | 17 | - first: The most left-hand tab button. 18 | - middle: Any middle tab button between first and last. 19 | - last: The most right-hand tab button 20 | */ 21 | public enum TabPosition { 22 | case first 23 | case middle 24 | case last 25 | 26 | /** 27 | Convenience function to get TabPosition from a given index compared to a given total count. 28 | 29 | - parameter idx: The index for which one wants the position 30 | - parameter totalCount: The total count of tabs 31 | 32 | - returns: The tab position 33 | */ 34 | static func fromIndex(_ idx: Int, totalCount: Int) -> TabPosition { 35 | switch idx { 36 | case 0: return .first 37 | case totalCount-1: return .last 38 | default: return .middle 39 | } 40 | } 41 | } 42 | 43 | /** 44 | The tab width modes. 45 | 46 | - Full: The tab widths will be equally distributed accross the tabs control width. 47 | - Flexible: The tab widths will be adjusted between min and max, depending on the tabs control width. 48 | */ 49 | public enum TabWidth { 50 | case full 51 | case flexible(min: CGFloat, max: CGFloat) 52 | } 53 | 54 | /** 55 | The tab selection state. 56 | 57 | - Normal: The tab is not selected. 58 | - Selected: The tab is selected. 59 | - Unselectable: The tab is not selectable. 60 | */ 61 | public enum TabSelectionState { 62 | case normal 63 | case selected 64 | case unselectable 65 | } 66 | 67 | /** 68 | * Border mask option set, used in tab buttons and the tabs control itself. 69 | */ 70 | public struct BorderMask: OptionSet { 71 | public let rawValue: Int 72 | 73 | public init(rawValue: Int) { 74 | self.rawValue = rawValue 75 | } 76 | 77 | public static func all() -> BorderMask { 78 | return BorderMask.top.union(BorderMask.left).union(BorderMask.right).union(BorderMask.bottom) 79 | } 80 | 81 | public static let top = BorderMask(rawValue: 1 << 0) 82 | public static let left = BorderMask(rawValue: 1 << 1) 83 | public static let right = BorderMask(rawValue: 1 << 2) 84 | public static let bottom = BorderMask(rawValue: 1 << 3) 85 | } 86 | -------------------------------------------------------------------------------- /KPCTabsControl/DefaultStyle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DefaultStyle.swift 3 | // KPCTabsControl 4 | // 5 | // Created by Christian Tietze on 10/08/16. 6 | // Licensed under the MIT License (see LICENSE file) 7 | // 8 | 9 | import Cocoa 10 | 11 | public enum TitleDefaults { 12 | static let alignment = NSTextAlignment.center 13 | static let lineBreakMode = NSLineBreakMode.byTruncatingMiddle 14 | } 15 | 16 | /// Default implementation of Themed Style 17 | 18 | extension ThemedStyle { 19 | 20 | // MARK: - Tab Buttons 21 | 22 | public func tabButtonOffset(position: TabPosition) -> Offset { 23 | return NSPoint() 24 | } 25 | 26 | public func tabButtonBorderMask(_ position: TabPosition) -> BorderMask? { 27 | return BorderMask.all() 28 | } 29 | 30 | // MARK: - Tab Button Titles 31 | 32 | public func iconFrames(tabRect rect: NSRect) -> IconFrames { 33 | 34 | let verticalPadding: CGFloat = 4.0 35 | let paddedHeight = rect.height - 2*verticalPadding 36 | let x = rect.width / 2.0 - paddedHeight / 2.0 37 | 38 | return (NSRect(x: 10.0, y: verticalPadding, width: paddedHeight, height: paddedHeight), 39 | NSRect(x: x, y: verticalPadding, width: paddedHeight, height: paddedHeight)) 40 | } 41 | 42 | public func titleRect(title: NSAttributedString, inBounds rect: NSRect, showingIcon: Bool) -> NSRect { 43 | 44 | let titleSize = title.size() 45 | let fullWidthRect = NSRect(x: rect.minX, 46 | y: rect.midY - titleSize.height/2.0 - 0.5, 47 | width: rect.width, 48 | height: titleSize.height) 49 | 50 | return self.paddedRectForIcon(fullWidthRect, showingIcon: showingIcon) 51 | } 52 | 53 | fileprivate func paddedRectForIcon(_ rect: NSRect, showingIcon: Bool) -> NSRect { 54 | 55 | guard showingIcon else { 56 | return rect 57 | } 58 | 59 | let iconRect = self.iconFrames(tabRect: rect).iconFrame 60 | let pad = iconRect.maxX+titleMargin 61 | return rect.offsetBy(dx: pad, dy: 0.0).shrinkBy(dx: pad, dy: 0.0) 62 | } 63 | 64 | public func titleEditorSettings() -> TitleEditorSettings { 65 | return (textColor: NSColor(calibratedWhite: 1.0/6, alpha: 1.0), 66 | font: self.theme.tabButtonTheme.titleFont, 67 | alignment: TitleDefaults.alignment) 68 | } 69 | 70 | public func attributedTitle(content: String, selectionState: TabSelectionState) -> NSAttributedString { 71 | 72 | let activeTheme = self.theme.tabButtonTheme(fromSelectionState: selectionState) 73 | 74 | let paragraphStyle = NSMutableParagraphStyle() 75 | paragraphStyle.alignment = TitleDefaults.alignment 76 | paragraphStyle.lineBreakMode = TitleDefaults.lineBreakMode 77 | 78 | let attributes = [NSAttributedString.Key.foregroundColor: activeTheme.titleColor, 79 | NSAttributedString.Key.font: activeTheme.titleFont, 80 | NSAttributedString.Key.paragraphStyle: paragraphStyle] 81 | 82 | return NSAttributedString(string: content, attributes: attributes) 83 | } 84 | 85 | // MARK: - Tabs Control 86 | 87 | public func tabsControlBorderMask() -> BorderMask? { 88 | return BorderMask.top.union(BorderMask.bottom) 89 | } 90 | 91 | // MARK: - Drawing 92 | 93 | public func drawTabsControlBezel(frame: NSRect) { 94 | self.theme.tabsControlTheme.backgroundColor.setFill() 95 | frame.fill() 96 | 97 | let borderDrawing = BorderDrawing.fromMask(frame, borderMask: self.tabsControlBorderMask()) 98 | self.drawBorder(borderDrawing, color: self.theme.tabsControlTheme.borderColor) 99 | } 100 | 101 | public func drawTabButtonBezel(frame: NSRect, position: TabPosition, isSelected: Bool) { 102 | 103 | let activeTheme = isSelected ? self.theme.selectedTabButtonTheme : self.theme.tabButtonTheme 104 | activeTheme.backgroundColor.setFill() 105 | frame.fill() 106 | 107 | let borderDrawing = BorderDrawing.fromMask(frame, borderMask: self.tabButtonBorderMask(position)) 108 | self.drawBorder(borderDrawing, color: activeTheme.borderColor) 109 | } 110 | 111 | fileprivate func drawBorder(_ border: BorderDrawing, color: NSColor) { 112 | 113 | guard case let .draw(borderRects: borderRects, rectCount: _) = border 114 | else { return } 115 | 116 | color.setFill() 117 | color.setStroke() 118 | borderRects.fill() 119 | } 120 | } 121 | 122 | // MARK: - 123 | 124 | private enum BorderDrawing { 125 | case empty 126 | case draw(borderRects: [NSRect], rectCount: Int) 127 | 128 | fileprivate static func fromMask(_ sourceRect: NSRect, borderMask: BorderMask?) -> BorderDrawing { 129 | 130 | guard let mask = borderMask else { return .empty } 131 | 132 | var outputCount: Int = 0 133 | var remainderRect = NSRect.zero 134 | var borderRects: [NSRect] = [NSRect.zero, NSRect.zero, NSRect.zero, NSRect.zero] 135 | 136 | if mask.contains(.top) { 137 | NSDivideRect(sourceRect, &borderRects[outputCount], &remainderRect, 0.5, .minY) 138 | outputCount += 1 139 | } 140 | if mask.contains(.left) { 141 | NSDivideRect(sourceRect, &borderRects[outputCount], &remainderRect, 0.5, .minX) 142 | outputCount += 1 143 | } 144 | if mask.contains(.right) { 145 | NSDivideRect(sourceRect, &borderRects[outputCount], &remainderRect, 0.5, .maxX) 146 | outputCount += 1 147 | } 148 | if mask.contains(.bottom) { 149 | NSDivideRect(sourceRect, &borderRects[outputCount], &remainderRect, 0.5, .maxY) 150 | outputCount += 1 151 | } 152 | 153 | guard outputCount > 0 else { return .empty } 154 | 155 | return .draw(borderRects: borderRects, rectCount: outputCount) 156 | } 157 | } 158 | 159 | // MARK: - 160 | 161 | /** 162 | * The default TabsControl style. Used with the DefaultTheme, it provides an experience similar to Apple's Numbers.app. 163 | */ 164 | public struct DefaultStyle: ThemedStyle { 165 | public let theme: Theme 166 | public let tabButtonWidth: TabWidth 167 | public let tabsControlRecommendedHeight: CGFloat = 24.0 168 | 169 | public init(theme: Theme = DefaultTheme(), tabButtonWidth: TabWidth = .flexible(min: 50, max: 150)) { 170 | self.theme = theme 171 | self.tabButtonWidth = tabButtonWidth 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /KPCTabsControl/DefaultTheme.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DefaultTheme.swift 3 | // KPCTabsControl 4 | // 5 | // Created by Christian Tietze on 10/08/16. 6 | // Licensed under the MIT License (see LICENSE file) 7 | // 8 | 9 | import Cocoa 10 | 11 | /** 12 | * The default TabsControl theme. Used with the DefaultStyle, it provides an experience similar to Apple's Numbers.app. 13 | */ 14 | public struct DefaultTheme: Theme { 15 | 16 | public init() { } 17 | 18 | public let tabButtonTheme: TabButtonTheme = DefaultTabButtonTheme() 19 | public let selectedTabButtonTheme: TabButtonTheme = SelectedTabButtonTheme(base: DefaultTabButtonTheme()) 20 | public let unselectableTabButtonTheme: TabButtonTheme = UnselectableTabButtonTheme(base: DefaultTabButtonTheme()) 21 | public let tabsControlTheme: TabsControlTheme = DefaultTabsControlTheme() 22 | 23 | fileprivate static var sharedBorderColor: NSColor { return NSColor.lightGray } 24 | fileprivate static var sharedBackgroundColor: NSColor { return NSColor(calibratedWhite: 0.95, alpha: 1.0) } 25 | 26 | fileprivate struct DefaultTabButtonTheme: KPCTabsControl.TabButtonTheme { 27 | var backgroundColor: NSColor { return DefaultTheme.sharedBackgroundColor } 28 | var borderColor: NSColor { return DefaultTheme.sharedBorderColor } 29 | var titleColor: NSColor { return NSColor.darkGray } 30 | var titleFont: NSFont { return NSFont.systemFont(ofSize: 13) } 31 | } 32 | 33 | fileprivate struct SelectedTabButtonTheme: KPCTabsControl.TabButtonTheme { 34 | let base: DefaultTabButtonTheme 35 | let blueColor = NSColor(calibratedRed: 205.0/255.0, green: 222.0/255.0, blue: 244.0/255.0, alpha: 1.0) 36 | 37 | var backgroundColor: NSColor { return blueColor } 38 | var borderColor: NSColor { return blueColor.darkerColor() } 39 | var titleColor: NSColor { return NSColor(calibratedRed: 85.0/255.0, green: 102.0/255.0, blue: 124.0/255.0, alpha: 1.0) } 40 | var titleFont: NSFont { return NSFont.boldSystemFont(ofSize: 13) } 41 | } 42 | 43 | fileprivate struct UnselectableTabButtonTheme: KPCTabsControl.TabButtonTheme { 44 | let base: DefaultTabButtonTheme 45 | 46 | var backgroundColor: NSColor { return base.backgroundColor } 47 | var borderColor: NSColor { return base.borderColor } 48 | var titleColor: NSColor { return NSColor.lightGray } 49 | var titleFont: NSFont { return base.titleFont } 50 | } 51 | 52 | fileprivate struct DefaultTabsControlTheme: KPCTabsControl.TabsControlTheme { 53 | var backgroundColor: NSColor { return DefaultTheme.sharedBackgroundColor } 54 | var borderColor: NSColor { return DefaultTheme.sharedBorderColor } 55 | } 56 | } 57 | 58 | extension NSColor { 59 | func darkerColor() -> NSColor { 60 | var h: CGFloat = 0, s: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0 61 | self.getHue(&h, saturation: &s, brightness: &b, alpha: &a) 62 | return NSColor(calibratedHue: h, saturation: s, brightness: max(b - 0.2, 0.0), alpha: a) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /KPCTabsControl/Error.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Error.swift 3 | // KPCTabsControl 4 | // 5 | // Created by Christian Tietze on 04/08/16. 6 | // Licensed under the MIT License (see LICENSE file) 7 | // 8 | 9 | import Foundation 10 | 11 | func fatalMethodNotImplemented(_ function: String = #function) -> Never { 12 | fatalError("Method \(function) not implemented") 13 | } 14 | -------------------------------------------------------------------------------- /KPCTabsControl/Helpers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Helpers.swift 3 | // KPCTabsControl 4 | // 5 | // Created by Cédric Foellmi on 03/09/16. 6 | // Licensed under the MIT License (see LICENSE file) 7 | // 8 | 9 | import Foundation 10 | 11 | /// Offset is a simple NSPoint typealias to increase readability. 12 | public typealias Offset = NSPoint 13 | 14 | extension Offset { 15 | 16 | public init(x: CGFloat) { 17 | self.init() 18 | self.x = x 19 | self.y = 0 20 | } 21 | 22 | public init(y: CGFloat) { 23 | self.init() 24 | self.x = 0 25 | self.y = y 26 | } 27 | } 28 | 29 | /** 30 | Addition operator for NSPoints and Offsets. 31 | 32 | - parameter lhs: lef-hand side point 33 | - parameter rhs: right-hand side offset to be added to the point. 34 | 35 | - returns: A new and offset NSPoint. 36 | */ 37 | public func +(lhs: NSPoint, rhs: Offset) -> NSPoint { 38 | return NSPoint(x: lhs.x + rhs.x, y: lhs.y + rhs.y) 39 | } 40 | 41 | /** 42 | A convenience extension to easily shrink a NSRect 43 | */ 44 | extension NSRect { 45 | 46 | /// Change width and height by `-dx` and `-dy`. 47 | 48 | public func shrinkBy(dx: CGFloat, dy: CGFloat) -> NSRect { 49 | var result = self 50 | result.size = CGSize(width: result.size.width - dx, height: result.size.height - dy) 51 | return result 52 | } 53 | } 54 | 55 | /** 56 | Convenience function to easily compare tab widths. 57 | 58 | - parameter t1: The first tab width 59 | - parameter t2: The second tab width 60 | 61 | - returns: A boolean to indicate whether the tab widths are identical or not. 62 | */ 63 | func ==(t1: TabWidth, t2: TabWidth) -> Bool { 64 | return String(describing: t1) == String(describing: t2) 65 | } 66 | 67 | /// Helper functions to let compare optionals 68 | 69 | func < (lhs: T?, rhs: T?) -> Bool { 70 | switch (lhs, rhs) { 71 | case let (l?, r?): 72 | return l < r 73 | case (nil, _?): 74 | return true 75 | default: 76 | return false 77 | } 78 | } 79 | 80 | func >= (lhs: T?, rhs: T?) -> Bool { 81 | switch (lhs, rhs) { 82 | case let (l?, r?): 83 | return l >= r 84 | default: 85 | return !(lhs < rhs) 86 | } 87 | } 88 | 89 | func > (lhs: T?, rhs: T?) -> Bool { 90 | switch (lhs, rhs) { 91 | case let (l?, r?): 92 | return l > r 93 | default: 94 | return rhs < lhs 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /KPCTabsControl/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSHumanReadableCopyright 24 | Licensed under the MIT License (see LICENSE file) 25 | NSPrincipalClass 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /KPCTabsControl/MessageInterceptor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MessageInterceptor.swift 3 | // KPCTabsControl 4 | // 5 | // Created by Cédric Foellmi on 14/06/16. 6 | // Licensed under the MIT License (see LICENSE file) 7 | // 8 | 9 | import Foundation 10 | 11 | internal class MessageInterceptor: NSObject { 12 | var receiver: NSObject? 13 | var middleMan: NSObject? 14 | 15 | override func forwardingTarget(for aSelector: Selector) -> Any? { 16 | if self.middleMan?.responds(to: aSelector) == true { return self.middleMan } 17 | if self.receiver?.responds(to: aSelector) == true { return self.receiver } 18 | return super.forwardingTarget(for: aSelector) 19 | } 20 | 21 | override func responds(to aSelector: Selector) -> Bool { 22 | if self.middleMan?.responds(to: aSelector) == true { return true } 23 | if self.receiver?.responds(to: aSelector) == true { return true } 24 | return super.responds(to: aSelector) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /KPCTabsControl/NSButton+TabsControl.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSButton+KPCTabsControl.swift 3 | // KPCTabsControl 4 | // 5 | // Created by Cédric Foellmi on 14/06/16. 6 | // Licensed under the MIT License (see LICENSE file) 7 | // 8 | 9 | import AppKit 10 | 11 | extension NSButton { 12 | static internal func auxiliaryButton(withImageNamed imageName: String, target: AnyObject?, action: Selector?) -> NSButton { 13 | 14 | let cell = TabButtonCell(textCell: "") 15 | let mask = NSEvent.EventTypeMask.leftMouseDown.union(NSEvent.EventTypeMask.periodic) 16 | cell.sendAction(on: NSEvent.EventTypeMask(rawValue: UInt64(Int(mask.rawValue)))) 17 | 18 | let button = NSButton() 19 | button.cell = cell 20 | 21 | button.target = target 22 | button.action = action 23 | button.isEnabled = (target != nil && action != nil) 24 | button.isContinuous = true 25 | button.imagePosition = .imageOnly 26 | #if SwiftPackage 27 | button.image = NSImage( 28 | contentsOfFile: Bundle.module.path( 29 | forResource: imageName, ofType: "pdf", inDirectory: "Resources" 30 | )! 31 | ) 32 | #else 33 | button.image = NSImage(named: NSImage.Name(imageName)) 34 | #endif 35 | if let img = button.image { 36 | var r: CGRect = CGRect.zero 37 | r.size = img.size 38 | r.size.width += 4.0 39 | button.frame = r 40 | } 41 | 42 | return button 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /KPCTabsControl/NSImage+TabsControl.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSImage+KPCTabsControl.swift 3 | // KPCTabsControl 4 | // 5 | // Created by Cédric Foellmi on 14/06/16. 6 | // Licensed under the MIT License (see LICENSE file) 7 | // 8 | 9 | import AppKit 10 | 11 | extension NSImage { 12 | internal func imageWithTint(_ tint: NSColor) -> NSImage { 13 | var imageRect = NSRect.zero 14 | imageRect.size = self.size 15 | 16 | let highlightImage = NSImage(size: imageRect.size) 17 | 18 | highlightImage.lockFocus() 19 | 20 | self.draw(in: imageRect, from: NSRect.zero, operation: .sourceOver, fraction: 1.0) 21 | 22 | tint.set() 23 | imageRect.fill(using: .sourceAtop) 24 | 25 | highlightImage.unlockFocus() 26 | 27 | return highlightImage 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /KPCTabsControl/Protocols.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TabsControlProtocols.swift 3 | // KPCTabsControl 4 | // 5 | // Created by Cédric Foellmi on 15/07/16. 6 | // Licensed under the MIT License (see LICENSE file) 7 | // 8 | 9 | import AppKit 10 | 11 | @objc public protocol TabsControlDataSource: NSObjectProtocol { 12 | /** 13 | Returns the number of tabs 14 | 15 | - parameter control: The instance of the tabs control. 16 | 17 | - returns: A unsigned integer indicating the number of tabs to display. 18 | */ 19 | func tabsControlNumberOfTabs(_ control: TabsControl) -> Int 20 | 21 | /** 22 | Return the item for the tab at the given index, similarly to a "representedObject" in a cell view. 23 | 24 | - parameter control: The instance of the tabs control. 25 | - parameter index: The index of the given item. 26 | 27 | - returns: An instance of an object representing the tab. 28 | */ 29 | func tabsControl(_ control: TabsControl, itemAtIndex index: Int) -> AnyObject 30 | 31 | /** 32 | Return the title for the tab of the given item 33 | 34 | - parameter control: The instance of the tabs control. 35 | - parameter item: The item representing the given tab. 36 | 37 | - returns: A string to be used as title of the tab. 38 | */ 39 | func tabsControl(_ control: TabsControl, titleForItem item: AnyObject) -> String 40 | 41 | /** 42 | If any, returns a menu for the tab, to be place to the right side of it. It is your responsability to fully 43 | configure its targets and actions before returning it to the tabs control. 44 | 45 | - parameter control: The instance of the tabs control. 46 | - parameter item: The item representing the given tab. 47 | 48 | - returns: A menu instance. 49 | */ 50 | @objc optional func tabsControl(_ control: TabsControl, menuForItem item: AnyObject) -> NSMenu? 51 | 52 | /** 53 | If any, returns an icon for the tab, to be placed to the left side of it. 54 | 55 | - parameter control: The instance of the tabs control. 56 | - parameter item: The item representing the given tab. 57 | 58 | - returns: An image instance for the icon. 59 | */ 60 | @objc optional func tabsControl(_ control: TabsControl, iconForItem item: AnyObject) -> NSImage? 61 | 62 | /** 63 | If the width of the tab is not large enough to draw the title, it is possible to provide here an alternate 64 | icon to replace it. The threshold at which one switch between the title and the icon is computed individually 65 | for each title. 66 | 67 | - parameter control: The instance of the tabs control. 68 | - parameter item: The item representing the given tab. 69 | 70 | - returns: An image instance for the alternate icon. 71 | */ 72 | @objc optional func tabsControl(_ control: TabsControl, titleAlternativeIconForItem item: AnyObject) -> NSImage? 73 | } 74 | 75 | @objc public protocol TabsControlDelegate: NSControlTextEditingDelegate { 76 | /** 77 | * Determine if the tab can be selected. 78 | * 79 | * - parameter tabControl: The instance of the tabs control. 80 | * - parameter item: The item representing the given tab. 81 | * 82 | * - returns: A boolean value indicating whether the tab can be selected or not. 83 | */ 84 | @objc optional func tabsControl(_ control: TabsControl, canSelectItem item: AnyObject) -> Bool 85 | 86 | /** 87 | * If implemented, the delegate is informed that the selected tab did change. 88 | * See also TabsControlSelectionDidChangeNotification 89 | * 90 | * - parameter tabControl: The instance of the tabs control. 91 | * - parameter item: The item representing the selected tab. 92 | */ 93 | @objc optional func tabsControlDidChangeSelection(_ control: TabsControl, item: AnyObject) 94 | 95 | /** 96 | * Return `true` if the tab is allowed to be reordered (by being dragged with the mouse). 97 | * This method has no effect if the one below is not implemented. 98 | * 99 | * - parameter tabControl: The instance of the tabs control. 100 | * - parameter item: The item representing the given tab. 101 | * 102 | * - returns: A boolean value indicating whether the tab can be reordered or not. 103 | */ 104 | @objc optional func tabsControl(_ control: TabsControl, canReorderItem item: AnyObject) -> Bool 105 | 106 | /** 107 | * If implemented, the delegate is informed that the tabs have been reordered. It is the delegate responsability 108 | * to store the new order of items. If not stored, the tabs will recover their original order. 109 | * 110 | * - parameter tabControl: The instance of the tabs control. 111 | * - parameter items: The array the items following the new orders. 112 | */ 113 | @objc optional func tabsControl(_ control: TabsControl, didReorderItems items: [AnyObject]) 114 | 115 | /** 116 | * Return `true` if you allow the editing of the title of the tab. By default, titles are not editable. 117 | * This method has no effect if the one below is not implemented. 118 | * 119 | * - parameter tabControl: The instance of the tabs control. 120 | * - parameter item: The item representing the given tab. 121 | * 122 | * - returns: A boolean value indicating whether the tab title can be edited or not. 123 | */ 124 | @objc optional func tabsControl(_ control: TabsControl, canEditTitleOfItem item: AnyObject) -> Bool 125 | 126 | /** 127 | * If implemented, the delegate is informed that the tab has been renamed to the given title. Again, it is the 128 | * delegate responsability to store the new title. 129 | * 130 | * - parameter tabControl: The instance of the tabs control. 131 | * - parameter newTitle: The new title value. 132 | * - parameter item: The item representing the given tab. 133 | */ 134 | @objc optional func tabsControl(_ control: TabsControl, setTitle newTitle: String, forItem item: AnyObject) 135 | } 136 | -------------------------------------------------------------------------------- /KPCTabsControl/Resources: -------------------------------------------------------------------------------- 1 | ../Resources -------------------------------------------------------------------------------- /KPCTabsControl/SafariStyle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SafariStyle.swift 3 | // KPCTabsControl 4 | // 5 | // Created by Cédric Foellmi on 27/08/16. 6 | // Licensed under the MIT License (see LICENSE file) 7 | // 8 | 9 | import AppKit 10 | 11 | /** 12 | * The Safari style. Use mostly the default implementation of Style. 13 | */ 14 | public struct SafariStyle: ThemedStyle { 15 | public let theme: Theme 16 | public let tabButtonWidth: TabWidth 17 | public let tabsControlRecommendedHeight: CGFloat = 24.0 18 | 19 | public init(theme: Theme = SafariTheme(), tabButtonWidth: TabWidth = .full) { 20 | self.theme = theme 21 | self.tabButtonWidth = tabButtonWidth 22 | } 23 | 24 | // There is no icons in Safari tabs. Here we force the absence of icon, even if some are provided. 25 | public func iconFrames(tabRect rect: NSRect) -> IconFrames { 26 | return (NSRect.zero, NSRect.zero) 27 | } 28 | 29 | public func tabButtonBorderMask(_ position: TabPosition) -> BorderMask? { 30 | return [.bottom, .top, .right] 31 | } 32 | 33 | public func tabsControlBorderMask() -> BorderMask? { 34 | return BorderMask.all() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /KPCTabsControl/SafariTheme.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SafariTheme.swift 3 | // KPCTabsControl 4 | // 5 | // Created by Cédric Foellmi on 27/08/16. 6 | // Licensed under the MIT License (see LICENSE file) 7 | // 8 | 9 | import AppKit 10 | 11 | public struct SafariTheme: Theme { 12 | 13 | public init() { } 14 | 15 | public let tabButtonTheme: TabButtonTheme = DefaultTabButtonTheme() 16 | public let selectedTabButtonTheme: TabButtonTheme = SelectedTabButtonTheme() 17 | public let unselectableTabButtonTheme: TabButtonTheme = UnselectableTabButtonTheme(base: DefaultTabButtonTheme()) 18 | public let tabsControlTheme: TabsControlTheme = DefaultTabsControlTheme() 19 | 20 | fileprivate static var sharedBackgroundColor: NSColor { return NSColor(white: 0.72, alpha: 1.0) } 21 | fileprivate static var sharedBorderColor: NSColor { return NSColor(white: 0.61, alpha: 1.0) } 22 | 23 | fileprivate struct DefaultTabButtonTheme: KPCTabsControl.TabButtonTheme { 24 | var backgroundColor: NSColor { return SafariTheme.sharedBackgroundColor } 25 | var borderColor: NSColor { return SafariTheme.sharedBorderColor } 26 | var titleColor: NSColor { return NSColor(white: 0.38, alpha: 1.0) } 27 | var titleFont: NSFont { return NSFont.systemFont(ofSize: NSFont.systemFontSize) } 28 | } 29 | 30 | fileprivate struct SelectedTabButtonTheme: KPCTabsControl.TabButtonTheme { 31 | var backgroundColor: NSColor { return NSColor(white: 0.79, alpha: 1.0) } 32 | var borderColor: NSColor { return NSColor(white: 0.64, alpha: 1.0) } 33 | var titleColor: NSColor { return NSColor(white: 0.08, alpha: 1.0) } 34 | var titleFont: NSFont { return NSFont.systemFont(ofSize: NSFont.systemFontSize) } 35 | } 36 | 37 | fileprivate struct UnselectableTabButtonTheme: KPCTabsControl.TabButtonTheme { 38 | let base: DefaultTabButtonTheme 39 | 40 | var backgroundColor: NSColor { return base.backgroundColor } 41 | var borderColor: NSColor { return base.borderColor } 42 | var titleColor: NSColor { return NSColor(white: 0.94, alpha: 1.0) } 43 | var titleFont: NSFont { return base.titleFont } 44 | } 45 | 46 | fileprivate struct DefaultTabsControlTheme: KPCTabsControl.TabsControlTheme { 47 | var backgroundColor: NSColor { return SafariTheme.sharedBackgroundColor } 48 | var borderColor: NSColor { return SafariTheme.sharedBorderColor } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /KPCTabsControl/SequenceType+TabsControl.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SequenceType.swift 3 | // KPCTabsControl 4 | // 5 | // Created by Christian Tietze on 04/08/16. 6 | // Licensed under the MIT License (see LICENSE file) 7 | // 8 | 9 | import Foundation 10 | 11 | extension Sequence { 12 | internal func findFirst(_ predicate: (Self.Iterator.Element) -> Bool) -> Self.Iterator.Element? { 13 | 14 | for element in self { 15 | if predicate(element) { 16 | return element 17 | } 18 | } 19 | 20 | return nil 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /KPCTabsControl/Style.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Style.swift 3 | // KPCTabsControl 4 | // 5 | // Created by Christian Tietze on 10/08/16. 6 | // Licensed under the MIT License (see LICENSE file) 7 | // 8 | 9 | import Cocoa 10 | 11 | public typealias IconFrames = (iconFrame: NSRect, alternativeTitleIconFrame: NSRect) 12 | 13 | public typealias TitleEditorSettings = (textColor: NSColor, font: NSFont, alignment: NSTextAlignment) 14 | 15 | /** 16 | * The Style protocol defines all the necessary things to let KPCTabsControl draw itself with tabs. 17 | */ 18 | public protocol Style { 19 | // Tab Buttons 20 | var tabButtonWidth: TabWidth { get } 21 | func tabButtonOffset(position: TabPosition) -> Offset 22 | func tabButtonBorderMask(_ position: TabPosition) -> BorderMask? 23 | 24 | // Tab Button Titles 25 | func iconFrames(tabRect rect: NSRect) -> IconFrames 26 | func titleRect(title: NSAttributedString, inBounds rect: NSRect, showingIcon: Bool) -> NSRect 27 | func titleEditorSettings() -> TitleEditorSettings 28 | func attributedTitle(content: String, selectionState: TabSelectionState) -> NSAttributedString 29 | 30 | // Tabs Control 31 | var tabsControlRecommendedHeight: CGFloat { get } 32 | func tabsControlBorderMask() -> BorderMask? 33 | 34 | // Drawing 35 | func drawTabButtonBezel(frame: NSRect, position: TabPosition, isSelected: Bool) 36 | func drawTabsControlBezel(frame: NSRect) 37 | } 38 | 39 | /** 40 | * The default Style protocol doesn't necessary have a theme associated with it, for custom styles. 41 | * However, provided styles (Numbers.app-like, Safari and Chrome) have an associated theme. 42 | */ 43 | public protocol ThemedStyle: Style { 44 | var theme: Theme { get } 45 | } 46 | -------------------------------------------------------------------------------- /KPCTabsControl/TabButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TabButton.swift 3 | // KPCTabsControl 4 | // 5 | // Created by Cédric Foellmi on 06/07/16. 6 | // Licensed under the MIT License (see LICENSE file) 7 | // 8 | 9 | import AppKit 10 | 11 | open class TabButton: NSButton { 12 | 13 | fileprivate var iconView: NSImageView? 14 | fileprivate var alternativeTitleIconView: NSImageView? 15 | fileprivate var trackingArea: NSTrackingArea? 16 | 17 | fileprivate var tabButtonCell: TabButtonCell? { 18 | get { return self.cell as? TabButtonCell } 19 | } 20 | 21 | open var item: AnyObject? { 22 | get { return self.cell?.representedObject as AnyObject? } 23 | set { self.cell?.representedObject = newValue } 24 | } 25 | 26 | open var style: Style! { 27 | didSet { self.tabButtonCell?.style = self.style } 28 | } 29 | 30 | /// The button is aware of its last known index in the tab bar. 31 | var index: Int? 32 | 33 | open var buttonPosition: TabPosition! { 34 | get { return tabButtonCell?.buttonPosition } 35 | set { self.tabButtonCell?.buttonPosition = newValue } 36 | } 37 | 38 | open var representedObject: AnyObject? { 39 | get { return self.tabButtonCell?.representedObject as AnyObject? } 40 | set { self.tabButtonCell?.representedObject = newValue } 41 | } 42 | 43 | open var editable: Bool { 44 | get { return self.tabButtonCell?.isEditable ?? false } 45 | set { self.tabButtonCell?.isEditable = newValue } 46 | } 47 | 48 | open var icon: NSImage? { 49 | didSet { 50 | if self.icon != nil && self.iconView == nil { 51 | self.iconView = NSImageView(frame: NSRect.zero) 52 | self.iconView?.imageFrameStyle = .none 53 | self.addSubview(self.iconView!) 54 | } else if self.icon == nil && self.iconView != nil { 55 | self.iconView?.removeFromSuperview() 56 | self.iconView = nil 57 | } 58 | self.iconView?.image = self.icon 59 | self.needsDisplay = true 60 | } 61 | } 62 | 63 | open var alternativeTitleIcon: NSImage? { 64 | didSet { 65 | self.tabButtonCell?.hasTitleAlternativeIcon = (self.alternativeTitleIcon != nil) 66 | 67 | if self.alternativeTitleIcon != nil && self.alternativeTitleIconView == nil { 68 | self.alternativeTitleIconView = NSImageView(frame: NSRect.zero) 69 | self.alternativeTitleIconView?.imageFrameStyle = .none 70 | self.addSubview(self.alternativeTitleIconView!) 71 | } else if self.alternativeTitleIcon == nil && self.alternativeTitleIconView != nil { 72 | self.alternativeTitleIconView?.removeFromSuperview() 73 | self.alternativeTitleIconView = nil 74 | } 75 | self.alternativeTitleIconView?.image = self.alternativeTitleIcon 76 | self.needsDisplay = true 77 | } 78 | } 79 | 80 | // MARK: - Init 81 | 82 | override init(frame frameRect: NSRect) { 83 | super.init(frame: frameRect) 84 | self.cell = TabButtonCell(textCell: "") 85 | } 86 | 87 | required public init?(coder: NSCoder) { 88 | fatalError("init(coder:) has not been implemented") 89 | } 90 | 91 | init(index: Int, item: AnyObject, target: AnyObject?, action: Selector, style: Style) { 92 | super.init(frame: NSRect.zero) 93 | 94 | self.index = index 95 | self.style = style 96 | 97 | let tabCell = TabButtonCell(textCell: "") 98 | 99 | tabCell.representedObject = item 100 | tabCell.imagePosition = .noImage 101 | 102 | tabCell.target = target 103 | tabCell.action = action 104 | tabCell.style = style 105 | 106 | tabCell.sendAction(on: NSEvent.EventTypeMask(rawValue: UInt64(Int(NSEvent.EventTypeMask.leftMouseDown.rawValue)))) 107 | self.cell = tabCell 108 | } 109 | 110 | override open func copy() -> Any { 111 | let copy = TabButton(frame: self.frame) 112 | copy.cell = self.cell?.copy() as? NSCell 113 | copy.icon = self.icon 114 | copy.style = self.style 115 | copy.alternativeTitleIcon = self.alternativeTitleIcon 116 | copy.state = self.state 117 | copy.index = self.index 118 | return copy 119 | } 120 | 121 | open override var menu: NSMenu? { 122 | get { return self.cell?.menu } 123 | set { 124 | self.cell?.menu = newValue 125 | self.updateTrackingAreas() 126 | } 127 | } 128 | 129 | // MARK: - Drawing 130 | 131 | open override func updateTrackingAreas() { 132 | if let ta = self.trackingArea { 133 | self.removeTrackingArea(ta) 134 | } 135 | 136 | let item: AnyObject? = self.cell?.representedObject as AnyObject? 137 | 138 | let userInfo: [String: AnyObject]? = (item != nil) ? ["item": item!] : nil 139 | self.trackingArea = NSTrackingArea(rect: self.bounds, 140 | options: [NSTrackingArea.Options.mouseEnteredAndExited, NSTrackingArea.Options.activeInActiveApp, NSTrackingArea.Options.inVisibleRect], 141 | owner: self, userInfo: userInfo) 142 | 143 | self.addTrackingArea(self.trackingArea!) 144 | 145 | if let w = self.window, let e = NSApp.currentEvent { 146 | let mouseLocation = w.mouseLocationOutsideOfEventStream 147 | let convertedMouseLocation = self.convert(mouseLocation, from: nil) 148 | 149 | if self.bounds.contains(convertedMouseLocation) { 150 | self.mouseEntered(with: e) 151 | } else { 152 | self.mouseExited(with: e) 153 | } 154 | } 155 | 156 | super.updateTrackingAreas() 157 | } 158 | 159 | open override func mouseEntered(with theEvent: NSEvent) { 160 | super.mouseEntered(with: theEvent) 161 | self.needsDisplay = true 162 | } 163 | 164 | open override func mouseExited(with theEvent: NSEvent) { 165 | super.mouseExited(with: theEvent) 166 | self.needsDisplay = true 167 | } 168 | 169 | open override func mouseDown(with theEvent: NSEvent) { 170 | super.mouseDown(with: theEvent) 171 | if self.isEnabled == false { 172 | NSSound.beep() 173 | } 174 | } 175 | 176 | open override func resetCursorRects() { 177 | self.addCursorRect(self.bounds, cursor: NSCursor.arrow) 178 | } 179 | 180 | open override func draw(_ dirtyRect: NSRect) { 181 | 182 | guard let tabButtonCell = self.tabButtonCell 183 | else { assertionFailure("TabButtonCell expected in drawRect(_:)"); return } 184 | 185 | let iconFrames = self.style.iconFrames(tabRect: self.frame) 186 | self.iconView?.frame = iconFrames.iconFrame 187 | self.alternativeTitleIconView?.frame = iconFrames.alternativeTitleIconFrame 188 | 189 | let scale: CGFloat = (self.layer != nil) ? self.layer!.contentsScale : 1.0 190 | 191 | if self.icon?.size.width > (iconFrames.iconFrame).height*scale { 192 | let smallIcon = NSImage(size: iconFrames.iconFrame.size) 193 | smallIcon.addRepresentation(NSBitmapImageRep(data: self.icon!.tiffRepresentation!)!) 194 | self.iconView?.image = smallIcon 195 | } 196 | 197 | if self.alternativeTitleIcon?.size.width > (iconFrames.alternativeTitleIconFrame).height*scale { 198 | let smallIcon = NSImage(size: iconFrames.alternativeTitleIconFrame.size) 199 | smallIcon.addRepresentation(NSBitmapImageRep(data: self.alternativeTitleIcon!.tiffRepresentation!)!) 200 | self.alternativeTitleIconView?.image = smallIcon 201 | } 202 | 203 | let hasRoom = tabButtonCell.hasRoomToDrawFullTitle(inRect: self.bounds) 204 | self.alternativeTitleIconView?.isHidden = hasRoom 205 | self.toolTip = (hasRoom == true) ? nil : self.title 206 | 207 | super.draw(dirtyRect) 208 | } 209 | 210 | // MARK: - Editing 211 | 212 | internal func edit(fieldEditor: NSText, delegate: NSTextDelegate) { 213 | self.tabButtonCell?.edit(fieldEditor: fieldEditor, inView: self, delegate: delegate) 214 | } 215 | 216 | internal func finishEditing(fieldEditor: NSText, newValue: String) { 217 | self.tabButtonCell?.finishEditing(fieldEditor: fieldEditor, newValue: newValue) 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /KPCTabsControl/TabButtonCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TabButtonCell.swift 3 | // KPCTabsControl 4 | // 5 | // Created by Cédric Foellmi on 14/06/16. 6 | // Licensed under the MIT License (see LICENSE file) 7 | // 8 | 9 | import Foundation 10 | import AppKit 11 | 12 | let titleMargin: CGFloat = 5.0 13 | 14 | class TabButtonCell: NSButtonCell { 15 | 16 | var hasTitleAlternativeIcon: Bool = false 17 | 18 | var isSelected: Bool { 19 | get { return self.state == NSControl.StateValue.on } 20 | } 21 | 22 | var selectionState: TabSelectionState { 23 | return self.isEnabled == false ? TabSelectionState.unselectable : (self.isSelected ? TabSelectionState.selected : TabSelectionState.normal) 24 | } 25 | 26 | var showsIcon: Bool { 27 | get { return (self.controlView as! TabButton).icon != nil } 28 | } 29 | 30 | var showsMenu: Bool { 31 | get { return self.menu?.items.count > 0 } 32 | } 33 | 34 | var buttonPosition: TabPosition = .middle { 35 | didSet { self.controlView?.needsDisplay = true } 36 | } 37 | 38 | var style: Style! 39 | 40 | // MARK: - Initializers & Copy 41 | 42 | override init(textCell aString: String) { 43 | super.init(textCell: aString) 44 | 45 | self.isBordered = true 46 | self.backgroundStyle = .light 47 | self.highlightsBy = NSCell.StyleMask.changeBackgroundCellMask 48 | self.lineBreakMode = .byTruncatingTail 49 | self.focusRingType = .none 50 | } 51 | 52 | required init(coder aDecoder: NSCoder) { 53 | super.init(coder: aDecoder) 54 | } 55 | 56 | override func copy() -> Any { 57 | let copy = TabButtonCell(textCell: self.title) 58 | 59 | copy.hasTitleAlternativeIcon = self.hasTitleAlternativeIcon 60 | copy.buttonPosition = self.buttonPosition 61 | 62 | copy.state = self.state 63 | copy.isHighlighted = self.isHighlighted 64 | 65 | return copy 66 | } 67 | 68 | // MARK: - Properties & Rects 69 | 70 | static func popupImage() -> NSImage { 71 | let path: String 72 | #if SwiftPackage 73 | path = Bundle.module.path(forResource: "KPCPullDownTemplate", ofType: "pdf", inDirectory: "Resources")! 74 | #else 75 | path = Bundle(for: self).pathForImageResource(NSImage.Name("KPCPullDownTemplate"))! 76 | #endif 77 | return NSImage(contentsOfFile: path)!.imageWithTint(NSColor.darkGray) 78 | } 79 | 80 | func hasRoomToDrawFullTitle(inRect rect: NSRect) -> Bool { 81 | let title = self.style.attributedTitle(content: self.title, selectionState: self.selectionState) 82 | let requiredMinimumWidth = title.size().width + 2.0*titleMargin 83 | let titleDrawRect = self.titleRect(forBounds: rect) 84 | return requiredMinimumWidth <= titleDrawRect.width 85 | } 86 | 87 | override func cellSize(forBounds aRect: NSRect) -> NSSize { 88 | let title = self.style.attributedTitle(content: self.title, selectionState: self.selectionState) 89 | let titleSize = title.size() 90 | let popupSize = (self.menu == nil) ? NSSize.zero : TabButtonCell.popupImage().size 91 | let cellSize = NSSize(width: titleSize.width + (popupSize.width * 2) + 36, height: max(titleSize.height, popupSize.height)) 92 | self.controlView?.invalidateIntrinsicContentSize() 93 | return cellSize 94 | } 95 | 96 | fileprivate func popupRectWithFrame(_ cellFrame: NSRect) -> NSRect { 97 | var popupRect = NSRect.zero 98 | popupRect.size = TabButtonCell.popupImage().size 99 | popupRect.origin = NSPoint(x: cellFrame.maxX - popupRect.width - 8, y: cellFrame.midY - popupRect.height / 2) 100 | return popupRect 101 | } 102 | 103 | override func trackMouse(with theEvent: NSEvent, 104 | in cellFrame: NSRect, 105 | of controlView: NSView, 106 | untilMouseUp flag: Bool) -> Bool { 107 | if self.hitTest(for: theEvent, 108 | in: controlView.superview!.frame, 109 | of: controlView.superview!) != NSCell.HitResult() { 110 | 111 | let popupRect = self.popupRectWithFrame(cellFrame) 112 | let location = controlView.convert(theEvent.locationInWindow, from: nil) 113 | 114 | if self.menu?.items.count > 0 && popupRect.contains(location) { 115 | self.menu?.popUp(positioning: self.menu!.items.first, 116 | at: NSPoint(x: popupRect.midX, y: popupRect.maxY), 117 | in: controlView) 118 | 119 | return true 120 | } 121 | } 122 | 123 | return super.trackMouse(with: theEvent, in: cellFrame, of: controlView, untilMouseUp: flag) 124 | } 125 | 126 | override func titleRect(forBounds theRect: NSRect) -> NSRect { 127 | let title = self.style.attributedTitle(content: self.title, selectionState: self.selectionState) 128 | var rect = self.style.titleRect(title: title, inBounds: theRect, showingIcon: self.showsIcon) 129 | if self.showsMenu { 130 | let popupRect = self.popupRectWithFrame(theRect) 131 | rect.size.width -= popupRect.width + 2*titleMargin 132 | } 133 | return rect 134 | } 135 | 136 | // MARK: - Editing 137 | 138 | func edit(fieldEditor: NSText, inView view: NSView, delegate: NSTextDelegate) { 139 | 140 | self.isHighlighted = true 141 | 142 | let frame = self.editingRectForBounds(view.bounds) 143 | self.select(withFrame: frame, 144 | in: view, 145 | editor: fieldEditor, 146 | delegate: delegate, 147 | start: 0, 148 | length: 0) 149 | 150 | fieldEditor.drawsBackground = false 151 | fieldEditor.isHorizontallyResizable = true 152 | fieldEditor.isEditable = true 153 | 154 | let editorSettings = self.style.titleEditorSettings() 155 | fieldEditor.font = editorSettings.font 156 | fieldEditor.alignment = editorSettings.alignment 157 | fieldEditor.textColor = editorSettings.textColor 158 | 159 | // Replace content so that resizing is triggered 160 | fieldEditor.string = "" 161 | fieldEditor.insertText(self.title ?? "") 162 | fieldEditor.selectAll(self) 163 | 164 | self.title = "" 165 | } 166 | 167 | func finishEditing(fieldEditor: NSText, newValue: String) { 168 | self.endEditing(fieldEditor) 169 | self.title = newValue 170 | } 171 | 172 | func editingRectForBounds(_ rect: NSRect) -> NSRect { 173 | return self.titleRect(forBounds: rect)// .offsetBy(dx: 0, dy: 1)) 174 | } 175 | 176 | // MARK: - Drawing 177 | 178 | override func draw(withFrame frame: NSRect, in controlView: NSView) { 179 | 180 | self.style.drawTabButtonBezel(frame: frame, position: self.buttonPosition, isSelected: self.isSelected) 181 | 182 | if self.hasRoomToDrawFullTitle(inRect: frame) || self.hasTitleAlternativeIcon == false { 183 | let title = self.style.attributedTitle(content: self.title, selectionState: self.selectionState) 184 | _ = self.drawTitle(title, withFrame: frame, in: controlView) 185 | } 186 | 187 | if self.showsMenu { 188 | self.drawPopupButtonWithFrame(frame) 189 | } 190 | } 191 | 192 | override func drawTitle(_ title: NSAttributedString, withFrame frame: NSRect, in controlView: NSView) -> NSRect { 193 | let titleRect = self.titleRect(forBounds: frame) 194 | title.draw(in: titleRect) 195 | return titleRect 196 | } 197 | 198 | fileprivate func drawPopupButtonWithFrame(_ frame: NSRect) { 199 | let image = TabButtonCell.popupImage() 200 | image.draw(in: self.popupRectWithFrame(frame), 201 | from: NSRect.zero, 202 | operation: .sourceOver, 203 | fraction: 1.0, 204 | respectFlipped: true, 205 | hints: nil) 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /KPCTabsControl/TabsControlCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TabsControlCell.swift 3 | // KPCTabsControl 4 | // 5 | // Created by Cédric Foellmi on 30/07/16. 6 | // Licensed under the MIT License (see LICENSE file) 7 | // 8 | 9 | import Cocoa 10 | 11 | class TabsControlCell: NSCell { 12 | 13 | var style: Style! { 14 | didSet { self.controlView?.needsDisplay = true } 15 | } 16 | 17 | override init(textCell aString: String) { 18 | super.init(textCell: aString) 19 | 20 | self.isBordered = true 21 | self.backgroundStyle = .light 22 | self.focusRingType = .none 23 | self.isEnabled = false 24 | self.font = NSFont.systemFont(ofSize: 13) 25 | } 26 | 27 | required init(coder aDecoder: NSCoder) { 28 | super.init(coder: aDecoder) 29 | } 30 | 31 | override func cellSize(forBounds aRect: NSRect) -> NSSize { 32 | return NSSize(width: 36.0, height: 0.0) 33 | } 34 | 35 | override func draw(withFrame cellFrame: NSRect, in controlView: NSView) { 36 | 37 | // TODO can we get rid of this by setting `style` earlier? 38 | guard self.style != nil else { return } 39 | 40 | self.style.drawTabsControlBezel(frame: cellFrame) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /KPCTabsControl/Theme.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Theme.swift 3 | // KPCTabsControl 4 | // 5 | // Created by Cédric Foellmi on 13/08/16. 6 | // Licensed under the MIT License (see LICENSE file) 7 | // 8 | 9 | import AppKit 10 | 11 | /** 12 | * The theme of a single Tab button 13 | */ 14 | public protocol TabButtonTheme { 15 | var backgroundColor: NSColor { get } 16 | var borderColor: NSColor { get } 17 | var titleColor: NSColor { get } 18 | var titleFont: NSFont { get } 19 | } 20 | 21 | /** 22 | * The theme of the whole TabsControl bar 23 | */ 24 | public protocol TabsControlTheme { 25 | var backgroundColor: NSColor { get } 26 | var borderColor: NSColor { get } 27 | } 28 | 29 | /** 30 | * The theme of a complete TabsControl 31 | */ 32 | public protocol Theme { 33 | var tabButtonTheme: TabButtonTheme { get } 34 | var selectedTabButtonTheme: TabButtonTheme { get } 35 | var unselectableTabButtonTheme: TabButtonTheme { get } 36 | var tabsControlTheme: TabsControlTheme { get } 37 | } 38 | 39 | extension Theme { 40 | /** 41 | Convenience function that select the theme corresponding to the right selection state. 42 | 43 | - parameter selectionState: The tab selection state 44 | 45 | - returns: The theme crresponding to the selection state. 46 | */ 47 | public func tabButtonTheme(fromSelectionState selectionState: TabSelectionState) -> TabButtonTheme { 48 | switch selectionState { 49 | case .normal: return self.tabButtonTheme 50 | case .selected: return self.selectedTabButtonTheme 51 | case .unselectable: return self.unselectableTabButtonTheme 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /KPCTabsControlDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // KPCTabsControlDemo 4 | // 5 | // Created by Cédric Foellmi on 15/07/16. 6 | // Licensed under the MIT License (see LICENSE file) 7 | // 8 | 9 | import Cocoa 10 | import KPCTabsControl 11 | 12 | // Bug from Apple ? 13 | // https://stackoverflow.com/questions/47051682/unknown-window-class-null-in-interface-builder-file-creating-generic-window-i 14 | class WorkedAroundWindow: NSWindow {} 15 | 16 | @NSApplicationMain 17 | class AppDelegate: NSObject, NSApplicationDelegate { 18 | 19 | func applicationDidFinishLaunching(_ aNotification: Notification) { 20 | } 21 | 22 | func applicationWillTerminate(_ aNotification: Notification) { 23 | // Insert code here to tear down your application 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /KPCTabsControlDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "size" : "16x16", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "size" : "16x16", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "size" : "32x32", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "size" : "32x32", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "size" : "128x128", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "size" : "128x128", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "size" : "256x256", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "size" : "256x256", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "size" : "512x512", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "size" : "512x512", 51 | "scale" : "2x" 52 | } 53 | ], 54 | "info" : { 55 | "version" : 1, 56 | "author" : "xcode" 57 | } 58 | } -------------------------------------------------------------------------------- /KPCTabsControlDemo/Assets.xcassets/ArrowTemplate.imageset/BoundariesTemplate.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onekiloparsec/KPCTabsControl/0ff97b8107cf587c161652ab98ccff894c9ee2db/KPCTabsControlDemo/Assets.xcassets/ArrowTemplate.imageset/BoundariesTemplate.pdf -------------------------------------------------------------------------------- /KPCTabsControlDemo/Assets.xcassets/ArrowTemplate.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "BoundariesTemplate.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /KPCTabsControlDemo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /KPCTabsControlDemo/Assets.xcassets/Oval.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Oval copy 2.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "Oval copy.png" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x", 16 | "filename" : "Oval.png" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /KPCTabsControlDemo/Assets.xcassets/Oval.imageset/Oval copy 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onekiloparsec/KPCTabsControl/0ff97b8107cf587c161652ab98ccff894c9ee2db/KPCTabsControlDemo/Assets.xcassets/Oval.imageset/Oval copy 2.png -------------------------------------------------------------------------------- /KPCTabsControlDemo/Assets.xcassets/Oval.imageset/Oval copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onekiloparsec/KPCTabsControl/0ff97b8107cf587c161652ab98ccff894c9ee2db/KPCTabsControlDemo/Assets.xcassets/Oval.imageset/Oval copy.png -------------------------------------------------------------------------------- /KPCTabsControlDemo/Assets.xcassets/Oval.imageset/Oval.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onekiloparsec/KPCTabsControl/0ff97b8107cf587c161652ab98ccff894c9ee2db/KPCTabsControlDemo/Assets.xcassets/Oval.imageset/Oval.png -------------------------------------------------------------------------------- /KPCTabsControlDemo/Assets.xcassets/Polygon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Polygon copy 2.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "Polygon copy.png" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x", 16 | "filename" : "Polygon.png" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /KPCTabsControlDemo/Assets.xcassets/Polygon.imageset/Polygon copy 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onekiloparsec/KPCTabsControl/0ff97b8107cf587c161652ab98ccff894c9ee2db/KPCTabsControlDemo/Assets.xcassets/Polygon.imageset/Polygon copy 2.png -------------------------------------------------------------------------------- /KPCTabsControlDemo/Assets.xcassets/Polygon.imageset/Polygon copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onekiloparsec/KPCTabsControl/0ff97b8107cf587c161652ab98ccff894c9ee2db/KPCTabsControlDemo/Assets.xcassets/Polygon.imageset/Polygon copy.png -------------------------------------------------------------------------------- /KPCTabsControlDemo/Assets.xcassets/Polygon.imageset/Polygon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onekiloparsec/KPCTabsControl/0ff97b8107cf587c161652ab98ccff894c9ee2db/KPCTabsControlDemo/Assets.xcassets/Polygon.imageset/Polygon.png -------------------------------------------------------------------------------- /KPCTabsControlDemo/Assets.xcassets/Rectangle.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Rectangle copy 2.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "Rectangle copy.png" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x", 16 | "filename" : "Rectangle.png" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /KPCTabsControlDemo/Assets.xcassets/Rectangle.imageset/Rectangle copy 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onekiloparsec/KPCTabsControl/0ff97b8107cf587c161652ab98ccff894c9ee2db/KPCTabsControlDemo/Assets.xcassets/Rectangle.imageset/Rectangle copy 2.png -------------------------------------------------------------------------------- /KPCTabsControlDemo/Assets.xcassets/Rectangle.imageset/Rectangle copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onekiloparsec/KPCTabsControl/0ff97b8107cf587c161652ab98ccff894c9ee2db/KPCTabsControlDemo/Assets.xcassets/Rectangle.imageset/Rectangle copy.png -------------------------------------------------------------------------------- /KPCTabsControlDemo/Assets.xcassets/Rectangle.imageset/Rectangle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onekiloparsec/KPCTabsControl/0ff97b8107cf587c161652ab98ccff894c9ee2db/KPCTabsControlDemo/Assets.xcassets/Rectangle.imageset/Rectangle.png -------------------------------------------------------------------------------- /KPCTabsControlDemo/Assets.xcassets/Spiral.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Spiral copy 2.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "Spiral copy.png" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x", 16 | "filename" : "Spiral.png" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /KPCTabsControlDemo/Assets.xcassets/Spiral.imageset/Spiral copy 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onekiloparsec/KPCTabsControl/0ff97b8107cf587c161652ab98ccff894c9ee2db/KPCTabsControlDemo/Assets.xcassets/Spiral.imageset/Spiral copy 2.png -------------------------------------------------------------------------------- /KPCTabsControlDemo/Assets.xcassets/Spiral.imageset/Spiral copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onekiloparsec/KPCTabsControl/0ff97b8107cf587c161652ab98ccff894c9ee2db/KPCTabsControlDemo/Assets.xcassets/Spiral.imageset/Spiral copy.png -------------------------------------------------------------------------------- /KPCTabsControlDemo/Assets.xcassets/Spiral.imageset/Spiral.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onekiloparsec/KPCTabsControl/0ff97b8107cf587c161652ab98ccff894c9ee2db/KPCTabsControlDemo/Assets.xcassets/Spiral.imageset/Spiral.png -------------------------------------------------------------------------------- /KPCTabsControlDemo/Assets.xcassets/Star.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Star copy 2.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "Star copy.png" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x", 16 | "filename" : "Star.png" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /KPCTabsControlDemo/Assets.xcassets/Star.imageset/Star copy 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onekiloparsec/KPCTabsControl/0ff97b8107cf587c161652ab98ccff894c9ee2db/KPCTabsControlDemo/Assets.xcassets/Star.imageset/Star copy 2.png -------------------------------------------------------------------------------- /KPCTabsControlDemo/Assets.xcassets/Star.imageset/Star copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onekiloparsec/KPCTabsControl/0ff97b8107cf587c161652ab98ccff894c9ee2db/KPCTabsControlDemo/Assets.xcassets/Star.imageset/Star copy.png -------------------------------------------------------------------------------- /KPCTabsControlDemo/Assets.xcassets/Star.imageset/Star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onekiloparsec/KPCTabsControl/0ff97b8107cf587c161652ab98ccff894c9ee2db/KPCTabsControlDemo/Assets.xcassets/Star.imageset/Star.png -------------------------------------------------------------------------------- /KPCTabsControlDemo/Assets.xcassets/Triangle.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Triangle copy 2.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "Triangle copy.png" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x", 16 | "filename" : "Triangle.png" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /KPCTabsControlDemo/Assets.xcassets/Triangle.imageset/Triangle copy 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onekiloparsec/KPCTabsControl/0ff97b8107cf587c161652ab98ccff894c9ee2db/KPCTabsControlDemo/Assets.xcassets/Triangle.imageset/Triangle copy 2.png -------------------------------------------------------------------------------- /KPCTabsControlDemo/Assets.xcassets/Triangle.imageset/Triangle copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onekiloparsec/KPCTabsControl/0ff97b8107cf587c161652ab98ccff894c9ee2db/KPCTabsControlDemo/Assets.xcassets/Triangle.imageset/Triangle copy.png -------------------------------------------------------------------------------- /KPCTabsControlDemo/Assets.xcassets/Triangle.imageset/Triangle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onekiloparsec/KPCTabsControl/0ff97b8107cf587c161652ab98ccff894c9ee2db/KPCTabsControlDemo/Assets.xcassets/Triangle.imageset/Triangle.png -------------------------------------------------------------------------------- /KPCTabsControlDemo/ColoredView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ColoredView.swift 3 | // KPCTabsControl 4 | // 5 | // Created by Christian Tietze on 13/08/16. 6 | // Licensed under the MIT License (see LICENSE file) 7 | // 8 | 9 | import Cocoa 10 | 11 | @IBDesignable open class ColoredView: NSView { 12 | 13 | @IBInspectable open var borderColor: NSColor? 14 | @IBInspectable open var backgroundColor: NSColor? = NSColor.blue 15 | 16 | override open func draw(_ dirtyRect: NSRect) { 17 | 18 | super.draw(dirtyRect) 19 | 20 | fillBackground() 21 | drawTopLine() 22 | } 23 | 24 | fileprivate func fillBackground() { 25 | 26 | guard let backgroundColor = self.backgroundColor else { return } 27 | 28 | backgroundColor.setFill() 29 | bounds.fill() 30 | } 31 | 32 | fileprivate func drawTopLine() { 33 | 34 | guard let borderColor = self.borderColor else { return } 35 | 36 | let line = NSBezierPath() 37 | line.move(to: NSPoint(x: 0, y: bounds.height)) 38 | line.line(to: NSPoint(x: bounds.width, y: bounds.height)) 39 | line.lineWidth = 1 40 | borderColor.setStroke() 41 | line.stroke() 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /KPCTabsControlDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSMinimumSystemVersion 26 | $(MACOSX_DEPLOYMENT_TARGET) 27 | NSHumanReadableCopyright 28 | Licensed under the MIT License (see LICENSE file) 29 | NSMainStoryboardFile 30 | Main 31 | NSPrincipalClass 32 | NSApplication 33 | 34 | 35 | -------------------------------------------------------------------------------- /KPCTabsControlDemo/PaneViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // KPCTabsControlDemo 4 | // 5 | // Created by Cédric Foellmi on 15/07/16. 6 | // Licensed under the MIT License (see LICENSE file) 7 | // 8 | 9 | import Cocoa 10 | import KPCTabsControl 11 | 12 | // We need a class (rather than a struct or a tuple – which would be nice) because TabsControlDelegate has 13 | // @optional methods. To have such optionaling, we need to mark the protocol as @objc. With such marking, 14 | // we can't have pure-Swift 'Any' return object or argument. Buh... 15 | 16 | class Item { 17 | var title: String = "" 18 | var icon: NSImage? 19 | var menu: NSMenu? 20 | var altIcon: NSImage? 21 | var selectable: Bool 22 | 23 | init(title: String, icon: NSImage?, menu: NSMenu?, altIcon: NSImage?, selectable: Bool = true) { 24 | self.title = title 25 | self.icon = icon 26 | self.menu = menu 27 | self.altIcon = altIcon 28 | self.selectable = selectable 29 | } 30 | } 31 | 32 | extension Item: Equatable { } 33 | 34 | func ==(lhs: Item, rhs: Item) -> Bool { 35 | return lhs.title == rhs.title 36 | } 37 | 38 | class PaneViewController: NSViewController, TabsControlDataSource, TabsControlDelegate { 39 | 40 | @IBOutlet weak var tabsBar: TabsControl? 41 | @IBOutlet weak var useFullWidthTabsCheckButton: NSButton? 42 | @IBOutlet weak var tabWidthsLabel: NSTextField? 43 | 44 | var items: [Item] = [] 45 | override var title: String? { 46 | didSet { self.tabWidthsLabel?.stringValue = self.title! } 47 | } 48 | 49 | override func viewDidLoad() { 50 | super.viewDidLoad() 51 | 52 | self.tabsBar?.dataSource = self 53 | self.tabsBar?.delegate = self 54 | self.tabsBar?.reloadTabs() 55 | } 56 | 57 | // MARK: TabsControlDataSource 58 | 59 | func tabsControlNumberOfTabs(_ control: TabsControl) -> Int { 60 | return self.items.count 61 | } 62 | 63 | func tabsControl(_ control: TabsControl, itemAtIndex index: Int) -> AnyObject { 64 | return self.items[index] 65 | } 66 | 67 | func tabsControl(_ control: TabsControl, titleForItem item: AnyObject) -> String { 68 | return (item as! Item).title 69 | } 70 | 71 | // MARK: TabsControlDataSource : Optionals 72 | 73 | func tabsControl(_ control: TabsControl, menuForItem item: AnyObject) -> NSMenu? { 74 | return (item as! Item).menu 75 | } 76 | 77 | func tabsControl(_ control: TabsControl, iconForItem item: AnyObject) -> NSImage? { 78 | return (item as! Item).icon 79 | } 80 | 81 | func tabsControl(_ control: TabsControl, titleAlternativeIconForItem item: AnyObject) -> NSImage? { 82 | return (item as! Item).altIcon 83 | } 84 | 85 | // MARK: TabsControlDelegate 86 | 87 | func tabsControl(_ control: TabsControl, canReorderItem item: AnyObject) -> Bool { 88 | return true 89 | } 90 | 91 | func tabsControl(_ control: TabsControl, didReorderItems items: [AnyObject]) { 92 | self.items = items.map { $0 as! Item } 93 | } 94 | 95 | func tabsControl(_ control: TabsControl, canEditTitleOfItem: AnyObject) -> Bool { 96 | return true 97 | } 98 | 99 | func tabsControl(_ control: TabsControl, setTitle newTitle: String, forItem item: AnyObject) { 100 | let typedItem = item as! Item 101 | let titles = self.items.map { $0.title } 102 | let index = titles.firstIndex(of: typedItem.title)! 103 | 104 | let newItem = Item(title: newTitle, icon: typedItem.icon, menu: typedItem.menu, altIcon: typedItem.altIcon) 105 | let range = index.. Bool { 110 | return (item as! Item).selectable 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /KPCTabsControlDemo/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // KPCTabsControl 4 | // 5 | // Created by Cédric Foellmi on 15/07/16. 6 | // Licensed under the MIT License (see LICENSE file) 7 | // 8 | 9 | import Cocoa 10 | import KPCTabsControl 11 | 12 | class ViewController: NSViewController { 13 | @IBOutlet var paneDefault: PaneViewController? 14 | @IBOutlet var paneChrome: PaneViewController? 15 | @IBOutlet var paneSafari: PaneViewController? 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | 20 | self.paneDefault?.title = "Default (~Numbers.app)" 21 | self.paneDefault?.tabsBar?.style = DefaultStyle() 22 | 23 | let tab2Menu = NSMenu() 24 | tab2Menu.addItem(withTitle: "Action 1: AddTabItem", action: #selector(ViewController.addTabItem), keyEquivalent: "") 25 | tab2Menu.addItem(withTitle: "Action 2: nil", action: nil, keyEquivalent: "") 26 | 27 | self.paneDefault?.items = [Item(title: "Default 1", icon: NSImage(named: NSImage.Name("Star")), menu: nil, altIcon: nil, selectable: false), 28 | Item(title: "Default 2", icon: NSImage(named: NSImage.Name("Oval")), menu: tab2Menu, altIcon: nil), 29 | Item(title: "Default 3 Long Title", icon: nil, menu: nil, altIcon: NSImage(named: NSImage.Name("ArrowTemplate"))), 30 | Item(title: "Default 4 Longish Title", icon: nil, menu: nil, altIcon: nil), 31 | Item(title: "Default 5", icon: nil, menu: nil, altIcon: nil)] 32 | 33 | self.paneDefault?.tabsBar?.reloadTabs() 34 | 35 | self.paneChrome?.title = "Chrome" 36 | self.paneChrome?.tabsBar?.style = ChromeStyle() 37 | 38 | self.paneChrome?.items = [Item(title: "Chrome 1", icon: NSImage(named: NSImage.Name("Star")), menu: nil, altIcon: nil, selectable: false), 39 | Item(title: "Chrome 2", icon: NSImage(named: NSImage.Name("Triangle")), menu: nil, altIcon: nil), 40 | Item(title: "Chrome 3", icon: NSImage(named: NSImage.Name("Spiral")), menu: nil, altIcon: nil), 41 | Item(title: "Chrome 4", icon: NSImage(named: NSImage.Name("Polygon")), menu: nil, altIcon: nil)] 42 | 43 | let style = self.paneChrome?.tabsBar?.style as! ThemedStyle 44 | (self.paneChrome?.view as? ColoredView)?.backgroundColor = style.theme.selectedTabButtonTheme.backgroundColor 45 | 46 | self.paneChrome?.tabsBar?.reloadTabs() 47 | self.paneChrome?.tabsBar?.selectItemAtIndex(3) 48 | 49 | self.paneSafari?.title = "Safari" 50 | self.paneSafari?.tabsBar?.style = SafariStyle() 51 | 52 | self.paneSafari?.items = [Item(title: "Safari 1", icon: NSImage(named: NSImage.Name("Star")), menu: nil, altIcon: nil), 53 | Item(title: "Safari 2", icon: NSImage(named: NSImage.Name("Triangle")), menu: nil, altIcon: nil), 54 | Item(title: "Safari 3", icon: NSImage(named: NSImage.Name("Spiral")), menu: nil, altIcon: nil)] 55 | 56 | self.paneSafari?.tabsBar?.reloadTabs() 57 | self.paneSafari?.tabsBar?.selectItemAtIndex(1) 58 | } 59 | 60 | @objc func addTabItem() { 61 | let title = "New Tab #\(paneDefault!.items.count + 1)" 62 | print("add tab item \(title)...") 63 | paneDefault?.items.append(Item(title: title, icon: nil, menu: nil, altIcon: nil)) 64 | paneDefault?.tabsBar?.reloadTabs() 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /KPCTabsControlTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /KPCTabsControlTests/KPCTabsControlTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KPCTabsControlTests.swift 3 | // KPCTabsControlTests 4 | // 5 | // Created by onekiloparsec on 09/11/2018. 6 | // Copyright © 2018 Cédric Foellmi. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class KPCTabsControlTests: XCTestCase { 12 | 13 | override func setUp() { 14 | // Put setup code here. This method is called before the invocation of each test method in the class. 15 | } 16 | 17 | override func tearDown() { 18 | // Put teardown code here. This method is called after the invocation of each test method in the class. 19 | } 20 | 21 | func testExample() { 22 | // This is an example of a functional test case. 23 | // Use XCTAssert and related functions to verify your tests produce the correct results. 24 | } 25 | 26 | func testPerformanceExample() { 27 | // This is an example of a performance test case. 28 | self.measure { 29 | // Put the code you want to measure the time of here. 30 | } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2016 Cédric Foellmi (@onekiloparsec) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.4 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "KPCTabsControl", 8 | platforms: [ 9 | .macOS(.v10_14) 10 | ], 11 | products: [ 12 | // Products define the executables and libraries produced by a package, and make them visible to other packages. 13 | .library( 14 | name: "KPCTabsControl", 15 | type: .dynamic, 16 | targets: ["KPCTabsControl"] 17 | ) 18 | ], 19 | targets: [ 20 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 21 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 22 | .target( 23 | name: "KPCTabsControl", 24 | path: "KPCTabsControl", 25 | resources: [ 26 | .copy("Resources") 27 | ], 28 | swiftSettings: [.define("SwiftPackage")] 29 | ) 30 | ] 31 | ) 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 |

5 | KPCTabsControl • 6 | KPCJumpBarControl • 7 | KPCSplitPanes • 8 | KPCAppTermination • 9 | KPCSearchableOutlineView • 10 | KPCImportSheetController 11 |

12 | 13 | ------- 14 | 15 | KPCTabsControl 16 | ============== 17 | 18 | ![](https://img.shields.io/badge/Swift-5.0-blue.svg?style=flat) 19 | [![Build Status](http://img.shields.io/travis/onekiloparsec/KPCTabsControl.svg?style=flat)](https://travis-ci.org/onekiloparsec/KPCTabsControl) 20 | ![Version](https://img.shields.io/cocoapods/v/KPCTabsControl.svg?style=flat) 21 | ![License](https://img.shields.io/cocoapods/l/KPCTabsControl.svg?style=flat) 22 | ![Platform](https://img.shields.io/cocoapods/p/KPCTabsControl.svg?style=flat) 23 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 24 | [![Codewake](https://www.codewake.com/badges/ask_question.svg)](https://www.codewake.com/p/kpctabscontrol) 25 | 26 | A multi-tabs control first designed to look and behave like the tab control in Apple's Numbers spreadsheet, with enhanced capabilities, but now with new tab styles, such as Chrome & Safari, as well as custom ones. 27 | 28 | On master, you'll find the latest Swift 5 releases. 29 | 30 | - If you need to stay with Swift 4, switch to the `swift-4.2` branch. 31 | - If you need to stay with Swift 3, switch to the `swift-3.1` branch. 32 | - If you need to stay with Swift 2, switch to the `swift-2.2` branch. 33 | 34 | 35 | ![Demo Tabs Screenshot](./assets/KPCTabsControl2Screenshot.png?raw=true) 36 | 37 | KPCTabsControl provides the following features: 38 | 39 | * Custom styles and themes! Default (Numbers.app-like), Chrome and Safari are provided. But you can easily write your own! 40 | * Styles & themes comprise title styles, title editor style, (un)selected/unselectable backgrounds, borders, colors, fonts etc. 41 | * Common dataSource/delegate Cocoa APIs 42 | * Tabs can span the whole view width, or be flexible inside min&max. 43 | * Tabs can be reordered, and renamed in place. 44 | * When provided, the title can be replaced by an alternative icon when the width is too narrow for the title to be drawn. 45 | 46 | 47 | ![Demo Auxiliary Icons](./assets/KPCTabsControl2Demo.gif?raw=true) 48 | 49 | 50 | Documentation 51 | ======= 52 | 53 | The documentation generated from the code itself is available at [http://onekiloparsec.github.io/KPCTabsControl](http://onekiloparsec.github.io/KPCTabsControl). 54 | 55 | 56 | Installation 57 | ------------ 58 | 59 | Using [Carthage](https://github.com/Carthage/Carthage): add `github "onekiloparsec/KPCTabsControl"` to your `Cartfile` and then run `carthage update`. 60 | 61 | Using [CocoaPods](http://cocoapods.org/): `pod 'KPCTabsControl'`. 62 | 63 | 64 | Usage 65 | ----- 66 | 67 | KPCTabsControl is designed for you to use only the `KPCTabsControl` class, and its associated data source methods. Simply place a `NSView` in a xib, where you need tabs, change its class to `KPCTabsControl` and assign its dataSource property. Then implement the data source methods in your controller. 68 | 69 | You can also assign a delegate if you want to play with the editing and the reordering of the tab titles. 70 | 71 | 72 | Authors 73 | ------ 74 | 75 | [Cédric Foellmi](https://github.com/onekiloparsec) ([@onekiloparsec](https://twitter.com/onekiloparsec))
76 | [Christian Tietze](https://github.com/DivineDominion) ([@ctietze](https://twitter.com/ctietze)) 77 | 78 | 79 | LICENSE & NOTES 80 | --------------- 81 | 82 | KPCTabsControl is licensed under the MIT license and hosted on GitHub at https://github.com/onekiloparsec/KPCTabsControl/ Fork the project and feel free to send pull requests with your changes! 83 | 84 | 85 | -------------------------------------------------------------------------------- /Resources/KPCPullDownTemplate.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onekiloparsec/KPCTabsControl/0ff97b8107cf587c161652ab98ccff894c9ee2db/Resources/KPCPullDownTemplate.pdf -------------------------------------------------------------------------------- /Resources/KPCTabLeftTemplate.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onekiloparsec/KPCTabsControl/0ff97b8107cf587c161652ab98ccff894c9ee2db/Resources/KPCTabLeftTemplate.pdf -------------------------------------------------------------------------------- /Resources/KPCTabPlusTemplate.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onekiloparsec/KPCTabsControl/0ff97b8107cf587c161652ab98ccff894c9ee2db/Resources/KPCTabPlusTemplate.pdf -------------------------------------------------------------------------------- /Resources/KPCTabRightTemplate.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onekiloparsec/KPCTabsControl/0ff97b8107cf587c161652ab98ccff894c9ee2db/Resources/KPCTabRightTemplate.pdf -------------------------------------------------------------------------------- /assets/1kpcProComponents.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onekiloparsec/KPCTabsControl/0ff97b8107cf587c161652ab98ccff894c9ee2db/assets/1kpcProComponents.png -------------------------------------------------------------------------------- /assets/KPCTabsControl2Demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onekiloparsec/KPCTabsControl/0ff97b8107cf587c161652ab98ccff894c9ee2db/assets/KPCTabsControl2Demo.gif -------------------------------------------------------------------------------- /assets/KPCTabsControl2Screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onekiloparsec/KPCTabsControl/0ff97b8107cf587c161652ab98ccff894c9ee2db/assets/KPCTabsControl2Screenshot.png -------------------------------------------------------------------------------- /assets/KPCTabsControlAuxiliaryIconMovie.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onekiloparsec/KPCTabsControl/0ff97b8107cf587c161652ab98ccff894c9ee2db/assets/KPCTabsControlAuxiliaryIconMovie.gif -------------------------------------------------------------------------------- /assets/KPCTabsControlScreenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onekiloparsec/KPCTabsControl/0ff97b8107cf587c161652ab98ccff894c9ee2db/assets/KPCTabsControlScreenshot1.png -------------------------------------------------------------------------------- /docs/Extensions.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Extensions Reference 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 |
16 |

KPCTabsControl Docs (25% documented)

17 |

View on GitHub

18 |

Install in Dash

19 |
20 |
21 |
22 | 27 |
28 |
29 | 148 |
149 |
150 |
151 |

Extensions

152 |

The following extensions are available globally.

153 | 154 |
155 |
156 |
157 |
    158 |
  • 159 |
    160 | 161 | 162 | 163 | Offset 164 | 165 |
    166 |
    167 |
    168 |
    169 |
    170 |
    171 | 172 | See more 173 |
    174 |
    175 |

    Declaration

    176 |
    177 |

    Swift

    178 |
    public typealias Offset = NSPoint
    179 | 180 |
    181 |
    182 |
    183 | Show on GitHub 184 |
    185 |
    186 |
    187 |
  • 188 |
  • 189 |
    190 | 191 | 192 | 193 | NSRect 194 | 195 |
    196 |
    197 |
    198 |
    199 |
    200 |
    201 |

    Undocumented

    202 | 203 | See more 204 |
    205 |
    206 |
    207 |
  • 208 |
209 |
210 |
211 |
212 | 216 |
217 |
218 | 219 | 220 | 221 | -------------------------------------------------------------------------------- /docs/Extensions/NSRect.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | NSRect Extension Reference 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 |

KPCTabsControl Docs (25% documented)

18 |

View on GitHub

19 |

Install in Dash

20 |
21 |
22 |
23 | 28 |
29 |
30 | 149 |
150 |
151 |
152 |

NSRect

153 |

Undocumented

154 | 155 |
156 |
157 |
158 |
    159 |
  • 160 |
    161 | 162 | 163 | 164 | shrinkBy(dx:dy:) 165 | 166 |
    167 |
    168 |
    169 |
    170 |
    171 |
    172 |

    Change width and height by -dx and -dy.

    173 | 174 |
    175 |
    176 |

    Declaration

    177 |
    178 |

    Swift

    179 |
    func shrinkBy(dx: CGFloat, dy: CGFloat) -> NSRect
    180 | 181 |
    182 |
    183 |
    184 | Show on GitHub 185 |
    186 |
    187 |
    188 |
  • 189 |
190 |
191 |
192 |
193 | 197 |
198 |
199 | 200 | 201 | 202 | -------------------------------------------------------------------------------- /docs/Global Variables.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Global Variables Reference 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 |
16 |

KPCTabsControl Docs (25% documented)

17 |

View on GitHub

18 |

Install in Dash

19 |
20 |
21 |
22 | 27 |
28 |
29 | 148 |
149 |
150 |
151 |

Global Variables

152 |

The following global variables are available globally.

153 | 154 |
155 |
156 |
157 |
    158 |
  • 159 |
    160 | 161 | 162 | 163 | TabsControlSelectionDidChangeNotification 164 | 165 |
    166 |
    167 |
    168 |
    169 |
    170 |
    171 |

    The name of the notification upon the selection of a new tab.

    172 | 173 |
    174 |
    175 |

    Declaration

    176 |
    177 |

    Swift

    178 |
    public let TabsControlSelectionDidChangeNotification = "TabsControlSelectionDidChangeNotification"
    179 | 180 |
    181 |
    182 |
    183 | Show on GitHub 184 |
    185 |
    186 |
    187 |
  • 188 |
189 |
190 |
191 |
192 | 196 |
197 |
198 | 199 | 200 | 201 | -------------------------------------------------------------------------------- /docs/Typealiases.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Typealiases Reference 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 |
16 |

KPCTabsControl Docs (25% documented)

17 |

View on GitHub

18 |

Install in Dash

19 |
20 |
21 |
22 | 27 |
28 |
29 | 148 |
149 |
150 |
151 |

Typealiases

152 |

The following typealiases are available globally.

153 | 154 |
155 |
156 |
157 |
    158 |
  • 159 |
    160 | 161 | 162 | 163 | Offset 164 | 165 |
    166 |
    167 |
    168 |
    169 |
    170 |
    171 |

    Offset is a simple NSPoint typealias to increase readability.

    172 | 173 |
    174 |
    175 |

    Declaration

    176 |
    177 |

    Swift

    178 |
    public typealias Offset = NSPoint
    179 | 180 |
    181 |
    182 |
    183 | Show on GitHub 184 |
    185 |
    186 |
    187 |
  • 188 |
189 |
190 |
191 |
192 | 196 |
197 |
198 | 199 | 200 | 201 | -------------------------------------------------------------------------------- /docs/badge.svg: -------------------------------------------------------------------------------- 1 | documentationdocumentation25%25% -------------------------------------------------------------------------------- /docs/css/highlight.css: -------------------------------------------------------------------------------- 1 | /* Credit to https://gist.github.com/wataru420/2048287 */ 2 | .highlight { 3 | /* Comment */ 4 | /* Error */ 5 | /* Keyword */ 6 | /* Operator */ 7 | /* Comment.Multiline */ 8 | /* Comment.Preproc */ 9 | /* Comment.Single */ 10 | /* Comment.Special */ 11 | /* Generic.Deleted */ 12 | /* Generic.Deleted.Specific */ 13 | /* Generic.Emph */ 14 | /* Generic.Error */ 15 | /* Generic.Heading */ 16 | /* Generic.Inserted */ 17 | /* Generic.Inserted.Specific */ 18 | /* Generic.Output */ 19 | /* Generic.Prompt */ 20 | /* Generic.Strong */ 21 | /* Generic.Subheading */ 22 | /* Generic.Traceback */ 23 | /* Keyword.Constant */ 24 | /* Keyword.Declaration */ 25 | /* Keyword.Pseudo */ 26 | /* Keyword.Reserved */ 27 | /* Keyword.Type */ 28 | /* Literal.Number */ 29 | /* Literal.String */ 30 | /* Name.Attribute */ 31 | /* Name.Builtin */ 32 | /* Name.Class */ 33 | /* Name.Constant */ 34 | /* Name.Entity */ 35 | /* Name.Exception */ 36 | /* Name.Function */ 37 | /* Name.Namespace */ 38 | /* Name.Tag */ 39 | /* Name.Variable */ 40 | /* Operator.Word */ 41 | /* Text.Whitespace */ 42 | /* Literal.Number.Float */ 43 | /* Literal.Number.Hex */ 44 | /* Literal.Number.Integer */ 45 | /* Literal.Number.Oct */ 46 | /* Literal.String.Backtick */ 47 | /* Literal.String.Char */ 48 | /* Literal.String.Doc */ 49 | /* Literal.String.Double */ 50 | /* Literal.String.Escape */ 51 | /* Literal.String.Heredoc */ 52 | /* Literal.String.Interpol */ 53 | /* Literal.String.Other */ 54 | /* Literal.String.Regex */ 55 | /* Literal.String.Single */ 56 | /* Literal.String.Symbol */ 57 | /* Name.Builtin.Pseudo */ 58 | /* Name.Variable.Class */ 59 | /* Name.Variable.Global */ 60 | /* Name.Variable.Instance */ 61 | /* Literal.Number.Integer.Long */ } 62 | .highlight .c { 63 | color: #999988; 64 | font-style: italic; } 65 | .highlight .err { 66 | color: #a61717; 67 | background-color: #e3d2d2; } 68 | .highlight .k { 69 | color: #000000; 70 | font-weight: bold; } 71 | .highlight .o { 72 | color: #000000; 73 | font-weight: bold; } 74 | .highlight .cm { 75 | color: #999988; 76 | font-style: italic; } 77 | .highlight .cp { 78 | color: #999999; 79 | font-weight: bold; } 80 | .highlight .c1 { 81 | color: #999988; 82 | font-style: italic; } 83 | .highlight .cs { 84 | color: #999999; 85 | font-weight: bold; 86 | font-style: italic; } 87 | .highlight .gd { 88 | color: #000000; 89 | background-color: #ffdddd; } 90 | .highlight .gd .x { 91 | color: #000000; 92 | background-color: #ffaaaa; } 93 | .highlight .ge { 94 | color: #000000; 95 | font-style: italic; } 96 | .highlight .gr { 97 | color: #aa0000; } 98 | .highlight .gh { 99 | color: #999999; } 100 | .highlight .gi { 101 | color: #000000; 102 | background-color: #ddffdd; } 103 | .highlight .gi .x { 104 | color: #000000; 105 | background-color: #aaffaa; } 106 | .highlight .go { 107 | color: #888888; } 108 | .highlight .gp { 109 | color: #555555; } 110 | .highlight .gs { 111 | font-weight: bold; } 112 | .highlight .gu { 113 | color: #aaaaaa; } 114 | .highlight .gt { 115 | color: #aa0000; } 116 | .highlight .kc { 117 | color: #000000; 118 | font-weight: bold; } 119 | .highlight .kd { 120 | color: #000000; 121 | font-weight: bold; } 122 | .highlight .kp { 123 | color: #000000; 124 | font-weight: bold; } 125 | .highlight .kr { 126 | color: #000000; 127 | font-weight: bold; } 128 | .highlight .kt { 129 | color: #445588; } 130 | .highlight .m { 131 | color: #009999; } 132 | .highlight .s { 133 | color: #d14; } 134 | .highlight .na { 135 | color: #008080; } 136 | .highlight .nb { 137 | color: #0086B3; } 138 | .highlight .nc { 139 | color: #445588; 140 | font-weight: bold; } 141 | .highlight .no { 142 | color: #008080; } 143 | .highlight .ni { 144 | color: #800080; } 145 | .highlight .ne { 146 | color: #990000; 147 | font-weight: bold; } 148 | .highlight .nf { 149 | color: #990000; } 150 | .highlight .nn { 151 | color: #555555; } 152 | .highlight .nt { 153 | color: #000080; } 154 | .highlight .nv { 155 | color: #008080; } 156 | .highlight .ow { 157 | color: #000000; 158 | font-weight: bold; } 159 | .highlight .w { 160 | color: #bbbbbb; } 161 | .highlight .mf { 162 | color: #009999; } 163 | .highlight .mh { 164 | color: #009999; } 165 | .highlight .mi { 166 | color: #009999; } 167 | .highlight .mo { 168 | color: #009999; } 169 | .highlight .sb { 170 | color: #d14; } 171 | .highlight .sc { 172 | color: #d14; } 173 | .highlight .sd { 174 | color: #d14; } 175 | .highlight .s2 { 176 | color: #d14; } 177 | .highlight .se { 178 | color: #d14; } 179 | .highlight .sh { 180 | color: #d14; } 181 | .highlight .si { 182 | color: #d14; } 183 | .highlight .sx { 184 | color: #d14; } 185 | .highlight .sr { 186 | color: #009926; } 187 | .highlight .s1 { 188 | color: #d14; } 189 | .highlight .ss { 190 | color: #990073; } 191 | .highlight .bp { 192 | color: #999999; } 193 | .highlight .vc { 194 | color: #008080; } 195 | .highlight .vg { 196 | color: #008080; } 197 | .highlight .vi { 198 | color: #008080; } 199 | .highlight .il { 200 | color: #009999; } 201 | -------------------------------------------------------------------------------- /docs/css/jazzy.css: -------------------------------------------------------------------------------- 1 | html, body, div, span, h1, h3, h4, p, a, code, em, img, ul, li, table, tbody, tr, td { 2 | background: transparent; 3 | border: 0; 4 | margin: 0; 5 | outline: 0; 6 | padding: 0; 7 | vertical-align: baseline; } 8 | 9 | body { 10 | background-color: #f2f2f2; 11 | font-family: Helvetica, freesans, Arial, sans-serif; 12 | font-size: 14px; 13 | -webkit-font-smoothing: subpixel-antialiased; 14 | word-wrap: break-word; } 15 | 16 | h1, h2, h3 { 17 | margin-top: 0.8em; 18 | margin-bottom: 0.3em; 19 | font-weight: 100; 20 | color: black; } 21 | 22 | h1 { 23 | font-size: 2.5em; } 24 | 25 | h2 { 26 | font-size: 2em; 27 | border-bottom: 1px solid #e2e2e2; } 28 | 29 | h4 { 30 | font-size: 13px; 31 | line-height: 1.5; 32 | margin-top: 21px; } 33 | 34 | h5 { 35 | font-size: 1.1em; } 36 | 37 | h6 { 38 | font-size: 1.1em; 39 | color: #777; } 40 | 41 | .section-name { 42 | color: gray; 43 | display: block; 44 | font-family: Helvetica; 45 | font-size: 22px; 46 | font-weight: 100; 47 | margin-bottom: 15px; } 48 | 49 | pre, code { 50 | font: 0.95em Menlo, monospace; 51 | color: #777; 52 | word-wrap: normal; } 53 | 54 | p code, li code { 55 | background-color: #eee; 56 | padding: 2px 4px; 57 | border-radius: 4px; } 58 | 59 | a { 60 | color: #0088cc; 61 | text-decoration: none; } 62 | 63 | ul { 64 | padding-left: 15px; } 65 | 66 | li { 67 | line-height: 1.8em; } 68 | 69 | img { 70 | max-width: 100%; } 71 | 72 | blockquote { 73 | margin-left: 0; 74 | padding: 0 10px; 75 | border-left: 4px solid #ccc; } 76 | 77 | .content-wrapper { 78 | margin: 0 auto; 79 | width: 980px; } 80 | 81 | header { 82 | font-size: 0.85em; 83 | line-height: 26px; 84 | background-color: #414141; 85 | position: fixed; 86 | width: 100%; 87 | z-index: 1; } 88 | header img { 89 | padding-right: 6px; 90 | vertical-align: -4px; 91 | height: 16px; } 92 | header a { 93 | color: #fff; } 94 | header p { 95 | float: left; 96 | color: #999; } 97 | header .header-right { 98 | float: right; 99 | margin-left: 16px; } 100 | 101 | #breadcrumbs { 102 | background-color: #f2f2f2; 103 | height: 27px; 104 | padding-top: 17px; 105 | position: fixed; 106 | width: 100%; 107 | z-index: 1; 108 | margin-top: 26px; } 109 | #breadcrumbs #carat { 110 | height: 10px; 111 | margin: 0 5px; } 112 | 113 | .sidebar { 114 | background-color: #f9f9f9; 115 | border: 1px solid #e2e2e2; 116 | overflow-y: auto; 117 | overflow-x: hidden; 118 | position: fixed; 119 | top: 70px; 120 | bottom: 0; 121 | width: 230px; 122 | word-wrap: normal; } 123 | 124 | .nav-groups { 125 | list-style-type: none; 126 | background: #fff; 127 | padding-left: 0; } 128 | 129 | .nav-group-name { 130 | border-bottom: 1px solid #e2e2e2; 131 | font-size: 1.1em; 132 | font-weight: 100; 133 | padding: 15px 0 15px 20px; } 134 | .nav-group-name > a { 135 | color: #333; } 136 | 137 | .nav-group-tasks { 138 | margin-top: 5px; } 139 | 140 | .nav-group-task { 141 | font-size: 0.9em; 142 | list-style-type: none; 143 | white-space: nowrap; } 144 | .nav-group-task a { 145 | color: #888; } 146 | 147 | .main-content { 148 | background-color: #fff; 149 | border: 1px solid #e2e2e2; 150 | margin-left: 246px; 151 | position: absolute; 152 | overflow: hidden; 153 | padding-bottom: 60px; 154 | top: 70px; 155 | width: 734px; } 156 | .main-content p, .main-content a, .main-content code, .main-content em, .main-content ul, .main-content table, .main-content blockquote { 157 | margin-bottom: 1em; } 158 | .main-content p { 159 | line-height: 1.8em; } 160 | .main-content section .section:first-child { 161 | margin-top: 0; 162 | padding-top: 0; } 163 | .main-content section .task-group-section .task-group:first-of-type { 164 | padding-top: 10px; } 165 | .main-content section .task-group-section .task-group:first-of-type .section-name { 166 | padding-top: 15px; } 167 | 168 | .section { 169 | padding: 0 25px; } 170 | 171 | .highlight { 172 | background-color: #eee; 173 | padding: 10px 12px; 174 | border: 1px solid #e2e2e2; 175 | border-radius: 4px; 176 | overflow-x: auto; } 177 | 178 | .declaration .highlight { 179 | overflow-x: initial; 180 | padding: 0 40px 40px 0; 181 | margin-bottom: -25px; 182 | background-color: transparent; 183 | border: none; } 184 | 185 | .section-name { 186 | margin: 0; 187 | margin-left: 18px; } 188 | 189 | .task-group-section { 190 | padding-left: 6px; 191 | border-top: 1px solid #e2e2e2; } 192 | 193 | .task-group { 194 | padding-top: 0px; } 195 | 196 | .task-name-container a[name]:before { 197 | content: ""; 198 | display: block; 199 | padding-top: 70px; 200 | margin: -70px 0 0; } 201 | 202 | .item { 203 | padding-top: 8px; 204 | width: 100%; 205 | list-style-type: none; } 206 | .item a[name]:before { 207 | content: ""; 208 | display: block; 209 | padding-top: 70px; 210 | margin: -70px 0 0; } 211 | .item code { 212 | background-color: transparent; 213 | padding: 0; } 214 | .item .token { 215 | padding-left: 3px; 216 | margin-left: 15px; 217 | font-size: 11.9px; } 218 | .item .declaration-note { 219 | font-size: .85em; 220 | color: gray; 221 | font-style: italic; } 222 | 223 | .pointer-container { 224 | border-bottom: 1px solid #e2e2e2; 225 | left: -23px; 226 | padding-bottom: 13px; 227 | position: relative; 228 | width: 110%; } 229 | 230 | .pointer { 231 | background: #f9f9f9; 232 | border-left: 1px solid #e2e2e2; 233 | border-top: 1px solid #e2e2e2; 234 | height: 12px; 235 | left: 21px; 236 | top: -7px; 237 | -webkit-transform: rotate(45deg); 238 | -moz-transform: rotate(45deg); 239 | -o-transform: rotate(45deg); 240 | transform: rotate(45deg); 241 | position: absolute; 242 | width: 12px; } 243 | 244 | .height-container { 245 | display: none; 246 | left: -25px; 247 | padding: 0 25px; 248 | position: relative; 249 | width: 100%; 250 | overflow: hidden; } 251 | .height-container .section { 252 | background: #f9f9f9; 253 | border-bottom: 1px solid #e2e2e2; 254 | left: -25px; 255 | position: relative; 256 | width: 100%; 257 | padding-top: 10px; 258 | padding-bottom: 5px; } 259 | 260 | .aside, .language { 261 | padding: 6px 12px; 262 | margin: 12px 0; 263 | border-left: 5px solid #dddddd; 264 | overflow-y: hidden; } 265 | .aside .aside-title, .language .aside-title { 266 | font-size: 9px; 267 | letter-spacing: 2px; 268 | text-transform: uppercase; 269 | padding-bottom: 0; 270 | margin: 0; 271 | color: #aaa; 272 | -webkit-user-select: none; } 273 | .aside p:last-child, .language p:last-child { 274 | margin-bottom: 0; } 275 | 276 | .language { 277 | border-left: 5px solid #cde9f4; } 278 | .language .aside-title { 279 | color: #4b8afb; } 280 | 281 | .aside-warning { 282 | border-left: 5px solid #ff6666; } 283 | .aside-warning .aside-title { 284 | color: #ff0000; } 285 | 286 | .graybox { 287 | border-collapse: collapse; 288 | width: 100%; } 289 | .graybox p { 290 | margin: 0; 291 | word-break: break-word; 292 | min-width: 50px; } 293 | .graybox td { 294 | border: 1px solid #e2e2e2; 295 | padding: 5px 25px 5px 10px; 296 | vertical-align: middle; } 297 | .graybox tr td:first-of-type { 298 | text-align: right; 299 | padding: 7px; 300 | vertical-align: top; 301 | word-break: normal; 302 | width: 40px; } 303 | 304 | .slightly-smaller { 305 | font-size: 0.9em; } 306 | 307 | #footer { 308 | position: absolute; 309 | bottom: 10px; 310 | margin-left: 25px; } 311 | #footer p { 312 | margin: 0; 313 | color: #aaa; 314 | font-size: 0.8em; } 315 | 316 | html.dash header, html.dash #breadcrumbs, html.dash .sidebar { 317 | display: none; } 318 | html.dash .main-content { 319 | width: 980px; 320 | margin-left: 0; 321 | border: none; 322 | width: 100%; 323 | top: 0; 324 | padding-bottom: 0; } 325 | html.dash .height-container { 326 | display: block; } 327 | html.dash .item .token { 328 | margin-left: 0; } 329 | html.dash .content-wrapper { 330 | width: auto; } 331 | html.dash #footer { 332 | position: static; } 333 | -------------------------------------------------------------------------------- /docs/docsets/KPCTabsControl.docset/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleIdentifier 6 | com.jazzy.kpctabscontrol 7 | CFBundleName 8 | KPCTabsControl 9 | DocSetPlatformFamily 10 | kpctabscontrol 11 | isDashDocset 12 | 13 | dashIndexFilePath 14 | index.html 15 | isJavaScriptEnabled 16 | 17 | DashDocSetFamily 18 | dashtoc 19 | 20 | 21 | -------------------------------------------------------------------------------- /docs/docsets/KPCTabsControl.docset/Contents/Resources/Documents/Extensions.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Extensions Reference 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 |
16 |

KPCTabsControl Docs (25% documented)

17 |

View on GitHub

18 |

Install in Dash

19 |
20 |
21 |
22 | 27 |
28 |
29 | 148 |
149 |
150 |
151 |

Extensions

152 |

The following extensions are available globally.

153 | 154 |
155 |
156 |
157 |
    158 |
  • 159 |
    160 | 161 | 162 | 163 | Offset 164 | 165 |
    166 |
    167 |
    168 |
    169 |
    170 |
    171 | 172 | See more 173 |
    174 |
    175 |

    Declaration

    176 |
    177 |

    Swift

    178 |
    public typealias Offset = NSPoint
    179 | 180 |
    181 |
    182 |
    183 | Show on GitHub 184 |
    185 |
    186 |
    187 |
  • 188 |
  • 189 |
    190 | 191 | 192 | 193 | NSRect 194 | 195 |
    196 |
    197 |
    198 |
    199 |
    200 |
    201 |

    Undocumented

    202 | 203 | See more 204 |
    205 |
    206 |
    207 |
  • 208 |
209 |
210 |
211 |
212 | 216 |
217 |
218 | 219 | 220 | 221 | -------------------------------------------------------------------------------- /docs/docsets/KPCTabsControl.docset/Contents/Resources/Documents/Extensions/NSRect.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | NSRect Extension Reference 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 |

KPCTabsControl Docs (25% documented)

18 |

View on GitHub

19 |

Install in Dash

20 |
21 |
22 |
23 | 28 |
29 |
30 | 149 |
150 |
151 |
152 |

NSRect

153 |

Undocumented

154 | 155 |
156 |
157 |
158 |
    159 |
  • 160 |
    161 | 162 | 163 | 164 | shrinkBy(dx:dy:) 165 | 166 |
    167 |
    168 |
    169 |
    170 |
    171 |
    172 |

    Change width and height by -dx and -dy.

    173 | 174 |
    175 |
    176 |

    Declaration

    177 |
    178 |

    Swift

    179 |
    func shrinkBy(dx: CGFloat, dy: CGFloat) -> NSRect
    180 | 181 |
    182 |
    183 |
    184 | Show on GitHub 185 |
    186 |
    187 |
    188 |
  • 189 |
190 |
191 |
192 |
193 | 197 |
198 |
199 | 200 | 201 | 202 | -------------------------------------------------------------------------------- /docs/docsets/KPCTabsControl.docset/Contents/Resources/Documents/Global Variables.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Global Variables Reference 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 |
16 |

KPCTabsControl Docs (25% documented)

17 |

View on GitHub

18 |

Install in Dash

19 |
20 |
21 |
22 | 27 |
28 |
29 | 148 |
149 |
150 |
151 |

Global Variables

152 |

The following global variables are available globally.

153 | 154 |
155 |
156 |
157 |
    158 |
  • 159 |
    160 | 161 | 162 | 163 | TabsControlSelectionDidChangeNotification 164 | 165 |
    166 |
    167 |
    168 |
    169 |
    170 |
    171 |

    The name of the notification upon the selection of a new tab.

    172 | 173 |
    174 |
    175 |

    Declaration

    176 |
    177 |

    Swift

    178 |
    public let TabsControlSelectionDidChangeNotification = "TabsControlSelectionDidChangeNotification"
    179 | 180 |
    181 |
    182 |
    183 | Show on GitHub 184 |
    185 |
    186 |
    187 |
  • 188 |
189 |
190 |
191 |
192 | 196 |
197 |
198 | 199 | 200 | 201 | -------------------------------------------------------------------------------- /docs/docsets/KPCTabsControl.docset/Contents/Resources/Documents/Typealiases.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Typealiases Reference 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 |
16 |

KPCTabsControl Docs (25% documented)

17 |

View on GitHub

18 |

Install in Dash

19 |
20 |
21 |
22 | 27 |
28 |
29 | 148 |
149 |
150 |
151 |

Typealiases

152 |

The following typealiases are available globally.

153 | 154 |
155 |
156 |
157 |
    158 |
  • 159 |
    160 | 161 | 162 | 163 | Offset 164 | 165 |
    166 |
    167 |
    168 |
    169 |
    170 |
    171 |

    Offset is a simple NSPoint typealias to increase readability.

    172 | 173 |
    174 |
    175 |

    Declaration

    176 |
    177 |

    Swift

    178 |
    public typealias Offset = NSPoint
    179 | 180 |
    181 |
    182 |
    183 | Show on GitHub 184 |
    185 |
    186 |
    187 |
  • 188 |
189 |
190 |
191 |
192 | 196 |
197 |
198 | 199 | 200 | 201 | -------------------------------------------------------------------------------- /docs/docsets/KPCTabsControl.docset/Contents/Resources/Documents/css/highlight.css: -------------------------------------------------------------------------------- 1 | /* Credit to https://gist.github.com/wataru420/2048287 */ 2 | .highlight { 3 | /* Comment */ 4 | /* Error */ 5 | /* Keyword */ 6 | /* Operator */ 7 | /* Comment.Multiline */ 8 | /* Comment.Preproc */ 9 | /* Comment.Single */ 10 | /* Comment.Special */ 11 | /* Generic.Deleted */ 12 | /* Generic.Deleted.Specific */ 13 | /* Generic.Emph */ 14 | /* Generic.Error */ 15 | /* Generic.Heading */ 16 | /* Generic.Inserted */ 17 | /* Generic.Inserted.Specific */ 18 | /* Generic.Output */ 19 | /* Generic.Prompt */ 20 | /* Generic.Strong */ 21 | /* Generic.Subheading */ 22 | /* Generic.Traceback */ 23 | /* Keyword.Constant */ 24 | /* Keyword.Declaration */ 25 | /* Keyword.Pseudo */ 26 | /* Keyword.Reserved */ 27 | /* Keyword.Type */ 28 | /* Literal.Number */ 29 | /* Literal.String */ 30 | /* Name.Attribute */ 31 | /* Name.Builtin */ 32 | /* Name.Class */ 33 | /* Name.Constant */ 34 | /* Name.Entity */ 35 | /* Name.Exception */ 36 | /* Name.Function */ 37 | /* Name.Namespace */ 38 | /* Name.Tag */ 39 | /* Name.Variable */ 40 | /* Operator.Word */ 41 | /* Text.Whitespace */ 42 | /* Literal.Number.Float */ 43 | /* Literal.Number.Hex */ 44 | /* Literal.Number.Integer */ 45 | /* Literal.Number.Oct */ 46 | /* Literal.String.Backtick */ 47 | /* Literal.String.Char */ 48 | /* Literal.String.Doc */ 49 | /* Literal.String.Double */ 50 | /* Literal.String.Escape */ 51 | /* Literal.String.Heredoc */ 52 | /* Literal.String.Interpol */ 53 | /* Literal.String.Other */ 54 | /* Literal.String.Regex */ 55 | /* Literal.String.Single */ 56 | /* Literal.String.Symbol */ 57 | /* Name.Builtin.Pseudo */ 58 | /* Name.Variable.Class */ 59 | /* Name.Variable.Global */ 60 | /* Name.Variable.Instance */ 61 | /* Literal.Number.Integer.Long */ } 62 | .highlight .c { 63 | color: #999988; 64 | font-style: italic; } 65 | .highlight .err { 66 | color: #a61717; 67 | background-color: #e3d2d2; } 68 | .highlight .k { 69 | color: #000000; 70 | font-weight: bold; } 71 | .highlight .o { 72 | color: #000000; 73 | font-weight: bold; } 74 | .highlight .cm { 75 | color: #999988; 76 | font-style: italic; } 77 | .highlight .cp { 78 | color: #999999; 79 | font-weight: bold; } 80 | .highlight .c1 { 81 | color: #999988; 82 | font-style: italic; } 83 | .highlight .cs { 84 | color: #999999; 85 | font-weight: bold; 86 | font-style: italic; } 87 | .highlight .gd { 88 | color: #000000; 89 | background-color: #ffdddd; } 90 | .highlight .gd .x { 91 | color: #000000; 92 | background-color: #ffaaaa; } 93 | .highlight .ge { 94 | color: #000000; 95 | font-style: italic; } 96 | .highlight .gr { 97 | color: #aa0000; } 98 | .highlight .gh { 99 | color: #999999; } 100 | .highlight .gi { 101 | color: #000000; 102 | background-color: #ddffdd; } 103 | .highlight .gi .x { 104 | color: #000000; 105 | background-color: #aaffaa; } 106 | .highlight .go { 107 | color: #888888; } 108 | .highlight .gp { 109 | color: #555555; } 110 | .highlight .gs { 111 | font-weight: bold; } 112 | .highlight .gu { 113 | color: #aaaaaa; } 114 | .highlight .gt { 115 | color: #aa0000; } 116 | .highlight .kc { 117 | color: #000000; 118 | font-weight: bold; } 119 | .highlight .kd { 120 | color: #000000; 121 | font-weight: bold; } 122 | .highlight .kp { 123 | color: #000000; 124 | font-weight: bold; } 125 | .highlight .kr { 126 | color: #000000; 127 | font-weight: bold; } 128 | .highlight .kt { 129 | color: #445588; } 130 | .highlight .m { 131 | color: #009999; } 132 | .highlight .s { 133 | color: #d14; } 134 | .highlight .na { 135 | color: #008080; } 136 | .highlight .nb { 137 | color: #0086B3; } 138 | .highlight .nc { 139 | color: #445588; 140 | font-weight: bold; } 141 | .highlight .no { 142 | color: #008080; } 143 | .highlight .ni { 144 | color: #800080; } 145 | .highlight .ne { 146 | color: #990000; 147 | font-weight: bold; } 148 | .highlight .nf { 149 | color: #990000; } 150 | .highlight .nn { 151 | color: #555555; } 152 | .highlight .nt { 153 | color: #000080; } 154 | .highlight .nv { 155 | color: #008080; } 156 | .highlight .ow { 157 | color: #000000; 158 | font-weight: bold; } 159 | .highlight .w { 160 | color: #bbbbbb; } 161 | .highlight .mf { 162 | color: #009999; } 163 | .highlight .mh { 164 | color: #009999; } 165 | .highlight .mi { 166 | color: #009999; } 167 | .highlight .mo { 168 | color: #009999; } 169 | .highlight .sb { 170 | color: #d14; } 171 | .highlight .sc { 172 | color: #d14; } 173 | .highlight .sd { 174 | color: #d14; } 175 | .highlight .s2 { 176 | color: #d14; } 177 | .highlight .se { 178 | color: #d14; } 179 | .highlight .sh { 180 | color: #d14; } 181 | .highlight .si { 182 | color: #d14; } 183 | .highlight .sx { 184 | color: #d14; } 185 | .highlight .sr { 186 | color: #009926; } 187 | .highlight .s1 { 188 | color: #d14; } 189 | .highlight .ss { 190 | color: #990073; } 191 | .highlight .bp { 192 | color: #999999; } 193 | .highlight .vc { 194 | color: #008080; } 195 | .highlight .vg { 196 | color: #008080; } 197 | .highlight .vi { 198 | color: #008080; } 199 | .highlight .il { 200 | color: #009999; } 201 | -------------------------------------------------------------------------------- /docs/docsets/KPCTabsControl.docset/Contents/Resources/Documents/css/jazzy.css: -------------------------------------------------------------------------------- 1 | html, body, div, span, h1, h3, h4, p, a, code, em, img, ul, li, table, tbody, tr, td { 2 | background: transparent; 3 | border: 0; 4 | margin: 0; 5 | outline: 0; 6 | padding: 0; 7 | vertical-align: baseline; } 8 | 9 | body { 10 | background-color: #f2f2f2; 11 | font-family: Helvetica, freesans, Arial, sans-serif; 12 | font-size: 14px; 13 | -webkit-font-smoothing: subpixel-antialiased; 14 | word-wrap: break-word; } 15 | 16 | h1, h2, h3 { 17 | margin-top: 0.8em; 18 | margin-bottom: 0.3em; 19 | font-weight: 100; 20 | color: black; } 21 | 22 | h1 { 23 | font-size: 2.5em; } 24 | 25 | h2 { 26 | font-size: 2em; 27 | border-bottom: 1px solid #e2e2e2; } 28 | 29 | h4 { 30 | font-size: 13px; 31 | line-height: 1.5; 32 | margin-top: 21px; } 33 | 34 | h5 { 35 | font-size: 1.1em; } 36 | 37 | h6 { 38 | font-size: 1.1em; 39 | color: #777; } 40 | 41 | .section-name { 42 | color: gray; 43 | display: block; 44 | font-family: Helvetica; 45 | font-size: 22px; 46 | font-weight: 100; 47 | margin-bottom: 15px; } 48 | 49 | pre, code { 50 | font: 0.95em Menlo, monospace; 51 | color: #777; 52 | word-wrap: normal; } 53 | 54 | p code, li code { 55 | background-color: #eee; 56 | padding: 2px 4px; 57 | border-radius: 4px; } 58 | 59 | a { 60 | color: #0088cc; 61 | text-decoration: none; } 62 | 63 | ul { 64 | padding-left: 15px; } 65 | 66 | li { 67 | line-height: 1.8em; } 68 | 69 | img { 70 | max-width: 100%; } 71 | 72 | blockquote { 73 | margin-left: 0; 74 | padding: 0 10px; 75 | border-left: 4px solid #ccc; } 76 | 77 | .content-wrapper { 78 | margin: 0 auto; 79 | width: 980px; } 80 | 81 | header { 82 | font-size: 0.85em; 83 | line-height: 26px; 84 | background-color: #414141; 85 | position: fixed; 86 | width: 100%; 87 | z-index: 1; } 88 | header img { 89 | padding-right: 6px; 90 | vertical-align: -4px; 91 | height: 16px; } 92 | header a { 93 | color: #fff; } 94 | header p { 95 | float: left; 96 | color: #999; } 97 | header .header-right { 98 | float: right; 99 | margin-left: 16px; } 100 | 101 | #breadcrumbs { 102 | background-color: #f2f2f2; 103 | height: 27px; 104 | padding-top: 17px; 105 | position: fixed; 106 | width: 100%; 107 | z-index: 1; 108 | margin-top: 26px; } 109 | #breadcrumbs #carat { 110 | height: 10px; 111 | margin: 0 5px; } 112 | 113 | .sidebar { 114 | background-color: #f9f9f9; 115 | border: 1px solid #e2e2e2; 116 | overflow-y: auto; 117 | overflow-x: hidden; 118 | position: fixed; 119 | top: 70px; 120 | bottom: 0; 121 | width: 230px; 122 | word-wrap: normal; } 123 | 124 | .nav-groups { 125 | list-style-type: none; 126 | background: #fff; 127 | padding-left: 0; } 128 | 129 | .nav-group-name { 130 | border-bottom: 1px solid #e2e2e2; 131 | font-size: 1.1em; 132 | font-weight: 100; 133 | padding: 15px 0 15px 20px; } 134 | .nav-group-name > a { 135 | color: #333; } 136 | 137 | .nav-group-tasks { 138 | margin-top: 5px; } 139 | 140 | .nav-group-task { 141 | font-size: 0.9em; 142 | list-style-type: none; 143 | white-space: nowrap; } 144 | .nav-group-task a { 145 | color: #888; } 146 | 147 | .main-content { 148 | background-color: #fff; 149 | border: 1px solid #e2e2e2; 150 | margin-left: 246px; 151 | position: absolute; 152 | overflow: hidden; 153 | padding-bottom: 60px; 154 | top: 70px; 155 | width: 734px; } 156 | .main-content p, .main-content a, .main-content code, .main-content em, .main-content ul, .main-content table, .main-content blockquote { 157 | margin-bottom: 1em; } 158 | .main-content p { 159 | line-height: 1.8em; } 160 | .main-content section .section:first-child { 161 | margin-top: 0; 162 | padding-top: 0; } 163 | .main-content section .task-group-section .task-group:first-of-type { 164 | padding-top: 10px; } 165 | .main-content section .task-group-section .task-group:first-of-type .section-name { 166 | padding-top: 15px; } 167 | 168 | .section { 169 | padding: 0 25px; } 170 | 171 | .highlight { 172 | background-color: #eee; 173 | padding: 10px 12px; 174 | border: 1px solid #e2e2e2; 175 | border-radius: 4px; 176 | overflow-x: auto; } 177 | 178 | .declaration .highlight { 179 | overflow-x: initial; 180 | padding: 0 40px 40px 0; 181 | margin-bottom: -25px; 182 | background-color: transparent; 183 | border: none; } 184 | 185 | .section-name { 186 | margin: 0; 187 | margin-left: 18px; } 188 | 189 | .task-group-section { 190 | padding-left: 6px; 191 | border-top: 1px solid #e2e2e2; } 192 | 193 | .task-group { 194 | padding-top: 0px; } 195 | 196 | .task-name-container a[name]:before { 197 | content: ""; 198 | display: block; 199 | padding-top: 70px; 200 | margin: -70px 0 0; } 201 | 202 | .item { 203 | padding-top: 8px; 204 | width: 100%; 205 | list-style-type: none; } 206 | .item a[name]:before { 207 | content: ""; 208 | display: block; 209 | padding-top: 70px; 210 | margin: -70px 0 0; } 211 | .item code { 212 | background-color: transparent; 213 | padding: 0; } 214 | .item .token { 215 | padding-left: 3px; 216 | margin-left: 15px; 217 | font-size: 11.9px; } 218 | .item .declaration-note { 219 | font-size: .85em; 220 | color: gray; 221 | font-style: italic; } 222 | 223 | .pointer-container { 224 | border-bottom: 1px solid #e2e2e2; 225 | left: -23px; 226 | padding-bottom: 13px; 227 | position: relative; 228 | width: 110%; } 229 | 230 | .pointer { 231 | background: #f9f9f9; 232 | border-left: 1px solid #e2e2e2; 233 | border-top: 1px solid #e2e2e2; 234 | height: 12px; 235 | left: 21px; 236 | top: -7px; 237 | -webkit-transform: rotate(45deg); 238 | -moz-transform: rotate(45deg); 239 | -o-transform: rotate(45deg); 240 | transform: rotate(45deg); 241 | position: absolute; 242 | width: 12px; } 243 | 244 | .height-container { 245 | display: none; 246 | left: -25px; 247 | padding: 0 25px; 248 | position: relative; 249 | width: 100%; 250 | overflow: hidden; } 251 | .height-container .section { 252 | background: #f9f9f9; 253 | border-bottom: 1px solid #e2e2e2; 254 | left: -25px; 255 | position: relative; 256 | width: 100%; 257 | padding-top: 10px; 258 | padding-bottom: 5px; } 259 | 260 | .aside, .language { 261 | padding: 6px 12px; 262 | margin: 12px 0; 263 | border-left: 5px solid #dddddd; 264 | overflow-y: hidden; } 265 | .aside .aside-title, .language .aside-title { 266 | font-size: 9px; 267 | letter-spacing: 2px; 268 | text-transform: uppercase; 269 | padding-bottom: 0; 270 | margin: 0; 271 | color: #aaa; 272 | -webkit-user-select: none; } 273 | .aside p:last-child, .language p:last-child { 274 | margin-bottom: 0; } 275 | 276 | .language { 277 | border-left: 5px solid #cde9f4; } 278 | .language .aside-title { 279 | color: #4b8afb; } 280 | 281 | .aside-warning { 282 | border-left: 5px solid #ff6666; } 283 | .aside-warning .aside-title { 284 | color: #ff0000; } 285 | 286 | .graybox { 287 | border-collapse: collapse; 288 | width: 100%; } 289 | .graybox p { 290 | margin: 0; 291 | word-break: break-word; 292 | min-width: 50px; } 293 | .graybox td { 294 | border: 1px solid #e2e2e2; 295 | padding: 5px 25px 5px 10px; 296 | vertical-align: middle; } 297 | .graybox tr td:first-of-type { 298 | text-align: right; 299 | padding: 7px; 300 | vertical-align: top; 301 | word-break: normal; 302 | width: 40px; } 303 | 304 | .slightly-smaller { 305 | font-size: 0.9em; } 306 | 307 | #footer { 308 | position: absolute; 309 | bottom: 10px; 310 | margin-left: 25px; } 311 | #footer p { 312 | margin: 0; 313 | color: #aaa; 314 | font-size: 0.8em; } 315 | 316 | html.dash header, html.dash #breadcrumbs, html.dash .sidebar { 317 | display: none; } 318 | html.dash .main-content { 319 | width: 980px; 320 | margin-left: 0; 321 | border: none; 322 | width: 100%; 323 | top: 0; 324 | padding-bottom: 0; } 325 | html.dash .height-container { 326 | display: block; } 327 | html.dash .item .token { 328 | margin-left: 0; } 329 | html.dash .content-wrapper { 330 | width: auto; } 331 | html.dash #footer { 332 | position: static; } 333 | -------------------------------------------------------------------------------- /docs/docsets/KPCTabsControl.docset/Contents/Resources/Documents/img/carat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onekiloparsec/KPCTabsControl/0ff97b8107cf587c161652ab98ccff894c9ee2db/docs/docsets/KPCTabsControl.docset/Contents/Resources/Documents/img/carat.png -------------------------------------------------------------------------------- /docs/docsets/KPCTabsControl.docset/Contents/Resources/Documents/img/dash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onekiloparsec/KPCTabsControl/0ff97b8107cf587c161652ab98ccff894c9ee2db/docs/docsets/KPCTabsControl.docset/Contents/Resources/Documents/img/dash.png -------------------------------------------------------------------------------- /docs/docsets/KPCTabsControl.docset/Contents/Resources/Documents/img/gh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onekiloparsec/KPCTabsControl/0ff97b8107cf587c161652ab98ccff894c9ee2db/docs/docsets/KPCTabsControl.docset/Contents/Resources/Documents/img/gh.png -------------------------------------------------------------------------------- /docs/docsets/KPCTabsControl.docset/Contents/Resources/Documents/js/jazzy.js: -------------------------------------------------------------------------------- 1 | window.jazzy = {'docset': false} 2 | if (typeof window.dash != 'undefined') { 3 | document.documentElement.className += ' dash' 4 | window.jazzy.docset = true 5 | } 6 | if (navigator.userAgent.match(/xcode/i)) { 7 | document.documentElement.className += ' xcode' 8 | window.jazzy.docset = true 9 | } 10 | 11 | // On doc load, toggle the URL hash discussion if present 12 | $(document).ready(function() { 13 | if (!window.jazzy.docset) { 14 | var linkToHash = $('a[href="' + window.location.hash +'"]'); 15 | linkToHash.trigger("click"); 16 | } 17 | }); 18 | 19 | // On token click, toggle its discussion and animate token.marginLeft 20 | $(".token").click(function(event) { 21 | if (window.jazzy.docset) { 22 | return; 23 | } 24 | var link = $(this); 25 | var animationDuration = 300; 26 | var tokenOffset = "15px"; 27 | var original = link.css('marginLeft') == tokenOffset; 28 | link.animate({'margin-left':original ? "0px" : tokenOffset}, animationDuration); 29 | $content = link.parent().parent().next(); 30 | $content.slideToggle(animationDuration); 31 | 32 | // Keeps the document from jumping to the hash. 33 | var href = $(this).attr('href'); 34 | if (history.pushState) { 35 | history.pushState({}, '', href); 36 | } else { 37 | location.hash = href; 38 | } 39 | event.preventDefault(); 40 | }); 41 | 42 | // Dumb down quotes within code blocks that delimit strings instead of quotations 43 | // https://github.com/realm/jazzy/issues/714 44 | $("code q").replaceWith(function () { 45 | return ["\"", $(this).contents(), "\""]; 46 | }); 47 | -------------------------------------------------------------------------------- /docs/docsets/KPCTabsControl.docset/Contents/Resources/docSet.dsidx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onekiloparsec/KPCTabsControl/0ff97b8107cf587c161652ab98ccff894c9ee2db/docs/docsets/KPCTabsControl.docset/Contents/Resources/docSet.dsidx -------------------------------------------------------------------------------- /docs/docsets/KPCTabsControl.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onekiloparsec/KPCTabsControl/0ff97b8107cf587c161652ab98ccff894c9ee2db/docs/docsets/KPCTabsControl.tgz -------------------------------------------------------------------------------- /docs/docsets/KPCTabsControl.xml: -------------------------------------------------------------------------------- 1 | 3.0.2http://onekiloparsec.github.io/KPCTabsControl/docsets/KPCTabsControl.tgz 2 | -------------------------------------------------------------------------------- /docs/img/carat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onekiloparsec/KPCTabsControl/0ff97b8107cf587c161652ab98ccff894c9ee2db/docs/img/carat.png -------------------------------------------------------------------------------- /docs/img/dash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onekiloparsec/KPCTabsControl/0ff97b8107cf587c161652ab98ccff894c9ee2db/docs/img/dash.png -------------------------------------------------------------------------------- /docs/img/gh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onekiloparsec/KPCTabsControl/0ff97b8107cf587c161652ab98ccff894c9ee2db/docs/img/gh.png -------------------------------------------------------------------------------- /docs/js/jazzy.js: -------------------------------------------------------------------------------- 1 | window.jazzy = {'docset': false} 2 | if (typeof window.dash != 'undefined') { 3 | document.documentElement.className += ' dash' 4 | window.jazzy.docset = true 5 | } 6 | if (navigator.userAgent.match(/xcode/i)) { 7 | document.documentElement.className += ' xcode' 8 | window.jazzy.docset = true 9 | } 10 | 11 | // On doc load, toggle the URL hash discussion if present 12 | $(document).ready(function() { 13 | if (!window.jazzy.docset) { 14 | var linkToHash = $('a[href="' + window.location.hash +'"]'); 15 | linkToHash.trigger("click"); 16 | } 17 | }); 18 | 19 | // On token click, toggle its discussion and animate token.marginLeft 20 | $(".token").click(function(event) { 21 | if (window.jazzy.docset) { 22 | return; 23 | } 24 | var link = $(this); 25 | var animationDuration = 300; 26 | var tokenOffset = "15px"; 27 | var original = link.css('marginLeft') == tokenOffset; 28 | link.animate({'margin-left':original ? "0px" : tokenOffset}, animationDuration); 29 | $content = link.parent().parent().next(); 30 | $content.slideToggle(animationDuration); 31 | 32 | // Keeps the document from jumping to the hash. 33 | var href = $(this).attr('href'); 34 | if (history.pushState) { 35 | history.pushState({}, '', href); 36 | } else { 37 | location.hash = href; 38 | } 39 | event.preventDefault(); 40 | }); 41 | 42 | // Dumb down quotes within code blocks that delimit strings instead of quotations 43 | // https://github.com/realm/jazzy/issues/714 44 | $("code q").replaceWith(function () { 45 | return ["\"", $(this).contents(), "\""]; 46 | }); 47 | --------------------------------------------------------------------------------