Initial, holiday fun

This commit is contained in:
Ryan McGrath 2018-12-26 18:34:03 -08:00
commit abcab0c509
No known key found for this signature in database
GPG key ID: 811674B62B666830
18 changed files with 2638 additions and 0 deletions

181
four/JSSwitch.swift Normal file
View file

@ -0,0 +1,181 @@
//
// JSSwitch.swift
// JSSwitch
//
// Created by Julien Sagot on 29/05/16.
// Modified slightly by Ryan McGrath on or about 12/24/2018.
// Copyright © 2016 Julien Sagot. All rights reserved.
//
import AppKit
public class JSSwitch: NSControl {
// MARK: - Properties
private var pressed = false
private let backgroundLayer = CALayer()
private let knobContainer = CALayer()
private let knobLayer = CALayer()
private let knobShadows = (smallStroke: CALayer(), smallShadow: CALayer(), mediumShadow: CALayer(), bigShadow: CALayer())
// MARK: Computed
public override var wantsUpdateLayer: Bool { return true }
public override var intrinsicContentSize: NSSize {
return CGSize(width: 52, height: 32)
}
private var scaleFactor: CGFloat {
return ceil(frame.size.height / 62) // Hardcoded base height
}
public var tintColor = NSColor(deviceRed: 76/255, green: 217/255, blue: 100/255, alpha: 1.0) {
didSet { needsDisplay = true }
}
public var on = false {
didSet { needsDisplay = true }
}
// MARK: - Initializers
public override init(frame: CGRect) {
super.init(frame: frame)
setupLayers()
}
required public init?(coder: NSCoder) {
super.init(coder: coder)
setupLayers()
}
// MARK: - Layers Setup
private func setupLayers() {
wantsLayer = true
layer?.masksToBounds = false
layerContentsRedrawPolicy = .onSetNeedsDisplay
// Background
setupBackgroundLayer()
layer?.addSublayer(backgroundLayer)
// Knob
setupKnobLayers()
layer?.addSublayer(knobContainer)
}
// MARK: Background Layer
private func setupBackgroundLayer() {
backgroundLayer.frame = bounds
backgroundLayer.autoresizingMask = [.layerWidthSizable, .layerHeightSizable]
}
// MARK: Knob
private func setupKnobLayers() {
setupKnobContainerLayer()
setupKnobLayer()
setupKnobLayerShadows()
knobContainer.addSublayer(knobLayer)
knobContainer.insertSublayer(knobShadows.smallStroke, below: knobLayer)
knobContainer.insertSublayer(knobShadows.smallShadow, below: knobShadows.smallStroke)
knobContainer.insertSublayer(knobShadows.mediumShadow, below: knobShadows.smallShadow)
knobContainer.insertSublayer(knobShadows.bigShadow, below: knobShadows.mediumShadow)
}
private func setupKnobContainerLayer() {
knobContainer.frame = knobFrameForState(on: false, pressed: false)
}
private func setupKnobLayer() {
knobLayer.autoresizingMask = [.layerWidthSizable]
knobLayer.backgroundColor = NSColor.white.cgColor
knobLayer.frame = knobContainer.bounds
knobLayer.cornerRadius = ceil(knobContainer.bounds.height / 2)
}
private func setupKnobLayerShadows() {
let effectScale = scaleFactor
// Small Stroke
let smallStroke = knobShadows.smallStroke
smallStroke.frame = knobLayer.frame.insetBy(dx: -1, dy: -1)
smallStroke.autoresizingMask = [.layerWidthSizable]
smallStroke.backgroundColor = NSColor.black.withAlphaComponent(0.06).cgColor
smallStroke.cornerRadius = ceil(smallStroke.bounds.height / 2)
let smallShadow = knobShadows.smallShadow
smallShadow.frame = knobLayer.frame.insetBy(dx: 2, dy: 2)
smallShadow.autoresizingMask = [.layerWidthSizable]
smallShadow.cornerRadius = ceil(smallShadow.bounds.height / 2)
smallShadow.backgroundColor = NSColor.red.cgColor
smallShadow.shadowColor = NSColor.black.cgColor
smallShadow.shadowOffset = CGSize(width: 0, height: -3 * effectScale)
smallShadow.shadowOpacity = 0.12
smallShadow.shadowRadius = 2.0 * effectScale
let mediumShadow = knobShadows.mediumShadow
mediumShadow.frame = smallShadow.frame
mediumShadow.autoresizingMask = [.layerWidthSizable]
mediumShadow.cornerRadius = smallShadow.cornerRadius
mediumShadow.backgroundColor = NSColor.red.cgColor
mediumShadow.shadowColor = NSColor.black.cgColor
mediumShadow.shadowOffset = CGSize(width: 0, height: -9 * effectScale)
mediumShadow.shadowOpacity = 0.16
mediumShadow.shadowRadius = 6.0 * effectScale
let bigShadow = knobShadows.bigShadow
bigShadow.frame = smallShadow.frame
bigShadow.autoresizingMask = [.layerWidthSizable]
bigShadow.cornerRadius = smallShadow.cornerRadius
bigShadow.backgroundColor = NSColor.red.cgColor
bigShadow.shadowColor = NSColor.black.cgColor
bigShadow.shadowOffset = CGSize(width: 0, height: -9 * effectScale)
bigShadow.shadowOpacity = 0.06
bigShadow.shadowRadius = 0.5 * effectScale
}
// MARK: - Drawing
public override func updateLayer() {
// Background
backgroundLayer.cornerRadius = ceil(bounds.height / 2)
backgroundLayer.borderWidth = on ? ceil(bounds.height) : 3.0 * scaleFactor
backgroundLayer.borderColor = on ? tintColor.cgColor : NSColor.black.withAlphaComponent(0.09).cgColor
// Knob
knobContainer.frame = knobFrameForState(on: on, pressed: pressed)
knobLayer.cornerRadius = ceil(knobContainer.bounds.height / 2)
}
// MARK: - Helpers
private func knobFrameForState(on: Bool, pressed: Bool) -> CGRect {
let borderWidth = 3.0 * scaleFactor
var origin: CGPoint
var size: CGSize {
if pressed {
return CGSize(
width: ceil(bounds.width * 0.69) - (2 * borderWidth),
height: bounds.height - (2 * borderWidth)
)
}
return CGSize(width: bounds.height - (2 * borderWidth), height: bounds.height - (2 * borderWidth))
}
if on {
origin = CGPoint(x: bounds.width - size.width - borderWidth, y: borderWidth)
} else {
origin = CGPoint(x: borderWidth, y: borderWidth)
}
return CGRect(origin: origin, size: size)
}
// MARK: - Events
public override func mouseDown(with theEvent: NSEvent) {
super.mouseDown(with: theEvent)
pressed = true
needsDisplay = true
}
public override func mouseUp(with theEvent: NSEvent) {
super.mouseUp(with: theEvent)
pressed = false
on = !on
if(action != nil && target != nil) {
sendAction(action, to: target)
}
}
}