TDKCandidates // Refactor for simplicity.
This commit is contained in:
parent
e71f46f2f5
commit
f4a786904e
|
@ -13,13 +13,15 @@ let package = Package(
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
.package(path: "../vChewing_Shared")
|
.package(path: "../vChewing_Shared"),
|
||||||
|
.package(path: "../ShapsBenkau_SwiftUIBackports"),
|
||||||
],
|
],
|
||||||
targets: [
|
targets: [
|
||||||
.target(
|
.target(
|
||||||
name: "CandidateWindow",
|
name: "CandidateWindow",
|
||||||
dependencies: [
|
dependencies: [
|
||||||
.product(name: "Shared", package: "vChewing_Shared")
|
.product(name: "Shared", package: "vChewing_Shared"),
|
||||||
|
.product(name: "SwiftUIBackports", package: "ShapsBenkau_SwiftUIBackports"),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
.testTarget(
|
.testTarget(
|
||||||
|
|
|
@ -11,6 +11,12 @@ TDK 選字窗同時支援橫排矩陣佈局與縱排矩陣佈局。然而,目
|
||||||
- 因 SwiftUI 自身特性所導致的嚴重的效能問題(可能只會在幾年前的老電腦上出現)。基本上來講,如果您經常使用全字庫模式的話,請在偏好設定內啟用效能更高的 IMK 選字窗。
|
- 因 SwiftUI 自身特性所導致的嚴重的效能問題(可能只會在幾年前的老電腦上出現)。基本上來講,如果您經常使用全字庫模式的話,請在偏好設定內啟用效能更高的 IMK 選字窗。
|
||||||
- 同樣出於上述原因,為了讓田所選字窗至少處於可在生產力環境下正常使用的狀態,就犧牲了捲動檢視的功能。也就是說,每次只顯示三行/三列,但顯示內容則隨著使用者的游標操作而更新。
|
- 同樣出於上述原因,為了讓田所選字窗至少處於可在生產力環境下正常使用的狀態,就犧牲了捲動檢視的功能。也就是說,每次只顯示三行/三列,但顯示內容則隨著使用者的游標操作而更新。
|
||||||
|
|
||||||
|
TDK 選字窗會在 macOS 10.15 與 macOS 11 系統下有下述特性折扣:
|
||||||
|
|
||||||
|
- 所有的與介面配色有關的內容設定都是手動重新指定的,所以介面調色盤會與 macOS 12 開始的系統專用的田所選字窗完整版相比有出入。
|
||||||
|
- 無法支援「根據不同的選字窗內容文本語言區域,使用對應區域的系統介面字型來顯示」的特性。
|
||||||
|
- 原因:與該特性有關的幾個關鍵 API 都是 macOS 12 開始才有的 API。
|
||||||
|
|
||||||
```
|
```
|
||||||
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||||
// ====================
|
// ====================
|
||||||
|
|
|
@ -12,53 +12,91 @@ import Shared
|
||||||
/// 候選字窗會用到的資料池單位。
|
/// 候選字窗會用到的資料池單位。
|
||||||
public class CandidatePool {
|
public class CandidatePool {
|
||||||
public let blankCell = CandidateCellData(key: " ", displayedText: " ", isSelected: false)
|
public let blankCell = CandidateCellData(key: " ", displayedText: " ", isSelected: false)
|
||||||
|
public var currentLayout: NSUserInterfaceLayoutOrientation = .horizontal
|
||||||
public private(set) var candidateDataAll: [CandidateCellData] = []
|
public private(set) var candidateDataAll: [CandidateCellData] = []
|
||||||
public private(set) var selectionKeys: String
|
public private(set) var selectionKeys: String
|
||||||
public private(set) var highlightedIndex: Int = 0
|
public private(set) var highlightedIndex: Int = 0
|
||||||
|
|
||||||
// 下述變數只有橫排選字窗才會用到
|
// 下述變數只有橫排選字窗才會用到
|
||||||
public var currentRowNumber = 0
|
private var currentRowNumber = 0
|
||||||
public var maximumRowsPerPage = 3
|
private var maxRowsPerPage = 3
|
||||||
public private(set) var maxRowCapacity: Int = 6
|
private var maxRowCapacity: Int = 6
|
||||||
public private(set) var candidateRows: [[CandidateCellData]] = []
|
private var candidateRows: [[CandidateCellData]] = []
|
||||||
|
|
||||||
// 下述變數只有縱排選字窗才會用到
|
// 下述變數只有縱排選字窗才會用到
|
||||||
public var currentColumnNumber = 0
|
private var currentColumnNumber = 0
|
||||||
public var maximumColumnsPerPage = 3
|
private var maxColumnsPerPage = 3
|
||||||
public private(set) var maxColumnCapacity: Int = 6
|
private var maxColumnCapacity: Int = 6
|
||||||
public private(set) var candidateColumns: [[CandidateCellData]] = []
|
private var candidateColumns: [[CandidateCellData]] = []
|
||||||
|
|
||||||
|
// MARK: - 動態變數
|
||||||
|
|
||||||
// 動態變數
|
|
||||||
public var maxRowWidth: Int { Int(Double(maxRowCapacity + 3) * 2) * Int(ceil(CandidateCellData.unifiedSize)) }
|
public var maxRowWidth: Int { Int(Double(maxRowCapacity + 3) * 2) * Int(ceil(CandidateCellData.unifiedSize)) }
|
||||||
public var maxWindowWidth: Double {
|
public var maxWindowWidth: Double {
|
||||||
ceil(Double(maxRowCapacity + 3) * 2.7 * ceil(CandidateCellData.unifiedSize) * 1.2)
|
ceil(Double(maxRowCapacity + 3) * 2.7 * ceil(CandidateCellData.unifiedSize) * 1.2)
|
||||||
}
|
}
|
||||||
|
|
||||||
public var rangeForCurrentHorizontalPage: Range<Int> {
|
public var currentLineNumber: Int {
|
||||||
currentRowNumber..<min(candidateRows.count, currentRowNumber + maximumRowsPerPage)
|
switch currentLayout {
|
||||||
|
case .horizontal:
|
||||||
|
return currentRowNumber
|
||||||
|
case .vertical:
|
||||||
|
return currentColumnNumber
|
||||||
|
@unknown default:
|
||||||
|
return 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public var rangeForCurrentVerticalPage: Range<Int> {
|
public var candidateLines: [[CandidateCellData]] {
|
||||||
currentColumnNumber..<min(candidateColumns.count, currentColumnNumber + maximumColumnsPerPage)
|
switch currentLayout {
|
||||||
|
case .horizontal:
|
||||||
|
return candidateRows
|
||||||
|
case .vertical:
|
||||||
|
return candidateColumns
|
||||||
|
@unknown default:
|
||||||
|
return []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public var rangeForLastHorizontalPageBlanked: Range<Int> {
|
public var maxLineCapacity: Int {
|
||||||
0..<(maximumRowsPerPage - rangeForCurrentHorizontalPage.count)
|
switch currentLayout {
|
||||||
|
case .horizontal:
|
||||||
|
return maxRowCapacity
|
||||||
|
case .vertical:
|
||||||
|
return maxColumnCapacity
|
||||||
|
@unknown default:
|
||||||
|
return 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public var rangeForLastVerticalPageBlanked: Range<Int> {
|
public var maxLinesPerPage: Int {
|
||||||
0..<(maximumColumnsPerPage - rangeForCurrentVerticalPage.count)
|
switch currentLayout {
|
||||||
|
case .horizontal:
|
||||||
|
return maxRowsPerPage
|
||||||
|
case .vertical:
|
||||||
|
return maxColumnsPerPage
|
||||||
|
@unknown default:
|
||||||
|
return 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum VerticalDirection {
|
public var rangeForLastPageBlanked: Range<Int> {
|
||||||
case up
|
switch currentLayout {
|
||||||
case down
|
case .horizontal: return rangeForLastHorizontalPageBlanked
|
||||||
|
case .vertical: return rangeForLastVerticalPageBlanked
|
||||||
|
@unknown default: return 0..<0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum HorizontalDirection {
|
public var rangeForCurrentPage: Range<Int> {
|
||||||
case left
|
switch currentLayout {
|
||||||
case right
|
case .horizontal: return rangeForCurrentHorizontalPage
|
||||||
|
case .vertical: return rangeForCurrentVerticalPage
|
||||||
|
@unknown default: return 0..<0
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Constructors
|
||||||
|
|
||||||
/// 初期化一個縱排候選字窗專用資料池。
|
/// 初期化一個縱排候選字窗專用資料池。
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
|
@ -84,6 +122,7 @@ public class CandidatePool {
|
||||||
currentColumn.append(candidate)
|
currentColumn.append(candidate)
|
||||||
}
|
}
|
||||||
candidateColumns.append(currentColumn)
|
candidateColumns.append(currentColumn)
|
||||||
|
currentLayout = .vertical
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 初期化一個橫排候選字窗專用資料池。
|
/// 初期化一個橫排候選字窗專用資料池。
|
||||||
|
@ -111,9 +150,58 @@ public class CandidatePool {
|
||||||
currentRow.append(candidate)
|
currentRow.append(candidate)
|
||||||
}
|
}
|
||||||
candidateRows.append(currentRow)
|
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<Int> {
|
||||||
|
0..<(maxRowsPerPage - rangeForCurrentHorizontalPage.count)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var rangeForLastVerticalPageBlanked: Range<Int> {
|
||||||
|
0..<(maxColumnsPerPage - rangeForCurrentVerticalPage.count)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var rangeForCurrentHorizontalPage: Range<Int> {
|
||||||
|
currentRowNumber..<min(candidateRows.count, currentRowNumber + maxRowsPerPage)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var rangeForCurrentVerticalPage: Range<Int> {
|
||||||
|
currentColumnNumber..<min(candidateColumns.count, currentColumnNumber + maxColumnsPerPage)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func selectNewNeighborRow(direction: VerticalDirection) {
|
||||||
let currentSubIndex = candidateDataAll[highlightedIndex].subIndex
|
let currentSubIndex = candidateDataAll[highlightedIndex].subIndex
|
||||||
var result = currentSubIndex
|
var result = currentSubIndex
|
||||||
switch direction {
|
switch direction {
|
||||||
|
@ -151,7 +239,7 @@ public class CandidatePool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func selectNewNeighborColumn(direction: HorizontalDirection) {
|
private func selectNewNeighborColumn(direction: HorizontalDirection) {
|
||||||
let currentSubIndex = candidateDataAll[highlightedIndex].subIndex
|
let currentSubIndex = candidateDataAll[highlightedIndex].subIndex
|
||||||
switch direction {
|
switch direction {
|
||||||
case .left:
|
case .left:
|
||||||
|
@ -180,7 +268,7 @@ public class CandidatePool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func highlightHorizontal(at indexSpecified: Int) {
|
private func highlightHorizontal(at indexSpecified: Int) {
|
||||||
var indexSpecified = indexSpecified
|
var indexSpecified = indexSpecified
|
||||||
highlightedIndex = indexSpecified
|
highlightedIndex = indexSpecified
|
||||||
if !(0..<candidateDataAll.count).contains(highlightedIndex) {
|
if !(0..<candidateDataAll.count).contains(highlightedIndex) {
|
||||||
|
@ -213,7 +301,7 @@ public class CandidatePool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func highlightVertical(at indexSpecified: Int) {
|
private func highlightVertical(at indexSpecified: Int) {
|
||||||
var indexSpecified = indexSpecified
|
var indexSpecified = indexSpecified
|
||||||
highlightedIndex = indexSpecified
|
highlightedIndex = indexSpecified
|
||||||
if !(0..<candidateDataAll.count).contains(highlightedIndex) {
|
if !(0..<candidateDataAll.count).contains(highlightedIndex) {
|
||||||
|
|
|
@ -1,251 +0,0 @@
|
||||||
// (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 12, *)
|
|
||||||
public class CtlCandidateTDK: 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 {
|
|
||||||
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..<thePoolHorizontal.maximumRowsPerPage {
|
|
||||||
thePoolHorizontal.selectNewNeighborRow(direction: .down)
|
|
||||||
}
|
|
||||||
case .vertical:
|
|
||||||
for _ in 0..<thePoolVertical.maximumColumnsPerPage {
|
|
||||||
thePoolVertical.selectNewNeighborColumn(direction: .right)
|
|
||||||
}
|
|
||||||
@unknown default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
updateDisplay()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
@discardableResult override public func showNextLine() -> 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..<thePoolHorizontal.maximumRowsPerPage {
|
|
||||||
thePoolHorizontal.selectNewNeighborRow(direction: .up)
|
|
||||||
}
|
|
||||||
case .vertical:
|
|
||||||
for _ in 0..<thePoolVertical.maximumColumnsPerPage {
|
|
||||||
thePoolVertical.selectNewNeighborColumn(direction: .left)
|
|
||||||
}
|
|
||||||
@unknown default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
updateDisplay()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
@discardableResult override public func showPreviousLine() -> 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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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..<min(thePool.maxLinesPerPage, count) {
|
||||||
|
thePool.selectNewNeighborLine(isForward: true)
|
||||||
|
}
|
||||||
|
updateDisplay()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@discardableResult override public func showPreviousPage() -> 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..<min(thePool.maxLinesPerPage, count) {
|
||||||
|
thePool.selectNewNeighborLine(isForward: false)
|
||||||
|
}
|
||||||
|
updateDisplay()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@discardableResult override public func highlightNextCandidate() -> 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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,7 +24,7 @@ struct CandidatePoolViewUIHorizontal_Previews: PreviewProvider {
|
||||||
static var thePool: CandidatePool {
|
static var thePool: CandidatePool {
|
||||||
let result = CandidatePool(candidates: testCandidates, rowCapacity: 6)
|
let result = CandidatePool(candidates: testCandidates, rowCapacity: 6)
|
||||||
// 下一行待解決:無論這裡怎麼指定高亮選中項是哪一筆,其所在行都得被卷動到使用者眼前。
|
// 下一行待解決:無論這裡怎麼指定高亮選中項是哪一筆,其所在行都得被卷動到使用者眼前。
|
||||||
result.highlightHorizontal(at: 5)
|
result.highlight(at: 5)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,9 +53,9 @@ public struct VwrCandidateHorizontal: View {
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
ScrollView(.vertical, showsIndicators: false) {
|
ScrollView(.vertical, showsIndicators: false) {
|
||||||
VStack(alignment: .leading, spacing: 1.6) {
|
VStack(alignment: .leading, spacing: 1.6) {
|
||||||
ForEach(thePool.rangeForCurrentHorizontalPage, id: \.self) { rowIndex in
|
ForEach(thePool.rangeForCurrentPage, id: \.self) { rowIndex in
|
||||||
HStack(spacing: 10) {
|
HStack(spacing: 10) {
|
||||||
ForEach(Array(thePool.candidateRows[rowIndex]), id: \.self) { currentCandidate in
|
ForEach(Array(thePool.candidateLines[rowIndex]), id: \.self) { currentCandidate in
|
||||||
currentCandidate.attributedStringForSwiftUI.fixedSize()
|
currentCandidate.attributedStringForSwiftUI.fixedSize()
|
||||||
.frame(
|
.frame(
|
||||||
maxWidth: .infinity,
|
maxWidth: .infinity,
|
||||||
|
@ -71,8 +71,8 @@ public struct VwrCandidateHorizontal: View {
|
||||||
).id(rowIndex)
|
).id(rowIndex)
|
||||||
Divider()
|
Divider()
|
||||||
}
|
}
|
||||||
if thePool.maximumRowsPerPage - thePool.rangeForCurrentHorizontalPage.count > 0 {
|
if thePool.maxLinesPerPage - thePool.rangeForCurrentPage.count > 0 {
|
||||||
ForEach(thePool.rangeForLastHorizontalPageBlanked, id: \.self) { _ in
|
ForEach(thePool.rangeForLastPageBlanked, id: \.self) { _ in
|
||||||
HStack(spacing: 0) {
|
HStack(spacing: 0) {
|
||||||
thePool.blankCell.attributedStringForSwiftUI
|
thePool.blankCell.attributedStringForSwiftUI
|
||||||
.frame(maxWidth: .infinity, alignment: .topLeading)
|
.frame(maxWidth: .infinity, alignment: .topLeading)
|
|
@ -23,7 +23,7 @@ struct CandidatePoolViewUIVertical_Previews: PreviewProvider {
|
||||||
static var thePool: CandidatePool {
|
static var thePool: CandidatePool {
|
||||||
let result = CandidatePool(candidates: testCandidates, columnCapacity: 6, selectionKeys: "123456789")
|
let result = CandidatePool(candidates: testCandidates, columnCapacity: 6, selectionKeys: "123456789")
|
||||||
// 下一行待解決:無論這裡怎麼指定高亮選中項是哪一筆,其所在行都得被卷動到使用者眼前。
|
// 下一行待解決:無論這裡怎麼指定高亮選中項是哪一筆,其所在行都得被卷動到使用者眼前。
|
||||||
result.highlightVertical(at: 5)
|
result.highlight(at: 5)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,9 +52,9 @@ public struct VwrCandidateVertical: View {
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
ScrollView(.horizontal, showsIndicators: false) {
|
ScrollView(.horizontal, showsIndicators: false) {
|
||||||
HStack(alignment: .top, spacing: 10) {
|
HStack(alignment: .top, spacing: 10) {
|
||||||
ForEach(thePool.rangeForCurrentVerticalPage, id: \.self) { columnIndex in
|
ForEach(thePool.rangeForCurrentPage, id: \.self) { columnIndex in
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
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) {
|
HStack(spacing: 0) {
|
||||||
currentCandidate.attributedStringForSwiftUI.fixedSize(horizontal: false, vertical: true)
|
currentCandidate.attributedStringForSwiftUI.fixedSize(horizontal: false, vertical: true)
|
||||||
.frame(
|
.frame(
|
||||||
|
@ -71,10 +71,10 @@ public struct VwrCandidateVertical: View {
|
||||||
).id(columnIndex)
|
).id(columnIndex)
|
||||||
Divider()
|
Divider()
|
||||||
}
|
}
|
||||||
if thePool.maximumColumnsPerPage - thePool.rangeForCurrentVerticalPage.count > 0 {
|
if thePool.maxLinesPerPage - thePool.rangeForCurrentPage.count > 0 {
|
||||||
ForEach(thePool.rangeForLastVerticalPageBlanked, id: \.self) { _ in
|
ForEach(thePool.rangeForLastPageBlanked, id: \.self) { _ in
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
ForEach(0..<thePool.maxColumnCapacity, id: \.self) { _ in
|
ForEach(0..<thePool.maxLineCapacity, id: \.self) { _ in
|
||||||
thePool.blankCell.attributedStringForSwiftUI.fixedSize()
|
thePool.blankCell.attributedStringForSwiftUI.fixedSize()
|
||||||
.frame(width: Double(CandidateCellData.unifiedSize * 5), alignment: .topLeading)
|
.frame(width: Double(CandidateCellData.unifiedSize * 5), alignment: .topLeading)
|
||||||
.contentShape(Rectangle())
|
.contentShape(Rectangle())
|
|
@ -6,14 +6,13 @@
|
||||||
// marks, or product names of Contributor, except as required to fulfill notice
|
// marks, or product names of Contributor, except as required to fulfill notice
|
||||||
// requirements defined in MIT License.
|
// requirements defined in MIT License.
|
||||||
|
|
||||||
import CandidateWindow
|
|
||||||
import Shared
|
import Shared
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import SwiftUIBackports
|
import SwiftUIBackports
|
||||||
|
|
||||||
@available(macOS 10.15, *)
|
@available(macOS 10.15, *)
|
||||||
extension CandidateCellData {
|
extension CandidateCellData {
|
||||||
public var themeColor: some View {
|
public var themeColorBackports: some View {
|
||||||
// 設定當前高亮候選字的背景顏色。
|
// 設定當前高亮候選字的背景顏色。
|
||||||
let result: Color = {
|
let result: Color = {
|
||||||
switch locale {
|
switch locale {
|
||||||
|
@ -30,7 +29,7 @@ extension CandidateCellData {
|
||||||
var result: some View {
|
var result: some View {
|
||||||
ZStack(alignment: .leading) {
|
ZStack(alignment: .leading) {
|
||||||
if isSelected {
|
if isSelected {
|
||||||
themeColor.cornerRadius(6)
|
themeColorBackports.cornerRadius(6)
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
HStack(spacing: 4) {
|
HStack(spacing: 4) {
|
||||||
Text(key).font(.custom("Menlo", size: fontSizeKey))
|
Text(key).font(.custom("Menlo", size: fontSizeKey))
|
|
@ -6,7 +6,6 @@
|
||||||
// marks, or product names of Contributor, except as required to fulfill notice
|
// marks, or product names of Contributor, except as required to fulfill notice
|
||||||
// requirements defined in MIT License.
|
// requirements defined in MIT License.
|
||||||
|
|
||||||
import CandidateWindow
|
|
||||||
import Cocoa
|
import Cocoa
|
||||||
import Shared
|
import Shared
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
@ -26,19 +25,19 @@ struct CandidatePoolViewUIHorizontalBackports_Previews: PreviewProvider {
|
||||||
static var thePool: CandidatePool {
|
static var thePool: CandidatePool {
|
||||||
let result = CandidatePool(candidates: testCandidates, rowCapacity: 6)
|
let result = CandidatePool(candidates: testCandidates, rowCapacity: 6)
|
||||||
// 下一行待解決:無論這裡怎麼指定高亮選中項是哪一筆,其所在行都得被卷動到使用者眼前。
|
// 下一行待解決:無論這裡怎麼指定高亮選中項是哪一筆,其所在行都得被卷動到使用者眼前。
|
||||||
result.highlightHorizontal(at: 5)
|
result.highlight(at: 5)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
VwrCandidateHorizontal(controller: .init(.horizontal), thePool: thePool).fixedSize()
|
VwrCandidateHorizontalBackports(controller: .init(.horizontal), thePool: thePool).fixedSize()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(macOS 10.15, *)
|
@available(macOS 10.15, *)
|
||||||
public struct VwrCandidateHorizontal: View {
|
public struct VwrCandidateHorizontalBackports: View {
|
||||||
@Environment(\.colorScheme) var colorScheme
|
@Environment(\.colorScheme) var colorScheme
|
||||||
public var controller: CtlCandidateTDKBackports
|
public var controller: CtlCandidateTDK
|
||||||
@State public var thePool: CandidatePool
|
@State public var thePool: CandidatePool
|
||||||
@State public var hint: String = ""
|
@State public var hint: String = ""
|
||||||
|
|
||||||
|
@ -56,9 +55,9 @@ public struct VwrCandidateHorizontal: View {
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
ScrollView(.vertical, showsIndicators: false) {
|
ScrollView(.vertical, showsIndicators: false) {
|
||||||
VStack(alignment: .leading, spacing: 1.6) {
|
VStack(alignment: .leading, spacing: 1.6) {
|
||||||
ForEach(thePool.rangeForCurrentHorizontalPage, id: \.self) { rowIndex in
|
ForEach(thePool.rangeForCurrentPage, id: \.self) { rowIndex in
|
||||||
HStack(spacing: 10) {
|
HStack(spacing: 10) {
|
||||||
ForEach(Array(thePool.candidateRows[rowIndex]), id: \.self) { currentCandidate in
|
ForEach(Array(thePool.candidateLines[rowIndex]), id: \.self) { currentCandidate in
|
||||||
currentCandidate.attributedStringForSwiftUIBackports.fixedSize()
|
currentCandidate.attributedStringForSwiftUIBackports.fixedSize()
|
||||||
.frame(
|
.frame(
|
||||||
maxWidth: .infinity,
|
maxWidth: .infinity,
|
||||||
|
@ -74,8 +73,8 @@ public struct VwrCandidateHorizontal: View {
|
||||||
).id(rowIndex)
|
).id(rowIndex)
|
||||||
Divider()
|
Divider()
|
||||||
}
|
}
|
||||||
if thePool.maximumRowsPerPage - thePool.rangeForCurrentHorizontalPage.count > 0 {
|
if thePool.maxLinesPerPage - thePool.rangeForCurrentPage.count > 0 {
|
||||||
ForEach(thePool.rangeForLastHorizontalPageBlanked, id: \.self) { _ in
|
ForEach(thePool.rangeForLastPageBlanked, id: \.self) { _ in
|
||||||
HStack(spacing: 0) {
|
HStack(spacing: 0) {
|
||||||
thePool.blankCell.attributedStringForSwiftUIBackports
|
thePool.blankCell.attributedStringForSwiftUIBackports
|
||||||
.frame(maxWidth: .infinity, alignment: .topLeading)
|
.frame(maxWidth: .infinity, alignment: .topLeading)
|
||||||
|
@ -97,7 +96,7 @@ public struct VwrCandidateHorizontal: View {
|
||||||
if hint.isEmpty {
|
if hint.isEmpty {
|
||||||
Color(white: colorScheme == .dark ? 0.2 : 0.9)
|
Color(white: colorScheme == .dark ? 0.2 : 0.9)
|
||||||
} else {
|
} else {
|
||||||
controller.highlightedColorUI
|
controller.highlightedColorUIBackports
|
||||||
}
|
}
|
||||||
HStack(alignment: .bottom) {
|
HStack(alignment: .bottom) {
|
||||||
Text(hint).font(.system(size: max(CandidateCellData.unifiedSize * 0.7, 11), weight: .bold)).lineLimit(1)
|
Text(hint).font(.system(size: max(CandidateCellData.unifiedSize * 0.7, 11), weight: .bold)).lineLimit(1)
|
|
@ -6,7 +6,6 @@
|
||||||
// marks, or product names of Contributor, except as required to fulfill notice
|
// marks, or product names of Contributor, except as required to fulfill notice
|
||||||
// requirements defined in MIT License.
|
// requirements defined in MIT License.
|
||||||
|
|
||||||
import CandidateWindow
|
|
||||||
import Cocoa
|
import Cocoa
|
||||||
import Shared
|
import Shared
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
@ -25,19 +24,19 @@ struct CandidatePoolViewUIVerticalBackports_Previews: PreviewProvider {
|
||||||
static var thePool: CandidatePool {
|
static var thePool: CandidatePool {
|
||||||
let result = CandidatePool(candidates: testCandidates, columnCapacity: 6, selectionKeys: "123456789")
|
let result = CandidatePool(candidates: testCandidates, columnCapacity: 6, selectionKeys: "123456789")
|
||||||
// 下一行待解決:無論這裡怎麼指定高亮選中項是哪一筆,其所在行都得被卷動到使用者眼前。
|
// 下一行待解決:無論這裡怎麼指定高亮選中項是哪一筆,其所在行都得被卷動到使用者眼前。
|
||||||
result.highlightVertical(at: 5)
|
result.highlight(at: 5)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
VwrCandidateVertical(controller: .init(.horizontal), thePool: thePool).fixedSize()
|
VwrCandidateVerticalBackports(controller: .init(.horizontal), thePool: thePool).fixedSize()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(macOS 10.15, *)
|
@available(macOS 10.15, *)
|
||||||
public struct VwrCandidateVertical: View {
|
public struct VwrCandidateVerticalBackports: View {
|
||||||
@Environment(\.colorScheme) var colorScheme
|
@Environment(\.colorScheme) var colorScheme
|
||||||
public var controller: CtlCandidateTDKBackports
|
public var controller: CtlCandidateTDK
|
||||||
@State public var thePool: CandidatePool
|
@State public var thePool: CandidatePool
|
||||||
@State public var hint: String = ""
|
@State public var hint: String = ""
|
||||||
|
|
||||||
|
@ -55,9 +54,9 @@ public struct VwrCandidateVertical: View {
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
ScrollView(.horizontal, showsIndicators: false) {
|
ScrollView(.horizontal, showsIndicators: false) {
|
||||||
HStack(alignment: .top, spacing: 10) {
|
HStack(alignment: .top, spacing: 10) {
|
||||||
ForEach(thePool.rangeForCurrentVerticalPage, id: \.self) { columnIndex in
|
ForEach(thePool.rangeForCurrentPage, id: \.self) { columnIndex in
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
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) {
|
HStack(spacing: 0) {
|
||||||
currentCandidate.attributedStringForSwiftUIBackports.fixedSize(horizontal: false, vertical: true)
|
currentCandidate.attributedStringForSwiftUIBackports.fixedSize(horizontal: false, vertical: true)
|
||||||
.frame(
|
.frame(
|
||||||
|
@ -74,10 +73,10 @@ public struct VwrCandidateVertical: View {
|
||||||
).id(columnIndex)
|
).id(columnIndex)
|
||||||
Divider()
|
Divider()
|
||||||
}
|
}
|
||||||
if thePool.maximumColumnsPerPage - thePool.rangeForCurrentVerticalPage.count > 0 {
|
if thePool.maxLinesPerPage - thePool.rangeForCurrentPage.count > 0 {
|
||||||
ForEach(thePool.rangeForLastVerticalPageBlanked, id: \.self) { _ in
|
ForEach(thePool.rangeForLastPageBlanked, id: \.self) { _ in
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
ForEach(0..<thePool.maxColumnCapacity, id: \.self) { _ in
|
ForEach(0..<thePool.maxLineCapacity, id: \.self) { _ in
|
||||||
thePool.blankCell.attributedStringForSwiftUIBackports.fixedSize()
|
thePool.blankCell.attributedStringForSwiftUIBackports.fixedSize()
|
||||||
.frame(width: Double(CandidateCellData.unifiedSize * 5), alignment: .topLeading)
|
.frame(width: Double(CandidateCellData.unifiedSize * 5), alignment: .topLeading)
|
||||||
.contentShape(Rectangle())
|
.contentShape(Rectangle())
|
||||||
|
@ -98,7 +97,7 @@ public struct VwrCandidateVertical: View {
|
||||||
if hint.isEmpty {
|
if hint.isEmpty {
|
||||||
Color(white: colorScheme == .dark ? 0.2 : 0.9)
|
Color(white: colorScheme == .dark ? 0.2 : 0.9)
|
||||||
} else {
|
} else {
|
||||||
controller.highlightedColorUI
|
controller.highlightedColorUIBackports
|
||||||
}
|
}
|
||||||
HStack(alignment: .bottom) {
|
HStack(alignment: .bottom) {
|
||||||
Text(hint).font(.system(size: max(CandidateCellData.unifiedSize * 0.7, 11), weight: .bold)).lineLimit(1)
|
Text(hint).font(.system(size: max(CandidateCellData.unifiedSize * 0.7, 11), weight: .bold)).lineLimit(1)
|
|
@ -1,9 +0,0 @@
|
||||||
.DS_Store
|
|
||||||
/.build
|
|
||||||
/Packages
|
|
||||||
/*.xcodeproj
|
|
||||||
xcuserdata/
|
|
||||||
DerivedData/
|
|
||||||
.swiftpm/config/registries.json
|
|
||||||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
|
||||||
.netrc
|
|
|
@ -1,30 +0,0 @@
|
||||||
// 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"),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
]
|
|
||||||
)
|
|
|
@ -1,19 +0,0 @@
|
||||||
# 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.
|
|
||||||
```
|
|
|
@ -1,269 +0,0 @@
|
||||||
// (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 {
|
|
||||||
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..<thePoolHorizontal.maximumRowsPerPage {
|
|
||||||
thePoolHorizontal.selectNewNeighborRow(direction: .down)
|
|
||||||
}
|
|
||||||
case .vertical:
|
|
||||||
for _ in 0..<thePoolVertical.maximumColumnsPerPage {
|
|
||||||
thePoolVertical.selectNewNeighborColumn(direction: .right)
|
|
||||||
}
|
|
||||||
@unknown default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
updateDisplay()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
@discardableResult override public func showNextLine() -> 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..<thePoolHorizontal.maximumRowsPerPage {
|
|
||||||
thePoolHorizontal.selectNewNeighborRow(direction: .up)
|
|
||||||
}
|
|
||||||
case .vertical:
|
|
||||||
for _ in 0..<thePoolVertical.maximumColumnsPerPage {
|
|
||||||
thePoolVertical.selectNewNeighborColumn(direction: .left)
|
|
||||||
}
|
|
||||||
@unknown default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
updateDisplay()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
@discardableResult override public func showPreviousLine() -> 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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -12,7 +12,6 @@ import IMKUtils
|
||||||
import PopupCompositionBuffer
|
import PopupCompositionBuffer
|
||||||
import Shared
|
import Shared
|
||||||
import ShiftKeyUpChecker
|
import ShiftKeyUpChecker
|
||||||
import TDKCandidateBackports
|
|
||||||
import TooltipUI
|
import TooltipUI
|
||||||
|
|
||||||
/// 輸入法控制模組,乃在輸入法端用以控制輸入行為的基礎型別。
|
/// 輸入法控制模組,乃在輸入法端用以控制輸入行為的基礎型別。
|
||||||
|
@ -32,12 +31,9 @@ class SessionCtl: IMKInputController {
|
||||||
static var ctlCandidateCurrent: CtlCandidateProtocol = {
|
static var ctlCandidateCurrent: CtlCandidateProtocol = {
|
||||||
let direction: NSUserInterfaceLayoutOrientation =
|
let direction: NSUserInterfaceLayoutOrientation =
|
||||||
PrefMgr.shared.useHorizontalCandidateList ? .horizontal : .vertical
|
PrefMgr.shared.useHorizontalCandidateList ? .horizontal : .vertical
|
||||||
if #available(macOS 12, *) {
|
if #available(macOS 10.15, *) {
|
||||||
return PrefMgr.shared.useIMKCandidateWindow
|
return PrefMgr.shared.useIMKCandidateWindow
|
||||||
? CtlCandidateIMK(direction) : CtlCandidateTDK(direction)
|
? CtlCandidateIMK(direction) : CtlCandidateTDK(direction)
|
||||||
} else if #available(macOS 10.15, *) {
|
|
||||||
return PrefMgr.shared.useIMKCandidateWindow
|
|
||||||
? CtlCandidateIMK(direction) : CtlCandidateTDKBackports(direction)
|
|
||||||
} else {
|
} else {
|
||||||
return CtlCandidateIMK(direction)
|
return CtlCandidateIMK(direction)
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,6 @@
|
||||||
import CandidateWindow
|
import CandidateWindow
|
||||||
import NSAttributedTextView
|
import NSAttributedTextView
|
||||||
import Shared
|
import Shared
|
||||||
import TDKCandidateBackports
|
|
||||||
|
|
||||||
// MARK: - Tooltip Display and Candidate Display Methods
|
// MARK: - Tooltip Display and Candidate Display Methods
|
||||||
|
|
||||||
|
@ -104,14 +103,10 @@ extension SessionCtl {
|
||||||
? .vertical
|
? .vertical
|
||||||
: .horizontal)
|
: .horizontal)
|
||||||
|
|
||||||
if #available(macOS 12, *) {
|
if #available(macOS 10.15, *) {
|
||||||
Self.ctlCandidateCurrent =
|
Self.ctlCandidateCurrent =
|
||||||
PrefMgr.shared.useIMKCandidateWindow
|
PrefMgr.shared.useIMKCandidateWindow
|
||||||
? CtlCandidateIMK(candidateLayout) : CtlCandidateTDK(candidateLayout)
|
? CtlCandidateIMK(candidateLayout) : CtlCandidateTDK(candidateLayout)
|
||||||
} else if #available(macOS 10.15, *) {
|
|
||||||
Self.ctlCandidateCurrent =
|
|
||||||
PrefMgr.shared.useIMKCandidateWindow
|
|
||||||
? CtlCandidateIMK(candidateLayout) : CtlCandidateTDKBackports(candidateLayout)
|
|
||||||
} else {
|
} else {
|
||||||
Self.ctlCandidateCurrent = CtlCandidateIMK(candidateLayout)
|
Self.ctlCandidateCurrent = CtlCandidateIMK(candidateLayout)
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,6 @@
|
||||||
5B40113928D7050D00A9D4CB /* Shared in Frameworks */ = {isa = PBXBuildFile; productRef = 5B40113828D7050D00A9D4CB /* Shared */; };
|
5B40113928D7050D00A9D4CB /* Shared in Frameworks */ = {isa = PBXBuildFile; productRef = 5B40113828D7050D00A9D4CB /* Shared */; };
|
||||||
5B40113C28D71C0100A9D4CB /* Uninstaller in Frameworks */ = {isa = PBXBuildFile; productRef = 5B40113B28D71C0100A9D4CB /* Uninstaller */; };
|
5B40113C28D71C0100A9D4CB /* Uninstaller in Frameworks */ = {isa = PBXBuildFile; productRef = 5B40113B28D71C0100A9D4CB /* Uninstaller */; };
|
||||||
5B5A603028E81CC50001AE8D /* SwiftUIBackports in Frameworks */ = {isa = PBXBuildFile; productRef = 5B5A602F28E81CC50001AE8D /* SwiftUIBackports */; };
|
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 */; };
|
5B62A33D27AE7CC100A19448 /* ctlAboutWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B62A33C27AE7CC100A19448 /* ctlAboutWindow.swift */; };
|
||||||
5B6C141228A9D4B30098ADF8 /* SessionCtl_HandleEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6C141128A9D4B30098ADF8 /* SessionCtl_HandleEvent.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 */; };
|
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 = "<group>"; tabWidth = 2; usesTabs = 0; };
|
5B3133BE280B229700A4A505 /* KeyHandler_States.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = KeyHandler_States.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
|
||||||
5B40113A28D71B8700A9D4CB /* vChewing_Uninstaller */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = vChewing_Uninstaller; path = Packages/vChewing_Uninstaller; sourceTree = "<group>"; };
|
5B40113A28D71B8700A9D4CB /* vChewing_Uninstaller */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = vChewing_Uninstaller; path = Packages/vChewing_Uninstaller; sourceTree = "<group>"; };
|
||||||
5B5A602E28E81CB00001AE8D /* ShapsBenkau_SwiftUIBackports */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = ShapsBenkau_SwiftUIBackports; path = Packages/ShapsBenkau_SwiftUIBackports; sourceTree = "<group>"; };
|
5B5A602E28E81CB00001AE8D /* ShapsBenkau_SwiftUIBackports */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = ShapsBenkau_SwiftUIBackports; path = Packages/ShapsBenkau_SwiftUIBackports; sourceTree = "<group>"; };
|
||||||
5B5A603228E81F670001AE8D /* vChewing_TDKCandidateBackports */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = vChewing_TDKCandidateBackports; path = Packages/vChewing_TDKCandidateBackports; sourceTree = "<group>"; };
|
|
||||||
5B62A33C27AE7CC100A19448 /* ctlAboutWindow.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = ctlAboutWindow.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
|
5B62A33C27AE7CC100A19448 /* ctlAboutWindow.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = ctlAboutWindow.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
|
||||||
5B65B919284D0185007C558B /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
|
5B65B919284D0185007C558B /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
|
||||||
5B6C141128A9D4B30098ADF8 /* SessionCtl_HandleEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionCtl_HandleEvent.swift; sourceTree = "<group>"; };
|
5B6C141128A9D4B30098ADF8 /* SessionCtl_HandleEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionCtl_HandleEvent.swift; sourceTree = "<group>"; };
|
||||||
|
@ -347,7 +345,6 @@
|
||||||
5BFC63CC28D49BBC004A77B7 /* UpdateSputnik in Frameworks */,
|
5BFC63CC28D49BBC004A77B7 /* UpdateSputnik in Frameworks */,
|
||||||
5BDB7A4128D4824A001AC277 /* Megrez in Frameworks */,
|
5BDB7A4128D4824A001AC277 /* Megrez in Frameworks */,
|
||||||
5BFC63C928D49754004A77B7 /* IMKUtils in Frameworks */,
|
5BFC63C928D49754004A77B7 /* IMKUtils in Frameworks */,
|
||||||
5B5A603428E81FDF0001AE8D /* TDKCandidateBackports in Frameworks */,
|
|
||||||
5BDB7A4728D4824A001AC277 /* Tekkon in Frameworks */,
|
5BDB7A4728D4824A001AC277 /* Tekkon in Frameworks */,
|
||||||
5BDB7A4328D4824A001AC277 /* Preferences in Frameworks */,
|
5BDB7A4328D4824A001AC277 /* Preferences in Frameworks */,
|
||||||
5BDB7A3D28D4824A001AC277 /* Hotenka in Frameworks */,
|
5BDB7A3D28D4824A001AC277 /* Hotenka in Frameworks */,
|
||||||
|
@ -584,7 +581,6 @@
|
||||||
5BC5E02228DE07250094E427 /* vChewing_PopupCompositionBuffer */,
|
5BC5E02228DE07250094E427 /* vChewing_PopupCompositionBuffer */,
|
||||||
5B963C9E28D5C14600DCEE88 /* vChewing_Shared */,
|
5B963C9E28D5C14600DCEE88 /* vChewing_Shared */,
|
||||||
5B963CA128D5C22D00DCEE88 /* vChewing_SwiftExtension */,
|
5B963CA128D5C22D00DCEE88 /* vChewing_SwiftExtension */,
|
||||||
5B5A603228E81F670001AE8D /* vChewing_TDKCandidateBackports */,
|
|
||||||
5BDB7A3628D47587001AC277 /* vChewing_Tekkon */,
|
5BDB7A3628D47587001AC277 /* vChewing_Tekkon */,
|
||||||
5BC5E01F28DDEFD80094E427 /* vChewing_TooltipUI */,
|
5BC5E01F28DDEFD80094E427 /* vChewing_TooltipUI */,
|
||||||
5B40113A28D71B8700A9D4CB /* vChewing_Uninstaller */,
|
5B40113A28D71B8700A9D4CB /* vChewing_Uninstaller */,
|
||||||
|
@ -790,7 +786,6 @@
|
||||||
5BC5E02328DE07860094E427 /* PopupCompositionBuffer */,
|
5BC5E02328DE07860094E427 /* PopupCompositionBuffer */,
|
||||||
5BA8C30228DF0360004C5CC4 /* CandidateWindow */,
|
5BA8C30228DF0360004C5CC4 /* CandidateWindow */,
|
||||||
5B5A602F28E81CC50001AE8D /* SwiftUIBackports */,
|
5B5A602F28E81CC50001AE8D /* SwiftUIBackports */,
|
||||||
5B5A603328E81FDF0001AE8D /* TDKCandidateBackports */,
|
|
||||||
);
|
);
|
||||||
productName = vChewing;
|
productName = vChewing;
|
||||||
productReference = 6A0D4EA215FC0D2D00ABF4B3 /* vChewing.app */;
|
productReference = 6A0D4EA215FC0D2D00ABF4B3 /* vChewing.app */;
|
||||||
|
@ -1783,10 +1778,6 @@
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
productName = SwiftUIBackports;
|
productName = SwiftUIBackports;
|
||||||
};
|
};
|
||||||
5B5A603328E81FDF0001AE8D /* TDKCandidateBackports */ = {
|
|
||||||
isa = XCSwiftPackageProductDependency;
|
|
||||||
productName = TDKCandidateBackports;
|
|
||||||
};
|
|
||||||
5B963C9C28D5BFB800DCEE88 /* CocoaExtension */ = {
|
5B963C9C28D5BFB800DCEE88 /* CocoaExtension */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
productName = CocoaExtension;
|
productName = CocoaExtension;
|
||||||
|
|
Loading…
Reference in New Issue