mirror of
https://github.com/jellyfin/Swiftfin.git
synced 2024-11-23 14:10:01 +00:00
b038f05848
* update header * the rest after upgrading
150 lines
4.2 KiB
Swift
150 lines
4.2 KiB
Swift
//
|
|
// Swiftfin is subject to the terms of the Mozilla Public
|
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
|
//
|
|
// Copyright (c) 2024 Jellyfin & Jellyfin Contributors
|
|
//
|
|
|
|
import SwiftUI
|
|
|
|
struct FocusGuideModifier: ViewModifier {
|
|
|
|
@FocusState
|
|
var focusDirection: FocusDirection?
|
|
@EnvironmentObject
|
|
var focusGuide: FocusGuide
|
|
|
|
let focusConstructor: FocusConstructor
|
|
let onContentFocus: (() -> Void)?
|
|
|
|
let debug = false
|
|
|
|
func body(content: Content) -> some View {
|
|
VStack(spacing: 0) {
|
|
|
|
Color(debug ? .red : .clear)
|
|
.frame(height: 1)
|
|
.if(focusConstructor.topTarget != nil, transform: { boundary in
|
|
boundary.focusable()
|
|
})
|
|
.focused($focusDirection, equals: .top)
|
|
|
|
HStack(spacing: 0) {
|
|
Color(debug ? .red : .clear)
|
|
.frame(width: 1)
|
|
.if(focusConstructor.leftTarget != nil, transform: { boundary in
|
|
boundary.focusable()
|
|
})
|
|
.focused($focusDirection, equals: .left)
|
|
|
|
content
|
|
.focused($focusDirection, equals: .content)
|
|
|
|
Color(debug ? .red : .clear)
|
|
.frame(width: 1)
|
|
.if(focusConstructor.rightTarget != nil, transform: { boundary in
|
|
boundary.focusable()
|
|
})
|
|
.focused($focusDirection, equals: .right)
|
|
}
|
|
|
|
Color(debug ? .red : .clear)
|
|
.frame(height: 1)
|
|
.if(focusConstructor.bottomTarget != nil, transform: { boundary in
|
|
boundary.focusable()
|
|
})
|
|
.focused($focusDirection, equals: .bottom)
|
|
}
|
|
.onChange(of: focusDirection) { focusDirection in
|
|
guard let focusDirection = focusDirection else { return }
|
|
switch focusDirection {
|
|
case .top:
|
|
focusGuide.transition(to: focusConstructor.topTarget!)
|
|
case .bottom:
|
|
focusGuide.transition(to: focusConstructor.bottomTarget!)
|
|
case .left:
|
|
focusGuide.transition(to: focusConstructor.leftTarget!)
|
|
case .right:
|
|
focusGuide.transition(to: focusConstructor.rightTarget!)
|
|
case .content: ()
|
|
}
|
|
}
|
|
.onChange(of: focusGuide.focusedTag) { newTag in
|
|
if newTag == focusConstructor.tag {
|
|
if let onContentFocus = onContentFocus {
|
|
onContentFocus()
|
|
} else {
|
|
focusDirection = .content
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
extension View {
|
|
func focusGuide(
|
|
_ focusGuide: FocusGuide,
|
|
tag: String,
|
|
onContentFocus: (() -> Void)? = nil,
|
|
top: String? = nil,
|
|
bottom: String? = nil,
|
|
left: String? = nil,
|
|
right: String? = nil
|
|
) -> some View {
|
|
let focusConstructor = FocusConstructor(
|
|
tag: tag,
|
|
topTarget: top,
|
|
bottomTarget: bottom,
|
|
leftTarget: left,
|
|
rightTarget: right
|
|
)
|
|
return modifier(FocusGuideModifier(focusConstructor: focusConstructor, onContentFocus: onContentFocus))
|
|
.environmentObject(focusGuide)
|
|
}
|
|
}
|
|
|
|
enum FocusDirection: String {
|
|
case top
|
|
case bottom
|
|
case content
|
|
case left
|
|
case right
|
|
}
|
|
|
|
struct FocusConstructor {
|
|
|
|
let tag: String
|
|
let topTarget: String?
|
|
let bottomTarget: String?
|
|
let leftTarget: String?
|
|
let rightTarget: String?
|
|
|
|
init(
|
|
tag: String,
|
|
topTarget: String?,
|
|
bottomTarget: String?,
|
|
leftTarget: String?,
|
|
rightTarget: String?
|
|
) {
|
|
self.tag = tag
|
|
self.topTarget = topTarget
|
|
self.bottomTarget = bottomTarget
|
|
self.leftTarget = leftTarget
|
|
self.rightTarget = rightTarget
|
|
}
|
|
}
|
|
|
|
class FocusGuide: ObservableObject {
|
|
|
|
@Published
|
|
private(set) var focusedTag: String?
|
|
|
|
private(set) var lastFocusedTag: String?
|
|
|
|
func transition(to tag: String?) {
|
|
lastFocusedTag = focusedTag
|
|
focusedTag = tag
|
|
}
|
|
}
|