1
1
mirror of https://github.com/qvacua/vimr.git synced 2024-12-25 06:43:24 +03:00

Add selected

This commit is contained in:
Tae Won Ha 2020-11-28 15:58:12 +01:00
parent 63b8ea2ecd
commit 11e00760d3
No known key found for this signature in database
GPG Key ID: E40743465B5B8B44
8 changed files with 263 additions and 85 deletions

View File

@ -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"]

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -4,42 +4,159 @@
*/
import Cocoa
import MaterialIcons
public class Tab: NSView {
protocol TabDelegate: AnyObject {
func select(tab: Tab)
}
public var title: String
class Tab: NSView {
enum Position {
case first
case inBetween
case last
}
public init(withTitle title: String) {
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.layer?.backgroundColor = self.theme.backgroundColor.cgColor
self.autoSetDimension(.height, toSize: self.theme.tabHeight)
self.titleView.stringValue = title
self.autoSetDimensions(to: CGSize(width: 200, height: Defs.tabHeight))
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()
}
}

View File

@ -7,56 +7,99 @@ import Cocoa
import PureLayout
public class TabBar: NSView {
public private(set) var tabs = [Tab]()
public var theme: Theme { self._theme }
public init(withTheme theme: Theme) {
self._theme = theme
public init() {
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(scroll)
scroll.autoPinEdgesToSuperviewEdges()
self.addSubview(self.scrollView)
self.scrollView.autoPinEdgesToSuperviewEdges()
scroll.drawsBackground = false
scroll.hasHorizontalScroller = false
scroll.documentView = stack
self.scrollView.documentView = self.stackView
stack.autoPinEdge(toSuperviewEdge: .top)
stack.autoPinEdge(toSuperviewEdge: .left)
stack.autoPinEdge(toSuperviewEdge: .bottom)
self.stackView.autoPinEdge(toSuperviewEdge: .top)
self.stackView.autoPinEdge(toSuperviewEdge: .left)
self.stackView.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 }
self.stackView.spacing = Defs.tabPadding
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")
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.layer?.backgroundColor = NSColor.red.cgColor
tab2.layer?.backgroundColor = NSColor.blue.cgColor
tab3.layer?.backgroundColor = NSColor.green.cgColor
tab4.layer?.backgroundColor = NSColor.white.cgColor
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)

View 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() {}
}

View File

@ -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;
};

View File

@ -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) {