Skip to content

Commit

Permalink
Merge pull request #11 from ThasianX/feature/api-selectednode
Browse files Browse the repository at this point in the history
Feature/api selectednode
  • Loading branch information
ThasianX authored Aug 6, 2020
2 parents 9f7cdaf + c5bdc10 commit cf8628a
Show file tree
Hide file tree
Showing 11 changed files with 291 additions and 87 deletions.
2 changes: 1 addition & 1 deletion ElegantColorPalette.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'ElegantColorPalette'
s.version = '1.1.0'
s.version = '1.2.0'
s.summary = 'The elegant color picker missed in UIKit and SwiftUI'

s.description = <<-DESC
Expand Down
49 changes: 45 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ This example GIF is from [ElegantTimeline](https://github.com/ThasianX/ElegantTi

`ElegantColorPalette` comes with 24 different themes and is inspired by [TimePage](https://us.moleskine.com/timepage/p0486) and is part of a larger repository of elegant demonstrations like this: [TimePage Clone](https://github.com/ThasianX/TimePage-Clone).

The top level view is an `SKView` that presents an `SKScene` of colors nodes. The color nodes are `SKShapeNode` subclasses. When using this library, you are only interacting with the `SKView`: all you have to do is configure the size of the view either through autolayout or size constraints and the view does the rest.
The top level view is an `SKView` that presents an `SKScene` of colors nodes. The color nodes are `SKShapeNode` subclasses. For more experienced developers that are or want to learn SpriteKit, the `SKScene` that contains the color nodes is exposed for greater fine tuning on your end.

Features
* Dynamic color nodes - passing in a dynamic `UIColor` will allow the color node to properly adjust to light or dark mode
Expand Down Expand Up @@ -206,13 +206,54 @@ ColorPaletteBindingView(...)
.focus(location: .zero, speed: 1600, rate: 0.2)
```

Use `canMoveFocusedNode` to customize whether the focused node is movable or not.

```swift
// For UIKit
ColorPaletteView(...)
.canMoveFocusedNode(false)

// For SwiftUI
ColorPaletteBindingView(...)
.canMoveFocusedNode(false)
```


For SwiftUI, you can also customize the binding animation that occurs when a new palette color is selected.

```swift
ColorPaletteBindingView(...)
.bindingAnimation(.easeInOut)
```

### Scene Customization

Like mentioned in the introduction, the `SKScene` that drives the view is exposed through a property called `paletteScene`. If you are experienced with SpriteKit, you may tamper with the scene for greater flexibility.

Use `spawnConfiguration` to customize the allowable area of where nodes can spawn:

```swift
// For UIKit
ColorPaletteView(...)
.spawnConfiguration(widthRatio: 1, heightRatio: 1)

// For SwiftUI
ColorPaletteBindingView(...)
.spawnConfiguration(widthRatio: 1, heightRatio: 1)
```

Use `rotation` to customize how fast you want the nodes to rotate around your focus location:

```swift
// For UIKit
ColorPaletteView(...)
.rotation(multiplier: 4)

// For SwiftUI
ColorPaletteBindingView(...)
.rotation(multiplier: 4)
```

### Events

Use `didSelectColor` to react to any change of the selected palette color:
Expand Down Expand Up @@ -258,7 +299,7 @@ Inside `Sources`, drag the `ElegantColorPalette` folder into your project.
use_frameworks!

target 'YOUR_TARGET_NAME' do
pod 'ElegantColorPalette', '~> 1.1'
pod 'ElegantColorPalette', '~> 1.2'
end
```

Expand All @@ -273,7 +314,7 @@ $ pod install
Add this to `Cartfile`

```
github "ThasianX/ElegantColorPalette" ~> 1.1.0
github "ThasianX/ElegantColorPalette" ~> 1.2.0
```

```bash
Expand All @@ -294,7 +335,7 @@ import PackageDescription
let package = Package(
name: "TestProject",
dependencies: [
.package(url: "https://github.com/ThasianX/ElegantColorPalette", from: "1.1.0")
.package(url: "https://github.com/ThasianX/ElegantColorPalette", from: "1.2.0")
],
targets: [
.target(name: "TestProject", dependencies: ["ElegantColorPalette"])
Expand Down
43 changes: 39 additions & 4 deletions Sources/ElegantColorPalette/ColorPaletteBindingView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ public struct ColorPaletteBindingView: UIViewRepresentable {
var didSelectColor: ((PaletteColor) -> Void)?
var nodeStyle: NodeStyle = DefaultNodeStyle()
var focusSettings: FocusSettings = .default
var canMoveFocusedNode: Bool = true
var spawnConfiguration: SpawnConfiguration = .default
var rotationMultiplier: CGFloat = 1

/// Initializes a new `ColorPaletteBindingView`.
///
Expand All @@ -43,19 +46,22 @@ public struct ColorPaletteBindingView: UIViewRepresentable {
.didSelectColor(groupedCallback)
.nodeStyle(nodeStyle)
.focus(settings: focusSettings)
.canMoveFocusedNode(canMoveFocusedNode)
.spawnConfiguration(spawnConfiguration)
.rotation(multiplier: rotationMultiplier)
.update(withColors: colors, selectedColor: selectedColor)
}

private func groupedCallback(_ color: PaletteColor) {
withAnimation(bindingAnimation) {
bindingCallback(color)
}
bindingCallback(color)
didSelectColor?(color)
}

private func bindingCallback(_ color: PaletteColor) {
DispatchQueue.main.async {
self.selectedColor = color
withAnimation(self.bindingAnimation) {
self.selectedColor = color
}
}
}

Expand Down Expand Up @@ -97,8 +103,37 @@ extension ColorPaletteBindingView: Buildable {
smoothingRate: rate))
}

/// Configures the binding animation
///
/// - Parameter animation: the animation that is executed whenever the `selectedColor` changes
public func bindingAnimation(_ animation: Animation) -> Self {
mutating(keyPath: \.bindingAnimation, value: animation)
}

/// Configures whether the focused node can be moved or not.
///
/// - Parameter canMove: moveable or not - nodes that collide with the focused node will not move it either
@discardableResult
public func canMoveFocusedNode(_ canMove: Bool) -> Self {
mutating(keyPath: \.canMoveFocusedNode, value: canMove)
}

/// Configures the default spawn configuration settings.
///
/// - Parameter widthRatio: the ratio of the scene width that a node should be able to spawn on. Value between 0-1
/// - Parameter heightRatio: the ratio of the scene height that a node should be able to spawn on. Value between 0-1
@discardableResult
public func spawnConfiguration(widthRatio: CGFloat = 1, heightRatio: CGFloat = 0.65) -> Self {
mutating(keyPath: \.spawnConfiguration,
value: SpawnConfiguration(widthRatio: widthRatio, heightRatio: heightRatio))
}

/// Configures the default rotation multiplier of the nodes around the focus location.
///
/// - Parameter multiplier: the factor by which the nodes should speed up their rotation. The higher, the faster.
@discardableResult
public func rotation(multiplier: CGFloat = 1) -> Self {
mutating(keyPath: \.rotationMultiplier, value: multiplier)
}

}
36 changes: 35 additions & 1 deletion Sources/ElegantColorPalette/ColorPaletteView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import UIKit
///
public class ColorPaletteView: SKView {

private lazy var paletteScene: ColorPaletteScene = {
public lazy var paletteScene: ColorPaletteScene = {
let scene = ColorPaletteScene(paletteManager: paletteManager)
scene.scaleMode = .resizeFill
scene.anchorPoint = .init(x: 0.5, y: 0.5)
Expand Down Expand Up @@ -129,6 +129,34 @@ public extension ColorPaletteView {
return self
}

/// Configures whether the focused node can be moved or not.
///
/// - Parameter canMove: moveable or not - nodes that collide with the focused node will not move it either
@discardableResult
func canMoveFocusedNode(_ canMove: Bool) -> Self {
paletteManager.canMoveFocusedNode = canMove
return self
}

/// Configures the default spawn configuration settings.
///
/// - Parameter widthRatio: the ratio of the scene width that a node should be able to spawn on. Value between 0-1
/// - Parameter heightRatio: the ratio of the scene height that a node should be able to spawn on. Value between 0-1
@discardableResult
func spawnConfiguration(widthRatio: CGFloat = 1, heightRatio: CGFloat = 0.65) -> Self {
paletteManager.spawnConfiguration = SpawnConfiguration(widthRatio: widthRatio, heightRatio: heightRatio)
return self
}

/// Configures the default rotation multiplier of the nodes around the focus location.
///
/// - Parameter multiplier: the factor by which the nodes should speed up their rotation. The higher, the faster.
@discardableResult
func rotation(multiplier: CGFloat = 1) -> Self {
paletteManager.rotationMultiplier = multiplier
return self
}

}

// MARK: Internal Methods
Expand All @@ -140,4 +168,10 @@ extension ColorPaletteView {
return self
}

@discardableResult
func spawnConfiguration(_ config: SpawnConfiguration) -> Self {
paletteManager.spawnConfiguration = config
return self
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ class ColorPaletteManager: ObservableObject {

var nodeStyle: NodeStyle = DefaultNodeStyle()
var focusSettings: FocusSettings = .default
var canMoveFocusedNode: Bool = true
var spawnConfiguration: SpawnConfiguration = .default
var rotationMultiplier: CGFloat = 1

var didSelectColor: ((PaletteColor) -> Void)?

Expand All @@ -38,3 +41,37 @@ class ColorPaletteManager: ObservableObject {
}

}

protocol ColorPaletteManagerDirectAccess {

var paletteManager: ColorPaletteManager { get }

}

extension ColorPaletteManagerDirectAccess {

var selectedColor: PaletteColor? {
paletteManager.selectedColor
}

var nodeStyle: NodeStyle {
paletteManager.nodeStyle
}

var focusSettings: FocusSettings {
paletteManager.focusSettings
}

var canMoveFocusedNode: Bool {
paletteManager.canMoveFocusedNode
}

var spawnConfiguration: SpawnConfiguration {
paletteManager.spawnConfiguration
}

var rotationMultiplier: CGFloat {
paletteManager.rotationMultiplier
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public extension NodeStyleConfiguration {
var defaultStyledNode: ColorNode {
node
.scaleFade(!isFirstShown,
scale: isPressed ? 0.9 : 1,
scale: isPressed ? 0.9 : (isFocusing ? 1.2 : 1),
opacity: isPressed ? 0.3 : 1)
.highlight(isSelected)
.label(isFocused)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,33 +17,63 @@ public struct NodeStyleConfiguration {
/// Whether or not the node matches the selected `PaletteColor`.
public let isSelected: Bool

/// Whether or not the node is currently in the process of moving towards
/// its focused location.
public let isFocusing: Bool

/// Whether or not the node is currently focused.
///
/// Focused means that this node is the node the user most recently tapped on.
/// Focused means that this node is the node the user most recently tapped on
/// and it has arrived at its focus location.
public let isFocused: Bool

}

extension NodeStyleConfiguration {

static func firstShown(_ node: ColorNode, isSelected: Bool) -> NodeStyleConfiguration {
NodeStyleConfiguration(node: node, isFirstShown: true, isPressed: false, isSelected: isSelected, isFocused: false)
NodeStyleConfiguration(node: node,
isFirstShown: true,
isPressed: false,
isSelected: isSelected,
isFocusing: false,
isFocused: false)
}

static func touchedDown(_ node: ColorNode, isSelected: Bool, isFocused: Bool) -> NodeStyleConfiguration {
NodeStyleConfiguration(node: node, isFirstShown: false, isPressed: true, isSelected: isSelected, isFocused: isFocused)
NodeStyleConfiguration(node: node,
isFirstShown: false,
isPressed: true,
isSelected: isSelected,
isFocusing: false,
isFocused: isFocused)
}

static func touchedUp(_ node: ColorNode, isSelected: Bool, isFocused: Bool) -> NodeStyleConfiguration {
NodeStyleConfiguration(node: node, isFirstShown: false, isPressed: false, isSelected: isSelected, isFocused: isFocused)
static func touchedUp(_ node: ColorNode, isSelected: Bool, isFocusing: Bool, isFocused: Bool) -> NodeStyleConfiguration {
NodeStyleConfiguration(node: node,
isFirstShown: false,
isPressed: false,
isSelected: isSelected,
isFocusing: isFocusing,
isFocused: isFocused)
}

static func unselected(_ node: ColorNode) -> NodeStyleConfiguration {
NodeStyleConfiguration(node: node, isFirstShown: false, isPressed: false, isSelected: false, isFocused: false)
NodeStyleConfiguration(node: node,
isFirstShown: false,
isPressed: false,
isSelected: false,
isFocusing: false,
isFocused: false)
}

static func selectedAndFocused(_ node: ColorNode) -> NodeStyleConfiguration {
NodeStyleConfiguration(node: node, isFirstShown: false, isPressed: false, isSelected: true, isFocused: true)
NodeStyleConfiguration(node: node,
isFirstShown: false,
isPressed: false,
isSelected: true,
isFocusing: false,
isFocused: true)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Kevin Li - 4:01 PM - 8/5/20

import UIKit

struct SpawnConfiguration {

let widthRatio: CGFloat
let heightRatio: CGFloat

}

extension SpawnConfiguration {

static let `default` = SpawnConfiguration(widthRatio: 1, heightRatio: 0.65)

}
Loading

0 comments on commit cf8628a

Please sign in to comment.