mirror of
https://github.com/qvacua/vimr.git
synced 2024-12-24 22:33:52 +03:00
Add selected
This commit is contained in:
parent
63b8ea2ecd
commit
11e00760d3
@ -4,17 +4,17 @@
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "Tabs",
|
||||
name: "Components",
|
||||
platforms: [.macOS(.v10_13)],
|
||||
products: [
|
||||
// Products define the executables and libraries a package produces, and make them visible to other packages.
|
||||
.library(
|
||||
name: "Tabs",
|
||||
targets: ["Tabs"]
|
||||
),
|
||||
.library(name: "Tabs", targets: ["Tabs"]),
|
||||
],
|
||||
dependencies: [
|
||||
.package(name: "MaterialIcons", url: "https://github.com/qvacua/material-icons", .upToNextMinor(from: "0.1.0")),
|
||||
.package(
|
||||
name: "MaterialIcons",
|
||||
url: "https://github.com/qvacua/material-icons",
|
||||
.upToNextMinor(from: "0.1.0")
|
||||
),
|
||||
.package(
|
||||
name: "PureLayout",
|
||||
url: "https://github.com/PureLayout/PureLayout",
|
||||
@ -22,8 +22,6 @@ let package = Package(
|
||||
),
|
||||
],
|
||||
targets: [
|
||||
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
|
||||
// Targets can depend on other targets in this package, and on products in packages this package depends on.
|
||||
.target(
|
||||
name: "Tabs",
|
||||
dependencies: ["PureLayout", "MaterialIcons"]
|
||||
|
@ -1,17 +0,0 @@
|
||||
/**
|
||||
* Tae Won Ha - http://taewon.de - @hataewon
|
||||
* See LICENSE
|
||||
*/
|
||||
|
||||
import Cocoa
|
||||
|
||||
public enum Defs {
|
||||
public static let tabHeight = CGFloat(28)
|
||||
public static let tabMinWidth = CGFloat(100)
|
||||
public static let tabMaxWidth = CGFloat(400)
|
||||
public static let tabTitleFont = NSFont.systemFont(ofSize: 11)
|
||||
|
||||
public static let tabPadding = CGFloat(0)
|
||||
|
||||
public static let tabBarHeight = CGFloat(Self.tabHeight + 2 * Self.tabPadding)
|
||||
}
|
@ -13,6 +13,7 @@ import Cocoa
|
||||
|
||||
class DraggingSingleRowStackView: NSStackView {
|
||||
var isDraggingEnabled = true
|
||||
var postDraggingAction: ((NSStackView) -> Void)?
|
||||
|
||||
override func mouseDragged(with event: NSEvent) {
|
||||
guard self.isDraggingEnabled else {
|
||||
@ -23,6 +24,7 @@ class DraggingSingleRowStackView: NSStackView {
|
||||
let location = convert(event.locationInWindow, from: nil)
|
||||
if let dragged = views.first(where: { $0.hitTest(location) != nil }) {
|
||||
self.reorder(view: dragged, event: event)
|
||||
self.postDraggingAction?(self)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,42 +4,159 @@
|
||||
*/
|
||||
|
||||
import Cocoa
|
||||
import MaterialIcons
|
||||
|
||||
public class Tab: NSView {
|
||||
protocol TabDelegate: AnyObject {
|
||||
func select(tab: Tab)
|
||||
}
|
||||
|
||||
public var title: String
|
||||
|
||||
public init(withTitle title: String) {
|
||||
class Tab: NSView {
|
||||
enum Position {
|
||||
case first
|
||||
case inBetween
|
||||
case last
|
||||
}
|
||||
|
||||
var title: String {
|
||||
didSet {
|
||||
self.titleView.stringValue = self.title
|
||||
self.adjustWidth()
|
||||
}
|
||||
}
|
||||
|
||||
init(withTitle title: String, in tabBar: TabBar) {
|
||||
self.title = title
|
||||
self.attributedTitle = NSAttributedString(string: title, attributes: [
|
||||
.font: Defs.tabTitleFont
|
||||
])
|
||||
self.theme = tabBar.theme
|
||||
super.init(frame: .zero)
|
||||
|
||||
self.configureForAutoLayout()
|
||||
self.wantsLayer = true
|
||||
|
||||
#if DEBUG
|
||||
self.layer?.backgroundColor = NSColor.cyan.cgColor
|
||||
#endif
|
||||
|
||||
self.autoSetDimensions(to: CGSize(width: 200, height: Defs.tabHeight))
|
||||
self.layer?.backgroundColor = self.theme.backgroundColor.cgColor
|
||||
self.autoSetDimension(.height, toSize: self.theme.tabHeight)
|
||||
self.titleView.stringValue = title
|
||||
|
||||
self.addViews()
|
||||
self.adjustWidth()
|
||||
}
|
||||
|
||||
public override func mouseDown(with event: NSEvent) {
|
||||
Swift.print("mouse down in tab")
|
||||
override func mouseUp(with _: NSEvent) {
|
||||
self.delegate?.select(tab: self)
|
||||
}
|
||||
|
||||
public override func mouseUp(with event: NSEvent) {
|
||||
Swift.print("mouse up in tab")
|
||||
}
|
||||
|
||||
public override func draw(_ dirtyRect: NSRect) {
|
||||
self.attributedTitle.draw(in: self.bounds)
|
||||
override func draw(_: NSRect) {
|
||||
self.drawSeparators()
|
||||
self.drawSelectionIndicator()
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
||||
|
||||
private var attributedTitle: NSAttributedString
|
||||
var isSelected: Bool = false {
|
||||
didSet { self.needsDisplay = true }
|
||||
}
|
||||
|
||||
weak var delegate: TabDelegate?
|
||||
var position = Position.inBetween {
|
||||
didSet { self.needsDisplay = true }
|
||||
}
|
||||
|
||||
private let closeButton = NSButton(forAutoLayout: ())
|
||||
private let iconView = NSImageView(forAutoLayout: ())
|
||||
private let titleView = NSTextField(forAutoLayout: ())
|
||||
|
||||
private var theme: Theme
|
||||
}
|
||||
|
||||
extension Tab {
|
||||
private func adjustWidth() {
|
||||
let idealWidth = 4 * self.theme.tabHorizontalPadding + 2 * self.theme.iconDimension.width
|
||||
+ self.titleView.intrinsicContentSize.width
|
||||
let targetWidth = min(max(self.theme.tabMinWidth, idealWidth), self.theme.tabMaxWidth)
|
||||
self.autoSetDimension(.width, toSize: targetWidth)
|
||||
}
|
||||
|
||||
private func addViews() {
|
||||
let close = self.closeButton
|
||||
let icon = self.iconView
|
||||
let title = self.titleView
|
||||
|
||||
self.addSubview(close)
|
||||
self.addSubview(icon)
|
||||
self.addSubview(title)
|
||||
|
||||
close.imagePosition = .imageOnly
|
||||
close.image = Icon.close.asImage(
|
||||
dimension: self.theme.iconDimension.width,
|
||||
color: self.theme.foregroundColor
|
||||
)
|
||||
close.isBordered = false
|
||||
(close.cell as? NSButtonCell)?.highlightsBy = .contentsCellMask
|
||||
|
||||
icon.image = NSImage(named: NSImage.actionTemplateName)
|
||||
|
||||
title.drawsBackground = false
|
||||
title.font = self.theme.titleFont
|
||||
title.textColor = self.theme.foregroundColor
|
||||
title.isEditable = false
|
||||
title.isBordered = false
|
||||
title.isSelectable = false
|
||||
title.usesSingleLineMode = true
|
||||
title.lineBreakMode = .byTruncatingTail
|
||||
|
||||
close.autoSetDimensions(to: self.theme.iconDimension)
|
||||
close.autoPinEdge(toSuperviewEdge: .left, withInset: self.theme.tabHorizontalPadding)
|
||||
close.autoAlignAxis(toSuperviewAxis: .horizontal)
|
||||
|
||||
icon.autoSetDimensions(to: self.theme.iconDimension)
|
||||
icon.autoPinEdge(.left, to: .right, of: close, withOffset: self.theme.tabHorizontalPadding)
|
||||
icon.autoAlignAxis(toSuperviewAxis: .horizontal)
|
||||
|
||||
title.autoPinEdge(.left, to: .right, of: icon, withOffset: self.theme.tabHorizontalPadding)
|
||||
title.autoPinEdge(toSuperviewEdge: .right, withInset: self.theme.tabHorizontalPadding)
|
||||
title.autoAlignAxis(toSuperviewAxis: .horizontal)
|
||||
}
|
||||
|
||||
private func drawSeparators() {
|
||||
let b = self.bounds
|
||||
let left = CGRect(x: 0, y: 0, width: self.theme.separatorThickness, height: b.height)
|
||||
let right = CGRect(x: b.maxX - 1, y: 0, width: self.theme.separatorThickness, height: b.height)
|
||||
let bottom = CGRect(
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: b.width,
|
||||
height: self.theme.separatorThickness
|
||||
)
|
||||
|
||||
guard let context = NSGraphicsContext.current?.cgContext else { return }
|
||||
context.saveGState()
|
||||
defer { context.restoreGState() }
|
||||
self.theme.separatorColor.set()
|
||||
|
||||
switch self.position {
|
||||
case .first:
|
||||
right.fill()
|
||||
case .inBetween:
|
||||
left.fill()
|
||||
right.fill()
|
||||
case .last:
|
||||
left.fill()
|
||||
}
|
||||
|
||||
if !self.isSelected { bottom.fill() }
|
||||
}
|
||||
|
||||
private func drawSelectionIndicator() {
|
||||
guard self.isSelected else { return }
|
||||
|
||||
let b = self.bounds
|
||||
let rect = CGRect(x: 0, y: 0, width: b.width, height: self.theme.tabSelectionIndicatorThickness)
|
||||
|
||||
guard let context = NSGraphicsContext.current?.cgContext else { return }
|
||||
context.saveGState()
|
||||
defer { context.restoreGState() }
|
||||
self.theme.tabSelectedIndicatorColor.set()
|
||||
|
||||
rect.fill()
|
||||
}
|
||||
}
|
||||
|
@ -7,57 +7,100 @@ import Cocoa
|
||||
import PureLayout
|
||||
|
||||
public class TabBar: NSView {
|
||||
public private(set) var tabs = [Tab]()
|
||||
|
||||
public init() {
|
||||
public var theme: Theme { self._theme }
|
||||
|
||||
public init(withTheme theme: Theme) {
|
||||
self._theme = theme
|
||||
|
||||
super.init(frame: .zero)
|
||||
self.configureForAutoLayout()
|
||||
self.wantsLayer = true
|
||||
|
||||
#if DEBUG
|
||||
self.layer?.backgroundColor = NSColor.yellow.cgColor
|
||||
#endif
|
||||
self.layer?.backgroundColor = theme.backgroundColor.cgColor
|
||||
|
||||
self.addViews()
|
||||
self.addTestTabs()
|
||||
}
|
||||
|
||||
override public func draw(_: NSRect) {
|
||||
self.drawSeparator()
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
||||
|
||||
var tabs = [Tab]()
|
||||
|
||||
private var _theme: Theme
|
||||
private let scrollView = HorizontalOnlyScrollView(forAutoLayout: ())
|
||||
private let stackView = DraggingSingleRowStackView(forAutoLayout: ())
|
||||
}
|
||||
|
||||
extension TabBar: TabDelegate {
|
||||
func select(tab: Tab) {
|
||||
self.stackView.arrangedSubviews.forEach { ($0 as? Tab)?.isSelected = false }
|
||||
tab.isSelected = true
|
||||
}
|
||||
}
|
||||
|
||||
extension TabBar {
|
||||
private func drawSeparator() {
|
||||
let b = self.bounds
|
||||
let rect = CGRect(x: 0, y: 0, width: b.width, height: self._theme.separatorThickness)
|
||||
|
||||
guard let context = NSGraphicsContext.current?.cgContext else { return }
|
||||
context.saveGState()
|
||||
defer { context.restoreGState() }
|
||||
self._theme.separatorColor.set()
|
||||
|
||||
rect.fill()
|
||||
}
|
||||
|
||||
private func addViews() {
|
||||
#if DEBUG
|
||||
self.scrollView.backgroundColor = .brown
|
||||
#endif
|
||||
let scroll = self.scrollView
|
||||
let stack = self.stackView
|
||||
|
||||
self.scrollView.hasHorizontalScroller = false
|
||||
|
||||
self.addSubview(self.scrollView)
|
||||
self.scrollView.autoPinEdgesToSuperviewEdges()
|
||||
|
||||
self.scrollView.documentView = self.stackView
|
||||
|
||||
self.stackView.autoPinEdge(toSuperviewEdge: .top)
|
||||
self.stackView.autoPinEdge(toSuperviewEdge: .left)
|
||||
self.stackView.autoPinEdge(toSuperviewEdge: .bottom)
|
||||
|
||||
self.stackView.spacing = Defs.tabPadding
|
||||
self.addSubview(scroll)
|
||||
scroll.autoPinEdgesToSuperviewEdges()
|
||||
|
||||
scroll.drawsBackground = false
|
||||
scroll.hasHorizontalScroller = false
|
||||
scroll.documentView = stack
|
||||
|
||||
stack.autoPinEdge(toSuperviewEdge: .top)
|
||||
stack.autoPinEdge(toSuperviewEdge: .left)
|
||||
stack.autoPinEdge(toSuperviewEdge: .bottom)
|
||||
|
||||
stack.spacing = self._theme.tabSpacing
|
||||
stack.postDraggingAction = { stackView in
|
||||
let endIndex = stackView.arrangedSubviews.endIndex - 1
|
||||
stackView.arrangedSubviews.enumerated().forEach { index, view in
|
||||
guard let tab = view as? Tab else { return }
|
||||
|
||||
if index == 0 { tab.position = .first }
|
||||
else if index == endIndex { tab.position = .last }
|
||||
else { tab.position = .inBetween }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func addTestTabs() {
|
||||
let tab1 = Tab(withTitle: "Test 1")
|
||||
let tab2 = Tab(withTitle: "Test 2")
|
||||
let tab3 = Tab(withTitle: "Test 3")
|
||||
let tab4 = Tab(withTitle: "Test 4")
|
||||
|
||||
tab1.layer?.backgroundColor = NSColor.red.cgColor
|
||||
tab2.layer?.backgroundColor = NSColor.blue.cgColor
|
||||
tab3.layer?.backgroundColor = NSColor.green.cgColor
|
||||
tab4.layer?.backgroundColor = NSColor.white.cgColor
|
||||
|
||||
let tab1 = Tab(withTitle: "Test 1", in: self)
|
||||
let tab2 = Tab(withTitle: "Test 2 Some", in: self)
|
||||
let tab3 = Tab(withTitle: "Test 3", in: self)
|
||||
let tab4 = Tab(
|
||||
withTitle: "Test 4 More Text More Less Really??? More Text How long should it be?",
|
||||
in: self
|
||||
)
|
||||
|
||||
tab1.delegate = self
|
||||
tab2.delegate = self
|
||||
tab3.delegate = self
|
||||
tab4.delegate = self
|
||||
|
||||
tab1.position = .first
|
||||
tab4.position = .last
|
||||
tab2.isSelected = true
|
||||
|
||||
self.stackView.addArrangedSubview(tab1)
|
||||
self.stackView.addArrangedSubview(tab2)
|
||||
self.stackView.addArrangedSubview(tab3)
|
||||
|
35
Tabs/Sources/Tabs/Theme.swift
Normal file
35
Tabs/Sources/Tabs/Theme.swift
Normal file
@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Tae Won Ha - http://taewon.de - @hataewon
|
||||
* See LICENSE
|
||||
*/
|
||||
|
||||
import Cocoa
|
||||
|
||||
public struct Theme {
|
||||
public static let `default` = Self()
|
||||
|
||||
public var foregroundColor = NSColor.textColor
|
||||
public var backgroundColor = NSColor.controlBackgroundColor
|
||||
public var separatorColor = NSColor.controlShadowColor
|
||||
|
||||
public var tabSelectedIndicatorColor = NSColor.selectedControlColor
|
||||
|
||||
public var titleFont = NSFont.systemFont(ofSize: 11)
|
||||
|
||||
public var tabHeight = CGFloat(28)
|
||||
|
||||
public var tabMaxWidth = CGFloat(250)
|
||||
public var separatorThickness = CGFloat(1)
|
||||
public var tabHorizontalPadding = CGFloat(4)
|
||||
public var tabSelectionIndicatorThickness = CGFloat(4)
|
||||
public var iconDimension = CGSize(width: 16, height: 16)
|
||||
|
||||
public var tabMinWidth: CGFloat {
|
||||
4 * self.tabHorizontalPadding + 2 * self.iconDimension.width + 32
|
||||
}
|
||||
|
||||
public var tabBarHeight: CGFloat { self.tabHeight }
|
||||
public var tabSpacing = CGFloat(-1)
|
||||
|
||||
public init() {}
|
||||
}
|
@ -7,10 +7,10 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
4B2A75652572869C0002D722 /* Tabs in Frameworks */ = {isa = PBXBuildFile; productRef = 4B2A75642572869C0002D722 /* Tabs */; };
|
||||
4BEBD4BA256A76BB002218F8 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEBD4B9256A76BB002218F8 /* AppDelegate.swift */; };
|
||||
4BEBD4BC256A76BB002218F8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4BEBD4BB256A76BB002218F8 /* Assets.xcassets */; };
|
||||
4BEBD4BF256A76BB002218F8 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4BEBD4BD256A76BB002218F8 /* MainMenu.xib */; };
|
||||
4BEBD4CC256A7740002218F8 /* Tabs in Frameworks */ = {isa = PBXBuildFile; productRef = 4BEBD4CB256A7740002218F8 /* Tabs */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
@ -26,7 +26,7 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
4BEBD4CC256A7740002218F8 /* Tabs in Frameworks */,
|
||||
4B2A75652572869C0002D722 /* Tabs in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -85,7 +85,7 @@
|
||||
);
|
||||
name = TabsSupport;
|
||||
packageProductDependencies = (
|
||||
4BEBD4CB256A7740002218F8 /* Tabs */,
|
||||
4B2A75642572869C0002D722 /* Tabs */,
|
||||
);
|
||||
productName = TabsSupport;
|
||||
productReference = 4BEBD4B6256A76BB002218F8 /* TabsSupport.app */;
|
||||
@ -339,7 +339,7 @@
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
4BEBD4CB256A7740002218F8 /* Tabs */ = {
|
||||
4B2A75642572869C0002D722 /* Tabs */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = Tabs;
|
||||
};
|
||||
|
@ -14,7 +14,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
@IBOutlet var window: NSWindow!
|
||||
|
||||
override init() {
|
||||
self.tabBar = TabBar()
|
||||
self.tabBar = TabBar(withTheme: .default)
|
||||
super.init()
|
||||
}
|
||||
|
||||
@ -24,7 +24,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
self.tabBar.autoPinEdge(toSuperviewEdge: .top)
|
||||
self.tabBar.autoPinEdge(toSuperviewEdge: .left)
|
||||
self.tabBar.autoPinEdge(toSuperviewEdge: .right)
|
||||
self.tabBar.autoSetDimension(.height, toSize: Defs.tabBarHeight)
|
||||
self.tabBar.autoSetDimension(.height, toSize: Theme().tabBarHeight)
|
||||
}
|
||||
|
||||
func applicationWillTerminate(_: Notification) {
|
||||
|
Loading…
Reference in New Issue
Block a user