vChewing-macOS/Packages/vChewing_MainAssembly/Sources/MainAssembly/Settings/SettingsUI/UserDefRenderableImpl.swift

248 lines
8.7 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
import Foundation
import IMKUtils
import Shared
import SwiftUI
// MARK: - UserDefRenderable Extension
public extension UserDefRenderable<String> {
@ViewBuilder
func render() -> some View {
if let metaData = metaData {
VStack(alignment: .leading) {
Group {
switch (def.dataType, def) {
case (.array, .kAppleLanguages):
Picker(LocalizedStringKey(metaData.shortTitle ?? ""), selection: binding) {
Text(LocalizedStringKey("Follow OS settings")).tag("auto")
Text(LocalizedStringKey("Simplified Chinese")).tag("zh-Hans")
Text(LocalizedStringKey("Traditional Chinese")).tag("zh-Hant")
Text(LocalizedStringKey("Japanese")).tag("ja")
Text(LocalizedStringKey("English")).tag("en")
}
case (.string, .kCandidateKeys):
HStack {
Text(LocalizedStringKey(metaData.shortTitle ?? ""))
Spacer()
ComboBox(
items: CandidateKey.suggestions,
text: binding
).frame(width: 180)
}
case (.string, .kAlphanumericalKeyboardLayout):
Picker(LocalizedStringKey(metaData.shortTitle ?? ""), selection: binding) {
ForEach(0 ... (IMKHelper.allowedAlphanumericalTISInputSources.count - 1), id: \.self) { id in
let theEntry = IMKHelper.allowedAlphanumericalTISInputSources[id]
Text(theEntry.titleLocalized).tag(theEntry.id)
}.id(UUID())
}
case (.string, .kBasicKeyboardLayout):
Picker(LocalizedStringKey(metaData.shortTitle ?? ""), selection: binding) {
ForEach(0 ... (IMKHelper.allowedBasicLayoutsAsTISInputSources.count - 1), id: \.self) { id in
let theEntry = IMKHelper.allowedBasicLayoutsAsTISInputSources[id]
if let theEntry = theEntry {
Text(theEntry.titleLocalized).tag(theEntry.id)
} else {
Divider()
}
}.id(UUID())
}
case (.string, .kCassettePath): EmptyView()
case (.string, .kUserDataFolderSpecified): EmptyView()
default: EmptyView()
}
}.disabled(OS.ifUnavailable(metaData.minimumOS))
descriptionView()
}
}
}
}
public extension UserDefRenderable<Bool> {
@ViewBuilder
func render() -> some View {
if let metaData = metaData {
VStack(alignment: .leading) {
Group {
switch def.dataType {
case .bool where options.isEmpty: //
Toggle(LocalizedStringKey(metaData.shortTitle ?? ""), isOn: binding)
case .bool where !options.isEmpty: //
let shortTitle = metaData.shortTitle
let picker = Picker(
LocalizedStringKey(metaData.shortTitle ?? ""),
selection: binding
) {
ForEach(options, id: \.key) { theTag, strOption in
Text(LocalizedStringKey(strOption)).tag(theTag == 0 ? false : true)
}
}
if shortTitle == nil {
picker.labelsHidden()
} else {
picker
}
default: Text("[Debug] Control Type Mismatch: \(def.rawValue)")
}
}.disabled(OS.ifUnavailable(metaData.minimumOS))
descriptionView()
}
}
}
}
public extension UserDefRenderable<Int> {
@ViewBuilder
func render() -> some View {
if let metaData = metaData {
VStack(alignment: .leading) {
Group {
switch def.dataType {
case .integer where options.isEmpty && def != .kKeyboardParser:
Text("[Debug] Needs Review: \(def.rawValue)")
case .integer where options.isEmpty && def == .kKeyboardParser: //
Picker(
LocalizedStringKey(metaData.shortTitle ?? ""),
selection: binding
) {
ForEach(KeyboardParser.allCases, id: \.self) { item in
if [7, 100].contains(item.rawValue) { Divider() }
Text(item.localizedMenuName).tag(item.rawValue)
}.id(UUID())
}
case .integer where !options.isEmpty:
VStack(alignment: .leading) {
let shortTitle = metaData.shortTitle
let picker = Picker(
LocalizedStringKey(metaData.shortTitle ?? ""),
selection: binding
) {
ForEach(options, id: \.key) { theTag, strOption in
Text(LocalizedStringKey(strOption)).tag(theTag)
}
}
if shortTitle == nil {
picker.labelsHidden()
} else {
picker
}
}
default: Text("[Debug] Control Type Mismatch: \(def.rawValue)")
}
}.disabled(OS.ifUnavailable(metaData.minimumOS))
descriptionView()
}
}
}
}
public extension UserDefRenderable<Double> {
@ViewBuilder
func render() -> some View {
if let metaData = metaData {
VStack(alignment: .leading) {
Group {
switch def.dataType {
case .double where options.isEmpty: // RAW Double
Text("[Debug] Needs Review: \(def.rawValue)")
case .double where !options.isEmpty:
VStack(alignment: .leading) {
let shortTitle = metaData.shortTitle
let picker = Picker(
LocalizedStringKey(metaData.shortTitle ?? ""),
selection: binding
) {
ForEach(options, id: \.key) { theTag, strOption in
Text(LocalizedStringKey(strOption)).tag(Double(theTag))
}
}
if shortTitle == nil {
picker.labelsHidden()
} else {
picker
}
}
default: Text("[Debug] Control Type Mismatch: \(def.rawValue)")
}
}.disabled(OS.ifUnavailable(metaData.minimumOS))
descriptionView()
}
}
}
}
// MARK: - NSComboBox
// Ref: https://stackoverflow.com/a/71058587/4162914
// License: https://creativecommons.org/licenses/by-sa/4.0/
@available(macOS 10.15, *)
public struct ComboBox: NSViewRepresentable {
// The items that will show up in the pop-up menu:
public var items: [String] = []
// The property on our parent view that gets synced to the current
// stringValue of the NSComboBox, whether the user typed it in or
// selected it from the list:
@Binding public var text: String
public func makeCoordinator() -> Coordinator {
Coordinator(self)
}
public func makeNSView(context: Context) -> NSComboBox {
let comboBox = NSComboBox()
comboBox.usesDataSource = false
comboBox.completes = false
comboBox.delegate = context.coordinator
comboBox.intercellSpacing = NSSize(width: 0.0, height: 10.0)
return comboBox
}
public func updateNSView(_ nsView: NSComboBox, context: Context) {
nsView.removeAllItems()
nsView.addItems(withObjectValues: items)
// ComboBox doesn't automatically select the item matching its text;
// we must do that manually. But we need the delegate to ignore that
// selection-change or we'll get a "state modified during view update;
// will cause undefined behavior" warning.
context.coordinator.ignoreSelectionChanges = true
nsView.stringValue = text
nsView.selectItem(withObjectValue: text)
context.coordinator.ignoreSelectionChanges = false
}
public class Coordinator: NSObject, NSComboBoxDelegate {
public var parent: ComboBox
public var ignoreSelectionChanges = false
public init(_ parent: ComboBox) {
self.parent = parent
}
public func comboBoxSelectionDidChange(_ notification: Notification) {
if !ignoreSelectionChanges,
let box: NSComboBox = notification.object as? NSComboBox,
let newStringValue: String = box.objectValueOfSelectedItem as? String
{
parent.text = newStringValue
}
}
public func controlTextDidEndEditing(_ obj: Notification) {
if let textField = obj.object as? NSTextField {
parent.text = textField.stringValue
}
}
}
}