From 0685f2a390d0b324f5b5d06ff8aed35abc15c924 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sat, 1 Oct 2022 15:09:44 +0800 Subject: [PATCH] Repo // Add package "TDKCandidateBackport" --- .../vChewing_TDKCandidateBackports/.gitignore | 9 + .../Package.swift | 30 +++ .../vChewing_TDKCandidateBackports/README.md | 19 ++ .../CandidateCellDataExtensionBackports.swift | 56 +++++ .../CtlCandidateTDKBackports.swift | 227 ++++++++++++++++++ .../VwrCandidateHorizontalBackports.swift | 116 +++++++++ .../VwrCandidateVerticalBackports.swift | 116 +++++++++ vChewing.xcodeproj/project.pbxproj | 9 + 8 files changed, 582 insertions(+) create mode 100644 Packages/vChewing_TDKCandidateBackports/.gitignore create mode 100644 Packages/vChewing_TDKCandidateBackports/Package.swift create mode 100644 Packages/vChewing_TDKCandidateBackports/README.md create mode 100644 Packages/vChewing_TDKCandidateBackports/Sources/TDKCandidateBackports/CandidateCellDataExtensionBackports.swift create mode 100644 Packages/vChewing_TDKCandidateBackports/Sources/TDKCandidateBackports/INMUCandidateSuiteBackports/CtlCandidateTDKBackports.swift create mode 100644 Packages/vChewing_TDKCandidateBackports/Sources/TDKCandidateBackports/INMUCandidateSuiteBackports/VwrCandidateHorizontalBackports.swift create mode 100644 Packages/vChewing_TDKCandidateBackports/Sources/TDKCandidateBackports/INMUCandidateSuiteBackports/VwrCandidateVerticalBackports.swift diff --git a/Packages/vChewing_TDKCandidateBackports/.gitignore b/Packages/vChewing_TDKCandidateBackports/.gitignore new file mode 100644 index 00000000..3b298120 --- /dev/null +++ b/Packages/vChewing_TDKCandidateBackports/.gitignore @@ -0,0 +1,9 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj +xcuserdata/ +DerivedData/ +.swiftpm/config/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/Packages/vChewing_TDKCandidateBackports/Package.swift b/Packages/vChewing_TDKCandidateBackports/Package.swift new file mode 100644 index 00000000..5a8e4b69 --- /dev/null +++ b/Packages/vChewing_TDKCandidateBackports/Package.swift @@ -0,0 +1,30 @@ +// swift-tools-version:5.3 +import PackageDescription + +let package = Package( + name: "TDKCandidateBackports", + platforms: [ + .macOS(.v10_11) + ], + products: [ + .library( + name: "TDKCandidateBackports", + targets: ["TDKCandidateBackports"] + ) + ], + dependencies: [ + .package(path: "../vChewing_Shared"), + .package(path: "../vChewing_CandidateWindow"), + .package(path: "../ShapsBenkau_SwiftUIBackports"), + ], + targets: [ + .target( + name: "TDKCandidateBackports", + dependencies: [ + .product(name: "Shared", package: "vChewing_Shared"), + .product(name: "CandidateWindow", package: "vChewing_CandidateWindow"), + .product(name: "SwiftUIBackports", package: "ShapsBenkau_SwiftUIBackports"), + ] + ) + ] +) diff --git a/Packages/vChewing_TDKCandidateBackports/README.md b/Packages/vChewing_TDKCandidateBackports/README.md new file mode 100644 index 00000000..b60709c1 --- /dev/null +++ b/Packages/vChewing_TDKCandidateBackports/README.md @@ -0,0 +1,19 @@ +# TDKCandidateBackports + +田所選字窗的分支版本,僅對 macOS 10.15 與 macOS 11 提供、且有下述特性折扣: + +- 所有的與介面配色有關的內容設定都是手動重新指定的,所以介面調色盤會與 macOS 12 開始的系統專用的田所選字窗完整版相比有出入。 +- 無法支援「根據不同的選字窗內容文本語言區域,使用對應區域的系統介面字型來顯示」的特性。 + - 原因:與該特性有關的幾個關鍵 API 都是 macOS 12 開始才有的 API。 + +詳情請參考 `vChewing_CandidateWindow` 這個 Swift Package。 + +``` +// (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. +``` diff --git a/Packages/vChewing_TDKCandidateBackports/Sources/TDKCandidateBackports/CandidateCellDataExtensionBackports.swift b/Packages/vChewing_TDKCandidateBackports/Sources/TDKCandidateBackports/CandidateCellDataExtensionBackports.swift new file mode 100644 index 00000000..e3906d68 --- /dev/null +++ b/Packages/vChewing_TDKCandidateBackports/Sources/TDKCandidateBackports/CandidateCellDataExtensionBackports.swift @@ -0,0 +1,56 @@ +// (c) 2022 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 CandidateWindow +import Shared +import SwiftUI +import SwiftUIBackports + +@available(macOS 10.15, *) +extension CandidateCellData { + public var themeColor: some View { + // 設定當前高亮候選字的背景顏色。 + let result: Color = { + switch locale { + case "zh-Hans": return Color.red + case "zh-Hant": return Color.blue + case "ja": return Color.pink + default: return Color.accentColor + } + }() + return result.opacity(0.85) + } + + public var attributedStringForSwiftUIBackports: some View { + var result: some View { + ZStack(alignment: .leading) { + if isSelected { + themeColor.cornerRadius(6) + VStack(spacing: 0) { + HStack(spacing: 4) { + Text(key).font(.custom("Menlo", size: fontSizeKey)) + .foregroundColor(Color.white.opacity(0.8)).lineLimit(1) + Text(displayedText).font(.system(size: fontSizeCandidate)) + .foregroundColor(Color(white: 1)).lineLimit(1) + }.padding(4).foregroundColor(Color(white: 0.9)) + } + } else { + VStack(spacing: 0) { + HStack(spacing: 4) { + Text(key).font(.custom("Menlo", size: fontSizeKey)) + .foregroundColor(Color.secondary).lineLimit(1) + Text(displayedText).font(.system(size: fontSizeCandidate)) + .foregroundColor(Color.primary).lineLimit(1) + }.padding(4).foregroundColor(Color(white: 0.9)) + } + } + }.fixedSize(horizontal: false, vertical: true) + } + return result + } +} diff --git a/Packages/vChewing_TDKCandidateBackports/Sources/TDKCandidateBackports/INMUCandidateSuiteBackports/CtlCandidateTDKBackports.swift b/Packages/vChewing_TDKCandidateBackports/Sources/TDKCandidateBackports/INMUCandidateSuiteBackports/CtlCandidateTDKBackports.swift new file mode 100644 index 00000000..c37b0537 --- /dev/null +++ b/Packages/vChewing_TDKCandidateBackports/Sources/TDKCandidateBackports/INMUCandidateSuiteBackports/CtlCandidateTDKBackports.swift @@ -0,0 +1,227 @@ +// (c) 2022 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 CandidateWindow +import Cocoa +import CocoaExtension +import Shared +import SwiftUI +import SwiftUIBackports + +@available(macOS 10.15, *) +public class CtlCandidateTDKBackports: CtlCandidate { + public var thePoolHorizontal: CandidatePool = .init(candidates: [], rowCapacity: 6) + public var theViewHorizontal: VwrCandidateHorizontal { + .init(controller: self, thePool: thePoolHorizontal, hint: hint) + } + + public var thePoolVertical: CandidatePool = .init(candidates: [], columnCapacity: 6) + public var theViewVertical: VwrCandidateVertical { .init(controller: self, thePool: thePoolVertical, hint: hint) } + public required init(_ layout: NSUserInterfaceLayoutOrientation = .horizontal) { + var contentRect = NSRect(x: 128.0, y: 128.0, width: 0.0, height: 0.0) + let styleMask: NSWindow.StyleMask = [.nonactivatingPanel] + let panel = NSPanel( + contentRect: contentRect, styleMask: styleMask, backing: .buffered, defer: false + ) + panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel) + 2) + panel.hasShadow = true + panel.isOpaque = false + panel.backgroundColor = NSColor.clear + + contentRect.origin = NSPoint.zero + + super.init(layout) + window = panel + currentLayout = layout + reloadData() + } + + @available(*, unavailable) + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override public func reloadData() { + CandidateCellData.highlightBackground = highlightedColor() + CandidateCellData.unifiedSize = candidateFont.pointSize + guard let delegate = delegate else { return } + switch currentLayout { + case .horizontal: + thePoolHorizontal = .init( + candidates: delegate.candidatePairs(conv: true).map(\.1), rowCapacity: 6, + selectionKeys: delegate.selectionKeys, locale: locale + ) + thePoolHorizontal.highlightHorizontal(at: 0) + case .vertical: + thePoolVertical = .init( + candidates: delegate.candidatePairs(conv: true).map(\.1), columnCapacity: 6, + selectionKeys: delegate.selectionKeys, locale: locale + ) + thePoolVertical.highlightVertical(at: 0) + @unknown default: + return + } + updateDisplay() + } + + override open func updateDisplay() { + switch currentLayout { + case .horizontal: + DispatchQueue.main.async { [self] in + let newView = NSHostingView(rootView: theViewHorizontal) + let newSize = newView.fittingSize + window?.contentView = newView + window?.setContentSize(newSize) + } + case .vertical: + DispatchQueue.main.async { [self] in + let newView = NSHostingView(rootView: theViewVertical) + let newSize = newView.fittingSize + window?.contentView = newView + window?.setContentSize(newSize) + } + @unknown default: + return + } + } + + @discardableResult override public func showNextPage() -> Bool { + switch currentLayout { + case .horizontal: + for _ in 0.. Bool { + switch currentLayout { + case .horizontal: + for _ in 0.. Bool { + switch currentLayout { + case .horizontal: + thePoolHorizontal.selectNewNeighborRow(direction: .down) + case .vertical: + thePoolVertical.selectNewNeighborColumn(direction: .right) + @unknown default: + return false + } + updateDisplay() + return true + } + + @discardableResult override public func showPreviousLine() -> Bool { + switch currentLayout { + case .horizontal: + thePoolHorizontal.selectNewNeighborRow(direction: .up) + case .vertical: + thePoolVertical.selectNewNeighborColumn(direction: .left) + @unknown default: + return false + } + updateDisplay() + return true + } + + @discardableResult override public func highlightNextCandidate() -> Bool { + switch currentLayout { + case .horizontal: + thePoolHorizontal.highlightHorizontal(at: thePoolHorizontal.highlightedIndex + 1) + case .vertical: + thePoolVertical.highlightVertical(at: thePoolVertical.highlightedIndex + 1) + @unknown default: + return false + } + updateDisplay() + return true + } + + @discardableResult override public func highlightPreviousCandidate() -> Bool { + switch currentLayout { + case .horizontal: + thePoolHorizontal.highlightHorizontal(at: thePoolHorizontal.highlightedIndex - 1) + case .vertical: + thePoolVertical.highlightVertical(at: thePoolVertical.highlightedIndex - 1) + @unknown default: + return false + } + updateDisplay() + return true + } + + override public func candidateIndexAtKeyLabelIndex(_ id: Int) -> Int { + switch currentLayout { + case .horizontal: + let currentRow = thePoolHorizontal.candidateRows[thePoolHorizontal.currentRowNumber] + let actualID = max(0, min(id, currentRow.count - 1)) + return thePoolHorizontal.candidateRows[thePoolHorizontal.currentRowNumber][actualID].index + case .vertical: + let currentColumn = thePoolVertical.candidateColumns[thePoolVertical.currentColumnNumber] + let actualID = max(0, min(id, currentColumn.count - 1)) + return thePoolVertical.candidateColumns[thePoolVertical.currentColumnNumber][actualID].index + @unknown default: + return 0 + } + } + + override public var selectedCandidateIndex: Int { + get { + switch currentLayout { + case .horizontal: return thePoolHorizontal.highlightedIndex + case .vertical: return thePoolVertical.highlightedIndex + @unknown default: return 0 + } + } + set { + switch currentLayout { + case .horizontal: thePoolHorizontal.highlightHorizontal(at: newValue) + case .vertical: thePoolVertical.highlightVertical(at: newValue) + @unknown default: return + } + updateDisplay() + } + } +} + +@available(macOS 10.15, *) +extension CtlCandidateTDKBackports { + public var highlightedColorUI: some View { + // 設定當前高亮候選字的背景顏色。 + let result: Color = { + switch locale { + case "zh-Hans": return Color.red + case "zh-Hant": return Color.blue + case "ja": return Color.pink + default: return Color.accentColor + } + }() + return result.opacity(0.85) + } +} diff --git a/Packages/vChewing_TDKCandidateBackports/Sources/TDKCandidateBackports/INMUCandidateSuiteBackports/VwrCandidateHorizontalBackports.swift b/Packages/vChewing_TDKCandidateBackports/Sources/TDKCandidateBackports/INMUCandidateSuiteBackports/VwrCandidateHorizontalBackports.swift new file mode 100644 index 00000000..cde0737a --- /dev/null +++ b/Packages/vChewing_TDKCandidateBackports/Sources/TDKCandidateBackports/INMUCandidateSuiteBackports/VwrCandidateHorizontalBackports.swift @@ -0,0 +1,116 @@ +// (c) 2022 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 CandidateWindow +import Cocoa +import Shared +import SwiftUI +import SwiftUIBackports + +// MARK: - Some useless tests + +@available(macOS 10.15, *) +struct CandidatePoolViewUIHorizontalBackports_Previews: PreviewProvider { + @State static var testCandidates: [String] = [ + "八月中秋山林涼", "八月中秋", "風吹大地", "山林涼", "草枝擺", "八月", "中秋", + "🐂🍺🐂🍺", "🐃🍺", "🐂🍺", "🐃🐂🍺🍺", "🐂🍺", "🐃🍺", "🐂🍺", "🐃🍺", "🐂🍺", "🐃🍺", + "山林", "風吹", "大地", "草枝", "八", "月", "中", "秋", "山", "林", "涼", "風", + "吹", "大", "地", "草", "枝", "擺", "八", "月", "中", "秋", "山", "林", "涼", "風", + "吹", "大", "地", "草", "枝", "擺", + ] + static var thePool: CandidatePool { + let result = CandidatePool(candidates: testCandidates, rowCapacity: 6) + // 下一行待解決:無論這裡怎麼指定高亮選中項是哪一筆,其所在行都得被卷動到使用者眼前。 + result.highlightHorizontal(at: 5) + return result + } + + static var previews: some View { + VwrCandidateHorizontal(controller: .init(.horizontal), thePool: thePool).fixedSize() + } +} + +@available(macOS 10.15, *) +public struct VwrCandidateHorizontal: View { + @Environment(\.colorScheme) var colorScheme + public var controller: CtlCandidateTDKBackports + @State public var thePool: CandidatePool + @State public var hint: String = "" + + private var positionLabel: String { + (thePool.highlightedIndex + 1).description + "/" + thePool.candidateDataAll.count.description + } + + private func didSelectCandidateAt(_ pos: Int) { + if let delegate = controller.delegate { + delegate.candidatePairSelected(at: pos) + } + } + + public var body: some View { + VStack(alignment: .leading, spacing: 0) { + ScrollView(.vertical, showsIndicators: false) { + VStack(alignment: .leading, spacing: 1.6) { + ForEach(thePool.rangeForCurrentHorizontalPage, id: \.self) { rowIndex in + HStack(spacing: 10) { + ForEach(Array(thePool.candidateRows[rowIndex]), id: \.self) { currentCandidate in + currentCandidate.attributedStringForSwiftUIBackports.fixedSize() + .frame( + maxWidth: .infinity, + alignment: .topLeading + ) + .contentShape(Rectangle()) + .onTapGesture { didSelectCandidateAt(currentCandidate.index) } + } + }.frame( + minWidth: 0, + maxWidth: .infinity, + alignment: .topLeading + ).id(rowIndex) + Divider() + } + if thePool.maximumRowsPerPage - thePool.rangeForCurrentHorizontalPage.count > 0 { + ForEach(thePool.rangeForLastHorizontalPageBlanked, id: \.self) { _ in + HStack(spacing: 0) { + thePool.blankCell.attributedStringForSwiftUIBackports + .frame(maxWidth: .infinity, alignment: .topLeading) + .contentShape(Rectangle()) + Spacer() + }.frame( + minWidth: 0, + maxWidth: .infinity, + alignment: .topLeading + ) + Divider() + } + } + } + } + .fixedSize(horizontal: false, vertical: true).padding(5) + .background(Color(white: colorScheme == .dark ? 0.1 : 1)) + ZStack(alignment: .leading) { + if hint.isEmpty { + Color(white: colorScheme == .dark ? 0.2 : 0.9) + } else { + controller.highlightedColorUI + } + HStack(alignment: .bottom) { + Text(hint).font(.system(size: max(CandidateCellData.unifiedSize * 0.7, 11), weight: .bold)).lineLimit(1) + Spacer() + Text(positionLabel).font(.system(size: max(CandidateCellData.unifiedSize * 0.7, 11), weight: .bold)) + .lineLimit( + 1) + } + .padding(6).foregroundColor( + hint.isEmpty && colorScheme == .light ? Color(white: 0.1) : Color(white: 0.9) + ) + } + } + .frame(minWidth: thePool.maxWindowWidth, maxWidth: thePool.maxWindowWidth) + } +} diff --git a/Packages/vChewing_TDKCandidateBackports/Sources/TDKCandidateBackports/INMUCandidateSuiteBackports/VwrCandidateVerticalBackports.swift b/Packages/vChewing_TDKCandidateBackports/Sources/TDKCandidateBackports/INMUCandidateSuiteBackports/VwrCandidateVerticalBackports.swift new file mode 100644 index 00000000..b6e3e948 --- /dev/null +++ b/Packages/vChewing_TDKCandidateBackports/Sources/TDKCandidateBackports/INMUCandidateSuiteBackports/VwrCandidateVerticalBackports.swift @@ -0,0 +1,116 @@ +// (c) 2022 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 CandidateWindow +import Cocoa +import Shared +import SwiftUI +import SwiftUIBackports + +// MARK: - Some useless tests + +@available(macOS 10.15, *) +struct CandidatePoolViewUIVerticalBackports_Previews: PreviewProvider { + @State static var testCandidates: [String] = [ + "八月中秋山林涼", "八月中秋", "風吹大地", "山林涼", "草枝擺", "🐂🍺", "🐃🍺", "八月", "中秋", + "山林", "風吹", "大地", "草枝", "八", "月", "中", "秋", "山", "林", "涼", "風", + "吹", "大", "地", "草", "枝", "擺", "八", "月", "中", "秋", "山", "林", "涼", "風", + "吹", "大", "地", "草", "枝", "擺", + ] + static var thePool: CandidatePool { + let result = CandidatePool(candidates: testCandidates, columnCapacity: 6, selectionKeys: "123456789") + // 下一行待解決:無論這裡怎麼指定高亮選中項是哪一筆,其所在行都得被卷動到使用者眼前。 + result.highlightVertical(at: 5) + return result + } + + static var previews: some View { + VwrCandidateVertical(controller: .init(.horizontal), thePool: thePool).fixedSize() + } +} + +@available(macOS 10.15, *) +public struct VwrCandidateVertical: View { + @Environment(\.colorScheme) var colorScheme + public var controller: CtlCandidateTDKBackports + @State public var thePool: CandidatePool + @State public var hint: String = "" + + private var positionLabel: String { + (thePool.highlightedIndex + 1).description + "/" + thePool.candidateDataAll.count.description + } + + private func didSelectCandidateAt(_ pos: Int) { + if let delegate = controller.delegate { + delegate.candidatePairSelected(at: pos) + } + } + + public var body: some View { + VStack(alignment: .leading, spacing: 0) { + ScrollView(.horizontal, showsIndicators: false) { + HStack(alignment: .top, spacing: 10) { + ForEach(thePool.rangeForCurrentVerticalPage, id: \.self) { columnIndex in + VStack(alignment: .leading, spacing: 0) { + ForEach(Array(thePool.candidateColumns[columnIndex]), id: \.self) { currentCandidate in + HStack(spacing: 0) { + currentCandidate.attributedStringForSwiftUIBackports.fixedSize(horizontal: false, vertical: true) + .frame( + maxWidth: .infinity, + alignment: .topLeading + ) + .contentShape(Rectangle()) + .onTapGesture { didSelectCandidateAt(currentCandidate.index) } + } + } + }.frame( + minWidth: Double(CandidateCellData.unifiedSize * 5), + alignment: .topLeading + ).id(columnIndex) + Divider() + } + if thePool.maximumColumnsPerPage - thePool.rangeForCurrentVerticalPage.count > 0 { + ForEach(thePool.rangeForLastVerticalPageBlanked, id: \.self) { _ in + VStack(alignment: .leading, spacing: 0) { + ForEach(0..