Skip to content

Commit

Permalink
coreoptions swiftui first pass
Browse files Browse the repository at this point in the history
Signed-off-by: Joseph Mattiello <git@joemattiello.com>
  • Loading branch information
JoeMatt committed Dec 4, 2024
1 parent 02c3269 commit dd7ed1e
Show file tree
Hide file tree
Showing 5 changed files with 374 additions and 23 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import Foundation
import RealmSwift
import PVCoreBridge
import PVLibrary
import Combine

@MainActor
final class CoreOptionsViewModel: ObservableObject {
/// Published list of available cores that implement CoreOptional
@Published private(set) var availableCores: [PVCore] = []

/// The currently selected core for options display
@Published var selectedCore: (core: PVCore, coreClass: CoreOptional.Type)?

private var cancellables = Set<AnyCancellable>()

init() {
loadAvailableCores()
}

/// Load all cores that implement CoreOptional
private func loadAvailableCores() {
let realm = try! Realm()
availableCores = realm.objects(PVCore.self)
.sorted(byKeyPath: "projectName")
.filter { pvcore in
guard let coreClass = NSClassFromString(pvcore.principleClass) as? CoreOptional.Type else {
return false
}
return true
}
}

/// Select a core to display its options
func selectCore(_ core: PVCore) {
guard let coreClass = NSClassFromString(core.principleClass) as? CoreOptional.Type else {
return
}

selectedCore = (core: core, coreClass: coreClass)
}

/// Get the current value for an option
func currentValue(for option: CoreOption) -> Any? {
guard let coreClass = selectedCore?.coreClass else { return nil }

switch option {
case .bool(_, let defaultValue):
return coreClass.storedValueForOption(Bool.self, option.key) ?? defaultValue
case .string(_, let defaultValue):
return coreClass.storedValueForOption(String.self, option.key) ?? defaultValue
case .enumeration(_, _, let defaultValue):
return coreClass.storedValueForOption(Int.self, option.key) ?? defaultValue
case .range(_, _, let defaultValue):
return coreClass.storedValueForOption(Int.self, option.key) ?? defaultValue
case .rangef(_, _, let defaultValue):
return coreClass.storedValueForOption(Float.self, option.key) ?? defaultValue
case .multi(_, let values):
return coreClass.storedValueForOption(String.self, option.key) ?? values.first?.title
case .group(_, _):
return nil
@unknown default:
return nil
}
}

/// Set a new value for an option
func setValue(_ value: Any, for option: CoreOption) {
guard let coreClass = selectedCore?.coreClass else { return }

switch value {
case let boolValue as Bool:
coreClass.setValue(boolValue, forOption: option)
case let stringValue as String:
coreClass.setValue(stringValue, forOption: option)
case let intValue as Int:
coreClass.setValue(intValue, forOption: option)
case let floatValue as Float:
coreClass.setValue(floatValue, forOption: option)
default:
break
}
}
}
220 changes: 220 additions & 0 deletions PVUI/Sources/PVSwiftUI/Settings/Views/CoreOptionsDetailView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
import SwiftUI
import PVCoreBridge
import PVLibrary

/// View that displays and allows editing of core options for a specific core
struct CoreOptionsDetailView: View {
let coreClass: CoreOptional.Type
let title: String

private var groupedOptions: [(title: String, options: [CoreOption])] {
var rootOptions = [CoreOption]()
var groups = [(title: String, options: [CoreOption])]()

// Process options into groups
coreClass.options.forEach { option in
switch option {
case let .group(display, subOptions):
groups.append((title: display.title, options: subOptions))
default:
rootOptions.append(option)
}
}

// Add root options as first group if any exist
if !rootOptions.isEmpty {
groups.insert((title: "General", options: rootOptions), at: 0)
}

return groups
}

var body: some View {
List {
ForEach(groupedOptions.indices, id: \.self) { sectionIndex in
let group = groupedOptions[sectionIndex]
Section(header: Text(group.title)) {
ForEach(group.options.indices, id: \.self) { optionIndex in
let option = group.options[optionIndex]
optionView(for: option)
}
}
}
}
.navigationTitle(title)
}

@ViewBuilder
private func optionView(for option: CoreOption) -> some View {
switch option {
case let .bool(display, defaultValue):
Toggle(isOn: Binding(
get: { coreClass.storedValueForOption(Bool.self, option.key) ?? defaultValue },
set: { coreClass.setValue($0, forOption: option) }
)) {
VStack(alignment: .leading) {
Text(display.title)
if let description = display.description {
Text(description)
.font(.caption)
.foregroundColor(.secondary)
}
}
}

case let .enumeration(display, values, defaultValue):
let selection = Binding(
get: { coreClass.storedValueForOption(Int.self, option.key) ?? defaultValue },
set: { coreClass.setValue($0, forOption: option) }
)

NavigationLink {
List {
ForEach(values, id: \.value) { value in
Button {
selection.wrappedValue = value.value
} label: {
HStack {
VStack(alignment: .leading) {
Text(value.title)
if let description = value.description {
Text(description)
.font(.caption)
.foregroundColor(.secondary)
}
}
Spacer()
if value.value == selection.wrappedValue {
Image(systemName: "checkmark")
}
}
}
}
}
.navigationTitle(display.title)
} label: {
VStack(alignment: .leading) {
Text(display.title)
if let description = display.description {
Text(description)
.font(.caption)
.foregroundColor(.secondary)
}
Text(values.first { $0.value == selection.wrappedValue }?.title ?? "")
.foregroundColor(.secondary)
}
}

case let .range(display, range, defaultValue):
VStack(alignment: .leading) {
Text(display.title)
if let description = display.description {
Text(description)
.font(.caption)
.foregroundColor(.secondary)
}
Slider(
value: Binding(
get: { Double(coreClass.storedValueForOption(Int.self, option.key) ?? defaultValue) },
set: { coreClass.setValue(Int($0), forOption: option) }
),
in: Double(range.min)...Double(range.max),
step: 1
) {
Text(display.title)
} minimumValueLabel: {
Text("\(range.min)")
} maximumValueLabel: {
Text("\(range.max)")
}
}

case let .rangef(display, range, defaultValue):
VStack(alignment: .leading) {
Text(display.title)
if let description = display.description {
Text(description)
.font(.caption)
.foregroundColor(.secondary)
}
Slider(
value: Binding(
get: { Double(coreClass.storedValueForOption(Float.self, option.key) ?? defaultValue) },
set: { coreClass.setValue(Float($0), forOption: option) }
),
in: Double(range.min)...Double(range.max),
step: 0.1
) {
Text(display.title)
} minimumValueLabel: {
Text(String(format: "%.1f", range.min))
} maximumValueLabel: {
Text(String(format: "%.1f", range.max))
}
}

case let .multi(display, values):
let selection = Binding(
get: { coreClass.storedValueForOption(String.self, option.key) ?? values.first?.title ?? "" },
set: { coreClass.setValue($0, forOption: option) }
)

NavigationLink {
List {
ForEach(values, id: \.title) { value in
Button {
selection.wrappedValue = value.title
} label: {
HStack {
VStack(alignment: .leading) {
Text(value.title)
if let description = value.description {
Text(description)
.font(.caption)
.foregroundColor(.secondary)
}
}
Spacer()
if value.title == selection.wrappedValue {
Image(systemName: "checkmark")
}
}
}
}
}
.navigationTitle(display.title)
} label: {
VStack(alignment: .leading) {
Text(display.title)
if let description = display.description {
Text(description)
.font(.caption)
.foregroundColor(.secondary)
}
Text(selection.wrappedValue)
.foregroundColor(.secondary)
}
}

case let .string(display, defaultValue):
let text = Binding(
get: { coreClass.storedValueForOption(String.self, option.key) ?? defaultValue },
set: { coreClass.setValue($0, forOption: option) }
)

VStack(alignment: .leading) {
Text(display.title)
if let description = display.description {
Text(description)
.font(.caption)
.foregroundColor(.secondary)
}
TextField("Value", text: text)
.textFieldStyle(RoundedBorderTextFieldStyle())
}

case .group(_, _):
EmptyView() // Groups are handled at the section level
}
}
}
63 changes: 63 additions & 0 deletions PVUI/Sources/PVSwiftUI/Settings/Views/CoreOptionsListView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import SwiftUI
import PVCoreBridge
import PVLibrary

/// A simple struct to hold core information for the list
private struct CoreListItem: Identifiable {
let id: String
let name: String
let coreClass: CoreOptional.Type

init(core: PVCore) {
self.id = core.identifier
self.name = core.projectName
self.coreClass = NSClassFromString(core.principleClass) as! CoreOptional.Type
}
}

/// View that lists all cores that implement CoreOptional
struct CoreOptionsListView: View {
@StateObject private var viewModel = CoreOptionsViewModel()

private var coreItems: [CoreListItem] {
viewModel.availableCores.compactMap { core in
guard let _ = NSClassFromString(core.principleClass) as? CoreOptional.Type else {
return nil
}
return CoreListItem(core: core)
}
}

var body: some View {
ScrollView {
LazyVStack(spacing: 8) {
ForEach(coreItems) { item in
CoreListItemView(item: item)
}
}
.padding(.vertical)
}
.navigationTitle("Core Options")
}
}

/// View for a single core item in the list
private struct CoreListItemView: View {
let item: CoreListItem

var body: some View {
NavigationLink {
CoreOptionsDetailView(coreClass: item.coreClass, title: item.name)
} label: {
VStack(alignment: .leading) {
Text(item.name)
.font(.headline)
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
.background(Color(.secondarySystemGroupedBackground))
.cornerRadius(10)
}
.padding(.horizontal)
}
}
Loading

0 comments on commit dd7ed1e

Please sign in to comment.