Skip to content

Commit

Permalink
Merge pull request #14 from zenangst/implement/grid-view
Browse files Browse the repository at this point in the history
Implement grid view
  • Loading branch information
zenangst authored Sep 30, 2018
2 parents 91b6dde + e041f50 commit 767c24a
Show file tree
Hide file tree
Showing 7 changed files with 177 additions and 17 deletions.
4 changes: 4 additions & 0 deletions Gray.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
2AECAC775C01910CCBFAD88E /* libPods-Gray.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9C02BDE925690A5BBA8E6D44 /* libPods-Gray.a */; };
BD111046215CEB240002D537 /* Shell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD111045215CEB240002D537 /* Shell.swift */; };
BD1BA9BE215E68840052633B /* IconController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD1BA9BD215E68840052633B /* IconController.swift */; };
BD1BA9C0215E6DBB0052633B /* ApplicationGridView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD1BA9BF215E6DBB0052633B /* ApplicationGridView.swift */; };
BD1D9545215E435F003ABBCF /* VersionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD1D9544215E435F003ABBCF /* VersionController.swift */; };
BD30844A215C198D002A9349 /* ApplicationsLogicController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD308449215C198D002A9349 /* ApplicationsLogicController.swift */; };
BD67CE0E215BF15F00216FDB /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD67CE0D215BF15F00216FDB /* AppDelegate.swift */; };
Expand All @@ -28,6 +29,7 @@
9C02BDE925690A5BBA8E6D44 /* libPods-Gray.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Gray.a"; sourceTree = BUILT_PRODUCTS_DIR; };
BD111045215CEB240002D537 /* Shell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shell.swift; sourceTree = "<group>"; };
BD1BA9BD215E68840052633B /* IconController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconController.swift; sourceTree = "<group>"; };
BD1BA9BF215E6DBB0052633B /* ApplicationGridView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationGridView.swift; sourceTree = "<group>"; };
BD1D9544215E435F003ABBCF /* VersionController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionController.swift; sourceTree = "<group>"; };
BD308449215C198D002A9349 /* ApplicationsLogicController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationsLogicController.swift; sourceTree = "<group>"; };
BD67CE0A215BF15F00216FDB /* Gray.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Gray.app; sourceTree = BUILT_PRODUCTS_DIR; };
Expand Down Expand Up @@ -123,6 +125,7 @@
BD308449215C198D002A9349 /* ApplicationsLogicController.swift */,
BD67CE1B215BF4FE00216FDB /* ApplicationsViewController.swift */,
BD9D3D7C215C2D7100233333 /* ApplicationListView.swift */,
BD1BA9BF215E6DBB0052633B /* ApplicationGridView.swift */,
);
path = Applications;
sourceTree = "<group>";
Expand Down Expand Up @@ -233,6 +236,7 @@
BD1BA9BE215E68840052633B /* IconController.swift in Sources */,
BD67CE1C215BF4FE00216FDB /* ApplicationsViewController.swift in Sources */,
BD67CE0E215BF15F00216FDB /* AppDelegate.swift in Sources */,
BD1BA9C0215E6DBB0052633B /* ApplicationGridView.swift in Sources */,
BD30844A215C198D002A9349 /* ApplicationsLogicController.swift in Sources */,
BD1D9545215E435F003ABBCF /* VersionController.swift in Sources */,
BD9D3D7D215C2D7100233333 /* ApplicationListView.swift in Sources */,
Expand Down
4 changes: 2 additions & 2 deletions Podfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PODS:
- Blueprints (0.8.0)
- Differific (0.5.0)
- Family (0.6.0)
- Family (0.6.1)
- UserInterface (0.5.0)
- Vaccine (0.12.2)

Expand All @@ -23,7 +23,7 @@ SPEC REPOS:
SPEC CHECKSUMS:
Blueprints: bd8e23ee3b1ee9faa751ddb6e9cd82fef862ce9a
Differific: 44185f21f5f5772c7f68077946b0f803f4934eb4
Family: dc169b9a69bad47a116c07f2134ab5baa84b52c5
Family: 4b14b8bbfa6b50de16404a8778262d0ea305d44a
UserInterface: 54e15db9aceaec2b9686d00f471adb15d2598ea3
Vaccine: a8ccb41c6b9aa93242560ceaa066040fa9e18b27

Expand Down
57 changes: 52 additions & 5 deletions Sources/Applications/ApplicationCollectionViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,22 @@ protocol ApplicationCollectionViewControllerDelegate: class {
application: Application)
}

class ApplicationCollectionViewController: NSViewController, ApplicationViewDelegate, NSCollectionViewDelegate {
class ApplicationCollectionViewController: NSViewController, ApplicationListViewDelegate, NSCollectionViewDelegate {
weak var delegate: ApplicationCollectionViewControllerDelegate?
let dataSource: ApplicationsDataSource
lazy var layout = VerticalBlueprintLayout(
lazy var listLayout = VerticalBlueprintLayout(
itemsPerRow: 1,
itemSize: .init(width: 100, height: 70),
minimumLineSpacing: 0)
lazy var collectionView = NSCollectionView(layout: layout,
register: ApplicationListView.self)

lazy var gridLayout = VerticalBlueprintLayout(
itemSize: .init(width: 157, height: 157),
minimumInteritemSpacing: 28,
minimumLineSpacing: 28,
sectionInset: .init(top: 28, left: 28, bottom: 28, right: 28))

lazy var collectionView = NSCollectionView(layout: gridLayout,
register: ApplicationListView.self, ApplicationGridView.self)

init(models: [Application] = []) {
self.dataSource = ApplicationsDataSource(models: models)
Expand All @@ -35,7 +42,8 @@ class ApplicationCollectionViewController: NSViewController, ApplicationViewDele
super.viewDidLoad()
collectionView.dataSource = dataSource
collectionView.delegate = self
collectionView.allowsEmptySelection = false
collectionView.isSelectable = true
collectionView.allowsMultipleSelection = false
view.addSubview(collectionView, pin: true)
}

Expand All @@ -47,6 +55,45 @@ class ApplicationCollectionViewController: NSViewController, ApplicationViewDele
(item as? ApplicationListView)?.delegate = self
}

func collectionView(_ collectionView: NSCollectionView, didSelectItemsAt indexPaths: Set<IndexPath>) {
guard let indexPath = indexPaths.first,
let item = collectionView.item(at: indexPath) as? ApplicationGridView else {
return
}

collectionView.deselectAll(nil)

let application = dataSource.model(at: indexPath)
let newAppearance: Application.Appearance = application.appearance == .light
? .dark
: .light
let duration: TimeInterval = 0.15

NSAnimationContext.runAnimationGroup({ (context) in
let scale: CGFloat = 0.8
let scaleTransform = CGAffineTransform.init(scaleX: scale, y: scale)
let (width, height) = (item.view.frame.width / 2, item.view.frame.height / 2)
let moveTransform = CGAffineTransform.init(translationX: width - (width * scale),
y: height - (height * scale))
let concatTransform = scaleTransform.concatenating(moveTransform)
context.duration = duration
context.allowsImplicitAnimation = true
item.view.animator().layer?.setAffineTransform(concatTransform)
}, completionHandler:{
NSAnimationContext.runAnimationGroup({ (context) in
context.duration = duration
context.allowsImplicitAnimation = true
item.view.animator().layer?.setAffineTransform(.identity)
item.update(with: newAppearance, duration: 0.5) { [weak self] in
guard let strongSelf = self else { return }
strongSelf.delegate?.applicationCollectionViewController(strongSelf,
toggleAppearance: newAppearance,
application: application)
}
})
})
}

// MARK: - ApplicationViewDelegate

func applicationView(_ view: ApplicationListView, didClickSegmentedControl segmentedControl: NSSegmentedControl) {
Expand Down
95 changes: 95 additions & 0 deletions Sources/Applications/ApplicationGridView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import Cocoa
import UserInterface

protocol ApplicationGridViewDelegate: class {
func applicationView(_ view: ApplicationGridView, didClickSegmentedControl segmentedControl: NSSegmentedControl)
}

class ApplicationGridView: NSCollectionViewItem {
weak var delegate: ApplicationGridViewDelegate?

lazy var iconView: NSImageView = .init()
lazy var titleLabel: NSTextField = .init()
lazy var subtitleLabel: NSTextField = .init()

override func loadView() {
let view = NSView()
view.wantsLayer = true
self.view = view
}

override func viewDidLoad() {
super.viewDidLoad()

view.layer?.backgroundColor = NSColor.white.cgColor
view.layer?.cornerRadius = 28
view.layer?.masksToBounds = true

titleLabel.backgroundColor = .clear
titleLabel.isBezeled = false
titleLabel.isEditable = false
titleLabel.maximumNumberOfLines = 1
titleLabel.font = NSFont.boldSystemFont(ofSize: 18)

subtitleLabel.backgroundColor = .clear
subtitleLabel.isBezeled = false
subtitleLabel.isEditable = false
subtitleLabel.maximumNumberOfLines = 1

view.addSubviews(iconView, titleLabel, subtitleLabel)

let margin: CGFloat = 20

NSLayoutConstraint.constrain(
iconView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10),
iconView.topAnchor.constraint(equalTo: view.topAnchor, constant: margin),
iconView.widthAnchor.constraint(equalToConstant: 50),
iconView.heightAnchor.constraint(equalToConstant: 50),

titleLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: margin),
titleLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -margin),
titleLabel.bottomAnchor.constraint(equalTo: subtitleLabel.topAnchor, constant: 0),

subtitleLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: margin),
subtitleLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -margin),
subtitleLabel.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -margin)
)
}

func update(with appearance: Application.Appearance, duration: TimeInterval = 0, then handler: (() -> Void)? = nil) {
if duration > 0 {
NSAnimationContext.current.allowsImplicitAnimation = true
NSAnimationContext.runAnimationGroup({ (context) in
context.duration = duration
switch appearance {
case .dark:
view.animator().layer?.backgroundColor = .black
titleLabel.animator().textColor = .white
subtitleLabel.animator().textColor = .lightGray
subtitleLabel.animator().stringValue = "Dark mode"
case .light:
view.animator().layer?.backgroundColor = .white
titleLabel.animator().textColor = .black
subtitleLabel.animator().textColor = .darkGray
subtitleLabel.animator().stringValue = "Light mode"
}
}, completionHandler:{
handler?()
})
} else {
switch appearance {
case .dark:
view.layer?.backgroundColor = .black
titleLabel.animator().textColor = .white
subtitleLabel.textColor = .lightGray
subtitleLabel.stringValue = "Dark mode"
case .light:
view.layer?.backgroundColor = .white
titleLabel.textColor = .black
subtitleLabel.textColor = .darkGray
subtitleLabel.stringValue = "Light mode"
}
}
}
}

4 changes: 2 additions & 2 deletions Sources/Applications/ApplicationListView.swift
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import Cocoa
import UserInterface

protocol ApplicationViewDelegate: class {
protocol ApplicationListViewDelegate: class {
func applicationView(_ view: ApplicationListView, didClickSegmentedControl segmentedControl: NSSegmentedControl)
}

class ApplicationListView: NSCollectionViewItem {
weak var delegate: ApplicationViewDelegate?
weak var delegate: ApplicationListViewDelegate?

lazy var iconView: NSImageView = .init()
lazy var label: NSTextField = .init()
Expand Down
27 changes: 21 additions & 6 deletions Sources/Applications/ApplicationsDataSource.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ import Differific
import UserInterface

class ApplicationsDataSource: NSObject, NSCollectionViewDataSource {
enum ViewStyle {
case list, grid
}

var viewStyle: ViewStyle = .grid
private(set) var models: [Application]
let iconController = IconController()

Expand Down Expand Up @@ -41,12 +46,22 @@ class ApplicationsDataSource: NSObject, NSCollectionViewDataSource {

func collectionView(_ collectionView: NSCollectionView,
itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {
return collectionView.dequeue(ApplicationListView.self, with: model(at: indexPath), for: indexPath) {
view, model in
view.iconView.image = self.iconController.icon(for: model)
view.label.stringValue = model.name
view.toggle.setSelected(model.appearance == .light, forSegment: 0)
view.toggle.setSelected(model.appearance == .dark, forSegment: 1)
switch viewStyle {
case .grid:
return collectionView.dequeue(ApplicationGridView.self, with: model(at: indexPath), for: indexPath) {
view, model in
view.iconView.image = self.iconController.icon(for: model)
view.titleLabel.stringValue = model.name
view.update(with: model.appearance)
}
case .list:
return collectionView.dequeue(ApplicationListView.self, with: model(at: indexPath), for: indexPath) {
view, model in
view.iconView.image = self.iconController.icon(for: model)
view.label.stringValue = model.name
view.toggle.setSelected(model.appearance == .light, forSegment: 0)
view.toggle.setSelected(model.appearance == .dark, forSegment: 1)
}
}
}
}
3 changes: 1 addition & 2 deletions Sources/Applications/ApplicationsViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ class ApplicationsViewController: FamilyViewController, ApplicationCollectionVie
func applicationCollectionViewController(_ controller: ApplicationCollectionViewController,
toggleAppearance newAppearance: Application.Appearance,
application: Application) {
logicController.toggleAppearance(for: application, newAppearance: newAppearance, then: { _ in
})
logicController.toggleAppearance(for: application, newAppearance: newAppearance, then: render)
}
}

0 comments on commit 767c24a

Please sign in to comment.