CtlCandidateTDK // Performance boost.
This commit is contained in:
parent
eadae22dbb
commit
b38938de54
|
@ -9,6 +9,7 @@ TDK 選字窗以純 SwiftUI 構築,用以取代此前自上游繼承來的 Vol
|
||||||
然而,TDK 選字窗目前有下述侷限:
|
然而,TDK 選字窗目前有下述侷限:
|
||||||
|
|
||||||
- 因 SwiftUI 自身特性所導致的嚴重的效能問題。基本上來講,如果您經常使用全字庫模式的話,請在偏好設定內啟用效能更高的 IMK 選字窗。
|
- 因 SwiftUI 自身特性所導致的嚴重的效能問題。基本上來講,如果您經常使用全字庫模式的話,請在偏好設定內啟用效能更高的 IMK 選字窗。
|
||||||
|
- 同樣出於上述原因,為了讓田所選字窗至少處於可在生產力環境下正常使用的狀態,就犧牲了捲動檢視的功能。也就是說,每次只顯示六行,但顯示內容則隨著使用者的游標操作而更新。
|
||||||
- TDK 選字窗目前僅完成了橫版矩陣陳列模式的實作,且尚未引入對縱排選字窗陳列佈局的支援。
|
- TDK 選字窗目前僅完成了橫版矩陣陳列模式的實作,且尚未引入對縱排選字窗陳列佈局的支援。
|
||||||
|
|
||||||
因為這些問題恐怕需要很久才能全部解決,所以威注音會在這段時間內推薦使用者們優先使用 IMK 選字窗。
|
因為這些問題恐怕需要很久才能全部解決,所以威注音會在這段時間內推薦使用者們優先使用 IMK 選字窗。
|
||||||
|
|
|
@ -9,8 +9,8 @@
|
||||||
import Cocoa
|
import Cocoa
|
||||||
import Shared
|
import Shared
|
||||||
|
|
||||||
/// 候選字窗會用到的資料池單位。用 class 型別會更方便一些。
|
/// 候選字窗會用到的資料池單位。
|
||||||
public class CandidatePool {
|
public struct CandidatePool {
|
||||||
public var currentRowNumber = 0
|
public var currentRowNumber = 0
|
||||||
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
|
||||||
|
@ -24,6 +24,8 @@ public class CandidatePool {
|
||||||
ceil(Double(maxColumnCapacity + 3) * 2.7 * ceil(CandidateCellData.unifiedSize) * 1.2)
|
ceil(Double(maxColumnCapacity + 3) * 2.7 * ceil(CandidateCellData.unifiedSize) * 1.2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var rangeForCurrentPage: Range<Int> { currentRowNumber..<min(candidateRows.count, currentRowNumber + 6) }
|
||||||
|
|
||||||
public enum VerticalDirection {
|
public enum VerticalDirection {
|
||||||
case up
|
case up
|
||||||
case down
|
case down
|
||||||
|
@ -54,7 +56,7 @@ public class CandidatePool {
|
||||||
candidateRows.append(currentColumn)
|
candidateRows.append(currentColumn)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func selectNewNeighborRow(direction: VerticalDirection) {
|
public mutating func selectNewNeighborRow(direction: VerticalDirection) {
|
||||||
let currentSubIndex = candidateDataAll[highlightedIndex].subIndex
|
let currentSubIndex = candidateDataAll[highlightedIndex].subIndex
|
||||||
var result = currentSubIndex
|
var result = currentSubIndex
|
||||||
switch direction {
|
switch direction {
|
||||||
|
@ -92,7 +94,7 @@ public class CandidatePool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func highlight(at indexSpecified: Int) {
|
public mutating func highlight(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) {
|
||||||
|
|
|
@ -43,25 +43,25 @@ public class CtlCandidateTDK: CtlCandidate {
|
||||||
CandidateCellData.highlightBackground = highlightedColor()
|
CandidateCellData.highlightBackground = highlightedColor()
|
||||||
CandidateCellData.unifiedSize = candidateFont.pointSize
|
CandidateCellData.unifiedSize = candidateFont.pointSize
|
||||||
guard let delegate = delegate else { return }
|
guard let delegate = delegate else { return }
|
||||||
let selectionKeys = keyLabels.map(\.key).joined()
|
|
||||||
thePool = .init(
|
thePool = .init(
|
||||||
candidates: delegate.candidatePairs(conv: true).map(\.1), selectionKeys: selectionKeys, locale: locale
|
candidates: delegate.candidatePairs(conv: true).map(\.1),
|
||||||
|
selectionKeys: delegate.selectionKeys, locale: locale
|
||||||
)
|
)
|
||||||
thePool.highlight(at: 0)
|
thePool.highlight(at: 0)
|
||||||
updateDisplay()
|
updateDisplay()
|
||||||
}
|
}
|
||||||
|
|
||||||
override open func updateDisplay() {
|
override open func updateDisplay() {
|
||||||
let newView = NSHostingView(rootView: theView.fixedSize())
|
DispatchQueue.main.async { [self] in
|
||||||
let newSize = newView.fittingSize
|
let newView = NSHostingView(rootView: theView.fixedSize())
|
||||||
var newFrame = NSRect.zero
|
let newSize = newView.fittingSize
|
||||||
if let window = window {
|
var newFrame = NSRect.zero
|
||||||
newFrame = window.frame
|
if let window = window { newFrame = window.frame }
|
||||||
|
newFrame.size = newSize
|
||||||
|
window?.setFrame(newFrame, display: false)
|
||||||
|
window?.contentView = NSHostingView(rootView: theView.fixedSize())
|
||||||
|
window?.setContentSize(newSize)
|
||||||
}
|
}
|
||||||
newFrame.size = newSize
|
|
||||||
window?.setFrame(newFrame, display: false)
|
|
||||||
window?.contentView = NSHostingView(rootView: theView.fixedSize())
|
|
||||||
window?.setContentSize(newSize)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@discardableResult override public func showNextPage() -> Bool {
|
@discardableResult override public func showNextPage() -> Bool {
|
||||||
|
|
|
@ -21,7 +21,7 @@ struct CandidatePoolViewUI_Previews: PreviewProvider {
|
||||||
"吹", "大", "地", "草", "枝", "擺",
|
"吹", "大", "地", "草", "枝", "擺",
|
||||||
]
|
]
|
||||||
static var thePool: CandidatePool {
|
static var thePool: CandidatePool {
|
||||||
let result = CandidatePool(candidates: testCandidates, columnCapacity: 6)
|
var result = CandidatePool(candidates: testCandidates, columnCapacity: 6)
|
||||||
// 下一行待解決:無論這裡怎麼指定高亮選中項是哪一筆,其所在行都得被卷動到使用者眼前。
|
// 下一行待解決:無論這裡怎麼指定高亮選中項是哪一筆,其所在行都得被卷動到使用者眼前。
|
||||||
result.highlight(at: 14)
|
result.highlight(at: 14)
|
||||||
return result
|
return result
|
||||||
|
@ -50,28 +50,24 @@ public struct VwrCandidateTDK: View {
|
||||||
|
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
ScrollViewReader { proxy in
|
ScrollView(.vertical, showsIndicators: true) {
|
||||||
ScrollView(.vertical, showsIndicators: true) {
|
VStack(alignment: .leading, spacing: 1.6) {
|
||||||
VStack(alignment: .leading, spacing: 1.6) {
|
ForEach(thePool.rangeForCurrentPage, id: \.self) { columnIndex in
|
||||||
ForEach(thePool.candidateRows.indices, id: \.self) { columnIndex in
|
HStack(spacing: 10) {
|
||||||
HStack(spacing: 10) {
|
ForEach(Array(thePool.candidateRows[columnIndex]), id: \.self) { currentCandidate in
|
||||||
ForEach(Array(thePool.candidateRows[columnIndex]), id: \.self) { currentCandidate in
|
currentCandidate.attributedStringForSwiftUI.fixedSize()
|
||||||
currentCandidate.attributedStringForSwiftUI.fixedSize()
|
.frame(maxWidth: .infinity, alignment: .topLeading)
|
||||||
.frame(maxWidth: .infinity, alignment: .topLeading)
|
.contentShape(Rectangle())
|
||||||
.contentShape(Rectangle())
|
.onTapGesture { didSelectCandidateAt(currentCandidate.index) }
|
||||||
.onTapGesture { didSelectCandidateAt(currentCandidate.index) }
|
}
|
||||||
}
|
Spacer()
|
||||||
Spacer()
|
}.frame(
|
||||||
}.frame(
|
minWidth: 0,
|
||||||
minWidth: 0,
|
maxWidth: .infinity,
|
||||||
maxWidth: .infinity,
|
alignment: .topLeading
|
||||||
alignment: .topLeading
|
).id(columnIndex)
|
||||||
).id(columnIndex)
|
Divider()
|
||||||
Divider()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}.onAppear {
|
|
||||||
proxy.scrollTo(thePool.currentRowNumber)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.frame(minHeight: thePool.maxWindowHeight, maxHeight: thePool.maxWindowHeight).padding(5)
|
.frame(minHeight: thePool.maxWindowHeight, maxHeight: thePool.maxWindowHeight).padding(5)
|
||||||
|
|
|
@ -44,9 +44,9 @@ public class CandidateCellData: Hashable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public var cellLength: Int {
|
public var cellLength: Int {
|
||||||
let rect = attributedString.boundingRect(
|
if displayedText.count <= 2 { return Int(ceil(size * 3)) }
|
||||||
with: NSSize(width: 1600.0, height: 1600.0),
|
let rect = attributedStringForLengthCalculation.boundingRect(
|
||||||
options: [.usesLineFragmentOrigin]
|
with: NSSize(width: 1600.0, height: 1600.0), options: [.usesLineFragmentOrigin]
|
||||||
)
|
)
|
||||||
let rawResult = ceil(rect.width + size / size)
|
let rawResult = ceil(rect.width + size / size)
|
||||||
return Int(rawResult)
|
return Int(rawResult)
|
||||||
|
@ -72,6 +72,14 @@ public class CandidateCellData: Hashable {
|
||||||
return attrStrKey
|
return attrStrKey
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var attributedStringForLengthCalculation: NSAttributedString {
|
||||||
|
let attrCandidate: [NSAttributedString.Key: AnyObject] = [
|
||||||
|
.font: NSFont.monospacedDigitSystemFont(ofSize: size, weight: .regular)
|
||||||
|
]
|
||||||
|
let attrStrCandidate = NSMutableAttributedString(string: displayedText + " ", attributes: attrCandidate)
|
||||||
|
return attrStrCandidate
|
||||||
|
}
|
||||||
|
|
||||||
public var attributedString: NSAttributedString {
|
public var attributedString: NSAttributedString {
|
||||||
let paraStyleKey = NSMutableParagraphStyle()
|
let paraStyleKey = NSMutableParagraphStyle()
|
||||||
paraStyleKey.setParagraphStyle(NSParagraphStyle.default)
|
paraStyleKey.setParagraphStyle(NSParagraphStyle.default)
|
||||||
|
|
|
@ -12,8 +12,9 @@ public protocol CtlCandidateDelegate: AnyObject {
|
||||||
func candidatePairs(conv: Bool) -> [(String, String)]
|
func candidatePairs(conv: Bool) -> [(String, String)]
|
||||||
func candidatePairAt(_ index: Int) -> (String, String)
|
func candidatePairAt(_ index: Int) -> (String, String)
|
||||||
func candidatePairSelected(at index: Int)
|
func candidatePairSelected(at index: Int)
|
||||||
|
func candidates(_ sender: Any!) -> [Any]!
|
||||||
func buzz()
|
func buzz()
|
||||||
func kanjiConversionIfRequired(_ target: String) -> String
|
var selectionKeys: String { get }
|
||||||
}
|
}
|
||||||
|
|
||||||
public protocol CtlCandidateProtocol {
|
public protocol CtlCandidateProtocol {
|
||||||
|
|
|
@ -46,17 +46,17 @@ extension SessionCtl: KeyHandlerDelegate {
|
||||||
// MARK: - Candidate Controller Delegate
|
// MARK: - Candidate Controller Delegate
|
||||||
|
|
||||||
extension SessionCtl: CtlCandidateDelegate {
|
extension SessionCtl: CtlCandidateDelegate {
|
||||||
|
var selectionKeys: String { PrefMgr.shared.candidateKeys }
|
||||||
|
|
||||||
func buzz() {
|
func buzz() {
|
||||||
IMEApp.buzz()
|
IMEApp.buzz()
|
||||||
}
|
}
|
||||||
|
|
||||||
func kanjiConversionIfRequired(_ target: String) -> String {
|
|
||||||
ChineseConverter.kanjiConversionIfRequired(target)
|
|
||||||
}
|
|
||||||
|
|
||||||
func candidatePairs(conv: Bool = false) -> [(String, String)] {
|
func candidatePairs(conv: Bool = false) -> [(String, String)] {
|
||||||
if !state.isCandidateContainer { return [] }
|
if !state.isCandidateContainer || state.candidates.isEmpty { return [] }
|
||||||
if !conv { return state.candidates }
|
if !conv || PrefMgr.shared.cns11643Enabled || state.candidates[0].0.contains("_punctuation") {
|
||||||
|
return state.candidates
|
||||||
|
}
|
||||||
let convertedCandidates: [(String, String)] = state.candidates.map { theCandidatePair -> (String, String) in
|
let convertedCandidates: [(String, String)] = state.candidates.map { theCandidatePair -> (String, String) in
|
||||||
let theCandidate = theCandidatePair.1
|
let theCandidate = theCandidatePair.1
|
||||||
let theConverted = ChineseConverter.kanjiConversionIfRequired(theCandidate)
|
let theConverted = ChineseConverter.kanjiConversionIfRequired(theCandidate)
|
||||||
|
|
|
@ -142,7 +142,6 @@ extension SessionCtl {
|
||||||
Self.ctlCandidateCurrent.hint = NSLocalizedString("Hold ⇧ to choose associates.", comment: "")
|
Self.ctlCandidateCurrent.hint = NSLocalizedString("Hold ⇧ to choose associates.", comment: "")
|
||||||
}
|
}
|
||||||
|
|
||||||
Self.ctlCandidateCurrent.delegate = self
|
|
||||||
Self.ctlCandidateCurrent.showPageButtons = PrefMgr.shared.showPageButtonsInCandidateWindow
|
Self.ctlCandidateCurrent.showPageButtons = PrefMgr.shared.showPageButtonsInCandidateWindow
|
||||||
Self.ctlCandidateCurrent.useLangIdentifier = PrefMgr.shared.handleDefaultCandidateFontsByLangIdentifier
|
Self.ctlCandidateCurrent.useLangIdentifier = PrefMgr.shared.handleDefaultCandidateFontsByLangIdentifier
|
||||||
Self.ctlCandidateCurrent.locale = {
|
Self.ctlCandidateCurrent.locale = {
|
||||||
|
@ -156,7 +155,6 @@ extension SessionCtl {
|
||||||
default: return ""
|
default: return ""
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
Self.ctlCandidateCurrent.reloadData()
|
|
||||||
|
|
||||||
if #available(macOS 10.14, *) {
|
if #available(macOS 10.14, *) {
|
||||||
// Spotlight 視窗會擋住 IMK 選字窗,所以需要特殊處理。
|
// Spotlight 視窗會擋住 IMK 選字窗,所以需要特殊處理。
|
||||||
|
@ -167,6 +165,7 @@ extension SessionCtl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Self.ctlCandidateCurrent.delegate = self
|
||||||
Self.ctlCandidateCurrent.visible = true
|
Self.ctlCandidateCurrent.visible = true
|
||||||
|
|
||||||
if isVerticalTyping {
|
if isVerticalTyping {
|
||||||
|
|
Loading…
Reference in New Issue