Skip to content

Commit

Permalink
Fix focusing of in-game menu buttons for tvOS and implement basics of…
Browse files Browse the repository at this point in the history
… VideoSettingsView for tvOS (which doesn't have any built-in Slider view)
  • Loading branch information
stackotter committed Apr 14, 2024
1 parent 756a374 commit 2685053
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 26 deletions.
42 changes: 33 additions & 9 deletions Sources/Client/Views/Play/InGameMenu.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,38 @@ struct InGameMenu: View {
case settings
}

#if os(tvOS)
@Namespace var focusNamespace
#endif

@EnvironmentObject var appState: StateWrapper<AppState>

@Binding var presented: Bool
@State var state: InGameMenuState = .menu

@FocusState var focusState: FocusElements?

func moveFocus(_ direction: MoveCommandDirection) {
if let focusState = focusState {
let step: Int
switch direction {
case .down:
step = 1
case .up:
step = -1
case .left, .right:
return
}
let count = FocusElements.allCases.count
// Add an extra count before taking the modulo cause Swift's mod operator isn't
// the real mathematical modulo.
let index = (focusState.rawValue + step + count) % count
self.focusState = FocusElements.allCases[index]
}
}

enum FocusElements: Int, CaseIterable {
case backToGame
case settings
case disconnect
}

init(presented: Binding<Bool>) {
_presented = presented
}
Expand All @@ -31,19 +54,20 @@ struct InGameMenu: View {
}
#if !os(tvOS)
.keyboardShortcut(.escape, modifiers: [])
#else
.prefersDefaultFocus(in: focusNamespace)
#endif
.focused($focusState, equals: .backToGame)
.buttonStyle(PrimaryButtonStyle())

Button("Settings") {
state = .settings
}
.focused($focusState, equals: .settings)
.buttonStyle(SecondaryButtonStyle())

Button("Disconnect") {
appState.update(to: .serverList)
}
.focused($focusState, equals: .disconnect)
.buttonStyle(SecondaryButtonStyle())
}
#if !os(tvOS)
Expand All @@ -57,10 +81,10 @@ struct InGameMenu: View {
}
.frame(width: geometry.size.width, height: geometry.size.height)
.background(Color.black.opacity(0.702), alignment: .center)
#if os(tvOS)
.onAppear {
focusState = .backToGame
}
.focusSection()
.focusScope(focusNamespace)
#endif
}
}
}
Expand Down
7 changes: 6 additions & 1 deletion Sources/Client/Views/Play/JoinServerAndThen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,17 @@ struct JoinServerAndThen<Content: View>: View {
HStack {
ProgressView(value: Double(received) / Double(total))
Text("\(received) of \(total)")
}.frame(maxWidth: 200)
}
#if !os(tvOS)
.frame(maxWidth: 200)
#endif
}

Button("Cancel", action: cancel)
.buttonStyle(SecondaryButtonStyle())
#if !os(tvOS)
.frame(width: 150)
#endif
case .joined:
if let client = client {
content(client)
Expand Down
5 changes: 3 additions & 2 deletions Sources/Client/Views/Settings/SettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ struct SettingsView: View {
self.isInGame = isInGame
self.done = done

#if os(iOS)
#if os(iOS) || os(tvOS)
// On iOS, the navigation isn't a split view, so we should show the settings page selection
// view first instead of auto-selecting the first page.
self._currentPage = State(initialValue: landingPage)
Expand All @@ -36,13 +36,14 @@ struct SettingsView: View {
var body: some View {
NavigationView {
List {
#if !os(tvOS)
NavigationLink(
"Video",
destination: VideoSettingsView().padding(),
tag: SettingsState.video,
selection: $currentPage
)

#if !os(tvOS)
NavigationLink(
"Controls",
destination: ControlsSettingsView().padding(),
Expand Down
52 changes: 38 additions & 14 deletions Sources/Client/Views/Settings/VideoSettingsView.swift
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import SwiftUI
import DeltaCore

#if !os(tvOS)
struct VideoSettingsView: View {
@EnvironmentObject var managedConfig: ManagedConfig

var body: some View {
ScrollView {
HStack {
Text("Render distance: \(managedConfig.render.renderDistance)")
Spacer()
#if os(tvOS)
ProgressView(value: Double(managedConfig.render.renderDistance) / 32)
#else
Slider(
value: Binding {
Float(managedConfig.render.renderDistance)
Expand All @@ -20,8 +21,23 @@ struct VideoSettingsView: View {
step: 1
)
.frame(width: 220)
#endif
}
#if os(tvOS)
.focusable()
.onMoveCommand { direction in
switch direction {
case .left:
managedConfig.render.renderDistance -= 1
case .right:
managedConfig.render.renderDistance += 1
default:
break
}
}
#endif

#if !os(tvOS)
HStack {
Text("FOV: \(Int(managedConfig.render.fovY.rounded()))")
Spacer()
Expand All @@ -35,6 +51,7 @@ struct VideoSettingsView: View {
)
.frame(width: 220)
}
#endif

HStack {
Text("Render mode")
Expand All @@ -49,23 +66,30 @@ struct VideoSettingsView: View {
#elseif os(iOS) || os(tvOS)
.pickerStyle(DefaultPickerStyle())
#endif
#if !os(tvOS)
.frame(width: 220)
#endif
}

HStack {
Text("Order independent transparency")
Spacer()
Toggle(
"Order independent transparency",
isOn: $managedConfig.render.enableOrderIndependentTransparency
)
.labelsHidden()
.toggleStyle(.switch)
.frame(width: 220)
}
// Order independent transparency doesn't work on tvOS yet (our implementation uses a Metal
// feature which isn't supported on tvOS).
#if !os(tvOS)
HStack {
Text("Order independent transparency")
Spacer()
Toggle(
"Order independent transparency",
isOn: $managedConfig.render.enableOrderIndependentTransparency
)
.labelsHidden()
.toggleStyle(.switch)
.frame(width: 220)
}
#endif
}
#if !os(tvOS)
.frame(width: 450)
#endif
.navigationTitle("Video")
}
}
#endif

0 comments on commit 2685053

Please sign in to comment.