diff --git a/Packages/vChewing_CandidateWindow/Package.swift b/Packages/vChewing_CandidateWindow/Package.swift index 837e3572..89b15c14 100644 --- a/Packages/vChewing_CandidateWindow/Package.swift +++ b/Packages/vChewing_CandidateWindow/Package.swift @@ -13,13 +13,15 @@ let package = Package( ) ], dependencies: [ - .package(path: "../vChewing_Shared") + .package(path: "../vChewing_Shared"), + .package(path: "../ShapsBenkau_SwiftUIBackports"), ], targets: [ .target( name: "CandidateWindow", dependencies: [ - .product(name: "Shared", package: "vChewing_Shared") + .product(name: "Shared", package: "vChewing_Shared"), + .product(name: "SwiftUIBackports", package: "ShapsBenkau_SwiftUIBackports"), ] ), .testTarget( diff --git a/Packages/vChewing_CandidateWindow/README.md b/Packages/vChewing_CandidateWindow/README.md index 50791042..5181f81c 100644 --- a/Packages/vChewing_CandidateWindow/README.md +++ b/Packages/vChewing_CandidateWindow/README.md @@ -11,6 +11,12 @@ TDK 選字窗同時支援橫排矩陣佈局與縱排矩陣佈局。然而,目 - 因 SwiftUI 自身特性所導致的嚴重的效能問題(可能只會在幾年前的老電腦上出現)。基本上來講,如果您經常使用全字庫模式的話,請在偏好設定內啟用效能更高的 IMK 選字窗。 - 同樣出於上述原因,為了讓田所選字窗至少處於可在生產力環境下正常使用的狀態,就犧牲了捲動檢視的功能。也就是說,每次只顯示三行/三列,但顯示內容則隨著使用者的游標操作而更新。 +TDK 選字窗會在 macOS 10.15 與 macOS 11 系統下有下述特性折扣: + +- 所有的與介面配色有關的內容設定都是手動重新指定的,所以介面調色盤會與 macOS 12 開始的系統專用的田所選字窗完整版相比有出入。 +- 無法支援「根據不同的選字窗內容文本語言區域,使用對應區域的系統介面字型來顯示」的特性。 + - 原因:與該特性有關的幾個關鍵 API 都是 macOS 12 開始才有的 API。 + ``` // (c) 2021 and onwards The vChewing Project (MIT-NTL License). // ==================== diff --git a/Packages/vChewing_CandidateWindow/Sources/CandidateWindow/CandidatePool.swift b/Packages/vChewing_CandidateWindow/Sources/CandidateWindow/CandidatePool.swift index c912f6e7..39df5788 100644 --- a/Packages/vChewing_CandidateWindow/Sources/CandidateWindow/CandidatePool.swift +++ b/Packages/vChewing_CandidateWindow/Sources/CandidateWindow/CandidatePool.swift @@ -12,54 +12,92 @@ import Shared /// 候選字窗會用到的資料池單位。 public class CandidatePool { public let blankCell = CandidateCellData(key: " ", displayedText: " ", isSelected: false) + public var currentLayout: NSUserInterfaceLayoutOrientation = .horizontal public private(set) var candidateDataAll: [CandidateCellData] = [] public private(set) var selectionKeys: String public private(set) var highlightedIndex: Int = 0 // 下述變數只有橫排選字窗才會用到 - public var currentRowNumber = 0 - public var maximumRowsPerPage = 3 - public private(set) var maxRowCapacity: Int = 6 - public private(set) var candidateRows: [[CandidateCellData]] = [] + private var currentRowNumber = 0 + private var maxRowsPerPage = 3 + private var maxRowCapacity: Int = 6 + private var candidateRows: [[CandidateCellData]] = [] // 下述變數只有縱排選字窗才會用到 - public var currentColumnNumber = 0 - public var maximumColumnsPerPage = 3 - public private(set) var maxColumnCapacity: Int = 6 - public private(set) var candidateColumns: [[CandidateCellData]] = [] + private var currentColumnNumber = 0 + private var maxColumnsPerPage = 3 + private var maxColumnCapacity: Int = 6 + private var candidateColumns: [[CandidateCellData]] = [] + + // MARK: - 動態變數 - // 動態變數 public var maxRowWidth: Int { Int(Double(maxRowCapacity + 3) * 2) * Int(ceil(CandidateCellData.unifiedSize)) } public var maxWindowWidth: Double { ceil(Double(maxRowCapacity + 3) * 2.7 * ceil(CandidateCellData.unifiedSize) * 1.2) } - public var rangeForCurrentHorizontalPage: Range { - currentRowNumber.. { - currentColumnNumber.. { - 0..<(maximumRowsPerPage - rangeForCurrentHorizontalPage.count) + public var maxLineCapacity: Int { + switch currentLayout { + case .horizontal: + return maxRowCapacity + case .vertical: + return maxColumnCapacity + @unknown default: + return 0 + } } - public var rangeForLastVerticalPageBlanked: Range { - 0..<(maximumColumnsPerPage - rangeForCurrentVerticalPage.count) + public var maxLinesPerPage: Int { + switch currentLayout { + case .horizontal: + return maxRowsPerPage + case .vertical: + return maxColumnsPerPage + @unknown default: + return 0 + } } - public enum VerticalDirection { - case up - case down + public var rangeForLastPageBlanked: Range { + switch currentLayout { + case .horizontal: return rangeForLastHorizontalPageBlanked + case .vertical: return rangeForLastVerticalPageBlanked + @unknown default: return 0..<0 + } } - public enum HorizontalDirection { - case left - case right + public var rangeForCurrentPage: Range { + switch currentLayout { + case .horizontal: return rangeForCurrentHorizontalPage + case .vertical: return rangeForCurrentVerticalPage + @unknown default: return 0..<0 + } } + // MARK: - Constructors + /// 初期化一個縱排候選字窗專用資料池。 /// - Parameters: /// - candidates: 要塞入的候選字詞陣列。 @@ -84,6 +122,7 @@ public class CandidatePool { currentColumn.append(candidate) } candidateColumns.append(currentColumn) + currentLayout = .vertical } /// 初期化一個橫排候選字窗專用資料池。 @@ -111,9 +150,58 @@ public class CandidatePool { currentRow.append(candidate) } candidateRows.append(currentRow) + currentLayout = .horizontal } - public func selectNewNeighborRow(direction: VerticalDirection) { + // MARK: Public Functions + + public func selectNewNeighborLine(isForward: Bool) { + switch currentLayout { + case .horizontal: selectNewNeighborRow(direction: isForward ? .down : .up) + case .vertical: selectNewNeighborColumn(direction: isForward ? .right : .left) + @unknown default: break + } + } + + public func highlight(at indexSpecified: Int) { + switch currentLayout { + case .horizontal: highlightHorizontal(at: indexSpecified) + case .vertical: highlightVertical(at: indexSpecified) + @unknown default: break + } + } +} + +// MARK: - Private Functions + +extension CandidatePool { + private enum VerticalDirection { + case up + case down + } + + private enum HorizontalDirection { + case left + case right + } + + private var rangeForLastHorizontalPageBlanked: Range { + 0..<(maxRowsPerPage - rangeForCurrentHorizontalPage.count) + } + + private var rangeForLastVerticalPageBlanked: Range { + 0..<(maxColumnsPerPage - rangeForCurrentVerticalPage.count) + } + + private var rangeForCurrentHorizontalPage: Range { + currentRowNumber.. { + currentColumnNumber.. Bool { - if (currentLayout == .horizontal && thePoolHorizontal.currentRowNumber == thePoolHorizontal.candidateRows.count - 1) - || (currentLayout == .vertical - && thePoolVertical.currentColumnNumber == thePoolVertical.candidateColumns.count - 1) - { - return highlightNextCandidate() - } - switch currentLayout { - case .horizontal: - for _ in 0.. Bool { - if (currentLayout == .horizontal && thePoolHorizontal.currentRowNumber == thePoolHorizontal.candidateRows.count - 1) - || (currentLayout == .vertical - && thePoolVertical.currentColumnNumber == thePoolVertical.candidateColumns.count - 1) - { - return highlightNextCandidate() - } - 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 showPreviousPage() -> Bool { - if (currentLayout == .horizontal && thePoolHorizontal.currentRowNumber == 0) - || (currentLayout == .vertical && thePoolVertical.currentColumnNumber == 0) - { - return highlightPreviousCandidate() - } - switch currentLayout { - case .horizontal: - for _ in 0.. Bool { - if (currentLayout == .horizontal && thePoolHorizontal.currentRowNumber == 0) - || (currentLayout == .vertical && thePoolVertical.currentColumnNumber == 0) - { - return highlightPreviousCandidate() - } - 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: - if thePoolHorizontal.highlightedIndex == thePoolHorizontal.candidateDataAll.count - 1 { - thePoolHorizontal.highlightHorizontal(at: 0) - delegate?.buzz() - break - } - thePoolHorizontal.highlightHorizontal(at: thePoolHorizontal.highlightedIndex + 1) - case .vertical: - if thePoolVertical.highlightedIndex == thePoolVertical.candidateDataAll.count - 1 { - thePoolVertical.highlightVertical(at: 0) - delegate?.buzz() - break - } - thePoolVertical.highlightVertical(at: thePoolVertical.highlightedIndex + 1) - @unknown default: - return false - } - updateDisplay() - return true - } - - @discardableResult override public func highlightPreviousCandidate() -> Bool { - switch currentLayout { - case .horizontal: - if thePoolHorizontal.highlightedIndex == 0 { - thePoolHorizontal.highlightHorizontal(at: thePoolHorizontal.candidateDataAll.count - 1) - delegate?.buzz() - break - } - thePoolHorizontal.highlightHorizontal(at: thePoolHorizontal.highlightedIndex - 1) - case .vertical: - if thePoolVertical.highlightedIndex == 0 { - thePoolVertical.highlightVertical(at: thePoolVertical.candidateDataAll.count - 1) - delegate?.buzz() - break - } - 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 currentRow[actualID].index - case .vertical: - let currentColumn = thePoolVertical.candidateColumns[thePoolVertical.currentColumnNumber] - let actualID = max(0, min(id, currentColumn.count - 1)) - return currentColumn[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() - } - } -} diff --git a/Packages/vChewing_CandidateWindow/Sources/CandidateWindow/CandidateCellDataExtension.swift b/Packages/vChewing_CandidateWindow/Sources/CandidateWindow/TDKCandidates/CandidateCellDataExtension.swift similarity index 100% rename from Packages/vChewing_CandidateWindow/Sources/CandidateWindow/CandidateCellDataExtension.swift rename to Packages/vChewing_CandidateWindow/Sources/CandidateWindow/TDKCandidates/CandidateCellDataExtension.swift diff --git a/Packages/vChewing_CandidateWindow/Sources/CandidateWindow/TDKCandidates/CtlCandidateTDK.swift b/Packages/vChewing_CandidateWindow/Sources/CandidateWindow/TDKCandidates/CtlCandidateTDK.swift new file mode 100644 index 00000000..2087bb02 --- /dev/null +++ b/Packages/vChewing_CandidateWindow/Sources/CandidateWindow/TDKCandidates/CtlCandidateTDK.swift @@ -0,0 +1,240 @@ +// (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 Cocoa +import CocoaExtension +import Shared +import SwiftUI + +@available(macOS 10.15, *) +public class CtlCandidateTDK: CtlCandidate { + public var thePoolHorizontal: CandidatePool = .init(candidates: [], rowCapacity: 6) + public var thePoolVertical: CandidatePool = .init(candidates: [], columnCapacity: 6) + + @available(macOS 12, *) + public var theViewHorizontal: VwrCandidateHorizontal { + .init(controller: self, thePool: thePoolHorizontal, hint: hint) + } + + @available(macOS 12, *) + public var theViewVertical: VwrCandidateVertical { + .init(controller: self, thePool: thePoolVertical, hint: hint) + } + + public var theViewHorizontalBackports: VwrCandidateHorizontalBackports { + .init(controller: self, thePool: thePoolHorizontal, hint: hint) + } + + public var theViewVerticalBackports: VwrCandidateVerticalBackports { + .init(controller: self, thePool: thePoolVertical, hint: hint) + } + + public var thePool: CandidatePool { + get { + switch currentLayout { + case .horizontal: return thePoolHorizontal + case .vertical: return thePoolVertical + @unknown default: return .init(candidates: [], rowCapacity: 0) + } + } + set { + switch currentLayout { + case .horizontal: thePoolHorizontal = newValue + case .vertical: thePoolVertical = newValue + @unknown default: break + } + } + } + + // MARK: - Constructors + + 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") + } + + // MARK: - Public functions + + 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.highlight(at: 0) + case .vertical: + thePoolVertical = .init( + candidates: delegate.candidatePairs(conv: true).map(\.1), columnCapacity: 6, + selectionKeys: delegate.selectionKeys, locale: locale + ) + thePoolVertical.highlight(at: 0) + @unknown default: + return + } + updateDisplay() + } + + override open func updateDisplay() { + switch currentLayout { + case .horizontal: + DispatchQueue.main.async { [self] in + if #available(macOS 12, *) { + let newView = NSHostingView(rootView: theViewHorizontal) + let newSize = newView.fittingSize + window?.contentView = newView + window?.setContentSize(newSize) + } else { + let newView = NSHostingView(rootView: theViewHorizontalBackports) + let newSize = newView.fittingSize + window?.contentView = newView + window?.setContentSize(newSize) + } + } + case .vertical: + DispatchQueue.main.async { [self] in + if #available(macOS 12, *) { + let newView = NSHostingView(rootView: theViewVertical) + let newSize = newView.fittingSize + window?.contentView = newView + window?.setContentSize(newSize) + } else { + let newView = NSHostingView(rootView: theViewVerticalBackports) + let newSize = newView.fittingSize + window?.contentView = newView + window?.setContentSize(newSize) + } + } + @unknown default: + return + } + } + + @discardableResult override public func showNextPage() -> Bool { + showNextLine(count: thePool.maxLinesPerPage) + } + + @discardableResult override public func showNextLine() -> Bool { + showNextLine(count: 1) + } + + public func showNextLine(count: Int) -> Bool { + if thePool.currentLineNumber == thePool.candidateLines.count - 1 { + return highlightNextCandidate() + } + if count <= 0 { return false } + for _ in 0.. Bool { + showPreviousLine(count: thePool.maxLinesPerPage) + } + + @discardableResult override public func showPreviousLine() -> Bool { + showPreviousLine(count: 1) + } + + public func showPreviousLine(count: Int) -> Bool { + if thePool.currentLineNumber == 0 { + return highlightPreviousCandidate() + } + if count <= 0 { return false } + for _ in 0.. Bool { + if thePool.highlightedIndex == thePool.candidateDataAll.count - 1 { + thePool.highlight(at: 0) + updateDisplay() + return false + } + thePool.highlight(at: thePool.highlightedIndex + 1) + updateDisplay() + return true + } + + @discardableResult override public func highlightPreviousCandidate() -> Bool { + if thePool.highlightedIndex == 0 { + thePool.highlight(at: thePool.candidateDataAll.count - 1) + updateDisplay() + return false + } + thePool.highlight(at: thePool.highlightedIndex - 1) + updateDisplay() + return true + } + + override public func candidateIndexAtKeyLabelIndex(_ id: Int) -> Int { + let arrCurrentLine = thePool.candidateLines[thePool.currentLineNumber] + let actualID = max(0, min(id, arrCurrentLine.count - 1)) + return arrCurrentLine[actualID].index + } + + override public var selectedCandidateIndex: Int { + get { thePool.highlightedIndex } + set { + thePool.highlight(at: newValue) + updateDisplay() + } + } +} + +@available(macOS 10.15, *) +extension CtlCandidateTDK { + private var isMontereyAvailable: Bool { + if #unavailable(macOS 12) { return false } + return true + } +} + +@available(macOS 10.15, *) +extension CtlCandidateTDK { + public var highlightedColorUIBackports: 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_CandidateWindow/Sources/CandidateWindow/INMUCandidateSuite/VwrCandidateHorizontal.swift b/Packages/vChewing_CandidateWindow/Sources/CandidateWindow/TDKCandidates/VwrCandidateHorizontal.swift similarity index 91% rename from Packages/vChewing_CandidateWindow/Sources/CandidateWindow/INMUCandidateSuite/VwrCandidateHorizontal.swift rename to Packages/vChewing_CandidateWindow/Sources/CandidateWindow/TDKCandidates/VwrCandidateHorizontal.swift index 04046933..19cb27ca 100644 --- a/Packages/vChewing_CandidateWindow/Sources/CandidateWindow/INMUCandidateSuite/VwrCandidateHorizontal.swift +++ b/Packages/vChewing_CandidateWindow/Sources/CandidateWindow/TDKCandidates/VwrCandidateHorizontal.swift @@ -24,7 +24,7 @@ struct CandidatePoolViewUIHorizontal_Previews: PreviewProvider { static var thePool: CandidatePool { let result = CandidatePool(candidates: testCandidates, rowCapacity: 6) // 下一行待解決:無論這裡怎麼指定高亮選中項是哪一筆,其所在行都得被卷動到使用者眼前。 - result.highlightHorizontal(at: 5) + result.highlight(at: 5) return result } @@ -53,9 +53,9 @@ public struct VwrCandidateHorizontal: View { VStack(alignment: .leading, spacing: 0) { ScrollView(.vertical, showsIndicators: false) { VStack(alignment: .leading, spacing: 1.6) { - ForEach(thePool.rangeForCurrentHorizontalPage, id: \.self) { rowIndex in + ForEach(thePool.rangeForCurrentPage, id: \.self) { rowIndex in HStack(spacing: 10) { - ForEach(Array(thePool.candidateRows[rowIndex]), id: \.self) { currentCandidate in + ForEach(Array(thePool.candidateLines[rowIndex]), id: \.self) { currentCandidate in currentCandidate.attributedStringForSwiftUI.fixedSize() .frame( maxWidth: .infinity, @@ -71,8 +71,8 @@ public struct VwrCandidateHorizontal: View { ).id(rowIndex) Divider() } - if thePool.maximumRowsPerPage - thePool.rangeForCurrentHorizontalPage.count > 0 { - ForEach(thePool.rangeForLastHorizontalPageBlanked, id: \.self) { _ in + if thePool.maxLinesPerPage - thePool.rangeForCurrentPage.count > 0 { + ForEach(thePool.rangeForLastPageBlanked, id: \.self) { _ in HStack(spacing: 0) { thePool.blankCell.attributedStringForSwiftUI .frame(maxWidth: .infinity, alignment: .topLeading) diff --git a/Packages/vChewing_CandidateWindow/Sources/CandidateWindow/INMUCandidateSuite/VwrCandidateVertical.swift b/Packages/vChewing_CandidateWindow/Sources/CandidateWindow/TDKCandidates/VwrCandidateVertical.swift similarity index 89% rename from Packages/vChewing_CandidateWindow/Sources/CandidateWindow/INMUCandidateSuite/VwrCandidateVertical.swift rename to Packages/vChewing_CandidateWindow/Sources/CandidateWindow/TDKCandidates/VwrCandidateVertical.swift index d3061d00..5de70019 100644 --- a/Packages/vChewing_CandidateWindow/Sources/CandidateWindow/INMUCandidateSuite/VwrCandidateVertical.swift +++ b/Packages/vChewing_CandidateWindow/Sources/CandidateWindow/TDKCandidates/VwrCandidateVertical.swift @@ -23,7 +23,7 @@ struct CandidatePoolViewUIVertical_Previews: PreviewProvider { static var thePool: CandidatePool { let result = CandidatePool(candidates: testCandidates, columnCapacity: 6, selectionKeys: "123456789") // 下一行待解決:無論這裡怎麼指定高亮選中項是哪一筆,其所在行都得被卷動到使用者眼前。 - result.highlightVertical(at: 5) + result.highlight(at: 5) return result } @@ -52,9 +52,9 @@ public struct VwrCandidateVertical: View { VStack(alignment: .leading, spacing: 0) { ScrollView(.horizontal, showsIndicators: false) { HStack(alignment: .top, spacing: 10) { - ForEach(thePool.rangeForCurrentVerticalPage, id: \.self) { columnIndex in + ForEach(thePool.rangeForCurrentPage, id: \.self) { columnIndex in VStack(alignment: .leading, spacing: 0) { - ForEach(Array(thePool.candidateColumns[columnIndex]), id: \.self) { currentCandidate in + ForEach(Array(thePool.candidateLines[columnIndex]), id: \.self) { currentCandidate in HStack(spacing: 0) { currentCandidate.attributedStringForSwiftUI.fixedSize(horizontal: false, vertical: true) .frame( @@ -71,10 +71,10 @@ public struct VwrCandidateVertical: View { ).id(columnIndex) Divider() } - if thePool.maximumColumnsPerPage - thePool.rangeForCurrentVerticalPage.count > 0 { - ForEach(thePool.rangeForLastVerticalPageBlanked, id: \.self) { _ in + if thePool.maxLinesPerPage - thePool.rangeForCurrentPage.count > 0 { + ForEach(thePool.rangeForLastPageBlanked, id: \.self) { _ in VStack(alignment: .leading, spacing: 0) { - ForEach(0.. 0 { - ForEach(thePool.rangeForLastHorizontalPageBlanked, id: \.self) { _ in + if thePool.maxLinesPerPage - thePool.rangeForCurrentPage.count > 0 { + ForEach(thePool.rangeForLastPageBlanked, id: \.self) { _ in HStack(spacing: 0) { thePool.blankCell.attributedStringForSwiftUIBackports .frame(maxWidth: .infinity, alignment: .topLeading) @@ -97,7 +96,7 @@ public struct VwrCandidateHorizontal: View { if hint.isEmpty { Color(white: colorScheme == .dark ? 0.2 : 0.9) } else { - controller.highlightedColorUI + controller.highlightedColorUIBackports } HStack(alignment: .bottom) { Text(hint).font(.system(size: max(CandidateCellData.unifiedSize * 0.7, 11), weight: .bold)).lineLimit(1) diff --git a/Packages/vChewing_TDKCandidateBackports/Sources/TDKCandidateBackports/INMUCandidateSuiteBackports/VwrCandidateVerticalBackports.swift b/Packages/vChewing_CandidateWindow/Sources/CandidateWindow/TDKCandidates_Backports/VwrCandidateVerticalBackports.swift similarity index 84% rename from Packages/vChewing_TDKCandidateBackports/Sources/TDKCandidateBackports/INMUCandidateSuiteBackports/VwrCandidateVerticalBackports.swift rename to Packages/vChewing_CandidateWindow/Sources/CandidateWindow/TDKCandidates_Backports/VwrCandidateVerticalBackports.swift index b6e3e948..73c9a473 100644 --- a/Packages/vChewing_TDKCandidateBackports/Sources/TDKCandidateBackports/INMUCandidateSuiteBackports/VwrCandidateVerticalBackports.swift +++ b/Packages/vChewing_CandidateWindow/Sources/CandidateWindow/TDKCandidates_Backports/VwrCandidateVerticalBackports.swift @@ -6,7 +6,6 @@ // 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 @@ -25,19 +24,19 @@ struct CandidatePoolViewUIVerticalBackports_Previews: PreviewProvider { static var thePool: CandidatePool { let result = CandidatePool(candidates: testCandidates, columnCapacity: 6, selectionKeys: "123456789") // 下一行待解決:無論這裡怎麼指定高亮選中項是哪一筆,其所在行都得被卷動到使用者眼前。 - result.highlightVertical(at: 5) + result.highlight(at: 5) return result } static var previews: some View { - VwrCandidateVertical(controller: .init(.horizontal), thePool: thePool).fixedSize() + VwrCandidateVerticalBackports(controller: .init(.horizontal), thePool: thePool).fixedSize() } } @available(macOS 10.15, *) -public struct VwrCandidateVertical: View { +public struct VwrCandidateVerticalBackports: View { @Environment(\.colorScheme) var colorScheme - public var controller: CtlCandidateTDKBackports + public var controller: CtlCandidateTDK @State public var thePool: CandidatePool @State public var hint: String = "" @@ -55,9 +54,9 @@ public struct VwrCandidateVertical: View { VStack(alignment: .leading, spacing: 0) { ScrollView(.horizontal, showsIndicators: false) { HStack(alignment: .top, spacing: 10) { - ForEach(thePool.rangeForCurrentVerticalPage, id: \.self) { columnIndex in + ForEach(thePool.rangeForCurrentPage, id: \.self) { columnIndex in VStack(alignment: .leading, spacing: 0) { - ForEach(Array(thePool.candidateColumns[columnIndex]), id: \.self) { currentCandidate in + ForEach(Array(thePool.candidateLines[columnIndex]), id: \.self) { currentCandidate in HStack(spacing: 0) { currentCandidate.attributedStringForSwiftUIBackports.fixedSize(horizontal: false, vertical: true) .frame( @@ -74,10 +73,10 @@ public struct VwrCandidateVertical: View { ).id(columnIndex) Divider() } - if thePool.maximumColumnsPerPage - thePool.rangeForCurrentVerticalPage.count > 0 { - ForEach(thePool.rangeForLastVerticalPageBlanked, id: \.self) { _ in + if thePool.maxLinesPerPage - thePool.rangeForCurrentPage.count > 0 { + ForEach(thePool.rangeForLastPageBlanked, id: \.self) { _ in VStack(alignment: .leading, spacing: 0) { - ForEach(0.. Bool { - if (currentLayout == .horizontal && thePoolHorizontal.currentRowNumber == thePoolHorizontal.candidateRows.count - 1) - || (currentLayout == .vertical - && thePoolVertical.currentColumnNumber == thePoolVertical.candidateColumns.count - 1) - { - return highlightNextCandidate() - } - switch currentLayout { - case .horizontal: - for _ in 0.. Bool { - if (currentLayout == .horizontal && thePoolHorizontal.currentRowNumber == thePoolHorizontal.candidateRows.count - 1) - || (currentLayout == .vertical - && thePoolVertical.currentColumnNumber == thePoolVertical.candidateColumns.count - 1) - { - return highlightNextCandidate() - } - 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 showPreviousPage() -> Bool { - if (currentLayout == .horizontal && thePoolHorizontal.currentRowNumber == 0) - || (currentLayout == .vertical && thePoolVertical.currentColumnNumber == 0) - { - return highlightPreviousCandidate() - } - switch currentLayout { - case .horizontal: - for _ in 0.. Bool { - if (currentLayout == .horizontal && thePoolHorizontal.currentRowNumber == 0) - || (currentLayout == .vertical && thePoolVertical.currentColumnNumber == 0) - { - return highlightPreviousCandidate() - } - 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: - if thePoolHorizontal.highlightedIndex == thePoolHorizontal.candidateDataAll.count - 1 { - thePoolHorizontal.highlightHorizontal(at: 0) - delegate?.buzz() - break - } - thePoolHorizontal.highlightHorizontal(at: thePoolHorizontal.highlightedIndex + 1) - case .vertical: - if thePoolVertical.highlightedIndex == thePoolVertical.candidateDataAll.count - 1 { - thePoolVertical.highlightVertical(at: 0) - delegate?.buzz() - break - } - thePoolVertical.highlightVertical(at: thePoolVertical.highlightedIndex + 1) - @unknown default: - return false - } - updateDisplay() - return true - } - - @discardableResult override public func highlightPreviousCandidate() -> Bool { - switch currentLayout { - case .horizontal: - if thePoolHorizontal.highlightedIndex == 0 { - thePoolHorizontal.highlightHorizontal(at: thePoolHorizontal.candidateDataAll.count - 1) - delegate?.buzz() - break - } - thePoolHorizontal.highlightHorizontal(at: thePoolHorizontal.highlightedIndex - 1) - case .vertical: - if thePoolVertical.highlightedIndex == 0 { - thePoolVertical.highlightVertical(at: thePoolVertical.candidateDataAll.count - 1) - delegate?.buzz() - break - } - 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 currentRow[actualID].index - case .vertical: - let currentColumn = thePoolVertical.candidateColumns[thePoolVertical.currentColumnNumber] - let actualID = max(0, min(id, currentColumn.count - 1)) - return currentColumn[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/Source/Modules/SessionCtl_Core.swift b/Source/Modules/SessionCtl_Core.swift index dd843a4f..cf06df1f 100644 --- a/Source/Modules/SessionCtl_Core.swift +++ b/Source/Modules/SessionCtl_Core.swift @@ -12,7 +12,6 @@ import IMKUtils import PopupCompositionBuffer import Shared import ShiftKeyUpChecker -import TDKCandidateBackports import TooltipUI /// 輸入法控制模組,乃在輸入法端用以控制輸入行為的基礎型別。 @@ -32,12 +31,9 @@ class SessionCtl: IMKInputController { static var ctlCandidateCurrent: CtlCandidateProtocol = { let direction: NSUserInterfaceLayoutOrientation = PrefMgr.shared.useHorizontalCandidateList ? .horizontal : .vertical - if #available(macOS 12, *) { + if #available(macOS 10.15, *) { return PrefMgr.shared.useIMKCandidateWindow ? CtlCandidateIMK(direction) : CtlCandidateTDK(direction) - } else if #available(macOS 10.15, *) { - return PrefMgr.shared.useIMKCandidateWindow - ? CtlCandidateIMK(direction) : CtlCandidateTDKBackports(direction) } else { return CtlCandidateIMK(direction) } diff --git a/Source/Modules/SessionCtl_HandleDisplay.swift b/Source/Modules/SessionCtl_HandleDisplay.swift index 9da1149e..c69ef086 100644 --- a/Source/Modules/SessionCtl_HandleDisplay.swift +++ b/Source/Modules/SessionCtl_HandleDisplay.swift @@ -9,7 +9,6 @@ import CandidateWindow import NSAttributedTextView import Shared -import TDKCandidateBackports // MARK: - Tooltip Display and Candidate Display Methods @@ -104,14 +103,10 @@ extension SessionCtl { ? .vertical : .horizontal) - if #available(macOS 12, *) { + if #available(macOS 10.15, *) { Self.ctlCandidateCurrent = PrefMgr.shared.useIMKCandidateWindow ? CtlCandidateIMK(candidateLayout) : CtlCandidateTDK(candidateLayout) - } else if #available(macOS 10.15, *) { - Self.ctlCandidateCurrent = - PrefMgr.shared.useIMKCandidateWindow - ? CtlCandidateIMK(candidateLayout) : CtlCandidateTDKBackports(candidateLayout) } else { Self.ctlCandidateCurrent = CtlCandidateIMK(candidateLayout) } diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index 48af4742..225a7168 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -19,7 +19,6 @@ 5B40113928D7050D00A9D4CB /* Shared in Frameworks */ = {isa = PBXBuildFile; productRef = 5B40113828D7050D00A9D4CB /* Shared */; }; 5B40113C28D71C0100A9D4CB /* Uninstaller in Frameworks */ = {isa = PBXBuildFile; productRef = 5B40113B28D71C0100A9D4CB /* Uninstaller */; }; 5B5A603028E81CC50001AE8D /* SwiftUIBackports in Frameworks */ = {isa = PBXBuildFile; productRef = 5B5A602F28E81CC50001AE8D /* SwiftUIBackports */; }; - 5B5A603428E81FDF0001AE8D /* TDKCandidateBackports in Frameworks */ = {isa = PBXBuildFile; productRef = 5B5A603328E81FDF0001AE8D /* TDKCandidateBackports */; }; 5B62A33D27AE7CC100A19448 /* ctlAboutWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B62A33C27AE7CC100A19448 /* ctlAboutWindow.swift */; }; 5B6C141228A9D4B30098ADF8 /* SessionCtl_HandleEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6C141128A9D4B30098ADF8 /* SessionCtl_HandleEvent.swift */; }; 5B73FB5E27B2BE1300E9BF49 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5B73FB6027B2BE1300E9BF49 /* InfoPlist.strings */; }; @@ -206,7 +205,6 @@ 5B3133BE280B229700A4A505 /* KeyHandler_States.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = KeyHandler_States.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; 5B40113A28D71B8700A9D4CB /* vChewing_Uninstaller */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = vChewing_Uninstaller; path = Packages/vChewing_Uninstaller; sourceTree = ""; }; 5B5A602E28E81CB00001AE8D /* ShapsBenkau_SwiftUIBackports */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = ShapsBenkau_SwiftUIBackports; path = Packages/ShapsBenkau_SwiftUIBackports; sourceTree = ""; }; - 5B5A603228E81F670001AE8D /* vChewing_TDKCandidateBackports */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = vChewing_TDKCandidateBackports; path = Packages/vChewing_TDKCandidateBackports; sourceTree = ""; }; 5B62A33C27AE7CC100A19448 /* ctlAboutWindow.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = ctlAboutWindow.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; 5B65B919284D0185007C558B /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 5B6C141128A9D4B30098ADF8 /* SessionCtl_HandleEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionCtl_HandleEvent.swift; sourceTree = ""; }; @@ -347,7 +345,6 @@ 5BFC63CC28D49BBC004A77B7 /* UpdateSputnik in Frameworks */, 5BDB7A4128D4824A001AC277 /* Megrez in Frameworks */, 5BFC63C928D49754004A77B7 /* IMKUtils in Frameworks */, - 5B5A603428E81FDF0001AE8D /* TDKCandidateBackports in Frameworks */, 5BDB7A4728D4824A001AC277 /* Tekkon in Frameworks */, 5BDB7A4328D4824A001AC277 /* Preferences in Frameworks */, 5BDB7A3D28D4824A001AC277 /* Hotenka in Frameworks */, @@ -584,7 +581,6 @@ 5BC5E02228DE07250094E427 /* vChewing_PopupCompositionBuffer */, 5B963C9E28D5C14600DCEE88 /* vChewing_Shared */, 5B963CA128D5C22D00DCEE88 /* vChewing_SwiftExtension */, - 5B5A603228E81F670001AE8D /* vChewing_TDKCandidateBackports */, 5BDB7A3628D47587001AC277 /* vChewing_Tekkon */, 5BC5E01F28DDEFD80094E427 /* vChewing_TooltipUI */, 5B40113A28D71B8700A9D4CB /* vChewing_Uninstaller */, @@ -790,7 +786,6 @@ 5BC5E02328DE07860094E427 /* PopupCompositionBuffer */, 5BA8C30228DF0360004C5CC4 /* CandidateWindow */, 5B5A602F28E81CC50001AE8D /* SwiftUIBackports */, - 5B5A603328E81FDF0001AE8D /* TDKCandidateBackports */, ); productName = vChewing; productReference = 6A0D4EA215FC0D2D00ABF4B3 /* vChewing.app */; @@ -1783,10 +1778,6 @@ isa = XCSwiftPackageProductDependency; productName = SwiftUIBackports; }; - 5B5A603328E81FDF0001AE8D /* TDKCandidateBackports */ = { - isa = XCSwiftPackageProductDependency; - productName = TDKCandidateBackports; - }; 5B963C9C28D5BFB800DCEE88 /* CocoaExtension */ = { isa = XCSwiftPackageProductDependency; productName = CocoaExtension;