Skip to content

Instantly share code, notes, and snippets.

@ejjonny
Created March 18, 2022 01:49
Show Gist options
  • Save ejjonny/0bea86ee81f1467e2048a80db0b73064 to your computer and use it in GitHub Desktop.
Save ejjonny/0bea86ee81f1467e2048a80db0b73064 to your computer and use it in GitHub Desktop.
import SwiftUI
import Foundation
import Combine
struct ContentView: View {
struct Mode: Identifiable {
let id = UUID()
let blend: BlendMode
init(_ blend: BlendMode) {
self.blend = blend
}
}
let blendModes: [Mode] = [
.init(.colorBurn),
.init(.colorDodge),
.init(.difference),
.init(.exclusion),
.init(.hue)
]
var body: some View {
ZStack {
Image("cool")
.resizable()
ForEach(blendModes.indices, id: \.self) { i in
Color.red
.mask {
RollingStack() {
$0 % blendModes.count == i
}
}
.blendMode(blendModes[i].blend)
.opacity(0.5)
}
}
.aspectRatio(contentMode: .fill)
.frame(height: 200)
.clipped()
}
}
struct RollingStack: View {
@State var visibleIndices: [Int]
let rate: Double
var timer: Timer.TimerPublisher
let count: Int
let highlight: (Int) -> Bool
init(_ count: Int = 20, rate: Double = 0.7, rollingWindowSize: Int = 10, highlight: @escaping (Int) -> Bool) {
var visible = (0..<rollingWindowSize).map { $0 }
visible.retreatWindow((0..<count).map { $0 })
self.count = count
self.visibleIndices = visible
self.rate = rate
self.timer = Timer.publish(every: rate, on: .main, in: .default)
self.highlight = highlight
}
var body: some View {
HStack(spacing: 0) {
ForEach(visibleIndices, id: \.self) { i in
Rectangle()
.frame(width: 80, height: 200)
.foregroundColor(highlight(i) ? .clear : .black)
}
}
.transformEffect(.init(a: 1, b: 0, c: -0.3, d: 1, tx: 0, ty: 0))
.onReceive(timer) { _ in
advanceMarquee()
}
.onAppear {
advanceMarquee()
_ = timer.connect()
}
}
func advanceMarquee() {
withAnimation(.linear(duration: rate)) {
visibleIndices.advanceWindow((0..<count).map { $0 })
}
}
}
extension Array where Element == Int {
mutating func advanceWindow(_ source: [Int]) {
if let last = last,
source.indices.contains(last + 1) {
removeFirst()
append(last + 1)
} else if let first = source.first {
removeFirst()
append(first)
}
}
mutating func retreatWindow(_ source: [Int]) {
if let first = first,
source.indices.contains(first - 1) {
removeLast()
insert(first - 1, at: 0)
} else if let last = source.last {
removeLast()
insert(last, at: 0)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment