Clang-format // Use Google Swift Format Style.
This commit is contained in:
parent
e1edd189ad
commit
856c5d02d7
|
@ -1,56 +0,0 @@
|
||||||
{
|
|
||||||
"fileScopedDeclarationPrivacy" : {
|
|
||||||
"accessLevel" : "private"
|
|
||||||
},
|
|
||||||
"indentation" : {
|
|
||||||
"spaces" : 2
|
|
||||||
},
|
|
||||||
"indentConditionalCompilationBlocks" : true,
|
|
||||||
"indentSwitchCaseLabels" : true,
|
|
||||||
"lineBreakAroundMultilineExpressionChainComponents" : false,
|
|
||||||
"lineBreakBeforeControlFlowKeywords" : false,
|
|
||||||
"lineBreakBeforeEachArgument" : false,
|
|
||||||
"lineBreakBeforeEachGenericRequirement" : false,
|
|
||||||
"lineLength" : 120,
|
|
||||||
"maximumBlankLines" : 1,
|
|
||||||
"prioritizeKeepingFunctionOutputTogether" : false,
|
|
||||||
"respectsExistingLineBreaks" : true,
|
|
||||||
"rules" : {
|
|
||||||
"AllPublicDeclarationsHaveDocumentation" : false,
|
|
||||||
"AlwaysUseLowerCamelCase" : true,
|
|
||||||
"AmbiguousTrailingClosureOverload" : true,
|
|
||||||
"BeginDocumentationCommentWithOneLineSummary" : false,
|
|
||||||
"DoNotUseSemicolons" : true,
|
|
||||||
"DontRepeatTypeInStaticProperties" : false,
|
|
||||||
"FileScopedDeclarationPrivacy" : true,
|
|
||||||
"FullyIndirectEnum" : true,
|
|
||||||
"GroupNumericLiterals" : true,
|
|
||||||
"IdentifiersMustBeASCII" : true,
|
|
||||||
"NeverForceUnwrap" : false,
|
|
||||||
"NeverUseForceTry" : false,
|
|
||||||
"NeverUseImplicitlyUnwrappedOptionals" : false,
|
|
||||||
"NoAccessLevelOnExtensionDeclaration" : true,
|
|
||||||
"NoBlockComments" : false,
|
|
||||||
"NoCasesWithOnlyFallthrough" : true,
|
|
||||||
"NoEmptyTrailingClosureParentheses" : true,
|
|
||||||
"NoLabelsInCasePatterns" : true,
|
|
||||||
"NoLeadingUnderscores" : false,
|
|
||||||
"NoParensAroundConditions" : true,
|
|
||||||
"NoVoidReturnOnFunctionSignature" : true,
|
|
||||||
"OneCasePerLine" : true,
|
|
||||||
"OneVariableDeclarationPerLine" : true,
|
|
||||||
"OnlyOneTrailingClosureArgument" : false,
|
|
||||||
"OrderedImports" : true,
|
|
||||||
"ReturnVoidInsteadOfEmptyTuple" : true,
|
|
||||||
"UseEarlyExits" : false,
|
|
||||||
"UseLetInEveryBoundCaseVariable" : false,
|
|
||||||
"UseShorthandTypeNames" : true,
|
|
||||||
"UseSingleLinePropertyGetter" : true,
|
|
||||||
"UseSynthesizedInitializer" : true,
|
|
||||||
"UseTripleSlashForDocumentationComments" : false,
|
|
||||||
"UseWhereClausesInForLoops" : false,
|
|
||||||
"ValidateDocumentationComments" : false
|
|
||||||
},
|
|
||||||
"tabWidth" : 8,
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
|
@ -0,0 +1,104 @@
|
||||||
|
# SwiftFormat config compliant with Google Swift Guideline
|
||||||
|
# https://google.github.io/swift/#control-flow-statements
|
||||||
|
|
||||||
|
# Specify version used in a project
|
||||||
|
|
||||||
|
--swiftversion 5.5
|
||||||
|
|
||||||
|
# Rules explicitly required by the guideline
|
||||||
|
|
||||||
|
--rules \
|
||||||
|
blankLinesAroundMark, \
|
||||||
|
blankLinesAtEndOfScope, \
|
||||||
|
blankLinesAtStartOfScope, \
|
||||||
|
blankLinesBetweenScopes, \
|
||||||
|
braces, \
|
||||||
|
consecutiveBlankLines, \
|
||||||
|
consecutiveSpaces, \
|
||||||
|
duplicateImports, \
|
||||||
|
elseOnSameLine, \
|
||||||
|
emptyBraces, \
|
||||||
|
enumNamespaces, \
|
||||||
|
extensionAccessControl, \
|
||||||
|
hoistPatternLet, \
|
||||||
|
indent, \
|
||||||
|
leadingDelimiters, \
|
||||||
|
linebreakAtEndOfFile, \
|
||||||
|
markTypes, \
|
||||||
|
organizeDeclarations, \
|
||||||
|
redundantInit, \
|
||||||
|
redundantParens, \
|
||||||
|
redundantPattern, \
|
||||||
|
redundantRawValues, \
|
||||||
|
redundantType, \
|
||||||
|
redundantVoidReturnType, \
|
||||||
|
semicolons, \
|
||||||
|
sortedImports, \
|
||||||
|
sortedSwitchCases, \
|
||||||
|
spaceAroundBraces, \
|
||||||
|
spaceAroundBrackets, \
|
||||||
|
spaceAroundComments, \
|
||||||
|
spaceAroundGenerics, \
|
||||||
|
spaceAroundOperators, \
|
||||||
|
spaceAroundParens, \
|
||||||
|
spaceInsideBraces, \
|
||||||
|
spaceInsideBrackets, \
|
||||||
|
spaceInsideComments, \
|
||||||
|
spaceInsideGenerics, \
|
||||||
|
spaceInsideParens, \
|
||||||
|
todos, \
|
||||||
|
trailingClosures, \
|
||||||
|
trailingCommas, \
|
||||||
|
trailingSpace, \
|
||||||
|
typeSugar, \
|
||||||
|
void, \
|
||||||
|
wrap, \
|
||||||
|
wrapArguments, \
|
||||||
|
wrapAttributes, \
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# Additional rules not mentioned in the guideline, but helping to keep the codebase clean
|
||||||
|
# Quoting the guideline:
|
||||||
|
# Common themes among the rules in this section are:
|
||||||
|
# avoid redundancy, avoid ambiguity, and prefer implicitness over explicitness
|
||||||
|
# unless being explicit improves readability and/or reduces ambiguity.
|
||||||
|
#
|
||||||
|
#
|
||||||
|
andOperator, \
|
||||||
|
isEmpty, \
|
||||||
|
redundantBackticks, \
|
||||||
|
redundantBreak, \
|
||||||
|
redundantExtensionACL, \
|
||||||
|
redundantGet, \
|
||||||
|
redundantLetError, \
|
||||||
|
redundantNilInit, \
|
||||||
|
redundantObjc, \
|
||||||
|
redundantReturn, \
|
||||||
|
redundantSelf, \
|
||||||
|
strongifiedSelf
|
||||||
|
|
||||||
|
|
||||||
|
# Options for basic rules
|
||||||
|
|
||||||
|
--extensionacl on-declarations
|
||||||
|
--funcattributes prev-line
|
||||||
|
--indent 2
|
||||||
|
--maxwidth 100
|
||||||
|
--typeattributes prev-line
|
||||||
|
--varattributes prev-line
|
||||||
|
--voidtype tuple
|
||||||
|
--wraparguments before-first
|
||||||
|
--wrapparameters before-first
|
||||||
|
--wrapcollections before-first
|
||||||
|
--wrapreturntype if-multiline
|
||||||
|
--wrapconditions after-first
|
||||||
|
|
||||||
|
# Option for additional rules
|
||||||
|
|
||||||
|
--self init-only
|
||||||
|
|
||||||
|
# Excluded folders
|
||||||
|
|
||||||
|
--exclude Pods,**/UNTESTED_TODO,vendor,fastlane
|
||||||
|
|
||||||
|
# https://github.com/NoemiRozpara/Google-SwiftFormat-Config
|
|
@ -10,8 +10,8 @@
|
||||||
|
|
||||||
import Cocoa
|
import Cocoa
|
||||||
|
|
||||||
extension String {
|
fileprivate extension String {
|
||||||
fileprivate mutating func regReplace(pattern: String, replaceWith: String = "") {
|
mutating func regReplace(pattern: String, replaceWith: String = "") {
|
||||||
// Ref: https://stackoverflow.com/a/40993403/4162914 && https://stackoverflow.com/a/71291137/4162914
|
// Ref: https://stackoverflow.com/a/40993403/4162914 && https://stackoverflow.com/a/71291137/4162914
|
||||||
do {
|
do {
|
||||||
let regex = try NSRegularExpression(
|
let regex = try NSRegularExpression(
|
||||||
|
|
|
@ -12,8 +12,8 @@ import Foundation
|
||||||
|
|
||||||
// MARK: - 前導工作
|
// MARK: - 前導工作
|
||||||
|
|
||||||
extension String {
|
fileprivate extension String {
|
||||||
fileprivate mutating func regReplace(pattern: String, replaceWith: String = "") {
|
mutating func regReplace(pattern: String, replaceWith: String = "") {
|
||||||
// Ref: https://stackoverflow.com/a/40993403/4162914 && https://stackoverflow.com/a/71291137/4162914
|
// Ref: https://stackoverflow.com/a/40993403/4162914 && https://stackoverflow.com/a/71291137/4162914
|
||||||
do {
|
do {
|
||||||
let regex = try NSRegularExpression(
|
let regex = try NSRegularExpression(
|
||||||
|
@ -29,21 +29,21 @@ extension String {
|
||||||
|
|
||||||
// MARK: - String charComponents Extension
|
// MARK: - String charComponents Extension
|
||||||
|
|
||||||
extension String {
|
public extension String {
|
||||||
public var charComponents: [String] { map { String($0) } }
|
var charComponents: [String] { map { String($0) } }
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Array where Element == String.Element {
|
public extension Array where Element == String.Element {
|
||||||
public var charComponents: [String] { map { String($0) } }
|
var charComponents: [String] { map { String($0) } }
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - StringView Ranges Extension (by Isaac Xen)
|
// MARK: - StringView Ranges Extension (by Isaac Xen)
|
||||||
|
|
||||||
extension String {
|
fileprivate extension String {
|
||||||
fileprivate func ranges(splitBy separator: Element) -> [Range<String.Index>] {
|
func ranges(splitBy separator: Element) -> [Range<String.Index>] {
|
||||||
var startIndex = startIndex
|
var startIndex = startIndex
|
||||||
return split(separator: separator).reduce(into: []) { ranges, substring in
|
return split(separator: separator).reduce(into: []) { ranges, substring in
|
||||||
_ = range(of: substring, range: startIndex..<endIndex).map { range in
|
_ = range(of: substring, range: startIndex ..< endIndex).map { range in
|
||||||
ranges.append(range)
|
ranges.append(range)
|
||||||
startIndex = range.upperBound
|
startIndex = range.upperBound
|
||||||
}
|
}
|
||||||
|
@ -54,8 +54,8 @@ extension String {
|
||||||
// MARK: - 引入小數點位數控制函式
|
// MARK: - 引入小數點位數控制函式
|
||||||
|
|
||||||
// Ref: https://stackoverflow.com/a/32581409/4162914
|
// Ref: https://stackoverflow.com/a/32581409/4162914
|
||||||
extension Double {
|
fileprivate extension Double {
|
||||||
fileprivate func rounded(toPlaces places: Int) -> Double {
|
func rounded(toPlaces places: Int) -> Double {
|
||||||
let divisor = pow(10.0, Double(places))
|
let divisor = pow(10.0, Double(places))
|
||||||
return (self * divisor).rounded() / divisor
|
return (self * divisor).rounded() / divisor
|
||||||
}
|
}
|
||||||
|
@ -173,15 +173,15 @@ func rawDictForPhrases(isCHS: Bool) -> [Unigram] {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
// 預處理格式
|
// 預處理格式
|
||||||
strRAW = strRAW.replacingOccurrences(of: " #MACOS", with: "") // 去掉 macOS 標記
|
strRAW = strRAW.replacingOccurrences(of: " #MACOS", with: "") // 去掉 macOS 標記
|
||||||
// CJKWhiteSpace (\x{3000}) to ASCII Space
|
// CJKWhiteSpace (\x{3000}) to ASCII Space
|
||||||
// NonBreakWhiteSpace (\x{A0}) to ASCII Space
|
// NonBreakWhiteSpace (\x{A0}) to ASCII Space
|
||||||
// Tab to ASCII Space
|
// Tab to ASCII Space
|
||||||
// 統整連續空格為一個 ASCII 空格
|
// 統整連續空格為一個 ASCII 空格
|
||||||
strRAW.regReplace(pattern: #"( +| +| +|\t+)+"#, replaceWith: " ")
|
strRAW.regReplace(pattern: #"( +| +| +|\t+)+"#, replaceWith: " ")
|
||||||
strRAW.regReplace(pattern: #"(^ | $)"#, replaceWith: "") // 去除行尾行首空格
|
strRAW.regReplace(pattern: #"(^ | $)"#, replaceWith: "") // 去除行尾行首空格
|
||||||
strRAW.regReplace(pattern: #"(\f+|\r+|\n+)+"#, replaceWith: "\n") // CR & Form Feed to LF, 且去除重複行
|
strRAW.regReplace(pattern: #"(\f+|\r+|\n+)+"#, replaceWith: "\n") // CR & Form Feed to LF, 且去除重複行
|
||||||
strRAW.regReplace(pattern: #"^(#.*|.*#WIN32.*)$"#, replaceWith: "") // 以#開頭的行都淨空+去掉所有 WIN32 特有的行
|
strRAW.regReplace(pattern: #"^(#.*|.*#WIN32.*)$"#, replaceWith: "") // 以#開頭的行都淨空+去掉所有 WIN32 特有的行
|
||||||
// 正式整理格式,現在就開始去重複:
|
// 正式整理格式,現在就開始去重複:
|
||||||
let arrData = Array(
|
let arrData = Array(
|
||||||
NSOrderedSet(array: strRAW.components(separatedBy: "\n")).array as! [String])
|
NSOrderedSet(array: strRAW.components(separatedBy: "\n")).array as! [String])
|
||||||
|
@ -202,25 +202,25 @@ func rawDictForPhrases(isCHS: Bool) -> [Unigram] {
|
||||||
}
|
}
|
||||||
// 然後直接乾脆就轉成 Unigram 吧。
|
// 然後直接乾脆就轉成 Unigram 吧。
|
||||||
let arrCells: [String] = varLineDataProcessed.components(separatedBy: "\t")
|
let arrCells: [String] = varLineDataProcessed.components(separatedBy: "\t")
|
||||||
count = 0 // 不需要再定義,因為之前已經有定義過了。
|
count = 0 // 不需要再定義,因為之前已經有定義過了。
|
||||||
var phone = ""
|
var phone = ""
|
||||||
var phrase = ""
|
var phrase = ""
|
||||||
var occurrence = 0
|
var occurrence = 0
|
||||||
for cell in arrCells {
|
for cell in arrCells {
|
||||||
count += 1
|
count += 1
|
||||||
switch count {
|
switch count {
|
||||||
case 1: phrase = cell
|
case 1: phrase = cell
|
||||||
case 3: phone = cell
|
case 3: phone = cell
|
||||||
case 2: occurrence = Int(cell) ?? 0
|
case 2: occurrence = Int(cell) ?? 0
|
||||||
default: break
|
default: break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if phrase != "" { // 廢掉空數據;之後無須再這樣處理。
|
if phrase != "" { // 廢掉空數據;之後無須再這樣處理。
|
||||||
arrUnigramRAW += [
|
arrUnigramRAW += [
|
||||||
Unigram(
|
Unigram(
|
||||||
key: phone, value: phrase, score: 0.0,
|
key: phone, value: phrase, score: 0.0,
|
||||||
count: occurrence
|
count: occurrence
|
||||||
)
|
),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -242,15 +242,15 @@ func rawDictForKanjis(isCHS: Bool) -> [Unigram] {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
// 預處理格式
|
// 預處理格式
|
||||||
strRAW = strRAW.replacingOccurrences(of: " #MACOS", with: "") // 去掉 macOS 標記
|
strRAW = strRAW.replacingOccurrences(of: " #MACOS", with: "") // 去掉 macOS 標記
|
||||||
// CJKWhiteSpace (\x{3000}) to ASCII Space
|
// CJKWhiteSpace (\x{3000}) to ASCII Space
|
||||||
// NonBreakWhiteSpace (\x{A0}) to ASCII Space
|
// NonBreakWhiteSpace (\x{A0}) to ASCII Space
|
||||||
// Tab to ASCII Space
|
// Tab to ASCII Space
|
||||||
// 統整連續空格為一個 ASCII 空格
|
// 統整連續空格為一個 ASCII 空格
|
||||||
strRAW.regReplace(pattern: #"( +| +| +|\t+)+"#, replaceWith: " ")
|
strRAW.regReplace(pattern: #"( +| +| +|\t+)+"#, replaceWith: " ")
|
||||||
strRAW.regReplace(pattern: #"(^ | $)"#, replaceWith: "") // 去除行尾行首空格
|
strRAW.regReplace(pattern: #"(^ | $)"#, replaceWith: "") // 去除行尾行首空格
|
||||||
strRAW.regReplace(pattern: #"(\f+|\r+|\n+)+"#, replaceWith: "\n") // CR & Form Feed to LF, 且去除重複行
|
strRAW.regReplace(pattern: #"(\f+|\r+|\n+)+"#, replaceWith: "\n") // CR & Form Feed to LF, 且去除重複行
|
||||||
strRAW.regReplace(pattern: #"^(#.*|.*#WIN32.*)$"#, replaceWith: "") // 以#開頭的行都淨空+去掉所有 WIN32 特有的行
|
strRAW.regReplace(pattern: #"^(#.*|.*#WIN32.*)$"#, replaceWith: "") // 以#開頭的行都淨空+去掉所有 WIN32 特有的行
|
||||||
// 正式整理格式,現在就開始去重複:
|
// 正式整理格式,現在就開始去重複:
|
||||||
let arrData = Array(
|
let arrData = Array(
|
||||||
NSOrderedSet(array: strRAW.components(separatedBy: "\n")).array as! [String])
|
NSOrderedSet(array: strRAW.components(separatedBy: "\n")).array as! [String])
|
||||||
|
@ -281,20 +281,20 @@ func rawDictForKanjis(isCHS: Bool) -> [Unigram] {
|
||||||
}
|
}
|
||||||
// 然後直接乾脆就轉成 Unigram 吧。
|
// 然後直接乾脆就轉成 Unigram 吧。
|
||||||
let arrCells: [String] = varLineDataProcessed.components(separatedBy: "\t")
|
let arrCells: [String] = varLineDataProcessed.components(separatedBy: "\t")
|
||||||
count = 0 // 不需要再定義,因為之前已經有定義過了。
|
count = 0 // 不需要再定義,因為之前已經有定義過了。
|
||||||
var phone = ""
|
var phone = ""
|
||||||
var phrase = ""
|
var phrase = ""
|
||||||
var occurrence = 0
|
var occurrence = 0
|
||||||
for cell in arrCells {
|
for cell in arrCells {
|
||||||
count += 1
|
count += 1
|
||||||
switch count {
|
switch count {
|
||||||
case 1: phrase = cell
|
case 1: phrase = cell
|
||||||
case 3: phone = cell
|
case 3: phone = cell
|
||||||
case 2: occurrence = Int(cell) ?? 0
|
case 2: occurrence = Int(cell) ?? 0
|
||||||
default: break
|
default: break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if phrase != "" { // 廢掉空數據;之後無須再這樣處理。
|
if phrase != "" { // 廢掉空數據;之後無須再這樣處理。
|
||||||
if !isReverseLookupDictionaryProcessed {
|
if !isReverseLookupDictionaryProcessed {
|
||||||
mapReverseLookup[phrase, default: []].append(cnvPhonabetToASCII(phone).data(using: .utf8)!)
|
mapReverseLookup[phrase, default: []].append(cnvPhonabetToASCII(phone).data(using: .utf8)!)
|
||||||
mapReverseLookupUnencrypted[phrase, default: []].append(phone)
|
mapReverseLookupUnencrypted[phrase, default: []].append(phone)
|
||||||
|
@ -303,7 +303,7 @@ func rawDictForKanjis(isCHS: Bool) -> [Unigram] {
|
||||||
Unigram(
|
Unigram(
|
||||||
key: phone, value: phrase, score: 0.0,
|
key: phone, value: phrase, score: 0.0,
|
||||||
count: occurrence
|
count: occurrence
|
||||||
)
|
),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -337,15 +337,15 @@ func rawDictForNonKanjis(isCHS: Bool) -> [Unigram] {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
// 預處理格式
|
// 預處理格式
|
||||||
strRAW = strRAW.replacingOccurrences(of: " #MACOS", with: "") // 去掉 macOS 標記
|
strRAW = strRAW.replacingOccurrences(of: " #MACOS", with: "") // 去掉 macOS 標記
|
||||||
// CJKWhiteSpace (\x{3000}) to ASCII Space
|
// CJKWhiteSpace (\x{3000}) to ASCII Space
|
||||||
// NonBreakWhiteSpace (\x{A0}) to ASCII Space
|
// NonBreakWhiteSpace (\x{A0}) to ASCII Space
|
||||||
// Tab to ASCII Space
|
// Tab to ASCII Space
|
||||||
// 統整連續空格為一個 ASCII 空格
|
// 統整連續空格為一個 ASCII 空格
|
||||||
strRAW.regReplace(pattern: #"( +| +| +|\t+)+"#, replaceWith: " ")
|
strRAW.regReplace(pattern: #"( +| +| +|\t+)+"#, replaceWith: " ")
|
||||||
strRAW.regReplace(pattern: #"(^ | $)"#, replaceWith: "") // 去除行尾行首空格
|
strRAW.regReplace(pattern: #"(^ | $)"#, replaceWith: "") // 去除行尾行首空格
|
||||||
strRAW.regReplace(pattern: #"(\f+|\r+|\n+)+"#, replaceWith: "\n") // CR & Form Feed to LF, 且去除重複行
|
strRAW.regReplace(pattern: #"(\f+|\r+|\n+)+"#, replaceWith: "\n") // CR & Form Feed to LF, 且去除重複行
|
||||||
strRAW.regReplace(pattern: #"^(#.*|.*#WIN32.*)$"#, replaceWith: "") // 以#開頭的行都淨空+去掉所有 WIN32 特有的行
|
strRAW.regReplace(pattern: #"^(#.*|.*#WIN32.*)$"#, replaceWith: "") // 以#開頭的行都淨空+去掉所有 WIN32 特有的行
|
||||||
// 正式整理格式,現在就開始去重複:
|
// 正式整理格式,現在就開始去重複:
|
||||||
let arrData = Array(
|
let arrData = Array(
|
||||||
NSOrderedSet(array: strRAW.components(separatedBy: "\n")).array as! [String])
|
NSOrderedSet(array: strRAW.components(separatedBy: "\n")).array as! [String])
|
||||||
|
@ -354,7 +354,7 @@ func rawDictForNonKanjis(isCHS: Bool) -> [Unigram] {
|
||||||
varLineData = lineData
|
varLineData = lineData
|
||||||
// 先完成某兩步需要分行處理才能完成的格式整理。
|
// 先完成某兩步需要分行處理才能完成的格式整理。
|
||||||
varLineData = varLineData.components(separatedBy: " ").prefix(3).joined(
|
varLineData = varLineData.components(separatedBy: " ").prefix(3).joined(
|
||||||
separator: "\t") // 提取前三欄的內容。
|
separator: "\t") // 提取前三欄的內容。
|
||||||
let arrLineData = varLineData.components(separatedBy: " ")
|
let arrLineData = varLineData.components(separatedBy: " ")
|
||||||
var varLineDataProcessed = ""
|
var varLineDataProcessed = ""
|
||||||
var count = 0
|
var count = 0
|
||||||
|
@ -370,26 +370,26 @@ func rawDictForNonKanjis(isCHS: Bool) -> [Unigram] {
|
||||||
}
|
}
|
||||||
// 然後直接乾脆就轉成 Unigram 吧。
|
// 然後直接乾脆就轉成 Unigram 吧。
|
||||||
let arrCells: [String] = varLineDataProcessed.components(separatedBy: "\t")
|
let arrCells: [String] = varLineDataProcessed.components(separatedBy: "\t")
|
||||||
count = 0 // 不需要再定義,因為之前已經有定義過了。
|
count = 0 // 不需要再定義,因為之前已經有定義過了。
|
||||||
var phone = ""
|
var phone = ""
|
||||||
var phrase = ""
|
var phrase = ""
|
||||||
var occurrence = 0
|
var occurrence = 0
|
||||||
for cell in arrCells {
|
for cell in arrCells {
|
||||||
count += 1
|
count += 1
|
||||||
switch count {
|
switch count {
|
||||||
case 1: phrase = cell
|
case 1: phrase = cell
|
||||||
case 3: phone = cell
|
case 3: phone = cell
|
||||||
case 2: occurrence = Int(cell) ?? 0
|
case 2: occurrence = Int(cell) ?? 0
|
||||||
default: break
|
default: break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if phrase != "" { // 廢掉空數據;之後無須再這樣處理。
|
if phrase != "" { // 廢掉空數據;之後無須再這樣處理。
|
||||||
exceptedChars.insert(phrase)
|
exceptedChars.insert(phrase)
|
||||||
arrUnigramRAW += [
|
arrUnigramRAW += [
|
||||||
Unigram(
|
Unigram(
|
||||||
key: phone, value: phrase, score: 0.0,
|
key: phone, value: phrase, score: 0.0,
|
||||||
count: occurrence
|
count: occurrence
|
||||||
)
|
),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -415,24 +415,24 @@ func weightAndSort(_ arrStructUncalculated: [Unigram], isCHS: Bool) -> [Unigram]
|
||||||
for unigram in arrStructUncalculated {
|
for unigram in arrStructUncalculated {
|
||||||
var weight: Double = 0
|
var weight: Double = 0
|
||||||
switch unigram.count {
|
switch unigram.count {
|
||||||
case -2: // 拗音假名
|
case -2: // 拗音假名
|
||||||
weight = -13
|
weight = -13
|
||||||
case -1: // 單個假名
|
case -1: // 單個假名
|
||||||
weight = -13
|
weight = -13
|
||||||
case 0: // 墊底低頻漢字與詞語
|
case 0: // 墊底低頻漢字與詞語
|
||||||
weight = log10(
|
weight = log10(
|
||||||
fscale ** (Double(unigram.value.count) / 3.0 - 1.0) * 0.25 / norm)
|
fscale ** (Double(unigram.value.count) / 3.0 - 1.0) * 0.25 / norm)
|
||||||
default:
|
default:
|
||||||
weight = log10(
|
weight = log10(
|
||||||
fscale ** (Double(unigram.value.count) / 3.0 - 1.0)
|
fscale ** (Double(unigram.value.count) / 3.0 - 1.0)
|
||||||
* Double(unigram.count) / norm) // Credit: MJHsieh.
|
* Double(unigram.count) / norm) // Credit: MJHsieh.
|
||||||
}
|
}
|
||||||
let weightRounded: Double = weight.rounded(toPlaces: 3) // 為了節省生成的檔案體積,僅保留小數點後三位。
|
let weightRounded: Double = weight.rounded(toPlaces: 3) // 為了節省生成的檔案體積,僅保留小數點後三位。
|
||||||
arrStructCalculated += [
|
arrStructCalculated += [
|
||||||
Unigram(
|
Unigram(
|
||||||
key: unigram.key, value: unigram.value, score: weightRounded,
|
key: unigram.key, value: unigram.value, score: weightRounded,
|
||||||
count: unigram.count
|
count: unigram.count
|
||||||
)
|
),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
NSLog(" - \(i18n): 成功計算權重。")
|
NSLog(" - \(i18n): 成功計算權重。")
|
||||||
|
@ -763,7 +763,7 @@ func healthCheck(_ data: [Unigram]) -> String {
|
||||||
|
|
||||||
let separator: String = {
|
let separator: String = {
|
||||||
var result = ""
|
var result = ""
|
||||||
for _ in 0..<72 { result += "-" }
|
for _ in 0 ..< 72 { result += "-" }
|
||||||
return result
|
return result
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -784,7 +784,7 @@ func healthCheck(_ data: [Unigram]) -> String {
|
||||||
printl("\n其中有:")
|
printl("\n其中有:")
|
||||||
|
|
||||||
var insufficientsMap = [Int: [(String, String, Double, [Unigram], Double)]]()
|
var insufficientsMap = [Int: [(String, String, Double, [Unigram], Double)]]()
|
||||||
for x in 2...10 {
|
for x in 2 ... 10 {
|
||||||
insufficientsMap[x] = insufficients.filter { $0.0.split(separator: "-").count == x }
|
insufficientsMap[x] = insufficients.filter { $0.0.split(separator: "-").count == x }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -53,7 +53,7 @@ class AppDelegate: NSWindowController, NSApplicationDelegate {
|
||||||
|
|
||||||
var allRegisteredInstancesOfThisInputMethod: [TISInputSource] {
|
var allRegisteredInstancesOfThisInputMethod: [TISInputSource] {
|
||||||
guard let components = Bundle(url: imeURLInstalled)?.infoDictionary?["ComponentInputModeDict"] as? [String: Any],
|
guard let components = Bundle(url: imeURLInstalled)?.infoDictionary?["ComponentInputModeDict"] as? [String: Any],
|
||||||
let tsInputModeListKey = components["tsInputModeListKey"] as? [String: Any]
|
let tsInputModeListKey = components["tsInputModeListKey"] as? [String: Any]
|
||||||
else {
|
else {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
@ -102,7 +102,7 @@ class AppDelegate: NSWindowController, NSApplicationDelegate {
|
||||||
let currentVersion = currentBundle?.infoDictionary?[kCFBundleVersionKey as String] as? String
|
let currentVersion = currentBundle?.infoDictionary?[kCFBundleVersionKey as String] as? String
|
||||||
currentVersionNumber = (currentVersion as NSString?)?.integerValue ?? 0
|
currentVersionNumber = (currentVersion as NSString?)?.integerValue ?? 0
|
||||||
if shortVersion != nil, let currentVersion = currentVersion,
|
if shortVersion != nil, let currentVersion = currentVersion,
|
||||||
currentVersion.compare(installingVersion, options: .numeric) == .orderedAscending
|
currentVersion.compare(installingVersion, options: .numeric) == .orderedAscending
|
||||||
{
|
{
|
||||||
// Upgrading confirmed.
|
// Upgrading confirmed.
|
||||||
installButton.title = NSLocalizedString("Upgrade", comment: "")
|
installButton.title = NSLocalizedString("Upgrade", comment: "")
|
||||||
|
|
|
@ -24,7 +24,7 @@ extension AppDelegate {
|
||||||
|
|
||||||
let shouldWaitForTranslocationRemoval =
|
let shouldWaitForTranslocationRemoval =
|
||||||
Reloc.isAppBundleTranslocated(atPath: kTargetPartialPath)
|
Reloc.isAppBundleTranslocated(atPath: kTargetPartialPath)
|
||||||
&& window.responds(to: #selector(NSWindow.beginSheet(_:completionHandler:)))
|
&& window.responds(to: #selector(NSWindow.beginSheet(_:completionHandler:)))
|
||||||
|
|
||||||
// 將既存輸入法扔到垃圾桶內
|
// 將既存輸入法扔到垃圾桶內
|
||||||
do {
|
do {
|
||||||
|
@ -116,7 +116,7 @@ extension AppDelegate {
|
||||||
_ = try? shell("/usr/bin/xattr -drs com.apple.quarantine \(kTargetPartialPath)")
|
_ = try? shell("/usr/bin/xattr -drs com.apple.quarantine \(kTargetPartialPath)")
|
||||||
|
|
||||||
guard let theBundle = Bundle(url: imeURLInstalled),
|
guard let theBundle = Bundle(url: imeURLInstalled),
|
||||||
let imeIdentifier = theBundle.bundleIdentifier
|
let imeIdentifier = theBundle.bundleIdentifier
|
||||||
else {
|
else {
|
||||||
endAppWithDelay()
|
endAppWithDelay()
|
||||||
return
|
return
|
||||||
|
|
15
Makefile
15
Makefile
|
@ -20,23 +20,14 @@ debug:
|
||||||
DSTROOT = /Library/Input Methods
|
DSTROOT = /Library/Input Methods
|
||||||
VC_APP_ROOT = $(DSTROOT)/vChewing.app
|
VC_APP_ROOT = $(DSTROOT)/vChewing.app
|
||||||
|
|
||||||
.PHONY: clang-format lint batchfix format clang-format-swift clang-format-cpp
|
.PHONY: lint format
|
||||||
|
|
||||||
format: batchfix clang-format lint
|
format:
|
||||||
|
@swiftformat --swiftversion 5.5 --indent 2 ./
|
||||||
clang-format:
|
|
||||||
@git ls-files --exclude-standard | grep -E '\.swift$$' | xargs swift-format format --in-place --configuration ./.clang-format-swift.json --parallel
|
|
||||||
@git ls-files --exclude-standard | grep -E '\.swift$$' | xargs swift-format lint --configuration ./.clang-format-swift.json --parallel
|
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
@git ls-files --exclude-standard | grep -E '\.swift$$' | xargs swift-format lint --configuration ./.clang-format-swift.json --parallel
|
|
||||||
|
|
||||||
batchfix:
|
|
||||||
@git ls-files --exclude-standard | grep -E '\.swift$$' | swiftlint --fix --autocorrect
|
@git ls-files --exclude-standard | grep -E '\.swift$$' | swiftlint --fix --autocorrect
|
||||||
|
|
||||||
advanced-lint:
|
|
||||||
@swiftformat --swiftversion 5.5 --indent 2 ./
|
|
||||||
|
|
||||||
.PHONY: permission-check install-debug install-release
|
.PHONY: permission-check install-debug install-release
|
||||||
|
|
||||||
permission-check:
|
permission-check:
|
||||||
|
|
|
@ -4,19 +4,19 @@ import PackageDescription
|
||||||
let package = Package(
|
let package = Package(
|
||||||
name: "FolderMonitor",
|
name: "FolderMonitor",
|
||||||
platforms: [
|
platforms: [
|
||||||
.macOS(.v10_11)
|
.macOS(.v10_11),
|
||||||
],
|
],
|
||||||
products: [
|
products: [
|
||||||
.library(
|
.library(
|
||||||
name: "FolderMonitor",
|
name: "FolderMonitor",
|
||||||
targets: ["FolderMonitor"]
|
targets: ["FolderMonitor"]
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
dependencies: [],
|
dependencies: [],
|
||||||
targets: [
|
targets: [
|
||||||
.target(
|
.target(
|
||||||
name: "FolderMonitor",
|
name: "FolderMonitor",
|
||||||
dependencies: []
|
dependencies: []
|
||||||
)
|
),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
|
@ -4,19 +4,19 @@ import PackageDescription
|
||||||
let package = Package(
|
let package = Package(
|
||||||
name: "NSAttributedTextView",
|
name: "NSAttributedTextView",
|
||||||
platforms: [
|
platforms: [
|
||||||
.macOS(.v10_11)
|
.macOS(.v10_11),
|
||||||
],
|
],
|
||||||
products: [
|
products: [
|
||||||
.library(
|
.library(
|
||||||
name: "NSAttributedTextView",
|
name: "NSAttributedTextView",
|
||||||
targets: ["NSAttributedTextView"]
|
targets: ["NSAttributedTextView"]
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
dependencies: [],
|
dependencies: [],
|
||||||
targets: [
|
targets: [
|
||||||
.target(
|
.target(
|
||||||
name: "NSAttributedTextView",
|
name: "NSAttributedTextView",
|
||||||
dependencies: []
|
dependencies: []
|
||||||
)
|
),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
|
@ -103,8 +103,8 @@ public class NSAttributedTextView: NSView {
|
||||||
@discardableResult public func shrinkFrame() -> NSRect {
|
@discardableResult public func shrinkFrame() -> NSRect {
|
||||||
let attrString: NSAttributedString = {
|
let attrString: NSAttributedString = {
|
||||||
switch direction {
|
switch direction {
|
||||||
case .horizontal: return attributedStringValue()
|
case .horizontal: return attributedStringValue()
|
||||||
default: return attributedStringValue(areaCalculation: true)
|
default: return attributedStringValue(areaCalculation: true)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
var rect = attrString.boundingRect(
|
var rect = attrString.boundingRect(
|
||||||
|
@ -130,14 +130,14 @@ public class NSAttributedTextView: NSView {
|
||||||
let path = CGPath(rect: rect, transform: nil)
|
let path = CGPath(rect: rect, transform: nil)
|
||||||
let theCTFrameProgression: CTFrameProgression = {
|
let theCTFrameProgression: CTFrameProgression = {
|
||||||
switch direction {
|
switch direction {
|
||||||
case .horizontal: return CTFrameProgression.topToBottom
|
case .horizontal: return CTFrameProgression.topToBottom
|
||||||
case .vertical: return CTFrameProgression.rightToLeft
|
case .vertical: return CTFrameProgression.rightToLeft
|
||||||
case .verticalReversed: return CTFrameProgression.leftToRight
|
case .verticalReversed: return CTFrameProgression.leftToRight
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
let frameAttrs: CFDictionary =
|
let frameAttrs: CFDictionary =
|
||||||
[
|
[
|
||||||
kCTFrameProgressionAttributeName: theCTFrameProgression.rawValue
|
kCTFrameProgressionAttributeName: theCTFrameProgression.rawValue,
|
||||||
] as CFDictionary
|
] as CFDictionary
|
||||||
let newFrame = CTFramesetterCreateFrame(setter, CFRangeMake(0, 0), path, frameAttrs)
|
let newFrame = CTFramesetterCreateFrame(setter, CFRangeMake(0, 0), path, frameAttrs)
|
||||||
ctFrame = newFrame
|
ctFrame = newFrame
|
||||||
|
|
|
@ -4,19 +4,19 @@ import PackageDescription
|
||||||
let package = Package(
|
let package = Package(
|
||||||
name: "BookmarkManager",
|
name: "BookmarkManager",
|
||||||
platforms: [
|
platforms: [
|
||||||
.macOS(.v10_11)
|
.macOS(.v10_11),
|
||||||
],
|
],
|
||||||
products: [
|
products: [
|
||||||
.library(
|
.library(
|
||||||
name: "BookmarkManager",
|
name: "BookmarkManager",
|
||||||
targets: ["BookmarkManager"]
|
targets: ["BookmarkManager"]
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
dependencies: [],
|
dependencies: [],
|
||||||
targets: [
|
targets: [
|
||||||
.target(
|
.target(
|
||||||
name: "BookmarkManager",
|
name: "BookmarkManager",
|
||||||
dependencies: []
|
dependencies: []
|
||||||
)
|
),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
|
@ -8,7 +8,7 @@ public class BookmarkManager {
|
||||||
// Save bookmark for URL. Use this inside the NSOpenPanel `begin` closure
|
// Save bookmark for URL. Use this inside the NSOpenPanel `begin` closure
|
||||||
public func saveBookmark(for url: URL) {
|
public func saveBookmark(for url: URL) {
|
||||||
guard let bookmarkDic = getBookmarkData(url: url),
|
guard let bookmarkDic = getBookmarkData(url: url),
|
||||||
let bookmarkURL = getBookmarkURL()
|
let bookmarkURL = getBookmarkURL()
|
||||||
else {
|
else {
|
||||||
NSLog("Error getting data or bookmarkURL")
|
NSLog("Error getting data or bookmarkURL")
|
||||||
return
|
return
|
||||||
|
|
|
@ -4,19 +4,19 @@ import PackageDescription
|
||||||
let package = Package(
|
let package = Package(
|
||||||
name: "ShiftKeyUpChecker",
|
name: "ShiftKeyUpChecker",
|
||||||
platforms: [
|
platforms: [
|
||||||
.macOS(.v10_11)
|
.macOS(.v10_11),
|
||||||
],
|
],
|
||||||
products: [
|
products: [
|
||||||
.library(
|
.library(
|
||||||
name: "ShiftKeyUpChecker",
|
name: "ShiftKeyUpChecker",
|
||||||
targets: ["ShiftKeyUpChecker"]
|
targets: ["ShiftKeyUpChecker"]
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
dependencies: [],
|
dependencies: [],
|
||||||
targets: [
|
targets: [
|
||||||
.target(
|
.target(
|
||||||
name: "ShiftKeyUpChecker",
|
name: "ShiftKeyUpChecker",
|
||||||
dependencies: []
|
dependencies: []
|
||||||
)
|
),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
|
@ -5,8 +5,8 @@
|
||||||
import Carbon
|
import Carbon
|
||||||
import Cocoa
|
import Cocoa
|
||||||
|
|
||||||
extension Date {
|
private extension Date {
|
||||||
fileprivate static func - (lhs: Date, rhs: Date) -> TimeInterval {
|
static func - (lhs: Date, rhs: Date) -> TimeInterval {
|
||||||
lhs.timeIntervalSinceReferenceDate - rhs.timeIntervalSinceReferenceDate
|
lhs.timeIntervalSinceReferenceDate - rhs.timeIntervalSinceReferenceDate
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,8 +40,8 @@ public struct ShiftKeyUpChecker {
|
||||||
|
|
||||||
private mutating func checkModifierKeyUp(event: NSEvent) -> Bool {
|
private mutating func checkModifierKeyUp(event: NSEvent) -> Bool {
|
||||||
if event.type == .flagsChanged,
|
if event.type == .flagsChanged,
|
||||||
event.modifierFlags.intersection(.deviceIndependentFlagsMask) == .init(rawValue: 0),
|
event.modifierFlags.intersection(.deviceIndependentFlagsMask) == .init(rawValue: 0),
|
||||||
Date() - lastTime <= delayInterval, shiftIsBeingPressed
|
Date() - lastTime <= delayInterval, shiftIsBeingPressed
|
||||||
{
|
{
|
||||||
// modifier keyup event
|
// modifier keyup event
|
||||||
lastTime = Date(timeInterval: -3600 * 4, since: Date())
|
lastTime = Date(timeInterval: -3600 * 4, since: Date())
|
||||||
|
@ -56,8 +56,8 @@ public struct ShiftKeyUpChecker {
|
||||||
print("isLeftShift: \(isLeftShift), isRightShift: \(isRightShift)")
|
print("isLeftShift: \(isLeftShift), isRightShift: \(isRightShift)")
|
||||||
let isKeyDown =
|
let isKeyDown =
|
||||||
event.type == .flagsChanged
|
event.type == .flagsChanged
|
||||||
&& checkModifier.contains(event.modifierFlags.intersection(.deviceIndependentFlagsMask))
|
&& checkModifier.contains(event.modifierFlags.intersection(.deviceIndependentFlagsMask))
|
||||||
&& checkKeyCode.contains(event.keyCode)
|
&& checkKeyCode.contains(event.keyCode)
|
||||||
if isKeyDown {
|
if isKeyDown {
|
||||||
// modifier keydown event
|
// modifier keydown event
|
||||||
lastTime = Date()
|
lastTime = Date()
|
||||||
|
|
|
@ -4,23 +4,23 @@ import PackageDescription
|
||||||
let package = Package(
|
let package = Package(
|
||||||
name: "LineReader",
|
name: "LineReader",
|
||||||
platforms: [
|
platforms: [
|
||||||
.macOS(.v10_11)
|
.macOS(.v10_11),
|
||||||
],
|
],
|
||||||
products: [
|
products: [
|
||||||
.library(
|
.library(
|
||||||
name: "LineReader",
|
name: "LineReader",
|
||||||
targets: ["LineReader"]
|
targets: ["LineReader"]
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
.package(path: "../vChewing_SwiftExtension")
|
.package(path: "../vChewing_SwiftExtension"),
|
||||||
],
|
],
|
||||||
targets: [
|
targets: [
|
||||||
.target(
|
.target(
|
||||||
name: "LineReader",
|
name: "LineReader",
|
||||||
dependencies: [
|
dependencies: [
|
||||||
.product(name: "SwiftExtension", package: "vChewing_SwiftExtension")
|
.product(name: "SwiftExtension", package: "vChewing_SwiftExtension"),
|
||||||
]
|
]
|
||||||
)
|
),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
|
@ -31,9 +31,9 @@ public class LineReader {
|
||||||
// get a data from the buffer up to the next delimiter
|
// get a data from the buffer up to the next delimiter
|
||||||
if let range = buffer.range(of: delimData) {
|
if let range = buffer.range(of: delimData) {
|
||||||
// convert data to a string
|
// convert data to a string
|
||||||
let line = String(data: buffer.subdata(in: 0..<range.lowerBound), encoding: encoding)!
|
let line = String(data: buffer.subdata(in: 0 ..< range.lowerBound), encoding: encoding)!
|
||||||
// remove that data from the buffer
|
// remove that data from the buffer
|
||||||
buffer.removeSubrange(0..<range.upperBound)
|
buffer.removeSubrange(0 ..< range.upperBound)
|
||||||
return line.trimmingCharacters(in: .newlines)
|
return line.trimmingCharacters(in: .newlines)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,10 +13,10 @@ let package = Package(
|
||||||
.library(
|
.library(
|
||||||
name: "SwiftUIBackports",
|
name: "SwiftUIBackports",
|
||||||
targets: ["SwiftUIBackports"]
|
targets: ["SwiftUIBackports"]
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
targets: [
|
targets: [
|
||||||
.target(name: "SwiftUIBackports")
|
.target(name: "SwiftUIBackports"),
|
||||||
],
|
],
|
||||||
swiftLanguageVersions: [.v5]
|
swiftLanguageVersions: [.v5]
|
||||||
)
|
)
|
||||||
|
|
|
@ -42,21 +42,21 @@ public struct Backport<Wrapped> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(macOS 10.15, *)
|
@available(macOS 10.15, *)
|
||||||
extension View {
|
public extension View {
|
||||||
/// Wraps a SwiftUI `View` that can be extended to provide backport functionality.
|
/// Wraps a SwiftUI `View` that can be extended to provide backport functionality.
|
||||||
public var backport: Backport<Self> { .init(self) }
|
var backport: Backport<Self> { .init(self) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(macOS 10.15, *)
|
@available(macOS 10.15, *)
|
||||||
extension NSObjectProtocol {
|
public extension NSObjectProtocol {
|
||||||
/// Wraps an `NSObject` that can be extended to provide backport functionality.
|
/// Wraps an `NSObject` that can be extended to provide backport functionality.
|
||||||
public var backport: Backport<Self> { .init(self) }
|
var backport: Backport<Self> { .init(self) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(macOS 10.15, *)
|
@available(macOS 10.15, *)
|
||||||
extension AnyTransition {
|
public extension AnyTransition {
|
||||||
/// Wraps an `AnyTransition` that can be extended to provide backport functionality.
|
/// Wraps an `AnyTransition` that can be extended to provide backport functionality.
|
||||||
public static var backport: Backport<AnyTransition> {
|
static var backport: Backport<AnyTransition> {
|
||||||
Backport(.identity)
|
Backport(.identity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import SwiftUI
|
||||||
@available(macOS 10.15, *)
|
@available(macOS 10.15, *)
|
||||||
/// A geometry reader that automatically sizes its height to 'fit' its content.
|
/// A geometry reader that automatically sizes its height to 'fit' its content.
|
||||||
public struct FittingGeometryReader<Content>: View where Content: View {
|
public struct FittingGeometryReader<Content>: View where Content: View {
|
||||||
@State private var height: CGFloat = 10 // must be non-zero
|
@State private var height: CGFloat = 10 // must be non-zero
|
||||||
private var content: (GeometryProxy) -> Content
|
private var content: (GeometryProxy) -> Content
|
||||||
|
|
||||||
@available(macOS 10.15, *)
|
@available(macOS 10.15, *)
|
||||||
|
|
|
@ -5,12 +5,12 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
@available(macOS 10.15, *)
|
@available(macOS 10.15, *)
|
||||||
extension EnvironmentValues {
|
private extension EnvironmentValues {
|
||||||
fileprivate func containsValue(forKey key: String) -> Bool {
|
func containsValue(forKey key: String) -> Bool {
|
||||||
value(forKey: key) != nil
|
value(forKey: key) != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func value<T>(forKey key: String, from mirror: Mirror, as _: T.Type) -> T? {
|
func value<T>(forKey key: String, from mirror: Mirror, as _: T.Type) -> T? {
|
||||||
// Found a match
|
// Found a match
|
||||||
if let value = mirror.descendant("value", "some") {
|
if let value = mirror.descendant("value", "some") {
|
||||||
if let typedValue = value as? T {
|
if let typedValue = value as? T {
|
||||||
|
@ -32,7 +32,7 @@ extension EnvironmentValues {
|
||||||
|
|
||||||
/// Extracts a value from the environment by the name of its associated EnvironmentKey.
|
/// Extracts a value from the environment by the name of its associated EnvironmentKey.
|
||||||
/// Can be used to grab private environment values such as foregroundColor ("ForegroundColorKey").
|
/// Can be used to grab private environment values such as foregroundColor ("ForegroundColorKey").
|
||||||
fileprivate func value<T>(forKey key: String, as _: T.Type) -> T? {
|
func value<T>(forKey key: String, as _: T.Type) -> T? {
|
||||||
if let mirror = value(forKey: key) as? Mirror {
|
if let mirror = value(forKey: key) as? Mirror {
|
||||||
return value(forKey: key, from: mirror, as: T.self)
|
return value(forKey: key, from: mirror, as: T.self)
|
||||||
} else if let value = value(forKey: key) as? T {
|
} else if let value = value(forKey: key) as? T {
|
||||||
|
@ -42,7 +42,7 @@ extension EnvironmentValues {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func value(forKey key: String) -> Any? {
|
func value(forKey key: String) -> Any? {
|
||||||
func keyFromTypeName(typeName: String) -> String? {
|
func keyFromTypeName(typeName: String) -> String? {
|
||||||
let expectedPrefix = "TypedElement<EnvironmentPropertyKey<"
|
let expectedPrefix = "TypedElement<EnvironmentPropertyKey<"
|
||||||
guard typeName.hasPrefix(expectedPrefix) else {
|
guard typeName.hasPrefix(expectedPrefix) else {
|
||||||
|
@ -74,7 +74,7 @@ extension EnvironmentValues {
|
||||||
// Environment values are stored in a doubly linked list. The "before" and "after" keys point
|
// Environment values are stored in a doubly linked list. The "before" and "after" keys point
|
||||||
// to the next environment member.
|
// to the next environment member.
|
||||||
if let linkedListMirror = mirror.superclassMirror,
|
if let linkedListMirror = mirror.superclassMirror,
|
||||||
let nextNode = linkedListMirror.descendant("after", "some")
|
let nextNode = linkedListMirror.descendant("after", "some")
|
||||||
{
|
{
|
||||||
return extract(startingAt: nextNode)
|
return extract(startingAt: nextNode)
|
||||||
}
|
}
|
||||||
|
|
|
@ -123,7 +123,7 @@ import SwiftUI
|
||||||
|
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
extension InspectionView {
|
extension InspectionView {
|
||||||
fileprivate struct Representable: UIViewRepresentable {
|
struct Representable: UIViewRepresentable {
|
||||||
let parent: InspectionView
|
let parent: InspectionView
|
||||||
|
|
||||||
func makeUIView(context _: Context) -> UIView { .init() }
|
func makeUIView(context _: Context) -> UIView { .init() }
|
||||||
|
@ -149,7 +149,7 @@ import SwiftUI
|
||||||
#elseif os(macOS)
|
#elseif os(macOS)
|
||||||
@available(macOS 10.15, *)
|
@available(macOS 10.15, *)
|
||||||
extension InspectionView {
|
extension InspectionView {
|
||||||
fileprivate struct Representable: NSViewRepresentable {
|
struct Representable: NSViewRepresentable {
|
||||||
let parent: InspectionView
|
let parent: InspectionView
|
||||||
|
|
||||||
func makeNSView(context _: Context) -> NSView {
|
func makeNSView(context _: Context) -> NSView {
|
||||||
|
|
|
@ -5,8 +5,8 @@
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
extension UIView {
|
public extension UIView {
|
||||||
public var parentController: UIViewController? {
|
var parentController: UIViewController? {
|
||||||
var responder: UIResponder? = self
|
var responder: UIResponder? = self
|
||||||
|
|
||||||
while !(responder is UIViewController), superview != nil {
|
while !(responder is UIViewController), superview != nil {
|
||||||
|
@ -24,8 +24,8 @@
|
||||||
import AppKit
|
import AppKit
|
||||||
|
|
||||||
@available(macOS 10.15, *)
|
@available(macOS 10.15, *)
|
||||||
extension NSView {
|
public extension NSView {
|
||||||
public var parentController: NSViewController? {
|
var parentController: NSViewController? {
|
||||||
var responder: NSResponder? = self
|
var responder: NSResponder? = self
|
||||||
|
|
||||||
while !(responder is NSViewController), superview != nil {
|
while !(responder is NSViewController), superview != nil {
|
||||||
|
|
|
@ -29,12 +29,12 @@ import SwiftUI
|
||||||
guard let viewClassNameUtf8 = (viewSubclassName as NSString).utf8String else { return }
|
guard let viewClassNameUtf8 = (viewSubclassName as NSString).utf8String else { return }
|
||||||
guard let viewSubclass = objc_allocateClassPair(viewClass, viewClassNameUtf8, 0) else { return }
|
guard let viewSubclass = objc_allocateClassPair(viewClass, viewClassNameUtf8, 0) else { return }
|
||||||
|
|
||||||
if let method = class_getInstanceMethod(UIView.self, #selector(getter:UIView.safeAreaInsets)) {
|
if let method = class_getInstanceMethod(UIView.self, #selector(getter: UIView.safeAreaInsets)) {
|
||||||
let safeAreaInsets: @convention(block) (AnyObject) -> UIEdgeInsets = { _ in
|
let safeAreaInsets: @convention(block) (AnyObject) -> UIEdgeInsets = { _ in
|
||||||
.zero
|
.zero
|
||||||
}
|
}
|
||||||
class_addMethod(
|
class_addMethod(
|
||||||
viewSubclass, #selector(getter:UIView.safeAreaInsets), imp_implementationWithBlock(safeAreaInsets),
|
viewSubclass, #selector(getter: UIView.safeAreaInsets), imp_implementationWithBlock(safeAreaInsets),
|
||||||
method_getTypeEncoding(method)
|
method_getTypeEncoding(method)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ extension Backport where Wrapped == Any {
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(macOS 10.15, *)
|
@available(macOS 10.15, *)
|
||||||
extension Backport.AppStorage {
|
public extension Backport.AppStorage {
|
||||||
/// Creates a property that can read and write to a boolean user default.
|
/// Creates a property that can read and write to a boolean user default.
|
||||||
///
|
///
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
|
@ -53,7 +53,7 @@ extension Backport.AppStorage {
|
||||||
/// store.
|
/// store.
|
||||||
/// - store: The store to read and write to. A value
|
/// - store: The store to read and write to. A value
|
||||||
/// of `nil` will use the user default store from the environment.
|
/// of `nil` will use the user default store from the environment.
|
||||||
public init(wrappedValue: Value, _ key: String, store: UserDefaults = .standard) where Value == Bool {
|
init(wrappedValue: Value, _ key: String, store: UserDefaults = .standard) where Value == Bool {
|
||||||
let value = store.value(forKey: key) as? Value ?? wrappedValue
|
let value = store.value(forKey: key) as? Value ?? wrappedValue
|
||||||
self.init(
|
self.init(
|
||||||
value: value, store: store, key: key,
|
value: value, store: store, key: key,
|
||||||
|
@ -71,7 +71,7 @@ extension Backport.AppStorage {
|
||||||
/// store.
|
/// store.
|
||||||
/// - store: The store to read and write to. A value
|
/// - store: The store to read and write to. A value
|
||||||
/// of `nil` will use the user default store from the environment.
|
/// of `nil` will use the user default store from the environment.
|
||||||
public init(wrappedValue: Value, _ key: String, store: UserDefaults = .standard) where Value == Int {
|
init(wrappedValue: Value, _ key: String, store: UserDefaults = .standard) where Value == Int {
|
||||||
let value = store.value(forKey: key) as? Value ?? wrappedValue
|
let value = store.value(forKey: key) as? Value ?? wrappedValue
|
||||||
self.init(
|
self.init(
|
||||||
value: value, store: store, key: key,
|
value: value, store: store, key: key,
|
||||||
|
@ -89,7 +89,7 @@ extension Backport.AppStorage {
|
||||||
/// store.
|
/// store.
|
||||||
/// - store: The store to read and write to. A value
|
/// - store: The store to read and write to. A value
|
||||||
/// of `nil` will use the user default store from the environment.
|
/// of `nil` will use the user default store from the environment.
|
||||||
public init(wrappedValue: Value, _ key: String, store: UserDefaults = .standard) where Value == Double {
|
init(wrappedValue: Value, _ key: String, store: UserDefaults = .standard) where Value == Double {
|
||||||
let value = store.value(forKey: key) as? Value ?? wrappedValue
|
let value = store.value(forKey: key) as? Value ?? wrappedValue
|
||||||
self.init(
|
self.init(
|
||||||
value: value, store: store, key: key,
|
value: value, store: store, key: key,
|
||||||
|
@ -107,7 +107,7 @@ extension Backport.AppStorage {
|
||||||
/// store.
|
/// store.
|
||||||
/// - store: The store to read and write to. A value
|
/// - store: The store to read and write to. A value
|
||||||
/// of `nil` will use the user default store from the environment.
|
/// of `nil` will use the user default store from the environment.
|
||||||
public init(wrappedValue: Value, _ key: String, store: UserDefaults = .standard) where Value == String {
|
init(wrappedValue: Value, _ key: String, store: UserDefaults = .standard) where Value == String {
|
||||||
let value = store.value(forKey: key) as? Value ?? wrappedValue
|
let value = store.value(forKey: key) as? Value ?? wrappedValue
|
||||||
self.init(
|
self.init(
|
||||||
value: value, store: store, key: key,
|
value: value, store: store, key: key,
|
||||||
|
@ -125,7 +125,7 @@ extension Backport.AppStorage {
|
||||||
/// store.
|
/// store.
|
||||||
/// - store: The store to read and write to. A value
|
/// - store: The store to read and write to. A value
|
||||||
/// of `nil` will use the user default store from the environment.
|
/// of `nil` will use the user default store from the environment.
|
||||||
public init(wrappedValue: Value, _ key: String, store: UserDefaults = .standard) where Value == URL {
|
init(wrappedValue: Value, _ key: String, store: UserDefaults = .standard) where Value == URL {
|
||||||
let value = store.url(forKey: key) ?? wrappedValue
|
let value = store.url(forKey: key) ?? wrappedValue
|
||||||
self.init(
|
self.init(
|
||||||
value: value, store: store, key: key,
|
value: value, store: store, key: key,
|
||||||
|
@ -146,7 +146,7 @@ extension Backport.AppStorage {
|
||||||
/// store.
|
/// store.
|
||||||
/// - store: The store to read and write to. A value
|
/// - store: The store to read and write to. A value
|
||||||
/// of `nil` will use the user default store from the environment.
|
/// of `nil` will use the user default store from the environment.
|
||||||
public init(wrappedValue: Value, _ key: String, store: UserDefaults = .standard) where Value == Data {
|
init(wrappedValue: Value, _ key: String, store: UserDefaults = .standard) where Value == Data {
|
||||||
let value = store.value(forKey: key) as? Data ?? wrappedValue
|
let value = store.value(forKey: key) as? Data ?? wrappedValue
|
||||||
self.init(
|
self.init(
|
||||||
value: value, store: store, key: key,
|
value: value, store: store, key: key,
|
||||||
|
@ -157,7 +157,7 @@ extension Backport.AppStorage {
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(macOS 10.15, *)
|
@available(macOS 10.15, *)
|
||||||
extension Backport.AppStorage where Wrapped == Any, Value: ExpressibleByNilLiteral {
|
public extension Backport.AppStorage where Wrapped == Any, Value: ExpressibleByNilLiteral {
|
||||||
/// Creates a property that can read and write an Optional boolean user
|
/// Creates a property that can read and write an Optional boolean user
|
||||||
/// default.
|
/// default.
|
||||||
///
|
///
|
||||||
|
@ -168,7 +168,7 @@ extension Backport.AppStorage where Wrapped == Any, Value: ExpressibleByNilLiter
|
||||||
/// store.
|
/// store.
|
||||||
/// - store: The store to read and write to. A value
|
/// - store: The store to read and write to. A value
|
||||||
/// of `nil` will use the user default store from the environment.
|
/// of `nil` will use the user default store from the environment.
|
||||||
public init(_ key: String, store: UserDefaults = .standard) where Value == Bool? {
|
init(_ key: String, store: UserDefaults = .standard) where Value == Bool? {
|
||||||
let value = store.value(forKey: key) as? Value ?? .none
|
let value = store.value(forKey: key) as? Value ?? .none
|
||||||
self.init(
|
self.init(
|
||||||
value: value, store: store, key: key,
|
value: value, store: store, key: key,
|
||||||
|
@ -187,7 +187,7 @@ extension Backport.AppStorage where Wrapped == Any, Value: ExpressibleByNilLiter
|
||||||
/// store.
|
/// store.
|
||||||
/// - store: The store to read and write to. A value
|
/// - store: The store to read and write to. A value
|
||||||
/// of `nil` will use the user default store from the environment.
|
/// of `nil` will use the user default store from the environment.
|
||||||
public init(_ key: String, store: UserDefaults = .standard) where Value == Int? {
|
init(_ key: String, store: UserDefaults = .standard) where Value == Int? {
|
||||||
let value = store.value(forKey: key) as? Value ?? .none
|
let value = store.value(forKey: key) as? Value ?? .none
|
||||||
self.init(
|
self.init(
|
||||||
value: value, store: store, key: key,
|
value: value, store: store, key: key,
|
||||||
|
@ -206,7 +206,7 @@ extension Backport.AppStorage where Wrapped == Any, Value: ExpressibleByNilLiter
|
||||||
/// store.
|
/// store.
|
||||||
/// - store: The store to read and write to. A value
|
/// - store: The store to read and write to. A value
|
||||||
/// of `nil` will use the user default store from the environment.
|
/// of `nil` will use the user default store from the environment.
|
||||||
public init(_ key: String, store: UserDefaults = .standard) where Value == Double? {
|
init(_ key: String, store: UserDefaults = .standard) where Value == Double? {
|
||||||
let value = store.value(forKey: key) as? Value ?? .none
|
let value = store.value(forKey: key) as? Value ?? .none
|
||||||
self.init(
|
self.init(
|
||||||
value: value, store: store, key: key,
|
value: value, store: store, key: key,
|
||||||
|
@ -225,7 +225,7 @@ extension Backport.AppStorage where Wrapped == Any, Value: ExpressibleByNilLiter
|
||||||
/// store.
|
/// store.
|
||||||
/// - store: The store to read and write to. A value
|
/// - store: The store to read and write to. A value
|
||||||
/// of `nil` will use the user default store from the environment.
|
/// of `nil` will use the user default store from the environment.
|
||||||
public init(_ key: String, store: UserDefaults = .standard) where Value == String? {
|
init(_ key: String, store: UserDefaults = .standard) where Value == String? {
|
||||||
let value = store.value(forKey: key) as? Value ?? .none
|
let value = store.value(forKey: key) as? Value ?? .none
|
||||||
self.init(
|
self.init(
|
||||||
value: value, store: store, key: key,
|
value: value, store: store, key: key,
|
||||||
|
@ -244,7 +244,7 @@ extension Backport.AppStorage where Wrapped == Any, Value: ExpressibleByNilLiter
|
||||||
/// store.
|
/// store.
|
||||||
/// - store: The store to read and write to. A value
|
/// - store: The store to read and write to. A value
|
||||||
/// of `nil` will use the user default store from the environment.
|
/// of `nil` will use the user default store from the environment.
|
||||||
public init(_ key: String, store: UserDefaults = .standard) where Value == URL? {
|
init(_ key: String, store: UserDefaults = .standard) where Value == URL? {
|
||||||
let value = store.url(forKey: key) ?? .none
|
let value = store.url(forKey: key) ?? .none
|
||||||
self.init(
|
self.init(
|
||||||
value: value, store: store, key: key,
|
value: value, store: store, key: key,
|
||||||
|
@ -263,7 +263,7 @@ extension Backport.AppStorage where Wrapped == Any, Value: ExpressibleByNilLiter
|
||||||
/// store.
|
/// store.
|
||||||
/// - store: The store to read and write to. A value
|
/// - store: The store to read and write to. A value
|
||||||
/// of `nil` will use the user default store from the environment.
|
/// of `nil` will use the user default store from the environment.
|
||||||
public init(_ key: String, store: UserDefaults = .standard) where Value == Data? {
|
init(_ key: String, store: UserDefaults = .standard) where Value == Data? {
|
||||||
let value = store.value(forKey: key) as? Value ?? .none
|
let value = store.value(forKey: key) as? Value ?? .none
|
||||||
self.init(
|
self.init(
|
||||||
value: value, store: store, key: key,
|
value: value, store: store, key: key,
|
||||||
|
@ -274,7 +274,7 @@ extension Backport.AppStorage where Wrapped == Any, Value: ExpressibleByNilLiter
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(macOS 10.15, *)
|
@available(macOS 10.15, *)
|
||||||
extension Backport.AppStorage where Wrapped == Any, Value: RawRepresentable {
|
public extension Backport.AppStorage where Wrapped == Any, Value: RawRepresentable {
|
||||||
/// Creates a property that can read and write to a string user default,
|
/// Creates a property that can read and write to a string user default,
|
||||||
/// transforming that to `RawRepresentable` data type.
|
/// transforming that to `RawRepresentable` data type.
|
||||||
///
|
///
|
||||||
|
@ -295,7 +295,7 @@ extension Backport.AppStorage where Wrapped == Any, Value: RawRepresentable {
|
||||||
/// store.
|
/// store.
|
||||||
/// - store: The store to read and write to. A value
|
/// - store: The store to read and write to. A value
|
||||||
/// of `nil` will use the user default store from the environment.
|
/// of `nil` will use the user default store from the environment.
|
||||||
public init(wrappedValue: Value, _ key: String, store: UserDefaults = .standard) where Value.RawValue == String {
|
init(wrappedValue: Value, _ key: String, store: UserDefaults = .standard) where Value.RawValue == String {
|
||||||
let rawValue = store.value(forKey: key) as? Value.RawValue
|
let rawValue = store.value(forKey: key) as? Value.RawValue
|
||||||
let value = rawValue.flatMap(Value.init) ?? wrappedValue
|
let value = rawValue.flatMap(Value.init) ?? wrappedValue
|
||||||
self.init(
|
self.init(
|
||||||
|
@ -325,7 +325,7 @@ extension Backport.AppStorage where Wrapped == Any, Value: RawRepresentable {
|
||||||
/// store.
|
/// store.
|
||||||
/// - store: The store to read and write to. A value
|
/// - store: The store to read and write to. A value
|
||||||
/// of `nil` will use the user default store from the environment.
|
/// of `nil` will use the user default store from the environment.
|
||||||
public init(wrappedValue: Value, _ key: String, store: UserDefaults = .standard) where Value.RawValue == Int {
|
init(wrappedValue: Value, _ key: String, store: UserDefaults = .standard) where Value.RawValue == Int {
|
||||||
let rawValue = store.value(forKey: key) as? Value.RawValue
|
let rawValue = store.value(forKey: key) as? Value.RawValue
|
||||||
let value = rawValue.flatMap(Value.init) ?? wrappedValue
|
let value = rawValue.flatMap(Value.init) ?? wrappedValue
|
||||||
self.init(
|
self.init(
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
@available(macOS 10.15, *)
|
@available(macOS 10.15, *)
|
||||||
extension Backport where Wrapped: View {
|
public extension Backport where Wrapped: View {
|
||||||
/// Layers the views that you specify behind this view.
|
/// Layers the views that you specify behind this view.
|
||||||
///
|
///
|
||||||
/// Use this modifier to place one or more views behind another view.
|
/// Use this modifier to place one or more views behind another view.
|
||||||
|
@ -128,7 +128,7 @@ extension Backport where Wrapped: View {
|
||||||
/// The last view that you list appears at the front of the stack.
|
/// The last view that you list appears at the front of the stack.
|
||||||
///
|
///
|
||||||
/// - Returns: A view that uses the specified content as a background.
|
/// - Returns: A view that uses the specified content as a background.
|
||||||
public func background<Content: View>(alignment: Alignment = .center, @ViewBuilder _ content: () -> Content)
|
func background<Content: View>(alignment: Alignment = .center, @ViewBuilder _ content: () -> Content)
|
||||||
-> some View
|
-> some View
|
||||||
{
|
{
|
||||||
self.content.background(content(), alignment: alignment)
|
self.content.background(content(), alignment: alignment)
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
@available(macOS 10.15, *)
|
@available(macOS 10.15, *)
|
||||||
extension EnvironmentValues {
|
public extension EnvironmentValues {
|
||||||
/// An action that dismisses the current presentation.
|
/// An action that dismisses the current presentation.
|
||||||
///
|
///
|
||||||
/// Use this environment value to get the ``Backport.DismissAction`` instance
|
/// Use this environment value to get the ``Backport.DismissAction`` instance
|
||||||
|
@ -73,7 +73,7 @@ extension EnvironmentValues {
|
||||||
/// The dismiss action has no effect on a view that isn't currently
|
/// The dismiss action has no effect on a view that isn't currently
|
||||||
/// presented. If you need to query whether SwiftUI is currently presenting
|
/// presented. If you need to query whether SwiftUI is currently presenting
|
||||||
/// a view, read the ``EnvironmentValues/backportIsPresented`` environment value.
|
/// a view, read the ``EnvironmentValues/backportIsPresented`` environment value.
|
||||||
public var backportDismiss: Backport<Any>.DismissAction {
|
var backportDismiss: Backport<Any>.DismissAction {
|
||||||
.init(presentation: presentationMode)
|
.init(presentation: presentationMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,7 +104,7 @@ extension EnvironmentValues {
|
||||||
///
|
///
|
||||||
/// To dismiss the currently presented view, use
|
/// To dismiss the currently presented view, use
|
||||||
/// ``EnvironmentValues/backportDismiss``.
|
/// ``EnvironmentValues/backportDismiss``.
|
||||||
public var backportIsPresented: Bool {
|
var backportIsPresented: Bool {
|
||||||
presentationMode.wrappedValue.isPresented
|
presentationMode.wrappedValue.isPresented
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
@available(macOS 10.15, *)
|
@available(macOS 10.15, *)
|
||||||
extension Backport where Wrapped: View {
|
public extension Backport where Wrapped: View {
|
||||||
/// Sets the Dynamic Type size within the view to the given value.
|
/// Sets the Dynamic Type size within the view to the given value.
|
||||||
///
|
///
|
||||||
/// As an example, you can set a Dynamic Type size in `ContentView` to be
|
/// As an example, you can set a Dynamic Type size in `ContentView` to be
|
||||||
|
@ -31,7 +31,7 @@ extension Backport where Wrapped: View {
|
||||||
/// - Returns: A view that sets the Dynamic Type size to the specified
|
/// - Returns: A view that sets the Dynamic Type size to the specified
|
||||||
/// `size`.
|
/// `size`.
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
public func dynamicTypeSize(_ size: Backport<Any>.DynamicTypeSize) -> some View {
|
func dynamicTypeSize(_ size: Backport<Any>.DynamicTypeSize) -> some View {
|
||||||
content.environment(\.backportDynamicTypeSize, size)
|
content.environment(\.backportDynamicTypeSize, size)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,8 +66,9 @@ extension Backport where Wrapped: View {
|
||||||
/// - Returns: A view that constrains the Dynamic Type size of this view
|
/// - Returns: A view that constrains the Dynamic Type size of this view
|
||||||
/// within the specified `range`.
|
/// within the specified `range`.
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
public func dynamicTypeSize<T>(_ range: T) -> some View
|
func dynamicTypeSize<T>(_ range: T) -> some View
|
||||||
where T: RangeExpression, T.Bound == Backport<Any>.DynamicTypeSize {
|
where T: RangeExpression, T.Bound == Backport<Any>.DynamicTypeSize
|
||||||
|
{
|
||||||
if let range = range as? Range<T.Bound> {
|
if let range = range as? Range<T.Bound> {
|
||||||
content
|
content
|
||||||
.modifier(DynamicTypeRangeModifier())
|
.modifier(DynamicTypeRangeModifier())
|
||||||
|
|
|
@ -63,38 +63,6 @@ extension Backport where Wrapped == Any {
|
||||||
/// Create a Dynamic Type size from its `UIContentSizeCategory` equivalent.
|
/// Create a Dynamic Type size from its `UIContentSizeCategory` equivalent.
|
||||||
public init?(_ uiSizeCategory: UIContentSizeCategory) {
|
public init?(_ uiSizeCategory: UIContentSizeCategory) {
|
||||||
switch uiSizeCategory {
|
switch uiSizeCategory {
|
||||||
case .extraSmall:
|
|
||||||
self = .xSmall
|
|
||||||
case .small:
|
|
||||||
self = .small
|
|
||||||
case .medium:
|
|
||||||
self = .medium
|
|
||||||
case .large:
|
|
||||||
self = .medium
|
|
||||||
case .extraLarge:
|
|
||||||
self = .xLarge
|
|
||||||
case .extraExtraLarge:
|
|
||||||
self = .xxLarge
|
|
||||||
case .extraExtraExtraLarge:
|
|
||||||
self = .xxxLarge
|
|
||||||
case .accessibilityMedium:
|
|
||||||
self = .accessibility1
|
|
||||||
case .accessibilityLarge:
|
|
||||||
self = .accessibility2
|
|
||||||
case .accessibilityExtraLarge:
|
|
||||||
self = .accessibility3
|
|
||||||
case .accessibilityExtraExtraLarge:
|
|
||||||
self = .accessibility4
|
|
||||||
case .accessibilityExtraExtraExtraLarge:
|
|
||||||
self = .accessibility5
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
internal init(_ sizeCategory: ContentSizeCategory) {
|
|
||||||
switch sizeCategory {
|
|
||||||
case .extraSmall:
|
case .extraSmall:
|
||||||
self = .xSmall
|
self = .xSmall
|
||||||
case .small:
|
case .small:
|
||||||
|
@ -102,7 +70,7 @@ extension Backport where Wrapped == Any {
|
||||||
case .medium:
|
case .medium:
|
||||||
self = .medium
|
self = .medium
|
||||||
case .large:
|
case .large:
|
||||||
self = .large
|
self = .medium
|
||||||
case .extraLarge:
|
case .extraLarge:
|
||||||
self = .xLarge
|
self = .xLarge
|
||||||
case .extraExtraLarge:
|
case .extraExtraLarge:
|
||||||
|
@ -120,36 +88,68 @@ extension Backport where Wrapped == Any {
|
||||||
case .accessibilityExtraExtraExtraLarge:
|
case .accessibilityExtraExtraExtraLarge:
|
||||||
self = .accessibility5
|
self = .accessibility5
|
||||||
default:
|
default:
|
||||||
self = .large
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
internal init(_ sizeCategory: ContentSizeCategory) {
|
||||||
|
switch sizeCategory {
|
||||||
|
case .extraSmall:
|
||||||
|
self = .xSmall
|
||||||
|
case .small:
|
||||||
|
self = .small
|
||||||
|
case .medium:
|
||||||
|
self = .medium
|
||||||
|
case .large:
|
||||||
|
self = .large
|
||||||
|
case .extraLarge:
|
||||||
|
self = .xLarge
|
||||||
|
case .extraExtraLarge:
|
||||||
|
self = .xxLarge
|
||||||
|
case .extraExtraExtraLarge:
|
||||||
|
self = .xxxLarge
|
||||||
|
case .accessibilityMedium:
|
||||||
|
self = .accessibility1
|
||||||
|
case .accessibilityLarge:
|
||||||
|
self = .accessibility2
|
||||||
|
case .accessibilityExtraLarge:
|
||||||
|
self = .accessibility3
|
||||||
|
case .accessibilityExtraExtraLarge:
|
||||||
|
self = .accessibility4
|
||||||
|
case .accessibilityExtraExtraExtraLarge:
|
||||||
|
self = .accessibility5
|
||||||
|
default:
|
||||||
|
self = .large
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var sizeCategory: ContentSizeCategory {
|
var sizeCategory: ContentSizeCategory {
|
||||||
switch self {
|
switch self {
|
||||||
case .xSmall:
|
case .xSmall:
|
||||||
return .extraSmall
|
return .extraSmall
|
||||||
case .small:
|
case .small:
|
||||||
return .small
|
return .small
|
||||||
case .medium:
|
case .medium:
|
||||||
return .medium
|
return .medium
|
||||||
case .large:
|
case .large:
|
||||||
return .large
|
return .large
|
||||||
case .xLarge:
|
case .xLarge:
|
||||||
return .extraLarge
|
return .extraLarge
|
||||||
case .xxLarge:
|
case .xxLarge:
|
||||||
return .extraExtraLarge
|
return .extraExtraLarge
|
||||||
case .xxxLarge:
|
case .xxxLarge:
|
||||||
return .extraExtraExtraLarge
|
return .extraExtraExtraLarge
|
||||||
case .accessibility1:
|
case .accessibility1:
|
||||||
return .accessibilityMedium
|
return .accessibilityMedium
|
||||||
case .accessibility2:
|
case .accessibility2:
|
||||||
return .accessibilityLarge
|
return .accessibilityLarge
|
||||||
case .accessibility3:
|
case .accessibility3:
|
||||||
return .accessibilityExtraLarge
|
return .accessibilityExtraLarge
|
||||||
case .accessibility4:
|
case .accessibility4:
|
||||||
return .accessibilityExtraExtraLarge
|
return .accessibilityExtraExtraLarge
|
||||||
case .accessibility5:
|
case .accessibility5:
|
||||||
return .accessibilityExtraExtraExtraLarge
|
return .accessibilityExtraExtraExtraLarge
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -159,30 +159,30 @@ extension Backport where Wrapped == Any {
|
||||||
extension Backport.DynamicTypeSize {
|
extension Backport.DynamicTypeSize {
|
||||||
var dynamicTypeSize: DynamicTypeSize {
|
var dynamicTypeSize: DynamicTypeSize {
|
||||||
switch self {
|
switch self {
|
||||||
case .xSmall:
|
case .xSmall:
|
||||||
return .xSmall
|
return .xSmall
|
||||||
case .small:
|
case .small:
|
||||||
return .small
|
return .small
|
||||||
case .medium:
|
case .medium:
|
||||||
return .medium
|
return .medium
|
||||||
case .large:
|
case .large:
|
||||||
return .large
|
return .large
|
||||||
case .xLarge:
|
case .xLarge:
|
||||||
return .xLarge
|
return .xLarge
|
||||||
case .xxLarge:
|
case .xxLarge:
|
||||||
return .xxLarge
|
return .xxLarge
|
||||||
case .xxxLarge:
|
case .xxxLarge:
|
||||||
return .xxxLarge
|
return .xxxLarge
|
||||||
case .accessibility1:
|
case .accessibility1:
|
||||||
return .accessibility1
|
return .accessibility1
|
||||||
case .accessibility2:
|
case .accessibility2:
|
||||||
return .accessibility2
|
return .accessibility2
|
||||||
case .accessibility3:
|
case .accessibility3:
|
||||||
return .accessibility3
|
return .accessibility3
|
||||||
case .accessibility4:
|
case .accessibility4:
|
||||||
return .accessibility4
|
return .accessibility4
|
||||||
case .accessibility5:
|
case .accessibility5:
|
||||||
return .accessibility5
|
return .accessibility5
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -193,32 +193,32 @@ extension Backport.DynamicTypeSize {
|
||||||
extension UIContentSizeCategory {
|
extension UIContentSizeCategory {
|
||||||
public init(_ dynamicTypeSize: Backport<Any>.DynamicTypeSize?) {
|
public init(_ dynamicTypeSize: Backport<Any>.DynamicTypeSize?) {
|
||||||
switch dynamicTypeSize {
|
switch dynamicTypeSize {
|
||||||
case .xSmall:
|
case .xSmall:
|
||||||
self = .extraSmall
|
self = .extraSmall
|
||||||
case .small:
|
case .small:
|
||||||
self = .small
|
self = .small
|
||||||
case .medium:
|
case .medium:
|
||||||
self = .medium
|
self = .medium
|
||||||
case .large:
|
case .large:
|
||||||
self = .large
|
self = .large
|
||||||
case .xLarge:
|
case .xLarge:
|
||||||
self = .extraLarge
|
self = .extraLarge
|
||||||
case .xxLarge:
|
case .xxLarge:
|
||||||
self = .extraExtraLarge
|
self = .extraExtraLarge
|
||||||
case .xxxLarge:
|
case .xxxLarge:
|
||||||
self = .extraExtraExtraLarge
|
self = .extraExtraExtraLarge
|
||||||
case .accessibility1:
|
case .accessibility1:
|
||||||
self = .accessibilityMedium
|
self = .accessibilityMedium
|
||||||
case .accessibility2:
|
case .accessibility2:
|
||||||
self = .accessibilityLarge
|
self = .accessibilityLarge
|
||||||
case .accessibility3:
|
case .accessibility3:
|
||||||
self = .accessibilityExtraLarge
|
self = .accessibilityExtraLarge
|
||||||
case .accessibility4:
|
case .accessibility4:
|
||||||
self = .accessibilityExtraExtraLarge
|
self = .accessibilityExtraExtraLarge
|
||||||
case .accessibility5:
|
case .accessibility5:
|
||||||
self = .accessibilityExtraExtraExtraLarge
|
self = .accessibilityExtraExtraExtraLarge
|
||||||
case .none:
|
case .none:
|
||||||
self = .large
|
self = .large
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -111,14 +111,14 @@ extension Backport where Wrapped == Any {
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(macOS 10.15, *)
|
@available(macOS 10.15, *)
|
||||||
extension Backport.Label where Wrapped == Any, Title == Text, Icon == Image {
|
public extension Backport.Label where Wrapped == Any, Title == Text, Icon == Image {
|
||||||
/// Creates a label with an icon image and a title generated from a
|
/// Creates a label with an icon image and a title generated from a
|
||||||
/// localized string.
|
/// localized string.
|
||||||
///
|
///
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - titleKey: A title generated from a localized string.
|
/// - titleKey: A title generated from a localized string.
|
||||||
/// - image: The name of the image resource to lookup.
|
/// - image: The name of the image resource to lookup.
|
||||||
public init(_ titleKey: LocalizedStringKey, image name: String) {
|
init(_ titleKey: LocalizedStringKey, image name: String) {
|
||||||
self.init(title: { Text(titleKey) }, icon: { Image(name) })
|
self.init(title: { Text(titleKey) }, icon: { Image(name) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,7 +127,7 @@ extension Backport.Label where Wrapped == Any, Title == Text, Icon == Image {
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - title: A string used as the label's title.
|
/// - title: A string used as the label's title.
|
||||||
/// - image: The name of the image resource to lookup.
|
/// - image: The name of the image resource to lookup.
|
||||||
public init<S>(_ title: S, image name: String) where S: StringProtocol {
|
init<S>(_ title: S, image name: String) where S: StringProtocol {
|
||||||
self.init(title: { Text(title) }, icon: { Image(name) })
|
self.init(title: { Text(title) }, icon: { Image(name) })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -156,8 +156,9 @@ extension Backport.Label where Wrapped == Any, Title == Text, Icon == Image {
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(macOS 10.15, *)
|
@available(macOS 10.15, *)
|
||||||
extension Backport.Label
|
public extension Backport.Label
|
||||||
where Wrapped == Any, Title == Backport.LabelStyleConfiguration.Title, Icon == Backport.LabelStyleConfiguration.Icon {
|
where Wrapped == Any, Title == Backport.LabelStyleConfiguration.Title, Icon == Backport.LabelStyleConfiguration.Icon
|
||||||
|
{
|
||||||
/// Creates a label representing the configuration of a style.
|
/// Creates a label representing the configuration of a style.
|
||||||
///
|
///
|
||||||
/// You can use this initializer within the ``LabelStyle/makeBody(configuration:)``
|
/// You can use this initializer within the ``LabelStyle/makeBody(configuration:)``
|
||||||
|
@ -177,7 +178,7 @@ where Wrapped == Any, Title == Backport.LabelStyleConfiguration.Title, Icon == B
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// - Parameter configuration: The label style to use.
|
/// - Parameter configuration: The label style to use.
|
||||||
public init(_ configuration: Backport.LabelStyleConfiguration) {
|
init(_ configuration: Backport.LabelStyleConfiguration) {
|
||||||
config = configuration
|
config = configuration
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -137,8 +137,8 @@ extension Backport where Wrapped == Any {
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(macOS 10.15, *)
|
@available(macOS 10.15, *)
|
||||||
extension Backport.LabeledContent
|
public extension Backport.LabeledContent
|
||||||
where
|
where
|
||||||
Wrapped == Any, Label == Backport<Any>.LabeledContentStyleConfiguration.Label,
|
Wrapped == Any, Label == Backport<Any>.LabeledContentStyleConfiguration.Label,
|
||||||
Content == Backport<Any>.LabeledContentStyleConfiguration.Content
|
Content == Backport<Any>.LabeledContentStyleConfiguration.Content
|
||||||
{
|
{
|
||||||
|
@ -161,13 +161,13 @@ where
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// - Parameter configuration: The properties of the labeled content
|
/// - Parameter configuration: The properties of the labeled content
|
||||||
public init(_ config: Backport.LabeledContentStyleConfiguration) {
|
init(_ config: Backport.LabeledContentStyleConfiguration) {
|
||||||
self.config = config
|
self.config = config
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(macOS 10.15, *)
|
@available(macOS 10.15, *)
|
||||||
extension Backport.LabeledContent where Wrapped == Any, Label == Text, Content: View {
|
public extension Backport.LabeledContent where Wrapped == Any, Label == Text, Content: View {
|
||||||
/// Creates a labeled view that generates its label from a localized string
|
/// Creates a labeled view that generates its label from a localized string
|
||||||
/// key.
|
/// key.
|
||||||
///
|
///
|
||||||
|
@ -179,7 +179,7 @@ extension Backport.LabeledContent where Wrapped == Any, Label == Text, Content:
|
||||||
/// - titleKey: The key for the view's localized title, that describes
|
/// - titleKey: The key for the view's localized title, that describes
|
||||||
/// the purpose of the view.
|
/// the purpose of the view.
|
||||||
/// - content: The value content being labeled.
|
/// - content: The value content being labeled.
|
||||||
public init(_ titleKey: LocalizedStringKey, @ViewBuilder content: () -> Content) {
|
init(_ titleKey: LocalizedStringKey, @ViewBuilder content: () -> Content) {
|
||||||
config = .init(
|
config = .init(
|
||||||
label: Text(titleKey),
|
label: Text(titleKey),
|
||||||
content: content()
|
content: content()
|
||||||
|
@ -196,7 +196,7 @@ extension Backport.LabeledContent where Wrapped == Any, Label == Text, Content:
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - title: A string that describes the purpose of the view.
|
/// - title: A string that describes the purpose of the view.
|
||||||
/// - content: The value content being labeled.
|
/// - content: The value content being labeled.
|
||||||
public init<S>(_ title: S, @ViewBuilder content: () -> Content) where S: StringProtocol {
|
init<S>(_ title: S, @ViewBuilder content: () -> Content) where S: StringProtocol {
|
||||||
config = .init(
|
config = .init(
|
||||||
label: Text(title),
|
label: Text(title),
|
||||||
content: content()
|
content: content()
|
||||||
|
@ -226,7 +226,7 @@ extension Backport.LabeledContent: View where Wrapped == Any, Label: View, Conte
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(macOS 10.15, *)
|
@available(macOS 10.15, *)
|
||||||
extension Backport.LabeledContent where Wrapped == Any, Label == Text, Content == Text {
|
public extension Backport.LabeledContent where Wrapped == Any, Label == Text, Content == Text {
|
||||||
/// Creates a labeled informational view.
|
/// Creates a labeled informational view.
|
||||||
///
|
///
|
||||||
/// This initializer creates a ``Text`` label on your behalf, and treats the
|
/// This initializer creates a ``Text`` label on your behalf, and treats the
|
||||||
|
@ -243,7 +243,7 @@ extension Backport.LabeledContent where Wrapped == Any, Label == Text, Content =
|
||||||
/// - titleKey: The key for the view's localized title, that describes
|
/// - titleKey: The key for the view's localized title, that describes
|
||||||
/// the purpose of the view.
|
/// the purpose of the view.
|
||||||
/// - value: The value being labeled.
|
/// - value: The value being labeled.
|
||||||
public init<S: StringProtocol>(_ titleKey: LocalizedStringKey, value: S) {
|
init<S: StringProtocol>(_ titleKey: LocalizedStringKey, value: S) {
|
||||||
config = .init(
|
config = .init(
|
||||||
label: Text(titleKey),
|
label: Text(titleKey),
|
||||||
content: Text(value)
|
content: Text(value)
|
||||||
|
@ -266,7 +266,7 @@ extension Backport.LabeledContent where Wrapped == Any, Label == Text, Content =
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - title: A string that describes the purpose of the view.
|
/// - title: A string that describes the purpose of the view.
|
||||||
/// - value: The value being labeled.
|
/// - value: The value being labeled.
|
||||||
public init<S1: StringProtocol, S2: StringProtocol>(_ title: S1, value: S2) {
|
init<S1: StringProtocol, S2: StringProtocol>(_ title: S1, value: S2) {
|
||||||
config = .init(
|
config = .init(
|
||||||
label: Text(title),
|
label: Text(title),
|
||||||
content: Text(value)
|
content: Text(value)
|
||||||
|
|
|
@ -5,8 +5,8 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
@available(macOS 10.15, *)
|
@available(macOS 10.15, *)
|
||||||
extension Backport where Wrapped == Any {
|
public extension Backport where Wrapped == Any {
|
||||||
public struct AutomaticLabeledContentStyle: BackportLabeledContentStyle {
|
struct AutomaticLabeledContentStyle: BackportLabeledContentStyle {
|
||||||
public func makeBody(configuration: Configuration) -> some View {
|
public func makeBody(configuration: Configuration) -> some View {
|
||||||
HStack(alignment: .firstTextBaseline) {
|
HStack(alignment: .firstTextBaseline) {
|
||||||
configuration.label
|
configuration.label
|
||||||
|
|
|
@ -47,7 +47,7 @@ extension Backport where Wrapped: View {
|
||||||
.environment(
|
.environment(
|
||||||
\.navigationDestinations,
|
\.navigationDestinations,
|
||||||
[
|
[
|
||||||
.init(type: D.self): .init { destination($0 as! D) }
|
.init(type: D.self): .init { destination($0 as! D) },
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -96,8 +96,8 @@ private struct NavigationDestinationsEnvironmentKey: EnvironmentKey {
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(macOS 10.15, *)
|
@available(macOS 10.15, *)
|
||||||
extension EnvironmentValues {
|
private extension EnvironmentValues {
|
||||||
fileprivate var navigationDestinations: [AnyMetaType: DestinationView] {
|
var navigationDestinations: [AnyMetaType: DestinationView] {
|
||||||
get { self[NavigationDestinationsEnvironmentKey.self] }
|
get { self[NavigationDestinationsEnvironmentKey.self] }
|
||||||
set {
|
set {
|
||||||
var current = self[NavigationDestinationsEnvironmentKey.self]
|
var current = self[NavigationDestinationsEnvironmentKey.self]
|
||||||
|
@ -127,8 +127,8 @@ extension AnyMetaType: Hashable {
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(macOS 10.15, *)
|
@available(macOS 10.15, *)
|
||||||
extension Dictionary {
|
private extension Dictionary {
|
||||||
fileprivate subscript(_ key: Any.Type) -> Value? where Key == AnyMetaType {
|
subscript(_ key: Any.Type) -> Value? where Key == AnyMetaType {
|
||||||
get { self[.init(type: key)] }
|
get { self[.init(type: key)] }
|
||||||
_modify { yield &self[.init(type: key)] }
|
_modify { yield &self[.init(type: key)] }
|
||||||
}
|
}
|
||||||
|
|
|
@ -132,16 +132,16 @@ extension Backport where Wrapped == Any {
|
||||||
let result = handler(url).value
|
let result = handler(url).value
|
||||||
|
|
||||||
switch result {
|
switch result {
|
||||||
case .handled, .discarded: break
|
case .handled, .discarded: break
|
||||||
case let .systemAction(updatedUrl):
|
case let .systemAction(updatedUrl):
|
||||||
let resolved = updatedUrl ?? url
|
let resolved = updatedUrl ?? url
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
NSWorkspace.shared.open(resolved)
|
NSWorkspace.shared.open(resolved)
|
||||||
#elseif os(iOS) || os(tvOS)
|
#elseif os(iOS) || os(tvOS)
|
||||||
UIApplication.shared.open(resolved)
|
UIApplication.shared.open(resolved)
|
||||||
#else
|
#else
|
||||||
WKExtension.shared().openSystemURL(resolved)
|
WKExtension.shared().openSystemURL(resolved)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
@ -169,8 +169,8 @@ private struct BackportOpenURLKey: EnvironmentKey {
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(macOS 10.15, *)
|
@available(macOS 10.15, *)
|
||||||
extension EnvironmentValues {
|
public extension EnvironmentValues {
|
||||||
public var backportOpenURL: Backport<Any>.OpenURLAction {
|
var backportOpenURL: Backport<Any>.OpenURLAction {
|
||||||
get { self[BackportOpenURLKey.self] }
|
get { self[BackportOpenURLKey.self] }
|
||||||
set { self[BackportOpenURLKey.self] = newValue }
|
set { self[BackportOpenURLKey.self] = newValue }
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
@available(macOS 10.15, *)
|
@available(macOS 10.15, *)
|
||||||
extension Backport where Wrapped: View {
|
public extension Backport where Wrapped: View {
|
||||||
/// Layers the views that you specify in front of this view.
|
/// Layers the views that you specify in front of this view.
|
||||||
///
|
///
|
||||||
/// Use this modifier to place one or more views in front of another view.
|
/// Use this modifier to place one or more views in front of another view.
|
||||||
|
@ -121,8 +121,7 @@ extension Backport where Wrapped: View {
|
||||||
/// The last view that you list appears at the front of the stack.
|
/// The last view that you list appears at the front of the stack.
|
||||||
///
|
///
|
||||||
/// - Returns: A view that uses the specified content as a foreground.
|
/// - Returns: A view that uses the specified content as a foreground.
|
||||||
public func overlay<Content: View>(alignment: Alignment = .center, @ViewBuilder _ content: () -> Content) -> some View
|
func overlay<Content: View>(alignment: Alignment = .center, @ViewBuilder _ content: () -> Content) -> some View {
|
||||||
{
|
|
||||||
self.content.overlay(content(), alignment: alignment)
|
self.content.overlay(content(), alignment: alignment)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -104,7 +104,8 @@ extension Backport.ProgressView where Wrapped == Any {
|
||||||
/// task, meaning the task is complete if `value` equals `total`. The
|
/// task, meaning the task is complete if `value` equals `total`. The
|
||||||
/// default value is `1.0`.
|
/// default value is `1.0`.
|
||||||
public init<V>(value: V?, total: V = 1.0)
|
public init<V>(value: V?, total: V = 1.0)
|
||||||
where Label == EmptyView, CurrentValueLabel == EmptyView, V: BinaryFloatingPoint {
|
where Label == EmptyView, CurrentValueLabel == EmptyView, V: BinaryFloatingPoint
|
||||||
|
{
|
||||||
if let value = value {
|
if let value = value {
|
||||||
config = .init(fractionCompleted: Double(value) / Double(total), preferredKind: .linear, max: Double(total))
|
config = .init(fractionCompleted: Double(value) / Double(total), preferredKind: .linear, max: Double(total))
|
||||||
} else {
|
} else {
|
||||||
|
@ -130,7 +131,8 @@ extension Backport.ProgressView where Wrapped == Any {
|
||||||
/// - label: A view builder that creates a view that describes the task
|
/// - label: A view builder that creates a view that describes the task
|
||||||
/// in progress.
|
/// in progress.
|
||||||
public init<V>(value: V?, total: V = 1.0, @ViewBuilder label: () -> Label)
|
public init<V>(value: V?, total: V = 1.0, @ViewBuilder label: () -> Label)
|
||||||
where CurrentValueLabel == EmptyView, V: BinaryFloatingPoint {
|
where CurrentValueLabel == EmptyView, V: BinaryFloatingPoint
|
||||||
|
{
|
||||||
if let value = value {
|
if let value = value {
|
||||||
config = .init(
|
config = .init(
|
||||||
fractionCompleted: Double(value) / Double(total), label: .init(content: label()), preferredKind: .linear
|
fractionCompleted: Double(value) / Double(total), label: .init(content: label()), preferredKind: .linear
|
||||||
|
@ -200,7 +202,8 @@ extension Backport.ProgressView where Wrapped == Any {
|
||||||
/// task, meaning the task is complete if `value` equals `total`. The
|
/// task, meaning the task is complete if `value` equals `total`. The
|
||||||
/// default value is `1.0`.
|
/// default value is `1.0`.
|
||||||
public init<V>(_ titleKey: LocalizedStringKey, value: V?, total: V = 1.0)
|
public init<V>(_ titleKey: LocalizedStringKey, value: V?, total: V = 1.0)
|
||||||
where Label == Text, CurrentValueLabel == EmptyView, V: BinaryFloatingPoint {
|
where Label == Text, CurrentValueLabel == EmptyView, V: BinaryFloatingPoint
|
||||||
|
{
|
||||||
if let value = value {
|
if let value = value {
|
||||||
config = .init(
|
config = .init(
|
||||||
fractionCompleted: Double(value) / Double(total), label: .init(content: Text(titleKey)), preferredKind: .linear,
|
fractionCompleted: Double(value) / Double(total), label: .init(content: Text(titleKey)), preferredKind: .linear,
|
||||||
|
@ -237,7 +240,8 @@ extension Backport.ProgressView where Wrapped == Any {
|
||||||
/// task, meaning the task is complete if `value` equals `total`. The
|
/// task, meaning the task is complete if `value` equals `total`. The
|
||||||
/// default value is `1.0`.
|
/// default value is `1.0`.
|
||||||
public init<S, V>(_ title: S, value: V?, total: V = 1.0)
|
public init<S, V>(_ title: S, value: V?, total: V = 1.0)
|
||||||
where Label == Text, CurrentValueLabel == EmptyView, S: StringProtocol, V: BinaryFloatingPoint {
|
where Label == Text, CurrentValueLabel == EmptyView, S: StringProtocol, V: BinaryFloatingPoint
|
||||||
|
{
|
||||||
if let value = value {
|
if let value = value {
|
||||||
config = .init(
|
config = .init(
|
||||||
fractionCompleted: Double(value) / Double(total), label: .init(content: Text(title)), preferredKind: .linear,
|
fractionCompleted: Double(value) / Double(total), label: .init(content: Text(title)), preferredKind: .linear,
|
||||||
|
|
|
@ -42,8 +42,8 @@ extension Backport where Wrapped == Any {
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(macOS 10.15, *)
|
@available(macOS 10.15, *)
|
||||||
extension BackportProgressViewStyle where Self == Backport<Any>.CircularProgressViewStyle {
|
public extension BackportProgressViewStyle where Self == Backport<Any>.CircularProgressViewStyle {
|
||||||
public static var circular: Self { .init() }
|
static var circular: Self { .init() }
|
||||||
}
|
}
|
||||||
|
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
|
|
|
@ -31,24 +31,24 @@ extension Backport where Wrapped == Any {
|
||||||
/// its preferred progress type.
|
/// its preferred progress type.
|
||||||
public func makeBody(configuration: Configuration) -> some View {
|
public func makeBody(configuration: Configuration) -> some View {
|
||||||
switch configuration.preferredKind {
|
switch configuration.preferredKind {
|
||||||
case .circular:
|
case .circular:
|
||||||
Backport.CircularProgressViewStyle().makeBody(configuration: configuration)
|
Backport.CircularProgressViewStyle().makeBody(configuration: configuration)
|
||||||
case .linear:
|
case .linear:
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
if configuration.fractionCompleted == nil {
|
if configuration.fractionCompleted == nil {
|
||||||
Backport.CircularProgressViewStyle().makeBody(configuration: configuration)
|
Backport.CircularProgressViewStyle().makeBody(configuration: configuration)
|
||||||
} else {
|
} else {
|
||||||
Backport.LinearProgressViewStyle().makeBody(configuration: configuration)
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
Backport.LinearProgressViewStyle().makeBody(configuration: configuration)
|
Backport.LinearProgressViewStyle().makeBody(configuration: configuration)
|
||||||
#endif
|
}
|
||||||
|
#else
|
||||||
|
Backport.LinearProgressViewStyle().makeBody(configuration: configuration)
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(macOS 10.15, *)
|
@available(macOS 10.15, *)
|
||||||
extension BackportProgressViewStyle where Self == Backport<Any>.DefaultProgressViewStyle {
|
public extension BackportProgressViewStyle where Self == Backport<Any>.DefaultProgressViewStyle {
|
||||||
public static var automatic: Self { .init() }
|
static var automatic: Self { .init() }
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,8 +63,8 @@ extension Backport where Wrapped == Any {
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(macOS 10.15, *)
|
@available(macOS 10.15, *)
|
||||||
extension BackportProgressViewStyle where Self == Backport<Any>.LinearProgressViewStyle {
|
public extension BackportProgressViewStyle where Self == Backport<Any>.LinearProgressViewStyle {
|
||||||
public static var linear: Self { .init() }
|
static var linear: Self { .init() }
|
||||||
}
|
}
|
||||||
|
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
|
|
|
@ -9,7 +9,8 @@ import SwiftUI
|
||||||
|
|
||||||
final class PreviewController<Items>: UIViewController, UIAdaptivePresentationControllerDelegate,
|
final class PreviewController<Items>: UIViewController, UIAdaptivePresentationControllerDelegate,
|
||||||
QLPreviewControllerDelegate, QLPreviewControllerDataSource
|
QLPreviewControllerDelegate, QLPreviewControllerDataSource
|
||||||
where Items: RandomAccessCollection, Items.Element == URL {
|
where Items: RandomAccessCollection, Items.Element == URL
|
||||||
|
{
|
||||||
var items: Items
|
var items: Items
|
||||||
|
|
||||||
var selection: Binding<Items.Element?> {
|
var selection: Binding<Items.Element?> {
|
||||||
|
@ -34,14 +35,14 @@ import SwiftUI
|
||||||
|
|
||||||
private func updateControllerLifecycle(from oldValue: Items.Element?, to newValue: Items.Element?) {
|
private func updateControllerLifecycle(from oldValue: Items.Element?, to newValue: Items.Element?) {
|
||||||
switch (oldValue, newValue) {
|
switch (oldValue, newValue) {
|
||||||
case (.none, .some):
|
case (.none, .some):
|
||||||
presentController()
|
presentController()
|
||||||
case (.some, .some):
|
case (.some, .some):
|
||||||
updateController()
|
updateController()
|
||||||
case (.some, .none):
|
case (.some, .none):
|
||||||
dismissController()
|
dismissController()
|
||||||
case (.none, .none):
|
case (.none, .none):
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,8 @@ import SwiftUI
|
||||||
|
|
||||||
@available(macOS 10.15, *)
|
@available(macOS 10.15, *)
|
||||||
final class PreviewController<Items>: NSViewController, QLPreviewPanelDataSource, QLPreviewPanelDelegate
|
final class PreviewController<Items>: NSViewController, QLPreviewPanelDataSource, QLPreviewPanelDelegate
|
||||||
where Items: RandomAccessCollection, Items.Element == URL {
|
where Items: RandomAccessCollection, Items.Element == URL
|
||||||
|
{
|
||||||
private let panel = QLPreviewPanel.shared()!
|
private let panel = QLPreviewPanel.shared()!
|
||||||
private weak var windowResponder: NSResponder?
|
private weak var windowResponder: NSResponder?
|
||||||
|
|
||||||
|
@ -27,14 +28,14 @@ import SwiftUI
|
||||||
|
|
||||||
private func updateControllerLifecycle(from oldValue: Items.Element?, to newValue: Items.Element?) {
|
private func updateControllerLifecycle(from oldValue: Items.Element?, to newValue: Items.Element?) {
|
||||||
switch (oldValue, newValue) {
|
switch (oldValue, newValue) {
|
||||||
case (.none, .some):
|
case (.none, .some):
|
||||||
present()
|
present()
|
||||||
case (.some, .some):
|
case (.some, .some):
|
||||||
update()
|
update()
|
||||||
case (.some, .none):
|
case (.some, .none):
|
||||||
dismiss()
|
dismiss()
|
||||||
case (.none, .none):
|
case (.none, .none):
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,8 @@ extension Backport where Wrapped: View {
|
||||||
///
|
///
|
||||||
/// - Returns: A view that presents the preview of the contents of the URL.
|
/// - Returns: A view that presents the preview of the contents of the URL.
|
||||||
public func quickLookPreview<Items>(_ selection: Binding<Items.Element?>, in items: Items) -> some View
|
public func quickLookPreview<Items>(_ selection: Binding<Items.Element?>, in items: Items) -> some View
|
||||||
where Items: RandomAccessCollection, Items.Element == URL {
|
where Items: RandomAccessCollection, Items.Element == URL
|
||||||
|
{
|
||||||
#if os(iOS) || os(macOS)
|
#if os(iOS) || os(macOS)
|
||||||
content.background(QuicklookSheet(selection: selection, items: items))
|
content.background(QuicklookSheet(selection: selection, items: items))
|
||||||
#else
|
#else
|
||||||
|
@ -64,7 +65,8 @@ extension Backport where Wrapped: View {
|
||||||
|
|
||||||
@available(macOS 10.15, *)
|
@available(macOS 10.15, *)
|
||||||
private struct QuicklookSheet<Items>: NSViewControllerRepresentable
|
private struct QuicklookSheet<Items>: NSViewControllerRepresentable
|
||||||
where Items: RandomAccessCollection, Items.Element == URL {
|
where Items: RandomAccessCollection, Items.Element == URL
|
||||||
|
{
|
||||||
let selection: Binding<Items.Element?>
|
let selection: Binding<Items.Element?>
|
||||||
let items: Items
|
let items: Items
|
||||||
|
|
||||||
|
@ -81,7 +83,8 @@ extension Backport where Wrapped: View {
|
||||||
#elseif os(iOS)
|
#elseif os(iOS)
|
||||||
|
|
||||||
private struct QuicklookSheet<Items>: UIViewControllerRepresentable
|
private struct QuicklookSheet<Items>: UIViewControllerRepresentable
|
||||||
where Items: RandomAccessCollection, Items.Element == URL {
|
where Items: RandomAccessCollection, Items.Element == URL
|
||||||
|
{
|
||||||
let selection: Binding<Items.Element?>
|
let selection: Binding<Items.Element?>
|
||||||
let items: Items
|
let items: Items
|
||||||
|
|
||||||
|
|
|
@ -3,14 +3,14 @@ import SwiftUI
|
||||||
|
|
||||||
#if os(iOS) || os(macOS)
|
#if os(iOS) || os(macOS)
|
||||||
@available(macOS 10.15, *)
|
@available(macOS 10.15, *)
|
||||||
extension EnvironmentValues {
|
public extension EnvironmentValues {
|
||||||
/// An instance that tells StoreKit to request an App Store rating or review from the user, if appropriate.
|
/// An instance that tells StoreKit to request an App Store rating or review from the user, if appropriate.
|
||||||
/// Read the requestReview environment value to get an instance of this structure for a given Environment. Call the instance to tell StoreKit to ask the user to rate or review your app, if appropriate. You call the instance directly because it defines a callAsFunction() method that Swift calls when you call the instance.
|
/// Read the requestReview environment value to get an instance of this structure for a given Environment. Call the instance to tell StoreKit to ask the user to rate or review your app, if appropriate. You call the instance directly because it defines a callAsFunction() method that Swift calls when you call the instance.
|
||||||
///
|
///
|
||||||
/// Although you normally call this instance to request a review when it makes sense in the user experience flow of your app, the App Store policy governs the actual display of the rating and review request view. Because calling this instance may not present an alert, don’t call it in response to a user action, such as a button tap.
|
/// Although you normally call this instance to request a review when it makes sense in the user experience flow of your app, the App Store policy governs the actual display of the rating and review request view. Because calling this instance may not present an alert, don’t call it in response to a user action, such as a button tap.
|
||||||
///
|
///
|
||||||
/// > When you call this instance while your app is in development mode, the system always displays a rating and review request view so you can test the user interface and experience. This instance has no effect when you call it in an app that you distribute using TestFlight.
|
/// > When you call this instance while your app is in development mode, the system always displays a rating and review request view so you can test the user interface and experience. This instance has no effect when you call it in an app that you distribute using TestFlight.
|
||||||
@MainActor public var backportRequestReview: Backport<Any>.RequestReviewAction { .init() }
|
@MainActor var backportRequestReview: Backport<Any>.RequestReviewAction { .init() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An instance that tells StoreKit to request an App Store rating or review from the user, if appropriate.
|
/// An instance that tells StoreKit to request an App Store rating or review from the user, if appropriate.
|
||||||
|
|
|
@ -33,13 +33,13 @@ extension Backport where Wrapped == Any {
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(macOS 10.15, *)
|
@available(macOS 10.15, *)
|
||||||
extension Backport.Section where Wrapped == Any, Parent == Text, Footer == EmptyView {
|
public extension Backport.Section where Wrapped == Any, Parent == Text, Footer == EmptyView {
|
||||||
/// Creates a section with the provided section content.
|
/// Creates a section with the provided section content.
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - titleKey: The key for the section's localized title, which describes
|
/// - titleKey: The key for the section's localized title, which describes
|
||||||
/// the contents of the section.
|
/// the contents of the section.
|
||||||
/// - content: The section's content.
|
/// - content: The section's content.
|
||||||
public init(_ titleKey: LocalizedStringKey, @ViewBuilder content: @escaping () -> Content) {
|
init(_ titleKey: LocalizedStringKey, @ViewBuilder content: @escaping () -> Content) {
|
||||||
header = { Text(titleKey) }
|
header = { Text(titleKey) }
|
||||||
self.content = content
|
self.content = content
|
||||||
footer = { EmptyView() }
|
footer = { EmptyView() }
|
||||||
|
@ -50,7 +50,7 @@ extension Backport.Section where Wrapped == Any, Parent == Text, Footer == Empty
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - title: A string that describes the contents of the section.
|
/// - title: A string that describes the contents of the section.
|
||||||
/// - content: The section's content.
|
/// - content: The section's content.
|
||||||
public init<S>(_ title: S, @ViewBuilder content: @escaping () -> Content) where S: StringProtocol {
|
init<S>(_ title: S, @ViewBuilder content: @escaping () -> Content) where S: StringProtocol {
|
||||||
header = { Text(title) }
|
header = { Text(title) }
|
||||||
self.content = content
|
self.content = content
|
||||||
footer = { EmptyView() }
|
footer = { EmptyView() }
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
@available(macOS 10.15, *)
|
@available(macOS 10.15, *)
|
||||||
extension Backport where Wrapped == AnyTransition {
|
public extension Backport where Wrapped == AnyTransition {
|
||||||
/// Creates a transition that when added to a view will animate the view’s insertion by moving it in from the specified edge while fading it in, and animate its removal by moving it out towards the opposite edge and fading it out.
|
/// Creates a transition that when added to a view will animate the view’s insertion by moving it in from the specified edge while fading it in, and animate its removal by moving it out towards the opposite edge and fading it out.
|
||||||
/// - Parameter edge: the edge from which the view will be animated in.
|
/// - Parameter edge: the edge from which the view will be animated in.
|
||||||
/// - Returns: A transition that animates a view by moving and fading it.
|
/// - Returns: A transition that animates a view by moving and fading it.
|
||||||
|
@ -13,17 +13,17 @@ extension Backport where Wrapped == AnyTransition {
|
||||||
@available(watchOS, deprecated: 9.0)
|
@available(watchOS, deprecated: 9.0)
|
||||||
@available(macOS, deprecated: 13.0)
|
@available(macOS, deprecated: 13.0)
|
||||||
@available(tvOS, deprecated: 16.0)
|
@available(tvOS, deprecated: 16.0)
|
||||||
public func push(from edge: Edge) -> AnyTransition {
|
func push(from edge: Edge) -> AnyTransition {
|
||||||
var oppositeEdge: Edge
|
var oppositeEdge: Edge
|
||||||
switch edge {
|
switch edge {
|
||||||
case .top:
|
case .top:
|
||||||
oppositeEdge = .bottom
|
oppositeEdge = .bottom
|
||||||
case .leading:
|
case .leading:
|
||||||
oppositeEdge = .trailing
|
oppositeEdge = .trailing
|
||||||
case .bottom:
|
case .bottom:
|
||||||
oppositeEdge = .top
|
oppositeEdge = .top
|
||||||
case .trailing:
|
case .trailing:
|
||||||
oppositeEdge = .leading
|
oppositeEdge = .leading
|
||||||
}
|
}
|
||||||
|
|
||||||
return .asymmetric(
|
return .asymmetric(
|
||||||
|
|
|
@ -178,10 +178,10 @@ extension Backport where Wrapped == Any {
|
||||||
@available(macOS 10.15, *)
|
@available(macOS 10.15, *)
|
||||||
public static func < (lhs: PresentationDetent, rhs: PresentationDetent) -> Bool {
|
public static func < (lhs: PresentationDetent, rhs: PresentationDetent) -> Bool {
|
||||||
switch (lhs, rhs) {
|
switch (lhs, rhs) {
|
||||||
case (.large, .medium):
|
case (.large, .medium):
|
||||||
return false
|
return false
|
||||||
default:
|
default:
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -189,8 +189,8 @@ extension Backport where Wrapped == Any {
|
||||||
|
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
@available(iOS 15, *)
|
@available(iOS 15, *)
|
||||||
extension Backport where Wrapped == Any {
|
fileprivate extension Backport where Wrapped == Any {
|
||||||
fileprivate struct Representable: UIViewControllerRepresentable {
|
struct Representable: UIViewControllerRepresentable {
|
||||||
let detents: Set<Backport<Any>.PresentationDetent>
|
let detents: Set<Backport<Any>.PresentationDetent>
|
||||||
let selection: Binding<Backport<Any>.PresentationDetent>?
|
let selection: Binding<Backport<Any>.PresentationDetent>?
|
||||||
let largestUndimmed: Backport<Any>.PresentationDetent?
|
let largestUndimmed: Backport<Any>.PresentationDetent?
|
||||||
|
@ -207,8 +207,8 @@ extension Backport where Wrapped == Any {
|
||||||
|
|
||||||
@available(macOS 10.15, *)
|
@available(macOS 10.15, *)
|
||||||
@available(iOS 15, *)
|
@available(iOS 15, *)
|
||||||
extension Backport.Representable {
|
fileprivate extension Backport.Representable {
|
||||||
fileprivate final class Controller: UIViewController, UISheetPresentationControllerDelegate {
|
final class Controller: UIViewController, UISheetPresentationControllerDelegate {
|
||||||
var detents: Set<Backport<Any>.PresentationDetent>
|
var detents: Set<Backport<Any>.PresentationDetent>
|
||||||
var selection: Binding<Backport<Any>.PresentationDetent>?
|
var selection: Binding<Backport<Any>.PresentationDetent>?
|
||||||
var largestUndimmed: Backport<Any>.PresentationDetent?
|
var largestUndimmed: Backport<Any>.PresentationDetent?
|
||||||
|
@ -256,10 +256,10 @@ extension Backport where Wrapped == Any {
|
||||||
controller.animateChanges {
|
controller.animateChanges {
|
||||||
controller.detents = detents.sorted().map {
|
controller.detents = detents.sorted().map {
|
||||||
switch $0 {
|
switch $0 {
|
||||||
case .medium:
|
case .medium:
|
||||||
return .medium()
|
return .medium()
|
||||||
default:
|
default:
|
||||||
return .large()
|
return .large()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -48,8 +48,8 @@ extension Backport where Wrapped: View {
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
@available(iOS 15, *)
|
@available(iOS 15, *)
|
||||||
@available(macOS 10.15, *)
|
@available(macOS 10.15, *)
|
||||||
extension Backport where Wrapped == Any {
|
fileprivate extension Backport where Wrapped == Any {
|
||||||
fileprivate struct Representable: UIViewControllerRepresentable {
|
struct Representable: UIViewControllerRepresentable {
|
||||||
let visibility: Backport<Any>.Visibility
|
let visibility: Backport<Any>.Visibility
|
||||||
|
|
||||||
func makeUIViewController(context _: Context) -> Backport.Representable.Controller {
|
func makeUIViewController(context _: Context) -> Backport.Representable.Controller {
|
||||||
|
@ -64,8 +64,8 @@ extension Backport where Wrapped: View {
|
||||||
|
|
||||||
@available(macOS 10.15, *)
|
@available(macOS 10.15, *)
|
||||||
@available(iOS 15, *)
|
@available(iOS 15, *)
|
||||||
extension Backport.Representable {
|
fileprivate extension Backport.Representable {
|
||||||
fileprivate final class Controller: UIViewController {
|
final class Controller: UIViewController {
|
||||||
var visibility: Backport<Any>.Visibility
|
var visibility: Backport<Any>.Visibility
|
||||||
|
|
||||||
@available(macOS 10.15, *)
|
@available(macOS 10.15, *)
|
||||||
|
|
|
@ -53,8 +53,8 @@ extension Backport where Wrapped: View {
|
||||||
|
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
@available(iOS 15, *)
|
@available(iOS 15, *)
|
||||||
extension Backport where Wrapped == Any {
|
fileprivate extension Backport where Wrapped == Any {
|
||||||
fileprivate struct Representable: UIViewControllerRepresentable {
|
struct Representable: UIViewControllerRepresentable {
|
||||||
let identifier: Backport<Any>.PresentationDetent.Identifier?
|
let identifier: Backport<Any>.PresentationDetent.Identifier?
|
||||||
|
|
||||||
func makeUIViewController(context _: Context) -> Backport.Representable.Controller {
|
func makeUIViewController(context _: Context) -> Backport.Representable.Controller {
|
||||||
|
@ -68,8 +68,8 @@ extension Backport where Wrapped: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(iOS 15, *)
|
@available(iOS 15, *)
|
||||||
extension Backport.Representable {
|
fileprivate extension Backport.Representable {
|
||||||
fileprivate final class Controller: UIViewController {
|
final class Controller: UIViewController {
|
||||||
var identifier: Backport<Any>.PresentationDetent.Identifier?
|
var identifier: Backport<Any>.PresentationDetent.Identifier?
|
||||||
|
|
||||||
init(identifier: Backport<Any>.PresentationDetent.Identifier?) {
|
init(identifier: Backport<Any>.PresentationDetent.Identifier?) {
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
@available(macOS 10.15, *)
|
@available(macOS 10.15, *)
|
||||||
extension Backport where Wrapped: View {
|
public extension Backport where Wrapped: View {
|
||||||
/// Conditionally prevents interactive dismissal of a popover or a sheet.
|
/// Conditionally prevents interactive dismissal of a popover or a sheet.
|
||||||
///
|
///
|
||||||
/// Users can dismiss certain kinds of presentations using built-in
|
/// Users can dismiss certain kinds of presentations using built-in
|
||||||
|
@ -76,7 +76,7 @@ extension Backport where Wrapped: View {
|
||||||
@available(tvOS, deprecated: 16)
|
@available(tvOS, deprecated: 16)
|
||||||
@available(macOS, deprecated: 13)
|
@available(macOS, deprecated: 13)
|
||||||
@available(watchOS, deprecated: 9)
|
@available(watchOS, deprecated: 9)
|
||||||
public func interactiveDismissDisabled(_ isDisabled: Bool = true) -> some View {
|
func interactiveDismissDisabled(_ isDisabled: Bool = true) -> some View {
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
if #available(iOS 15, *) {
|
if #available(iOS 15, *) {
|
||||||
content.background(Backport<Any>.Representable(isModal: isDisabled, onAttempt: nil))
|
content.background(Backport<Any>.Representable(isModal: isDisabled, onAttempt: nil))
|
||||||
|
@ -157,7 +157,7 @@ extension Backport where Wrapped: View {
|
||||||
/// - Parameter onAttempt: A closure that will be called when an interactive dismiss attempt occurs.
|
/// - Parameter onAttempt: A closure that will be called when an interactive dismiss attempt occurs.
|
||||||
/// You can use this as an opportunity to present an confirmation or prompt to the user.
|
/// You can use this as an opportunity to present an confirmation or prompt to the user.
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
public func interactiveDismissDisabled(_ isDisabled: Bool = true, onAttempt: @escaping () -> Void) -> some View {
|
func interactiveDismissDisabled(_ isDisabled: Bool = true, onAttempt: @escaping () -> Void) -> some View {
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
if #available(iOS 15, *) {
|
if #available(iOS 15, *) {
|
||||||
content.background(Backport<Any>.Representable(isModal: isDisabled, onAttempt: onAttempt))
|
content.background(Backport<Any>.Representable(isModal: isDisabled, onAttempt: onAttempt))
|
||||||
|
@ -172,8 +172,8 @@ extension Backport where Wrapped: View {
|
||||||
|
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
@available(macOS 10.15, *)
|
@available(macOS 10.15, *)
|
||||||
extension Backport where Wrapped == Any {
|
fileprivate extension Backport where Wrapped == Any {
|
||||||
fileprivate struct Representable: UIViewControllerRepresentable {
|
struct Representable: UIViewControllerRepresentable {
|
||||||
let isModal: Bool
|
let isModal: Bool
|
||||||
let onAttempt: (() -> Void)?
|
let onAttempt: (() -> Void)?
|
||||||
|
|
||||||
|
@ -190,8 +190,8 @@ extension Backport where Wrapped: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(macOS 10.15, *)
|
@available(macOS 10.15, *)
|
||||||
extension Backport.Representable {
|
fileprivate extension Backport.Representable {
|
||||||
fileprivate final class Controller: UIViewController, UIAdaptivePresentationControllerDelegate {
|
final class Controller: UIViewController, UIAdaptivePresentationControllerDelegate {
|
||||||
var isModal: Bool
|
var isModal: Bool
|
||||||
var onAttempt: (() -> Void)?
|
var onAttempt: (() -> Void)?
|
||||||
weak var _delegate: UIAdaptivePresentationControllerDelegate?
|
weak var _delegate: UIAdaptivePresentationControllerDelegate?
|
||||||
|
|
|
@ -24,7 +24,7 @@ extension Backport where Wrapped == Any {
|
||||||
public var wrappedValue: Value {
|
public var wrappedValue: Value {
|
||||||
#if os(iOS) || os(tvOS)
|
#if os(iOS) || os(tvOS)
|
||||||
let traits = UITraitCollection(traitsFrom: [
|
let traits = UITraitCollection(traitsFrom: [
|
||||||
UITraitCollection(preferredContentSizeCategory: UIContentSizeCategory(sizeCategory: sizeCategory))
|
UITraitCollection(preferredContentSizeCategory: UIContentSizeCategory(sizeCategory: sizeCategory)),
|
||||||
])
|
])
|
||||||
|
|
||||||
return Value(metrics.scaledValue(for: CGFloat(baseValue), compatibleWith: traits))
|
return Value(metrics.scaledValue(for: CGFloat(baseValue), compatibleWith: traits))
|
||||||
|
@ -59,22 +59,22 @@ extension Backport where Wrapped == Any {
|
||||||
}
|
}
|
||||||
|
|
||||||
#if os(iOS) || os(tvOS)
|
#if os(iOS) || os(tvOS)
|
||||||
extension UIContentSizeCategory {
|
fileprivate extension UIContentSizeCategory {
|
||||||
fileprivate init(sizeCategory: ContentSizeCategory?) {
|
init(sizeCategory: ContentSizeCategory?) {
|
||||||
switch sizeCategory {
|
switch sizeCategory {
|
||||||
case .accessibilityExtraExtraExtraLarge: self = .accessibilityExtraExtraExtraLarge
|
case .accessibilityExtraExtraExtraLarge: self = .accessibilityExtraExtraExtraLarge
|
||||||
case .accessibilityExtraExtraLarge: self = .accessibilityExtraExtraLarge
|
case .accessibilityExtraExtraLarge: self = .accessibilityExtraExtraLarge
|
||||||
case .accessibilityExtraLarge: self = .accessibilityExtraLarge
|
case .accessibilityExtraLarge: self = .accessibilityExtraLarge
|
||||||
case .accessibilityLarge: self = .accessibilityLarge
|
case .accessibilityLarge: self = .accessibilityLarge
|
||||||
case .accessibilityMedium: self = .accessibilityMedium
|
case .accessibilityMedium: self = .accessibilityMedium
|
||||||
case .extraExtraExtraLarge: self = .extraExtraExtraLarge
|
case .extraExtraExtraLarge: self = .extraExtraExtraLarge
|
||||||
case .extraExtraLarge: self = .extraExtraLarge
|
case .extraExtraLarge: self = .extraExtraLarge
|
||||||
case .extraLarge: self = .extraLarge
|
case .extraLarge: self = .extraLarge
|
||||||
case .extraSmall: self = .extraSmall
|
case .extraSmall: self = .extraSmall
|
||||||
case .large: self = .large
|
case .large: self = .large
|
||||||
case .medium: self = .medium
|
case .medium: self = .medium
|
||||||
case .small: self = .small
|
case .small: self = .small
|
||||||
default: self = .unspecified
|
default: self = .unspecified
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,10 +27,10 @@ extension Backport where Wrapped == Any {
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
var scrollViewDismissMode: UIScrollView.KeyboardDismissMode {
|
var scrollViewDismissMode: UIScrollView.KeyboardDismissMode {
|
||||||
switch dismissMode {
|
switch dismissMode {
|
||||||
case .automatic: return .none
|
case .automatic: return .none
|
||||||
case .immediately: return .onDrag
|
case .immediately: return .onDrag
|
||||||
case .interactively: return .interactive
|
case .interactively: return .interactive
|
||||||
case .never: return .none
|
case .never: return .none
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -22,7 +22,7 @@ import SwiftUI
|
||||||
/// Setting a content configuration replaces the existing contentView of the
|
/// Setting a content configuration replaces the existing contentView of the
|
||||||
/// cell with a new content view instance from the configuration.
|
/// cell with a new content view instance from the configuration.
|
||||||
public var contentConfiguration: BackportUIContentConfiguration? {
|
public var contentConfiguration: BackportUIContentConfiguration? {
|
||||||
get { nil } // we can't really support anything here, so for now we'll return nil
|
get { nil } // we can't really support anything here, so for now we'll return nil
|
||||||
nonmutating set {
|
nonmutating set {
|
||||||
content.configuredView?.removeFromSuperview()
|
content.configuredView?.removeFromSuperview()
|
||||||
|
|
||||||
|
@ -39,8 +39,8 @@ import SwiftUI
|
||||||
|
|
||||||
let insets =
|
let insets =
|
||||||
Mirror(reflecting: configuration)
|
Mirror(reflecting: configuration)
|
||||||
.children.first(where: { $0.label == "insets" })?.value as? ProposedInsets
|
.children.first(where: { $0.label == "insets" })?.value as? ProposedInsets
|
||||||
?? .unspecified
|
?? .unspecified
|
||||||
|
|
||||||
insets.top.flatMap { contentView.directionalLayoutMargins.top = $0 }
|
insets.top.flatMap { contentView.directionalLayoutMargins.top = $0 }
|
||||||
insets.bottom.flatMap { contentView.directionalLayoutMargins.bottom = $0 }
|
insets.bottom.flatMap { contentView.directionalLayoutMargins.bottom = $0 }
|
||||||
|
|
|
@ -22,7 +22,7 @@ import SwiftUI
|
||||||
/// Setting a content configuration replaces the existing contentView of the
|
/// Setting a content configuration replaces the existing contentView of the
|
||||||
/// cell with a new content view instance from the configuration.
|
/// cell with a new content view instance from the configuration.
|
||||||
public var contentConfiguration: BackportUIContentConfiguration? {
|
public var contentConfiguration: BackportUIContentConfiguration? {
|
||||||
get { nil } // we can't really support anything here, so for now we'll return nil
|
get { nil } // we can't really support anything here, so for now we'll return nil
|
||||||
nonmutating set {
|
nonmutating set {
|
||||||
content.configuredView?.removeFromSuperview()
|
content.configuredView?.removeFromSuperview()
|
||||||
|
|
||||||
|
@ -39,8 +39,8 @@ import SwiftUI
|
||||||
|
|
||||||
let insets =
|
let insets =
|
||||||
Mirror(reflecting: configuration)
|
Mirror(reflecting: configuration)
|
||||||
.children.first(where: { $0.label == "insets" })?.value as? ProposedInsets
|
.children.first(where: { $0.label == "insets" })?.value as? ProposedInsets
|
||||||
?? .unspecified
|
?? .unspecified
|
||||||
|
|
||||||
insets.top.flatMap { contentView.directionalLayoutMargins.top = $0 }
|
insets.top.flatMap { contentView.directionalLayoutMargins.top = $0 }
|
||||||
insets.bottom.flatMap { contentView.directionalLayoutMargins.bottom = $0 }
|
insets.bottom.flatMap { contentView.directionalLayoutMargins.bottom = $0 }
|
||||||
|
|
|
@ -36,7 +36,8 @@ import SwiftUI
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
public struct UIHostingConfiguration<Label, Background>: BackportUIContentConfiguration
|
public struct UIHostingConfiguration<Label, Background>: BackportUIContentConfiguration
|
||||||
where Label: View, Background: View {
|
where Label: View, Background: View
|
||||||
|
{
|
||||||
var content: Label
|
var content: Label
|
||||||
var background: AnyView?
|
var background: AnyView?
|
||||||
var insets: ProposedInsets
|
var insets: ProposedInsets
|
||||||
|
@ -57,7 +58,8 @@ import SwiftUI
|
||||||
/// - Parameter background: The contents of the SwiftUI hierarchy to be
|
/// - Parameter background: The contents of the SwiftUI hierarchy to be
|
||||||
/// shown inside the background of the cell.
|
/// shown inside the background of the cell.
|
||||||
public func background<B>(@ViewBuilder background: () -> B) -> Backport.UIHostingConfiguration<Label, B>
|
public func background<B>(@ViewBuilder background: () -> B) -> Backport.UIHostingConfiguration<Label, B>
|
||||||
where B: View {
|
where B: View
|
||||||
|
{
|
||||||
.init(content: content, background: AnyView(background()), insets: insets, minSize: minSize)
|
.init(content: content, background: AnyView(background()), insets: insets, minSize: minSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,7 +97,7 @@ import SwiftUI
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Backport.UIHostingConfiguration {
|
public extension Backport.UIHostingConfiguration {
|
||||||
/// Sets the margins around the content of the configuration.
|
/// Sets the margins around the content of the configuration.
|
||||||
///
|
///
|
||||||
/// Use this modifier to replace the default margins applied to the root of
|
/// Use this modifier to replace the default margins applied to the root of
|
||||||
|
@ -112,7 +114,7 @@ import SwiftUI
|
||||||
/// use the system default values. The default value is
|
/// use the system default values. The default value is
|
||||||
/// ``Edge/Set/all``.
|
/// ``Edge/Set/all``.
|
||||||
/// - length: The amount to apply.
|
/// - length: The amount to apply.
|
||||||
public func margins(_ edges: Edge.Set = .all, _ length: CGFloat) -> Self {
|
func margins(_ edges: Edge.Set = .all, _ length: CGFloat) -> Self {
|
||||||
var view = self
|
var view = self
|
||||||
if edges.contains(.leading) { view.insets.leading = length }
|
if edges.contains(.leading) { view.insets.leading = length }
|
||||||
if edges.contains(.trailing) { view.insets.trailing = length }
|
if edges.contains(.trailing) { view.insets.trailing = length }
|
||||||
|
@ -138,7 +140,7 @@ import SwiftUI
|
||||||
/// use the system default values. The default value is
|
/// use the system default values. The default value is
|
||||||
/// ``Edge/Set/all``.
|
/// ``Edge/Set/all``.
|
||||||
/// - insets: The insets to apply.
|
/// - insets: The insets to apply.
|
||||||
public func margins(_ edges: Edge.Set = .all, _ insets: EdgeInsets) -> Self {
|
func margins(_ edges: Edge.Set = .all, _ insets: EdgeInsets) -> Self {
|
||||||
var view = self
|
var view = self
|
||||||
if edges.contains(.leading) { view.insets.leading = insets.leading }
|
if edges.contains(.leading) { view.insets.leading = insets.leading }
|
||||||
if edges.contains(.trailing) { view.insets.trailing = insets.trailing }
|
if edges.contains(.trailing) { view.insets.trailing = insets.trailing }
|
||||||
|
|
|
@ -4,19 +4,19 @@ import PackageDescription
|
||||||
let package = Package(
|
let package = Package(
|
||||||
name: "SSPreferences",
|
name: "SSPreferences",
|
||||||
platforms: [
|
platforms: [
|
||||||
.macOS(.v10_11)
|
.macOS(.v10_11),
|
||||||
],
|
],
|
||||||
products: [
|
products: [
|
||||||
.library(
|
.library(
|
||||||
name: "SSPreferences",
|
name: "SSPreferences",
|
||||||
targets: [
|
targets: [
|
||||||
"SSPreferences"
|
"SSPreferences",
|
||||||
]
|
]
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
targets: [
|
targets: [
|
||||||
.target(
|
.target(
|
||||||
name: "SSPreferences"
|
name: "SSPreferences"
|
||||||
)
|
),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
|
@ -5,12 +5,12 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
@available(macOS 10.15, *)
|
@available(macOS 10.15, *)
|
||||||
extension SSPreferences {
|
public extension SSPreferences {
|
||||||
/**
|
/**
|
||||||
Function builder for `Preferences` components used in order to restrict types of child views to be of type `Section`.
|
Function builder for `Preferences` components used in order to restrict types of child views to be of type `Section`.
|
||||||
*/
|
*/
|
||||||
@resultBuilder
|
@resultBuilder
|
||||||
public enum SectionBuilder {
|
enum SectionBuilder {
|
||||||
public static func buildBlock(_ sections: Section...) -> [Section] {
|
public static func buildBlock(_ sections: Section...) -> [Section] {
|
||||||
sections
|
sections
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ extension SSPreferences {
|
||||||
/**
|
/**
|
||||||
A view which holds `Preferences.Section` views and does all the alignment magic similar to `NSGridView` from AppKit.
|
A view which holds `Preferences.Section` views and does all the alignment magic similar to `NSGridView` from AppKit.
|
||||||
*/
|
*/
|
||||||
public struct Container: View {
|
struct Container: View {
|
||||||
private let sectionBuilder: () -> [Section]
|
private let sectionBuilder: () -> [Section]
|
||||||
private let contentWidth: Double
|
private let contentWidth: Double
|
||||||
private let minimumLabelWidth: Double
|
private let minimumLabelWidth: Double
|
||||||
|
@ -49,7 +49,7 @@ extension SSPreferences {
|
||||||
let sections = sectionBuilder()
|
let sections = sectionBuilder()
|
||||||
|
|
||||||
return VStack(alignment: .preferenceSectionLabel) {
|
return VStack(alignment: .preferenceSectionLabel) {
|
||||||
ForEach(0..<sections.count, id: \.self) { index in
|
ForEach(0 ..< sections.count, id: \.self) { index in
|
||||||
viewForSection(sections, index: index)
|
viewForSection(sections, index: index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -110,10 +110,10 @@ struct Localization {
|
||||||
// Iterate through all user-preferred languages until we find one that has a valid language code.
|
// Iterate through all user-preferred languages until we find one that has a valid language code.
|
||||||
let preferredLocale =
|
let preferredLocale =
|
||||||
Locale.preferredLanguages
|
Locale.preferredLanguages
|
||||||
.lazy
|
.lazy
|
||||||
.map { Locale(identifier: $0) }
|
.map { Locale(identifier: $0) }
|
||||||
.first { $0.languageCode != nil }
|
.first { $0.languageCode != nil }
|
||||||
?? .current
|
?? .current
|
||||||
|
|
||||||
guard let languageCode = preferredLocale.languageCode else {
|
guard let languageCode = preferredLocale.languageCode else {
|
||||||
return defaultLocalizedString
|
return defaultLocalizedString
|
||||||
|
|
|
@ -15,13 +15,13 @@ public protocol PreferencePaneConvertible {
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(macOS 10.15, *)
|
@available(macOS 10.15, *)
|
||||||
extension SSPreferences {
|
public extension SSPreferences {
|
||||||
/**
|
/**
|
||||||
Create a SwiftUI-based preference pane.
|
Create a SwiftUI-based preference pane.
|
||||||
|
|
||||||
SwiftUI equivalent of the `PreferencePane` protocol.
|
SwiftUI equivalent of the `PreferencePane` protocol.
|
||||||
*/
|
*/
|
||||||
public struct Pane<Content: View>: View, PreferencePaneConvertible {
|
struct Pane<Content: View>: View, PreferencePaneConvertible {
|
||||||
let identifier: PaneIdentifier
|
let identifier: PaneIdentifier
|
||||||
let title: String
|
let title: String
|
||||||
let toolbarIcon: NSImage
|
let toolbarIcon: NSImage
|
||||||
|
@ -49,7 +49,7 @@ extension SSPreferences {
|
||||||
/**
|
/**
|
||||||
Hosting controller enabling `Preferences.Pane` to be used alongside AppKit `NSViewController`'s.
|
Hosting controller enabling `Preferences.Pane` to be used alongside AppKit `NSViewController`'s.
|
||||||
*/
|
*/
|
||||||
public final class PaneHostingController<Content: View>: NSHostingController<Content>, PreferencePane {
|
final class PaneHostingController<Content: View>: NSHostingController<Content>, PreferencePane {
|
||||||
public let preferencePaneIdentifier: PaneIdentifier
|
public let preferencePaneIdentifier: PaneIdentifier
|
||||||
public let preferencePaneTitle: String
|
public let preferencePaneTitle: String
|
||||||
public let toolbarItemIcon: NSImage
|
public let toolbarItemIcon: NSImage
|
||||||
|
@ -84,11 +84,11 @@ extension SSPreferences {
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(macOS 10.15, *)
|
@available(macOS 10.15, *)
|
||||||
extension View {
|
public extension View {
|
||||||
/**
|
/**
|
||||||
Applies font and color for a label used for describing a preference.
|
Applies font and color for a label used for describing a preference.
|
||||||
*/
|
*/
|
||||||
public func preferenceDescription() -> some View {
|
func preferenceDescription() -> some View {
|
||||||
font(.system(size: 11.0))
|
font(.system(size: 11.0))
|
||||||
// TODO: Use `.foregroundStyle` when targeting macOS 12.
|
// TODO: Use `.foregroundStyle` when targeting macOS 12.
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
|
|
|
@ -4,8 +4,8 @@
|
||||||
|
|
||||||
import Cocoa
|
import Cocoa
|
||||||
|
|
||||||
extension SSPreferences {
|
public extension SSPreferences {
|
||||||
public struct PaneIdentifier: Hashable, RawRepresentable, Codable {
|
struct PaneIdentifier: Hashable, RawRepresentable, Codable {
|
||||||
public let rawValue: String
|
public let rawValue: String
|
||||||
|
|
||||||
public init(rawValue: String) {
|
public init(rawValue: String) {
|
||||||
|
@ -20,24 +20,24 @@ public protocol PreferencePane: NSViewController {
|
||||||
var toolbarItemIcon: NSImage { get }
|
var toolbarItemIcon: NSImage { get }
|
||||||
}
|
}
|
||||||
|
|
||||||
extension PreferencePane {
|
public extension PreferencePane {
|
||||||
public var toolbarItemIdentifier: NSToolbarItem.Identifier {
|
var toolbarItemIdentifier: NSToolbarItem.Identifier {
|
||||||
preferencePaneIdentifier.toolbarItemIdentifier
|
preferencePaneIdentifier.toolbarItemIdentifier
|
||||||
}
|
}
|
||||||
|
|
||||||
public var toolbarItemIcon: NSImage { .empty }
|
var toolbarItemIcon: NSImage { .empty }
|
||||||
}
|
}
|
||||||
|
|
||||||
extension SSPreferences.PaneIdentifier {
|
public extension SSPreferences.PaneIdentifier {
|
||||||
public init(_ rawValue: String) {
|
init(_ rawValue: String) {
|
||||||
self.init(rawValue: rawValue)
|
self.init(rawValue: rawValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(fromToolbarItemIdentifier itemIdentifier: NSToolbarItem.Identifier) {
|
init(fromToolbarItemIdentifier itemIdentifier: NSToolbarItem.Identifier) {
|
||||||
self.init(rawValue: itemIdentifier.rawValue)
|
self.init(rawValue: itemIdentifier.rawValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
public var toolbarItemIdentifier: NSToolbarItem.Identifier {
|
var toolbarItemIdentifier: NSToolbarItem.Identifier {
|
||||||
NSToolbarItem.Identifier(rawValue)
|
NSToolbarItem.Identifier(rawValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,8 @@
|
||||||
|
|
||||||
import Cocoa
|
import Cocoa
|
||||||
|
|
||||||
extension SSPreferences {
|
public extension SSPreferences {
|
||||||
public enum Style {
|
enum Style {
|
||||||
case toolbarItems
|
case toolbarItems
|
||||||
case segmentedControl
|
case segmentedControl
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,14 +45,14 @@ final class PreferencesTabViewController: NSViewController, PreferencesStyleCont
|
||||||
toolbar.delegate = self
|
toolbar.delegate = self
|
||||||
|
|
||||||
switch style {
|
switch style {
|
||||||
case .segmentedControl:
|
case .segmentedControl:
|
||||||
preferencesStyleController = SegmentedControlStyleViewController(preferencePanes: preferencePanes)
|
preferencesStyleController = SegmentedControlStyleViewController(preferencePanes: preferencePanes)
|
||||||
case .toolbarItems:
|
case .toolbarItems:
|
||||||
preferencesStyleController = ToolbarItemStyleViewController(
|
preferencesStyleController = ToolbarItemStyleViewController(
|
||||||
preferencePanes: preferencePanes,
|
preferencePanes: preferencePanes,
|
||||||
toolbar: toolbar,
|
toolbar: toolbar,
|
||||||
centerToolbarItems: false
|
centerToolbarItems: false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
preferencesStyleController.delegate = self
|
preferencesStyleController.delegate = self
|
||||||
|
|
||||||
|
@ -159,33 +159,31 @@ final class PreferencesTabViewController: NSViewController, PreferencesStyleCont
|
||||||
) {
|
) {
|
||||||
let isAnimated =
|
let isAnimated =
|
||||||
options
|
options
|
||||||
.intersection([
|
.intersection([
|
||||||
.crossfade,
|
.crossfade,
|
||||||
.slideUp,
|
.slideUp,
|
||||||
.slideDown,
|
.slideDown,
|
||||||
.slideForward,
|
.slideForward,
|
||||||
.slideBackward,
|
.slideBackward,
|
||||||
.slideLeft,
|
.slideLeft,
|
||||||
.slideRight,
|
.slideRight,
|
||||||
])
|
])
|
||||||
.isEmpty == false
|
.isEmpty == false
|
||||||
|
|
||||||
if isAnimated {
|
if isAnimated {
|
||||||
NSAnimationContext.runAnimationGroup(
|
NSAnimationContext.runAnimationGroup({ context in
|
||||||
{ context in
|
context.allowsImplicitAnimation = true
|
||||||
context.allowsImplicitAnimation = true
|
context.duration = 0.25
|
||||||
context.duration = 0.25
|
context.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
|
||||||
context.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
|
setWindowFrame(for: toViewController, animated: true)
|
||||||
setWindowFrame(for: toViewController, animated: true)
|
|
||||||
|
|
||||||
super.transition(
|
super.transition(
|
||||||
from: fromViewController,
|
from: fromViewController,
|
||||||
to: toViewController,
|
to: toViewController,
|
||||||
options: options,
|
options: options,
|
||||||
completionHandler: completion
|
completionHandler: completion
|
||||||
)
|
)
|
||||||
}, completionHandler: nil
|
}, completionHandler: nil)
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
super.transition(
|
super.transition(
|
||||||
from: fromViewController,
|
from: fromViewController,
|
||||||
|
|
|
@ -27,7 +27,7 @@ public final class PreferencesWindowController: NSWindowController {
|
||||||
private func updateToolbarVisibility() {
|
private func updateToolbarVisibility() {
|
||||||
window?.toolbar?.isVisible =
|
window?.toolbar?.isVisible =
|
||||||
(hidesToolbarForSingleItem == false)
|
(hidesToolbarForSingleItem == false)
|
||||||
|| (tabViewController.preferencePanesCount > 1)
|
|| (tabViewController.preferencePanesCount > 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
|
@ -54,10 +54,10 @@ public final class PreferencesWindowController: NSWindowController {
|
||||||
|
|
||||||
window.titleVisibility = {
|
window.titleVisibility = {
|
||||||
switch style {
|
switch style {
|
||||||
case .toolbarItems:
|
case .toolbarItems:
|
||||||
return .visible
|
return .visible
|
||||||
case .segmentedControl:
|
case .segmentedControl:
|
||||||
return preferencePanes.count <= 1 ? .visible : .hidden
|
return preferencePanes.count <= 1 ? .visible : .hidden
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -122,9 +122,9 @@ public final class PreferencesWindowController: NSWindowController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension PreferencesWindowController {
|
public extension PreferencesWindowController {
|
||||||
/// Returns the active pane if it responds to the given action.
|
/// Returns the active pane if it responds to the given action.
|
||||||
public override func supplementalTarget(forAction action: Selector, sender: Any?) -> Any? {
|
override func supplementalTarget(forAction action: Selector, sender: Any?) -> Any? {
|
||||||
if let target = super.supplementalTarget(forAction: action, sender: sender) {
|
if let target = super.supplementalTarget(forAction: action, sender: sender) {
|
||||||
return target
|
return target
|
||||||
}
|
}
|
||||||
|
@ -134,13 +134,13 @@ extension PreferencesWindowController {
|
||||||
}
|
}
|
||||||
|
|
||||||
if let target = NSApp.target(forAction: action, to: activeViewController, from: sender) as? NSResponder,
|
if let target = NSApp.target(forAction: action, to: activeViewController, from: sender) as? NSResponder,
|
||||||
target.responds(to: action)
|
target.responds(to: action)
|
||||||
{
|
{
|
||||||
return target
|
return target
|
||||||
}
|
}
|
||||||
|
|
||||||
if let target = activeViewController.supplementalTarget(forAction: action, sender: sender) as? NSResponder,
|
if let target = activeViewController.supplementalTarget(forAction: action, sender: sender) as? NSResponder,
|
||||||
target.responds(to: action)
|
target.responds(to: action)
|
||||||
{
|
{
|
||||||
return target
|
return target
|
||||||
}
|
}
|
||||||
|
@ -150,11 +150,11 @@ extension PreferencesWindowController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(macOS 10.15, *)
|
@available(macOS 10.15, *)
|
||||||
extension PreferencesWindowController {
|
public extension PreferencesWindowController {
|
||||||
/**
|
/**
|
||||||
Create a preferences window from only SwiftUI-based preference panes.
|
Create a preferences window from only SwiftUI-based preference panes.
|
||||||
*/
|
*/
|
||||||
public convenience init(
|
convenience init(
|
||||||
panes: [PreferencePaneConvertible],
|
panes: [PreferencePaneConvertible],
|
||||||
style: SSPreferences.Style = .toolbarItems,
|
style: SSPreferences.Style = .toolbarItems,
|
||||||
animated: Bool = true,
|
animated: Bool = true,
|
||||||
|
|
|
@ -5,12 +5,12 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
@available(macOS 10.15, *)
|
@available(macOS 10.15, *)
|
||||||
extension SSPreferences {
|
public extension SSPreferences {
|
||||||
/**
|
/**
|
||||||
Represents a section with right-aligned title and optional bottom divider.
|
Represents a section with right-aligned title and optional bottom divider.
|
||||||
*/
|
*/
|
||||||
@available(macOS 10.15, *)
|
@available(macOS 10.15, *)
|
||||||
public struct Section: View {
|
struct Section: View {
|
||||||
/**
|
/**
|
||||||
Preference key holding max width of section labels.
|
Preference key holding max width of section labels.
|
||||||
*/
|
*/
|
||||||
|
@ -74,7 +74,7 @@ extension SSPreferences {
|
||||||
) {
|
) {
|
||||||
self.label = label()
|
self.label = label()
|
||||||
.overlay(LabelOverlay())
|
.overlay(LabelOverlay())
|
||||||
.eraseToAnyView() // TODO: Remove use of `AnyView`.
|
.eraseToAnyView() // TODO: Remove use of `AnyView`.
|
||||||
self.bottomDivider = bottomDivider
|
self.bottomDivider = bottomDivider
|
||||||
self.verticalAlignment = verticalAlignment
|
self.verticalAlignment = verticalAlignment
|
||||||
let stack = VStack(alignment: .leading) { content() }
|
let stack = VStack(alignment: .leading) { content() }
|
||||||
|
|
|
@ -61,7 +61,7 @@ final class SegmentedControlStyleViewController: NSViewController, PreferencesSt
|
||||||
let title = preferencePane.preferencePaneTitle
|
let title = preferencePane.preferencePaneTitle
|
||||||
let titleSize = title.size(
|
let titleSize = title.size(
|
||||||
withAttributes: [
|
withAttributes: [
|
||||||
.font: NSFont.systemFont(ofSize: NSFont.systemFontSize(for: .regular))
|
.font: NSFont.systemFont(ofSize: NSFont.systemFontSize(for: .regular)),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -4,13 +4,13 @@ import PackageDescription
|
||||||
let package = Package(
|
let package = Package(
|
||||||
name: "CandidateWindow",
|
name: "CandidateWindow",
|
||||||
platforms: [
|
platforms: [
|
||||||
.macOS(.v10_11)
|
.macOS(.v10_11),
|
||||||
],
|
],
|
||||||
products: [
|
products: [
|
||||||
.library(
|
.library(
|
||||||
name: "CandidateWindow",
|
name: "CandidateWindow",
|
||||||
targets: ["CandidateWindow"]
|
targets: ["CandidateWindow"]
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
.package(path: "../vChewing_Shared"),
|
.package(path: "../vChewing_Shared"),
|
||||||
|
|
|
@ -28,8 +28,8 @@ public class CandidateCellData: Hashable {
|
||||||
public var displayedText: String
|
public var displayedText: String
|
||||||
public var size: Double { Self.unifiedSize }
|
public var size: Double { Self.unifiedSize }
|
||||||
public var isSelected: Bool = false
|
public var isSelected: Bool = false
|
||||||
public var whichRow: Int = 0 // 橫排選字窗專用
|
public var whichRow: Int = 0 // 橫排選字窗專用
|
||||||
public var whichColumn: Int = 0 // 縱排選字窗專用
|
public var whichColumn: Int = 0 // 縱排選字窗專用
|
||||||
public var index: Int = 0
|
public var index: Int = 0
|
||||||
public var subIndex: Int = 0
|
public var subIndex: Int = 0
|
||||||
|
|
||||||
|
@ -149,8 +149,8 @@ public class CandidateCellData: Hashable {
|
||||||
// MARK: - Contents specifically made for macOS 12 and newer.
|
// MARK: - Contents specifically made for macOS 12 and newer.
|
||||||
|
|
||||||
@available(macOS 12, *)
|
@available(macOS 12, *)
|
||||||
extension CandidateCellData {
|
public extension CandidateCellData {
|
||||||
public var attributedStringForSwiftUI: some View {
|
var attributedStringForSwiftUI: some View {
|
||||||
var result: some View {
|
var result: some View {
|
||||||
ZStack(alignment: .leading) {
|
ZStack(alignment: .leading) {
|
||||||
if isSelected {
|
if isSelected {
|
||||||
|
@ -179,21 +179,21 @@ extension CandidateCellData {
|
||||||
// MARK: - Contents specifically made for macOS 10.15 and macOS 11.
|
// MARK: - Contents specifically made for macOS 10.15 and macOS 11.
|
||||||
|
|
||||||
@available(macOS 10.15, *)
|
@available(macOS 10.15, *)
|
||||||
extension CandidateCellData {
|
public extension CandidateCellData {
|
||||||
public var themeColorBackports: some View {
|
var themeColorBackports: some View {
|
||||||
// 設定當前高亮候選字的背景顏色。
|
// 設定當前高亮候選字的背景顏色。
|
||||||
let result: Color = {
|
let result: Color = {
|
||||||
switch locale {
|
switch locale {
|
||||||
case "zh-Hans": return Color.red
|
case "zh-Hans": return Color.red
|
||||||
case "zh-Hant": return Color.blue
|
case "zh-Hant": return Color.blue
|
||||||
case "ja": return Color.pink
|
case "ja": return Color.pink
|
||||||
default: return Color.accentColor
|
default: return Color.accentColor
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
return result.opacity(0.85)
|
return result.opacity(0.85)
|
||||||
}
|
}
|
||||||
|
|
||||||
public var attributedStringForSwiftUIBackports: some View {
|
var attributedStringForSwiftUIBackports: some View {
|
||||||
var result: some View {
|
var result: some View {
|
||||||
ZStack(alignment: .leading) {
|
ZStack(alignment: .leading) {
|
||||||
if isSelected {
|
if isSelected {
|
||||||
|
|
|
@ -38,73 +38,73 @@ public struct CandidatePool {
|
||||||
|
|
||||||
public var currentLineNumber: Int {
|
public var currentLineNumber: Int {
|
||||||
switch currentLayout {
|
switch currentLayout {
|
||||||
case .horizontal:
|
case .horizontal:
|
||||||
return currentRowNumber
|
return currentRowNumber
|
||||||
case .vertical:
|
case .vertical:
|
||||||
return currentColumnNumber
|
return currentColumnNumber
|
||||||
@unknown default:
|
@unknown default:
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public var candidateLines: [[CandidateCellData]] {
|
public var candidateLines: [[CandidateCellData]] {
|
||||||
switch currentLayout {
|
switch currentLayout {
|
||||||
case .horizontal:
|
case .horizontal:
|
||||||
return candidateRows
|
return candidateRows
|
||||||
case .vertical:
|
case .vertical:
|
||||||
return candidateColumns
|
return candidateColumns
|
||||||
@unknown default:
|
@unknown default:
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public var maxLineCapacity: Int {
|
public var maxLineCapacity: Int {
|
||||||
switch currentLayout {
|
switch currentLayout {
|
||||||
case .horizontal:
|
case .horizontal:
|
||||||
return maxRowCapacity
|
return maxRowCapacity
|
||||||
case .vertical:
|
case .vertical:
|
||||||
return maxColumnCapacity
|
return maxColumnCapacity
|
||||||
@unknown default:
|
@unknown default:
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public var maxLinesPerPage: Int {
|
public var maxLinesPerPage: Int {
|
||||||
get {
|
get {
|
||||||
switch currentLayout {
|
switch currentLayout {
|
||||||
case .horizontal:
|
case .horizontal:
|
||||||
return maxRowsPerPage
|
return maxRowsPerPage
|
||||||
case .vertical:
|
case .vertical:
|
||||||
return maxColumnsPerPage
|
return maxColumnsPerPage
|
||||||
@unknown default:
|
@unknown default:
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
set {
|
set {
|
||||||
switch currentLayout {
|
switch currentLayout {
|
||||||
case .horizontal:
|
case .horizontal:
|
||||||
maxRowsPerPage = newValue
|
maxRowsPerPage = newValue
|
||||||
case .vertical:
|
case .vertical:
|
||||||
maxColumnsPerPage = newValue
|
maxColumnsPerPage = newValue
|
||||||
@unknown default:
|
@unknown default:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public var rangeForLastPageBlanked: Range<Int> {
|
public var rangeForLastPageBlanked: Range<Int> {
|
||||||
switch currentLayout {
|
switch currentLayout {
|
||||||
case .horizontal: return rangeForLastHorizontalPageBlanked
|
case .horizontal: return rangeForLastHorizontalPageBlanked
|
||||||
case .vertical: return rangeForLastVerticalPageBlanked
|
case .vertical: return rangeForLastVerticalPageBlanked
|
||||||
@unknown default: return 0..<0
|
@unknown default: return 0 ..< 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public var rangeForCurrentPage: Range<Int> {
|
public var rangeForCurrentPage: Range<Int> {
|
||||||
switch currentLayout {
|
switch currentLayout {
|
||||||
case .horizontal: return rangeForCurrentHorizontalPage
|
case .horizontal: return rangeForCurrentHorizontalPage
|
||||||
case .vertical: return rangeForCurrentVerticalPage
|
case .vertical: return rangeForCurrentVerticalPage
|
||||||
@unknown default: return 0..<0
|
@unknown default: return 0 ..< 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,17 +176,17 @@ public struct CandidatePool {
|
||||||
|
|
||||||
public mutating func selectNewNeighborLine(isForward: Bool) {
|
public mutating func selectNewNeighborLine(isForward: Bool) {
|
||||||
switch currentLayout {
|
switch currentLayout {
|
||||||
case .horizontal: selectNewNeighborRow(direction: isForward ? .down : .up)
|
case .horizontal: selectNewNeighborRow(direction: isForward ? .down : .up)
|
||||||
case .vertical: selectNewNeighborColumn(direction: isForward ? .right : .left)
|
case .vertical: selectNewNeighborColumn(direction: isForward ? .right : .left)
|
||||||
@unknown default: break
|
@unknown default: break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public mutating func highlight(at indexSpecified: Int) {
|
public mutating func highlight(at indexSpecified: Int) {
|
||||||
switch currentLayout {
|
switch currentLayout {
|
||||||
case .horizontal: highlightHorizontal(at: indexSpecified)
|
case .horizontal: highlightHorizontal(at: indexSpecified)
|
||||||
case .vertical: highlightVertical(at: indexSpecified)
|
case .vertical: highlightVertical(at: indexSpecified)
|
||||||
@unknown default: break
|
@unknown default: break
|
||||||
}
|
}
|
||||||
vCLog("\n" + candidateDataAll[highlightedIndex].charDescriptions)
|
vCLog("\n" + candidateDataAll[highlightedIndex].charDescriptions)
|
||||||
}
|
}
|
||||||
|
@ -206,102 +206,102 @@ extension CandidatePool {
|
||||||
}
|
}
|
||||||
|
|
||||||
private var rangeForLastHorizontalPageBlanked: Range<Int> {
|
private var rangeForLastHorizontalPageBlanked: Range<Int> {
|
||||||
0..<(maxRowsPerPage - rangeForCurrentHorizontalPage.count)
|
0 ..< (maxRowsPerPage - rangeForCurrentHorizontalPage.count)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var rangeForLastVerticalPageBlanked: Range<Int> {
|
private var rangeForLastVerticalPageBlanked: Range<Int> {
|
||||||
0..<(maxColumnsPerPage - rangeForCurrentVerticalPage.count)
|
0 ..< (maxColumnsPerPage - rangeForCurrentVerticalPage.count)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var rangeForCurrentHorizontalPage: Range<Int> {
|
private var rangeForCurrentHorizontalPage: Range<Int> {
|
||||||
currentRowNumber..<min(candidateRows.count, currentRowNumber + maxRowsPerPage)
|
currentRowNumber ..< min(candidateRows.count, currentRowNumber + maxRowsPerPage)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var rangeForCurrentVerticalPage: Range<Int> {
|
private var rangeForCurrentVerticalPage: Range<Int> {
|
||||||
currentColumnNumber..<min(candidateColumns.count, currentColumnNumber + maxColumnsPerPage)
|
currentColumnNumber ..< min(candidateColumns.count, currentColumnNumber + maxColumnsPerPage)
|
||||||
}
|
}
|
||||||
|
|
||||||
private mutating func selectNewNeighborRow(direction: VerticalDirection) {
|
private 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 {
|
||||||
case .up:
|
case .up:
|
||||||
if currentRowNumber <= 0 {
|
if currentRowNumber <= 0 {
|
||||||
if candidateRows.isEmpty { break }
|
if candidateRows.isEmpty { break }
|
||||||
let firstRow = candidateRows[0]
|
let firstRow = candidateRows[0]
|
||||||
let newSubIndex = min(currentSubIndex, firstRow.count - 1)
|
let newSubIndex = min(currentSubIndex, firstRow.count - 1)
|
||||||
highlightHorizontal(at: firstRow[newSubIndex].index)
|
highlightHorizontal(at: firstRow[newSubIndex].index)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if currentRowNumber >= candidateRows.count - 1 { currentRowNumber = candidateRows.count - 1 }
|
if currentRowNumber >= candidateRows.count - 1 { currentRowNumber = candidateRows.count - 1 }
|
||||||
if candidateRows[currentRowNumber].count != candidateRows[currentRowNumber - 1].count {
|
if candidateRows[currentRowNumber].count != candidateRows[currentRowNumber - 1].count {
|
||||||
let ratio: Double = min(1, Double(currentSubIndex) / Double(candidateRows[currentRowNumber].count))
|
let ratio: Double = min(1, Double(currentSubIndex) / Double(candidateRows[currentRowNumber].count))
|
||||||
result = Int(floor(Double(candidateRows[currentRowNumber - 1].count) * ratio))
|
result = Int(floor(Double(candidateRows[currentRowNumber - 1].count) * ratio))
|
||||||
}
|
}
|
||||||
let targetRow = candidateRows[currentRowNumber - 1]
|
let targetRow = candidateRows[currentRowNumber - 1]
|
||||||
let newSubIndex = min(result, targetRow.count - 1)
|
let newSubIndex = min(result, targetRow.count - 1)
|
||||||
highlightHorizontal(at: targetRow[newSubIndex].index)
|
highlightHorizontal(at: targetRow[newSubIndex].index)
|
||||||
case .down:
|
case .down:
|
||||||
if currentRowNumber >= candidateRows.count - 1 {
|
if currentRowNumber >= candidateRows.count - 1 {
|
||||||
if candidateRows.isEmpty { break }
|
if candidateRows.isEmpty { break }
|
||||||
let finalRow = candidateRows[candidateRows.count - 1]
|
let finalRow = candidateRows[candidateRows.count - 1]
|
||||||
let newSubIndex = min(currentSubIndex, finalRow.count - 1)
|
let newSubIndex = min(currentSubIndex, finalRow.count - 1)
|
||||||
highlightHorizontal(at: finalRow[newSubIndex].index)
|
highlightHorizontal(at: finalRow[newSubIndex].index)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if candidateRows[currentRowNumber].count != candidateRows[currentRowNumber + 1].count {
|
if candidateRows[currentRowNumber].count != candidateRows[currentRowNumber + 1].count {
|
||||||
let ratio: Double = min(1, Double(currentSubIndex) / Double(candidateRows[currentRowNumber].count))
|
let ratio: Double = min(1, Double(currentSubIndex) / Double(candidateRows[currentRowNumber].count))
|
||||||
result = Int(floor(Double(candidateRows[currentRowNumber + 1].count) * ratio))
|
result = Int(floor(Double(candidateRows[currentRowNumber + 1].count) * ratio))
|
||||||
}
|
}
|
||||||
let targetRow = candidateRows[currentRowNumber + 1]
|
let targetRow = candidateRows[currentRowNumber + 1]
|
||||||
let newSubIndex = min(result, targetRow.count - 1)
|
let newSubIndex = min(result, targetRow.count - 1)
|
||||||
highlightHorizontal(at: targetRow[newSubIndex].index)
|
highlightHorizontal(at: targetRow[newSubIndex].index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private mutating func selectNewNeighborColumn(direction: HorizontalDirection) {
|
private mutating func selectNewNeighborColumn(direction: HorizontalDirection) {
|
||||||
let currentSubIndex = candidateDataAll[highlightedIndex].subIndex
|
let currentSubIndex = candidateDataAll[highlightedIndex].subIndex
|
||||||
switch direction {
|
switch direction {
|
||||||
case .left:
|
case .left:
|
||||||
if currentColumnNumber <= 0 {
|
if currentColumnNumber <= 0 {
|
||||||
if candidateColumns.isEmpty { break }
|
if candidateColumns.isEmpty { break }
|
||||||
let firstColumn = candidateColumns[0]
|
let firstColumn = candidateColumns[0]
|
||||||
let newSubIndex = min(currentSubIndex, firstColumn.count - 1)
|
let newSubIndex = min(currentSubIndex, firstColumn.count - 1)
|
||||||
highlightVertical(at: firstColumn[newSubIndex].index)
|
highlightVertical(at: firstColumn[newSubIndex].index)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if currentColumnNumber >= candidateColumns.count - 1 { currentColumnNumber = candidateColumns.count - 1 }
|
if currentColumnNumber >= candidateColumns.count - 1 { currentColumnNumber = candidateColumns.count - 1 }
|
||||||
let targetColumn = candidateColumns[currentColumnNumber - 1]
|
let targetColumn = candidateColumns[currentColumnNumber - 1]
|
||||||
let newSubIndex = min(currentSubIndex, targetColumn.count - 1)
|
let newSubIndex = min(currentSubIndex, targetColumn.count - 1)
|
||||||
highlightVertical(at: targetColumn[newSubIndex].index)
|
highlightVertical(at: targetColumn[newSubIndex].index)
|
||||||
case .right:
|
case .right:
|
||||||
if currentColumnNumber >= candidateColumns.count - 1 {
|
if currentColumnNumber >= candidateColumns.count - 1 {
|
||||||
if candidateColumns.isEmpty { break }
|
if candidateColumns.isEmpty { break }
|
||||||
let finalColumn = candidateColumns[candidateColumns.count - 1]
|
let finalColumn = candidateColumns[candidateColumns.count - 1]
|
||||||
let newSubIndex = min(currentSubIndex, finalColumn.count - 1)
|
let newSubIndex = min(currentSubIndex, finalColumn.count - 1)
|
||||||
highlightVertical(at: finalColumn[newSubIndex].index)
|
highlightVertical(at: finalColumn[newSubIndex].index)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
let targetColumn = candidateColumns[currentColumnNumber + 1]
|
let targetColumn = candidateColumns[currentColumnNumber + 1]
|
||||||
let newSubIndex = min(currentSubIndex, targetColumn.count - 1)
|
let newSubIndex = min(currentSubIndex, targetColumn.count - 1)
|
||||||
highlightVertical(at: targetColumn[newSubIndex].index)
|
highlightVertical(at: targetColumn[newSubIndex].index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private mutating func highlightHorizontal(at indexSpecified: Int) {
|
private mutating 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) {
|
||||||
switch highlightedIndex {
|
switch highlightedIndex {
|
||||||
case candidateDataAll.count...:
|
case candidateDataAll.count...:
|
||||||
currentRowNumber = candidateRows.count - 1
|
currentRowNumber = candidateRows.count - 1
|
||||||
highlightedIndex = max(0, candidateDataAll.count - 1)
|
highlightedIndex = max(0, candidateDataAll.count - 1)
|
||||||
indexSpecified = highlightedIndex
|
indexSpecified = highlightedIndex
|
||||||
case ..<0:
|
case ..<0:
|
||||||
highlightedIndex = 0
|
highlightedIndex = 0
|
||||||
currentRowNumber = 0
|
currentRowNumber = 0
|
||||||
indexSpecified = highlightedIndex
|
indexSpecified = highlightedIndex
|
||||||
default: break
|
default: break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (i, candidate) in candidateDataAll.enumerated() {
|
for (i, candidate) in candidateDataAll.enumerated() {
|
||||||
|
@ -324,17 +324,17 @@ extension CandidatePool {
|
||||||
private mutating func highlightVertical(at indexSpecified: Int) {
|
private mutating 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) {
|
||||||
switch highlightedIndex {
|
switch highlightedIndex {
|
||||||
case candidateDataAll.count...:
|
case candidateDataAll.count...:
|
||||||
currentColumnNumber = candidateColumns.count - 1
|
currentColumnNumber = candidateColumns.count - 1
|
||||||
highlightedIndex = max(0, candidateDataAll.count - 1)
|
highlightedIndex = max(0, candidateDataAll.count - 1)
|
||||||
indexSpecified = highlightedIndex
|
indexSpecified = highlightedIndex
|
||||||
case ..<0:
|
case ..<0:
|
||||||
highlightedIndex = 0
|
highlightedIndex = 0
|
||||||
currentColumnNumber = 0
|
currentColumnNumber = 0
|
||||||
indexSpecified = highlightedIndex
|
indexSpecified = highlightedIndex
|
||||||
default: break
|
default: break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (i, candidate) in candidateDataAll.enumerated() {
|
for (i, candidate) in candidateDataAll.enumerated() {
|
||||||
|
|
|
@ -24,13 +24,13 @@ open class CtlCandidate: NSWindowController, CtlCandidateProtocol {
|
||||||
}
|
}
|
||||||
// 設定當前高亮候選字的背景顏色。
|
// 設定當前高亮候選字的背景顏色。
|
||||||
switch locale {
|
switch locale {
|
||||||
case "zh-Hans":
|
case "zh-Hans":
|
||||||
result = NSColor.systemRed
|
result = NSColor.systemRed
|
||||||
case "zh-Hant":
|
case "zh-Hant":
|
||||||
result = NSColor.systemBlue
|
result = NSColor.systemBlue
|
||||||
case "ja":
|
case "ja":
|
||||||
result = NSColor.systemBrown
|
result = NSColor.systemBrown
|
||||||
default: break
|
default: break
|
||||||
}
|
}
|
||||||
var blendingAgainstTarget: NSColor = NSApplication.isDarkMode ? NSColor.black : NSColor.white
|
var blendingAgainstTarget: NSColor = NSApplication.isDarkMode ? NSColor.black : NSColor.white
|
||||||
if #unavailable(macOS 10.14) {
|
if #unavailable(macOS 10.14) {
|
||||||
|
|
|
@ -52,16 +52,16 @@ public class CtlCandidateTDK: CtlCandidate {
|
||||||
private var thePool: CandidatePool {
|
private var thePool: CandidatePool {
|
||||||
get {
|
get {
|
||||||
switch currentLayout {
|
switch currentLayout {
|
||||||
case .horizontal: return Self.thePoolHorizontal
|
case .horizontal: return Self.thePoolHorizontal
|
||||||
case .vertical: return Self.thePoolVertical
|
case .vertical: return Self.thePoolVertical
|
||||||
@unknown default: return .init(candidates: [], rowCapacity: 0)
|
@unknown default: return .init(candidates: [], rowCapacity: 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
set {
|
set {
|
||||||
switch currentLayout {
|
switch currentLayout {
|
||||||
case .horizontal: Self.thePoolHorizontal = newValue
|
case .horizontal: Self.thePoolHorizontal = newValue
|
||||||
case .vertical: Self.thePoolVertical = newValue
|
case .vertical: Self.thePoolVertical = newValue
|
||||||
@unknown default: break
|
@unknown default: break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -98,20 +98,20 @@ public class CtlCandidateTDK: CtlCandidate {
|
||||||
guard let delegate = delegate else { return }
|
guard let delegate = delegate else { return }
|
||||||
|
|
||||||
switch currentLayout {
|
switch currentLayout {
|
||||||
case .horizontal:
|
case .horizontal:
|
||||||
Self.thePoolHorizontal = .init(
|
Self.thePoolHorizontal = .init(
|
||||||
candidates: delegate.candidatePairs(conv: true).map(\.1), rowCapacity: 6,
|
candidates: delegate.candidatePairs(conv: true).map(\.1), rowCapacity: 6,
|
||||||
rows: maxLinesPerPage, selectionKeys: delegate.selectionKeys, locale: locale
|
rows: maxLinesPerPage, selectionKeys: delegate.selectionKeys, locale: locale
|
||||||
)
|
)
|
||||||
Self.thePoolHorizontal.highlight(at: 0)
|
Self.thePoolHorizontal.highlight(at: 0)
|
||||||
case .vertical:
|
case .vertical:
|
||||||
Self.thePoolVertical = .init(
|
Self.thePoolVertical = .init(
|
||||||
candidates: delegate.candidatePairs(conv: true).map(\.1), columnCapacity: 6,
|
candidates: delegate.candidatePairs(conv: true).map(\.1), columnCapacity: 6,
|
||||||
columns: maxLinesPerPage, selectionKeys: delegate.selectionKeys, locale: locale
|
columns: maxLinesPerPage, selectionKeys: delegate.selectionKeys, locale: locale
|
||||||
)
|
)
|
||||||
Self.thePoolVertical.highlight(at: 0)
|
Self.thePoolVertical.highlight(at: 0)
|
||||||
@unknown default:
|
@unknown default:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
updateDisplay()
|
updateDisplay()
|
||||||
}
|
}
|
||||||
|
@ -120,30 +120,30 @@ public class CtlCandidateTDK: CtlCandidate {
|
||||||
guard let window = window else { return }
|
guard let window = window else { return }
|
||||||
reverseLookupResult = delegate?.reverseLookup(for: currentSelectedCandidateText) ?? []
|
reverseLookupResult = delegate?.reverseLookup(for: currentSelectedCandidateText) ?? []
|
||||||
switch currentLayout {
|
switch currentLayout {
|
||||||
case .horizontal:
|
case .horizontal:
|
||||||
DispatchQueue.main.async { [self] in
|
DispatchQueue.main.async { [self] in
|
||||||
if #available(macOS 12, *) {
|
if #available(macOS 12, *) {
|
||||||
Self.currentView = NSHostingView(rootView: theViewHorizontal)
|
Self.currentView = NSHostingView(rootView: theViewHorizontal)
|
||||||
} else {
|
} else {
|
||||||
Self.currentView = NSHostingView(rootView: theViewHorizontalBackports)
|
Self.currentView = NSHostingView(rootView: theViewHorizontalBackports)
|
||||||
}
|
|
||||||
let newSize = Self.currentView.fittingSize
|
|
||||||
window.contentView = Self.currentView
|
|
||||||
window.setContentSize(newSize)
|
|
||||||
}
|
}
|
||||||
case .vertical:
|
let newSize = Self.currentView.fittingSize
|
||||||
DispatchQueue.main.async { [self] in
|
window.contentView = Self.currentView
|
||||||
if #available(macOS 12, *) {
|
window.setContentSize(newSize)
|
||||||
Self.currentView = NSHostingView(rootView: theViewVertical)
|
}
|
||||||
} else {
|
case .vertical:
|
||||||
Self.currentView = NSHostingView(rootView: theViewVerticalBackports)
|
DispatchQueue.main.async { [self] in
|
||||||
}
|
if #available(macOS 12, *) {
|
||||||
let newSize = Self.currentView.fittingSize
|
Self.currentView = NSHostingView(rootView: theViewVertical)
|
||||||
window.contentView = Self.currentView
|
} else {
|
||||||
window.setContentSize(newSize)
|
Self.currentView = NSHostingView(rootView: theViewVerticalBackports)
|
||||||
}
|
}
|
||||||
@unknown default:
|
let newSize = Self.currentView.fittingSize
|
||||||
return
|
window.contentView = Self.currentView
|
||||||
|
window.setContentSize(newSize)
|
||||||
|
}
|
||||||
|
@unknown default:
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -160,7 +160,7 @@ public class CtlCandidateTDK: CtlCandidate {
|
||||||
return highlightNextCandidate()
|
return highlightNextCandidate()
|
||||||
}
|
}
|
||||||
if count <= 0 { return false }
|
if count <= 0 { return false }
|
||||||
for _ in 0..<min(thePool.maxLinesPerPage, count) {
|
for _ in 0 ..< min(thePool.maxLinesPerPage, count) {
|
||||||
thePool.selectNewNeighborLine(isForward: true)
|
thePool.selectNewNeighborLine(isForward: true)
|
||||||
}
|
}
|
||||||
updateDisplay()
|
updateDisplay()
|
||||||
|
@ -180,7 +180,7 @@ public class CtlCandidateTDK: CtlCandidate {
|
||||||
return highlightPreviousCandidate()
|
return highlightPreviousCandidate()
|
||||||
}
|
}
|
||||||
if count <= 0 { return false }
|
if count <= 0 { return false }
|
||||||
for _ in 0..<min(thePool.maxLinesPerPage, count) {
|
for _ in 0 ..< min(thePool.maxLinesPerPage, count) {
|
||||||
thePool.selectNewNeighborLine(isForward: false)
|
thePool.selectNewNeighborLine(isForward: false)
|
||||||
}
|
}
|
||||||
updateDisplay()
|
updateDisplay()
|
||||||
|
@ -211,7 +211,7 @@ public class CtlCandidateTDK: CtlCandidate {
|
||||||
|
|
||||||
override public func candidateIndexAtKeyLabelIndex(_ id: Int) -> Int {
|
override public func candidateIndexAtKeyLabelIndex(_ id: Int) -> Int {
|
||||||
let arrCurrentLine = thePool.candidateLines[thePool.currentLineNumber]
|
let arrCurrentLine = thePool.candidateLines[thePool.currentLineNumber]
|
||||||
if !(0..<arrCurrentLine.count).contains(id) { return -114_514 }
|
if !(0 ..< arrCurrentLine.count).contains(id) { return -114_514 }
|
||||||
let actualID = max(0, min(id, arrCurrentLine.count - 1))
|
let actualID = max(0, min(id, arrCurrentLine.count - 1))
|
||||||
return arrCurrentLine[actualID].index
|
return arrCurrentLine[actualID].index
|
||||||
}
|
}
|
||||||
|
@ -241,15 +241,15 @@ extension CtlCandidateTDK {
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(macOS 10.15, *)
|
@available(macOS 10.15, *)
|
||||||
extension CtlCandidateTDK {
|
public extension CtlCandidateTDK {
|
||||||
public var highlightedColorUIBackports: some View {
|
var highlightedColorUIBackports: some View {
|
||||||
// 設定當前高亮候選字的背景顏色。
|
// 設定當前高亮候選字的背景顏色。
|
||||||
let result: Color = {
|
let result: Color = {
|
||||||
switch locale {
|
switch locale {
|
||||||
case "zh-Hans": return Color.red
|
case "zh-Hans": return Color.red
|
||||||
case "zh-Hant": return Color.blue
|
case "zh-Hant": return Color.blue
|
||||||
case "ja": return Color.pink
|
case "ja": return Color.pink
|
||||||
default: return Color.accentColor
|
default: return Color.accentColor
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
return result.opacity(0.85)
|
return result.opacity(0.85)
|
||||||
|
|
|
@ -106,7 +106,7 @@ public struct VwrCandidateVertical: View {
|
||||||
if thePool.maxLinesPerPage - thePool.rangeForCurrentPage.count > 0 {
|
if thePool.maxLinesPerPage - thePool.rangeForCurrentPage.count > 0 {
|
||||||
ForEach(Array(thePool.rangeForLastPageBlanked.enumerated()), id: \.offset) { loopIndex, _ in
|
ForEach(Array(thePool.rangeForLastPageBlanked.enumerated()), id: \.offset) { loopIndex, _ in
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
ForEach(0..<thePool.maxLineCapacity, 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())
|
||||||
|
|
|
@ -107,7 +107,7 @@ public struct VwrCandidateVerticalBackports: View {
|
||||||
if thePool.maxLinesPerPage - thePool.rangeForCurrentPage.count > 0 {
|
if thePool.maxLinesPerPage - thePool.rangeForCurrentPage.count > 0 {
|
||||||
ForEach(Array(thePool.rangeForLastPageBlanked.enumerated()), id: \.offset) { loopIndex, _ in
|
ForEach(Array(thePool.rangeForLastPageBlanked.enumerated()), id: \.offset) { loopIndex, _ in
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
ForEach(0..<thePool.maxLineCapacity, 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())
|
||||||
|
|
|
@ -4,13 +4,13 @@ import PackageDescription
|
||||||
let package = Package(
|
let package = Package(
|
||||||
name: "CocoaExtension",
|
name: "CocoaExtension",
|
||||||
platforms: [
|
platforms: [
|
||||||
.macOS(.v10_11)
|
.macOS(.v10_11),
|
||||||
],
|
],
|
||||||
products: [
|
products: [
|
||||||
.library(
|
.library(
|
||||||
name: "CocoaExtension",
|
name: "CocoaExtension",
|
||||||
targets: ["CocoaExtension"]
|
targets: ["CocoaExtension"]
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
.package(path: "../vChewing_IMKUtils"),
|
.package(path: "../vChewing_IMKUtils"),
|
||||||
|
@ -23,6 +23,6 @@ let package = Package(
|
||||||
.product(name: "IMKUtils", package: "vChewing_IMKUtils"),
|
.product(name: "IMKUtils", package: "vChewing_IMKUtils"),
|
||||||
.product(name: "SwiftExtension", package: "vChewing_SwiftExtension"),
|
.product(name: "SwiftExtension", package: "vChewing_SwiftExtension"),
|
||||||
]
|
]
|
||||||
)
|
),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
|
@ -11,22 +11,22 @@ import SwiftExtension
|
||||||
|
|
||||||
// MARK: - NSMutableString extension
|
// MARK: - NSMutableString extension
|
||||||
|
|
||||||
extension NSMutableString {
|
public extension NSMutableString {
|
||||||
public var localized: String { NSLocalizedString(description, comment: "") }
|
var localized: String { NSLocalizedString(description, comment: "") }
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - NSRect Extension
|
// MARK: - NSRect Extension
|
||||||
|
|
||||||
extension NSRect {
|
public extension NSRect {
|
||||||
public static var seniorTheBeast: NSRect {
|
static var seniorTheBeast: NSRect {
|
||||||
NSRect(x: 0.0, y: 0.0, width: 0.114, height: 0.514)
|
NSRect(x: 0.0, y: 0.0, width: 0.114, height: 0.514)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Shell Extension
|
// MARK: - Shell Extension
|
||||||
|
|
||||||
extension NSApplication {
|
public extension NSApplication {
|
||||||
public static func shell(_ command: String) throws -> String {
|
static func shell(_ command: String) throws -> String {
|
||||||
let task = Process()
|
let task = Process()
|
||||||
let pipe = Pipe()
|
let pipe = Pipe()
|
||||||
|
|
||||||
|
@ -59,10 +59,10 @@ extension NSApplication {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension NSApplication {
|
public extension NSApplication {
|
||||||
// MARK: - System Dark Mode Status Detector.
|
// MARK: - System Dark Mode Status Detector.
|
||||||
|
|
||||||
public static var isDarkMode: Bool {
|
static var isDarkMode: Bool {
|
||||||
if #unavailable(macOS 10.14) { return false }
|
if #unavailable(macOS 10.14) { return false }
|
||||||
if #available(macOS 10.15, *) {
|
if #available(macOS 10.15, *) {
|
||||||
let appearanceDescription = NSApp.effectiveAppearance.debugDescription
|
let appearanceDescription = NSApp.effectiveAppearance.debugDescription
|
||||||
|
@ -76,23 +76,23 @@ extension NSApplication {
|
||||||
|
|
||||||
// MARK: - Tell whether this IME is running with Root privileges.
|
// MARK: - Tell whether this IME is running with Root privileges.
|
||||||
|
|
||||||
public static var isSudoMode: Bool {
|
static var isSudoMode: Bool {
|
||||||
NSUserName() == "root"
|
NSUserName() == "root"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Real Home Dir for Sandboxed Apps
|
// MARK: - Real Home Dir for Sandboxed Apps
|
||||||
|
|
||||||
extension FileManager {
|
public extension FileManager {
|
||||||
public static let realHomeDir = URL(
|
static let realHomeDir = URL(
|
||||||
fileURLWithFileSystemRepresentation: getpwuid(getuid()).pointee.pw_dir, isDirectory: true, relativeTo: nil
|
fileURLWithFileSystemRepresentation: getpwuid(getuid()).pointee.pw_dir, isDirectory: true, relativeTo: nil
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Trash a file if it exists.
|
// MARK: - Trash a file if it exists.
|
||||||
|
|
||||||
extension FileManager {
|
public extension FileManager {
|
||||||
@discardableResult public static func trashTargetIfExists(_ path: String) -> Bool {
|
@discardableResult static func trashTargetIfExists(_ path: String) -> Bool {
|
||||||
do {
|
do {
|
||||||
if FileManager.default.fileExists(atPath: path) {
|
if FileManager.default.fileExists(atPath: path) {
|
||||||
// 塞入垃圾桶
|
// 塞入垃圾桶
|
||||||
|
@ -113,9 +113,9 @@ extension FileManager {
|
||||||
// MARK: - Memory Footprint Calculator
|
// MARK: - Memory Footprint Calculator
|
||||||
|
|
||||||
// Ref: https://developer.apple.com/forums/thread/105088?answerId=357415022#357415022
|
// Ref: https://developer.apple.com/forums/thread/105088?answerId=357415022#357415022
|
||||||
extension NSApplication {
|
public extension NSApplication {
|
||||||
/// The memory footprint of the current application in bytes.
|
/// The memory footprint of the current application in bytes.
|
||||||
public static var memoryFootprint: UInt64? {
|
static var memoryFootprint: UInt64? {
|
||||||
// The `TASK_VM_INFO_COUNT` and `TASK_VM_INFO_REV1_COUNT` macros are too
|
// The `TASK_VM_INFO_COUNT` and `TASK_VM_INFO_REV1_COUNT` macros are too
|
||||||
// complex for the Swift C importer, so we have to define them ourselves.
|
// complex for the Swift C importer, so we have to define them ourselves.
|
||||||
let tskVMInfoCount = mach_msg_type_number_t(
|
let tskVMInfoCount = mach_msg_type_number_t(
|
||||||
|
@ -136,20 +136,20 @@ extension NSApplication {
|
||||||
|
|
||||||
// MARK: - Check whether current date is the given date.
|
// MARK: - Check whether current date is the given date.
|
||||||
|
|
||||||
extension Date {
|
public extension Date {
|
||||||
/// Check whether current date is the given date.
|
/// Check whether current date is the given date.
|
||||||
/// - Parameter dateDigits: `yyyyMMdd`, 8-digit integer. If only `MMdd`, then the year will be the current year.
|
/// - Parameter dateDigits: `yyyyMMdd`, 8-digit integer. If only `MMdd`, then the year will be the current year.
|
||||||
/// - Returns: The result. Will return false if the given dateDigits is invalid.
|
/// - Returns: The result. Will return false if the given dateDigits is invalid.
|
||||||
public static func isTodayTheDate(from dateDigits: Int) -> Bool {
|
static func isTodayTheDate(from dateDigits: Int) -> Bool {
|
||||||
let currentYear = Self.currentYear
|
let currentYear = Self.currentYear
|
||||||
var dateDigits = dateDigits
|
var dateDigits = dateDigits
|
||||||
let strDateDigits = dateDigits.description
|
let strDateDigits = dateDigits.description
|
||||||
switch strDateDigits.count {
|
switch strDateDigits.count {
|
||||||
case 3, 4: dateDigits = currentYear * 10000 + dateDigits
|
case 3, 4: dateDigits = currentYear * 10000 + dateDigits
|
||||||
case 8:
|
case 8:
|
||||||
if let theHighest = strDateDigits.first, "12".contains(theHighest) { break }
|
if let theHighest = strDateDigits.first, "12".contains(theHighest) { break }
|
||||||
return false
|
return false
|
||||||
default: return false
|
default: return false
|
||||||
}
|
}
|
||||||
let formatter = DateFormatter()
|
let formatter = DateFormatter()
|
||||||
formatter.dateFormat = "yyyyMMdd"
|
formatter.dateFormat = "yyyyMMdd"
|
||||||
|
@ -162,7 +162,7 @@ extension Date {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
public static var currentYear: Int {
|
static var currentYear: Int {
|
||||||
let formatter = DateFormatter()
|
let formatter = DateFormatter()
|
||||||
formatter.dateFormat = "yyyy"
|
formatter.dateFormat = "yyyy"
|
||||||
return (Int(formatter.string(from: Date())) ?? 1970)
|
return (Int(formatter.string(from: Date())) ?? 1970)
|
||||||
|
|
|
@ -11,8 +11,8 @@ import IMKUtils
|
||||||
|
|
||||||
// MARK: - NSEvent Extension - Reconstructors
|
// MARK: - NSEvent Extension - Reconstructors
|
||||||
|
|
||||||
extension NSEvent {
|
public extension NSEvent {
|
||||||
public func reinitiate(
|
func reinitiate(
|
||||||
with type: NSEvent.EventType? = nil,
|
with type: NSEvent.EventType? = nil,
|
||||||
location: NSPoint? = nil,
|
location: NSPoint? = nil,
|
||||||
modifierFlags: NSEvent.ModifierFlags? = nil,
|
modifierFlags: NSEvent.ModifierFlags? = nil,
|
||||||
|
@ -44,28 +44,28 @@ extension NSEvent {
|
||||||
/// 自 Emacs 熱鍵的 NSEvent 翻譯回標準 NSEvent。失敗的話則會返回原始 NSEvent 自身。
|
/// 自 Emacs 熱鍵的 NSEvent 翻譯回標準 NSEvent。失敗的話則會返回原始 NSEvent 自身。
|
||||||
/// - Parameter isVerticalTyping: 是否按照縱排來操作。
|
/// - Parameter isVerticalTyping: 是否按照縱排來操作。
|
||||||
/// - Returns: 翻譯結果。失敗的話則返回翻譯原文。
|
/// - Returns: 翻譯結果。失敗的話則返回翻譯原文。
|
||||||
public func convertFromEmacsKeyEvent(isVerticalContext: Bool) -> NSEvent {
|
func convertFromEmacsKeyEvent(isVerticalContext: Bool) -> NSEvent {
|
||||||
guard isEmacsKey else { return self }
|
guard isEmacsKey else { return self }
|
||||||
let newKeyCode: UInt16 = {
|
let newKeyCode: UInt16 = {
|
||||||
switch isVerticalContext {
|
switch isVerticalContext {
|
||||||
case false: return EmacsKey.charKeyMapHorizontal[charCode] ?? 0
|
case false: return EmacsKey.charKeyMapHorizontal[charCode] ?? 0
|
||||||
case true: return EmacsKey.charKeyMapVertical[charCode] ?? 0
|
case true: return EmacsKey.charKeyMapVertical[charCode] ?? 0
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
guard newKeyCode != 0 else { return self }
|
guard newKeyCode != 0 else { return self }
|
||||||
let newCharScalar: Unicode.Scalar = {
|
let newCharScalar: Unicode.Scalar = {
|
||||||
switch charCode {
|
switch charCode {
|
||||||
case 6:
|
case 6:
|
||||||
return isVerticalContext
|
return isVerticalContext
|
||||||
? NSEvent.SpecialKey.downArrow.unicodeScalar : NSEvent.SpecialKey.rightArrow.unicodeScalar
|
? NSEvent.SpecialKey.downArrow.unicodeScalar : NSEvent.SpecialKey.rightArrow.unicodeScalar
|
||||||
case 2:
|
case 2:
|
||||||
return isVerticalContext
|
return isVerticalContext
|
||||||
? NSEvent.SpecialKey.upArrow.unicodeScalar : NSEvent.SpecialKey.leftArrow.unicodeScalar
|
? NSEvent.SpecialKey.upArrow.unicodeScalar : NSEvent.SpecialKey.leftArrow.unicodeScalar
|
||||||
case 1: return NSEvent.SpecialKey.home.unicodeScalar
|
case 1: return NSEvent.SpecialKey.home.unicodeScalar
|
||||||
case 5: return NSEvent.SpecialKey.end.unicodeScalar
|
case 5: return NSEvent.SpecialKey.end.unicodeScalar
|
||||||
case 4: return NSEvent.SpecialKey.deleteForward.unicodeScalar // Use "deleteForward" for PC delete.
|
case 4: return NSEvent.SpecialKey.deleteForward.unicodeScalar // Use "deleteForward" for PC delete.
|
||||||
case 22: return NSEvent.SpecialKey.pageDown.unicodeScalar
|
case 22: return NSEvent.SpecialKey.pageDown.unicodeScalar
|
||||||
default: return .init(0)
|
default: return .init(0)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
let newChar = String(newCharScalar)
|
let newChar = String(newCharScalar)
|
||||||
|
@ -76,15 +76,15 @@ extension NSEvent {
|
||||||
|
|
||||||
// MARK: - NSEvent Extension - InputSignalProtocol
|
// MARK: - NSEvent Extension - InputSignalProtocol
|
||||||
|
|
||||||
extension NSEvent {
|
public extension NSEvent {
|
||||||
public var isTypingVertical: Bool { charactersIgnoringModifiers == "Vertical" }
|
var isTypingVertical: Bool { charactersIgnoringModifiers == "Vertical" }
|
||||||
public var text: String { characters ?? "" }
|
var text: String { characters ?? "" }
|
||||||
public var inputTextIgnoringModifiers: String? {
|
var inputTextIgnoringModifiers: String? {
|
||||||
guard charactersIgnoringModifiers != nil else { return nil }
|
guard charactersIgnoringModifiers != nil else { return nil }
|
||||||
return charactersIgnoringModifiers ?? characters ?? ""
|
return charactersIgnoringModifiers ?? characters ?? ""
|
||||||
}
|
}
|
||||||
|
|
||||||
public var charCode: UInt16 {
|
var charCode: UInt16 {
|
||||||
guard type != .flagsChanged else { return 0 }
|
guard type != .flagsChanged else { return 0 }
|
||||||
guard characters != nil else { return 0 }
|
guard characters != nil else { return 0 }
|
||||||
// 這裡不用「count > 0」,因為該整數變數只要「!isEmpty」那就必定滿足這個條件。
|
// 這裡不用「count > 0」,因為該整數變數只要「!isEmpty」那就必定滿足這個條件。
|
||||||
|
@ -94,9 +94,9 @@ extension NSEvent {
|
||||||
return result <= UInt16.max ? UInt16(result) : UInt16.max
|
return result <= UInt16.max ? UInt16(result) : UInt16.max
|
||||||
}
|
}
|
||||||
|
|
||||||
public var isFlagChanged: Bool { type == .flagsChanged }
|
var isFlagChanged: Bool { type == .flagsChanged }
|
||||||
|
|
||||||
public var isEmacsKey: Bool {
|
var isEmacsKey: Bool {
|
||||||
// 這裡不能只用 isControlHold,因為這裡對修飾鍵的要求有排他性。
|
// 這裡不能只用 isControlHold,因為這裡對修飾鍵的要求有排他性。
|
||||||
[6, 2, 1, 5, 4, 22].contains(charCode) && modifierFlags == .control
|
[6, 2, 1, 5, 4, 22].contains(charCode) && modifierFlags == .control
|
||||||
}
|
}
|
||||||
|
@ -104,86 +104,86 @@ extension NSEvent {
|
||||||
// 摁 Alt+Shift+主鍵盤區域數字鍵 的話,根據不同的 macOS 鍵盤佈局種類,會出現不同的符號結果。
|
// 摁 Alt+Shift+主鍵盤區域數字鍵 的話,根據不同的 macOS 鍵盤佈局種類,會出現不同的符號結果。
|
||||||
// 然而呢,KeyCode 卻是一致的。於是這裡直接準備一個換算表來用。
|
// 然而呢,KeyCode 卻是一致的。於是這裡直接準備一個換算表來用。
|
||||||
// 這句用來返回換算結果。
|
// 這句用來返回換算結果。
|
||||||
public var mainAreaNumKeyChar: String? { mapMainAreaNumKey[keyCode] }
|
var mainAreaNumKeyChar: String? { mapMainAreaNumKey[keyCode] }
|
||||||
|
|
||||||
// 除了 ANSI charCode 以外,其餘一律過濾掉,免得 InputHandler 被餵屎。
|
// 除了 ANSI charCode 以外,其餘一律過濾掉,免得 InputHandler 被餵屎。
|
||||||
public var isInvalid: Bool {
|
var isInvalid: Bool {
|
||||||
(0x20...0xFF).contains(charCode) ? false : !(isReservedKey && !isKeyCodeBlacklisted)
|
(0x20 ... 0xFF).contains(charCode) ? false : !(isReservedKey && !isKeyCodeBlacklisted)
|
||||||
}
|
}
|
||||||
|
|
||||||
public var isKeyCodeBlacklisted: Bool {
|
var isKeyCodeBlacklisted: Bool {
|
||||||
guard let code = KeyCodeBlackListed(rawValue: keyCode) else { return false }
|
guard let code = KeyCodeBlackListed(rawValue: keyCode) else { return false }
|
||||||
return code.rawValue != KeyCode.kNone.rawValue
|
return code.rawValue != KeyCode.kNone.rawValue
|
||||||
}
|
}
|
||||||
|
|
||||||
public var isReservedKey: Bool {
|
var isReservedKey: Bool {
|
||||||
guard let code = KeyCode(rawValue: keyCode) else { return false }
|
guard let code = KeyCode(rawValue: keyCode) else { return false }
|
||||||
return code.rawValue != KeyCode.kNone.rawValue
|
return code.rawValue != KeyCode.kNone.rawValue
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 單獨用 flags 來判定數字小鍵盤輸入的方法已經失效了,所以必須再增補用 KeyCode 判定的方法。
|
/// 單獨用 flags 來判定數字小鍵盤輸入的方法已經失效了,所以必須再增補用 KeyCode 判定的方法。
|
||||||
public var isJISAlphanumericalKey: Bool { KeyCode(rawValue: keyCode) == KeyCode.kJISAlphanumericalKey }
|
var isJISAlphanumericalKey: Bool { KeyCode(rawValue: keyCode) == KeyCode.kJISAlphanumericalKey }
|
||||||
public var isJISKanaSwappingKey: Bool { KeyCode(rawValue: keyCode) == KeyCode.kJISKanaSwappingKey }
|
var isJISKanaSwappingKey: Bool { KeyCode(rawValue: keyCode) == KeyCode.kJISKanaSwappingKey }
|
||||||
public var isNumericPadKey: Bool { arrNumpadKeyCodes.contains(keyCode) }
|
var isNumericPadKey: Bool { arrNumpadKeyCodes.contains(keyCode) }
|
||||||
public var isMainAreaNumKey: Bool { arrMainAreaNumKey.contains(keyCode) }
|
var isMainAreaNumKey: Bool { arrMainAreaNumKey.contains(keyCode) }
|
||||||
public var isShiftHold: Bool { modifierFlags.contains([.shift]) }
|
var isShiftHold: Bool { modifierFlags.contains([.shift]) }
|
||||||
public var isCommandHold: Bool { modifierFlags.contains([.command]) }
|
var isCommandHold: Bool { modifierFlags.contains([.command]) }
|
||||||
public var isControlHold: Bool { modifierFlags.contains([.control]) }
|
var isControlHold: Bool { modifierFlags.contains([.control]) }
|
||||||
public var isControlHotKey: Bool { modifierFlags.contains([.control]) && text.first?.isLetter ?? false }
|
var isControlHotKey: Bool { modifierFlags.contains([.control]) && text.first?.isLetter ?? false }
|
||||||
public var isOptionHold: Bool { modifierFlags.contains([.option]) }
|
var isOptionHold: Bool { modifierFlags.contains([.option]) }
|
||||||
public var isOptionHotKey: Bool { modifierFlags.contains([.option]) && text.first?.isLetter ?? false }
|
var isOptionHotKey: Bool { modifierFlags.contains([.option]) && text.first?.isLetter ?? false }
|
||||||
public var isCapsLockOn: Bool { modifierFlags.contains([.capsLock]) }
|
var isCapsLockOn: Bool { modifierFlags.contains([.capsLock]) }
|
||||||
public var isFunctionKeyHold: Bool { modifierFlags.contains([.function]) }
|
var isFunctionKeyHold: Bool { modifierFlags.contains([.function]) }
|
||||||
public var isNonLaptopFunctionKey: Bool { modifierFlags.contains([.numericPad]) && !isNumericPadKey }
|
var isNonLaptopFunctionKey: Bool { modifierFlags.contains([.numericPad]) && !isNumericPadKey }
|
||||||
public var isEnter: Bool { [KeyCode.kCarriageReturn, KeyCode.kLineFeed].contains(KeyCode(rawValue: keyCode)) }
|
var isEnter: Bool { [KeyCode.kCarriageReturn, KeyCode.kLineFeed].contains(KeyCode(rawValue: keyCode)) }
|
||||||
public var isTab: Bool { KeyCode(rawValue: keyCode) == KeyCode.kTab }
|
var isTab: Bool { KeyCode(rawValue: keyCode) == KeyCode.kTab }
|
||||||
public var isUp: Bool { KeyCode(rawValue: keyCode) == KeyCode.kUpArrow }
|
var isUp: Bool { KeyCode(rawValue: keyCode) == KeyCode.kUpArrow }
|
||||||
public var isDown: Bool { KeyCode(rawValue: keyCode) == KeyCode.kDownArrow }
|
var isDown: Bool { KeyCode(rawValue: keyCode) == KeyCode.kDownArrow }
|
||||||
public var isLeft: Bool { KeyCode(rawValue: keyCode) == KeyCode.kLeftArrow }
|
var isLeft: Bool { KeyCode(rawValue: keyCode) == KeyCode.kLeftArrow }
|
||||||
public var isRight: Bool { KeyCode(rawValue: keyCode) == KeyCode.kRightArrow }
|
var isRight: Bool { KeyCode(rawValue: keyCode) == KeyCode.kRightArrow }
|
||||||
public var isPageUp: Bool { KeyCode(rawValue: keyCode) == KeyCode.kPageUp }
|
var isPageUp: Bool { KeyCode(rawValue: keyCode) == KeyCode.kPageUp }
|
||||||
public var isPageDown: Bool { KeyCode(rawValue: keyCode) == KeyCode.kPageDown }
|
var isPageDown: Bool { KeyCode(rawValue: keyCode) == KeyCode.kPageDown }
|
||||||
public var isSpace: Bool { KeyCode(rawValue: keyCode) == KeyCode.kSpace }
|
var isSpace: Bool { KeyCode(rawValue: keyCode) == KeyCode.kSpace }
|
||||||
public var isBackSpace: Bool { KeyCode(rawValue: keyCode) == KeyCode.kBackSpace }
|
var isBackSpace: Bool { KeyCode(rawValue: keyCode) == KeyCode.kBackSpace }
|
||||||
public var isEsc: Bool { KeyCode(rawValue: keyCode) == KeyCode.kEscape }
|
var isEsc: Bool { KeyCode(rawValue: keyCode) == KeyCode.kEscape }
|
||||||
public var isHome: Bool { KeyCode(rawValue: keyCode) == KeyCode.kHome }
|
var isHome: Bool { KeyCode(rawValue: keyCode) == KeyCode.kHome }
|
||||||
public var isEnd: Bool { KeyCode(rawValue: keyCode) == KeyCode.kEnd }
|
var isEnd: Bool { KeyCode(rawValue: keyCode) == KeyCode.kEnd }
|
||||||
public var isDelete: Bool { KeyCode(rawValue: keyCode) == KeyCode.kWindowsDelete }
|
var isDelete: Bool { KeyCode(rawValue: keyCode) == KeyCode.kWindowsDelete }
|
||||||
|
|
||||||
public var isCursorBackward: Bool {
|
var isCursorBackward: Bool {
|
||||||
isTypingVertical
|
isTypingVertical
|
||||||
? KeyCode(rawValue: keyCode) == .kUpArrow
|
? KeyCode(rawValue: keyCode) == .kUpArrow
|
||||||
: KeyCode(rawValue: keyCode) == .kLeftArrow
|
: KeyCode(rawValue: keyCode) == .kLeftArrow
|
||||||
}
|
}
|
||||||
|
|
||||||
public var isCursorForward: Bool {
|
var isCursorForward: Bool {
|
||||||
isTypingVertical
|
isTypingVertical
|
||||||
? KeyCode(rawValue: keyCode) == .kDownArrow
|
? KeyCode(rawValue: keyCode) == .kDownArrow
|
||||||
: KeyCode(rawValue: keyCode) == .kRightArrow
|
: KeyCode(rawValue: keyCode) == .kRightArrow
|
||||||
}
|
}
|
||||||
|
|
||||||
public var isCursorClockRight: Bool {
|
var isCursorClockRight: Bool {
|
||||||
isTypingVertical
|
isTypingVertical
|
||||||
? KeyCode(rawValue: keyCode) == .kRightArrow
|
? KeyCode(rawValue: keyCode) == .kRightArrow
|
||||||
: KeyCode(rawValue: keyCode) == .kUpArrow
|
: KeyCode(rawValue: keyCode) == .kUpArrow
|
||||||
}
|
}
|
||||||
|
|
||||||
public var isCursorClockLeft: Bool {
|
var isCursorClockLeft: Bool {
|
||||||
isTypingVertical
|
isTypingVertical
|
||||||
? KeyCode(rawValue: keyCode) == .kLeftArrow
|
? KeyCode(rawValue: keyCode) == .kLeftArrow
|
||||||
: KeyCode(rawValue: keyCode) == .kDownArrow
|
: KeyCode(rawValue: keyCode) == .kDownArrow
|
||||||
}
|
}
|
||||||
|
|
||||||
public var isASCII: Bool { charCode < 0x80 }
|
var isASCII: Bool { charCode < 0x80 }
|
||||||
|
|
||||||
// 這裡必須加上「flags == .shift」,否則會出現某些情況下輸入法「誤判當前鍵入的非 Shift 字符為大寫」的問題
|
// 這裡必須加上「flags == .shift」,否則會出現某些情況下輸入法「誤判當前鍵入的非 Shift 字符為大寫」的問題
|
||||||
public var isUpperCaseASCIILetterKey: Bool {
|
var isUpperCaseASCIILetterKey: Bool {
|
||||||
(65...90).contains(charCode) && modifierFlags == .shift
|
(65 ... 90).contains(charCode) && modifierFlags == .shift
|
||||||
}
|
}
|
||||||
|
|
||||||
// 這裡必須用 KeyCode,這樣才不會受隨 macOS 版本更動的 Apple 動態注音鍵盤排列內容的影響。
|
// 這裡必須用 KeyCode,這樣才不會受隨 macOS 版本更動的 Apple 動態注音鍵盤排列內容的影響。
|
||||||
// 只是必須得與 ![input isShiftHold] 搭配使用才可以(也就是僅判定 Shift 沒被摁下的情形)。
|
// 只是必須得與 ![input isShiftHold] 搭配使用才可以(也就是僅判定 Shift 沒被摁下的情形)。
|
||||||
public var isSymbolMenuPhysicalKey: Bool {
|
var isSymbolMenuPhysicalKey: Bool {
|
||||||
[KeyCode.kSymbolMenuPhysicalKeyIntl, KeyCode.kSymbolMenuPhysicalKeyJIS].contains(KeyCode(rawValue: keyCode))
|
[KeyCode.kSymbolMenuPhysicalKeyIntl, KeyCode.kSymbolMenuPhysicalKeyJIS].contains(KeyCode(rawValue: keyCode))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -195,11 +195,11 @@ extension NSEvent {
|
||||||
// Also: HIToolbox.framework/Versions/A/Headers/Events.h
|
// Also: HIToolbox.framework/Versions/A/Headers/Events.h
|
||||||
public enum KeyCode: UInt16 {
|
public enum KeyCode: UInt16 {
|
||||||
case kNone = 0
|
case kNone = 0
|
||||||
case kCarriageReturn = 36 // Renamed from "kReturn" to avoid nomenclatural confusions.
|
case kCarriageReturn = 36 // Renamed from "kReturn" to avoid nomenclatural confusions.
|
||||||
case kTab = 48
|
case kTab = 48
|
||||||
case kSpace = 49
|
case kSpace = 49
|
||||||
case kSymbolMenuPhysicalKeyIntl = 50 // vChewing Specific (Non-JIS)
|
case kSymbolMenuPhysicalKeyIntl = 50 // vChewing Specific (Non-JIS)
|
||||||
case kBackSpace = 51 // Renamed from "kDelete" to avoid nomenclatural confusions.
|
case kBackSpace = 51 // Renamed from "kDelete" to avoid nomenclatural confusions.
|
||||||
case kEscape = 53
|
case kEscape = 53
|
||||||
case kCommand = 55
|
case kCommand = 55
|
||||||
case kShift = 56
|
case kShift = 56
|
||||||
|
@ -214,12 +214,12 @@ public enum KeyCode: UInt16 {
|
||||||
case kVolumeUp = 72
|
case kVolumeUp = 72
|
||||||
case kVolumeDown = 73
|
case kVolumeDown = 73
|
||||||
case kMute = 74
|
case kMute = 74
|
||||||
case kLineFeed = 76 // Another keyCode to identify the Enter Key, typable by Fn+Enter.
|
case kLineFeed = 76 // Another keyCode to identify the Enter Key, typable by Fn+Enter.
|
||||||
case kF18 = 79
|
case kF18 = 79
|
||||||
case kF19 = 80
|
case kF19 = 80
|
||||||
case kF20 = 90
|
case kF20 = 90
|
||||||
case kYen = 93
|
case kYen = 93
|
||||||
case kSymbolMenuPhysicalKeyJIS = 94 // vChewing Specific (JIS)
|
case kSymbolMenuPhysicalKeyJIS = 94 // vChewing Specific (JIS)
|
||||||
case kJISNumPadComma = 95
|
case kJISNumPadComma = 95
|
||||||
case kF5 = 96
|
case kF5 = 96
|
||||||
case kF6 = 97
|
case kF6 = 97
|
||||||
|
@ -230,17 +230,17 @@ public enum KeyCode: UInt16 {
|
||||||
case kJISAlphanumericalKey = 102
|
case kJISAlphanumericalKey = 102
|
||||||
case kF11 = 103
|
case kF11 = 103
|
||||||
case kJISKanaSwappingKey = 104
|
case kJISKanaSwappingKey = 104
|
||||||
case kF13 = 105 // PrtSc
|
case kF13 = 105 // PrtSc
|
||||||
case kF16 = 106
|
case kF16 = 106
|
||||||
case kF14 = 107
|
case kF14 = 107
|
||||||
case kF10 = 109
|
case kF10 = 109
|
||||||
case kContextMenu = 110
|
case kContextMenu = 110
|
||||||
case kF12 = 111
|
case kF12 = 111
|
||||||
case kF15 = 113
|
case kF15 = 113
|
||||||
case kHelp = 114 // Insert
|
case kHelp = 114 // Insert
|
||||||
case kHome = 115
|
case kHome = 115
|
||||||
case kPageUp = 116
|
case kPageUp = 116
|
||||||
case kWindowsDelete = 117 // Renamed from "kForwardDelete" to avoid nomenclatural confusions.
|
case kWindowsDelete = 117 // Renamed from "kForwardDelete" to avoid nomenclatural confusions.
|
||||||
case kF4 = 118
|
case kF4 = 118
|
||||||
case kEnd = 119
|
case kEnd = 119
|
||||||
case kF2 = 120
|
case kF2 = 120
|
||||||
|
@ -267,13 +267,13 @@ enum KeyCodeBlackListed: UInt16 {
|
||||||
case kF8 = 100
|
case kF8 = 100
|
||||||
case kF9 = 101
|
case kF9 = 101
|
||||||
case kF11 = 103
|
case kF11 = 103
|
||||||
case kF13 = 105 // PrtSc
|
case kF13 = 105 // PrtSc
|
||||||
case kF16 = 106
|
case kF16 = 106
|
||||||
case kF14 = 107
|
case kF14 = 107
|
||||||
case kF10 = 109
|
case kF10 = 109
|
||||||
case kF12 = 111
|
case kF12 = 111
|
||||||
case kF15 = 113
|
case kF15 = 113
|
||||||
case kHelp = 114 // Insert
|
case kHelp = 114 // Insert
|
||||||
case kF4 = 118
|
case kF4 = 118
|
||||||
case kF2 = 120
|
case kF2 = 120
|
||||||
case kF1 = 122
|
case kF1 = 122
|
||||||
|
@ -323,8 +323,8 @@ let arrAppleABCKeyboardMap: [UInt16: (String, String)] = [
|
||||||
45: ("n", "N"), 46: ("m", "M"), 43: (",", "<"), 47: (".", ">"), 44: ("/", "?"),
|
45: ("n", "N"), 46: ("m", "M"), 43: (",", "<"), 47: (".", ">"), 44: ("/", "?"),
|
||||||
]
|
]
|
||||||
|
|
||||||
extension NSEvent {
|
public extension NSEvent {
|
||||||
public var inAppleABCStaticForm: NSEvent {
|
var inAppleABCStaticForm: NSEvent {
|
||||||
if type == .flagsChanged { return self }
|
if type == .flagsChanged { return self }
|
||||||
guard modifierFlags == .shift || modifierFlags == [] else { return self }
|
guard modifierFlags == .shift || modifierFlags == [] else { return self }
|
||||||
if !arrAppleABCKeyboardMap.keys.contains(keyCode) { return self }
|
if !arrAppleABCKeyboardMap.keys.contains(keyCode) { return self }
|
||||||
|
|
|
@ -9,8 +9,8 @@
|
||||||
import AVFoundation
|
import AVFoundation
|
||||||
import Cocoa
|
import Cocoa
|
||||||
|
|
||||||
extension NSSound {
|
public extension NSSound {
|
||||||
public static func buzz(fart: Bool = false) {
|
static func buzz(fart: Bool = false) {
|
||||||
let filePath = Bundle.main.path(forResource: fart ? "Fart" : "Beep", ofType: "m4a")!
|
let filePath = Bundle.main.path(forResource: fart ? "Fart" : "Beep", ofType: "m4a")!
|
||||||
let fileURL = URL(fileURLWithPath: filePath)
|
let fileURL = URL(fileURLWithPath: filePath)
|
||||||
var soundID: SystemSoundID = 0
|
var soundID: SystemSoundID = 0
|
||||||
|
@ -18,12 +18,12 @@ extension NSSound {
|
||||||
AudioServicesPlaySystemSound(soundID)
|
AudioServicesPlaySystemSound(soundID)
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func buzz(fart: Bool = false, count: Int) {
|
static func buzz(fart: Bool = false, count: Int) {
|
||||||
if count <= 1 {
|
if count <= 1 {
|
||||||
NSSound.buzz(fart: fart)
|
NSSound.buzz(fart: fart)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _ in 0...count {
|
for _ in 0 ... count {
|
||||||
NSSound.buzz(fart: fart)
|
NSSound.buzz(fart: fart)
|
||||||
usleep(500_000)
|
usleep(500_000)
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,8 +9,8 @@
|
||||||
import Cocoa
|
import Cocoa
|
||||||
import InputMethodKit
|
import InputMethodKit
|
||||||
|
|
||||||
extension NSWindowController {
|
public extension NSWindowController {
|
||||||
public func orderFront() {
|
func orderFront() {
|
||||||
window?.orderFront(self)
|
window?.orderFront(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ extension NSWindowController {
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - windowTopLeftPoint: 給定的視窗顯示位置。
|
/// - windowTopLeftPoint: 給定的視窗顯示位置。
|
||||||
/// - heightDelta: 為了「防止選字窗抻出螢幕下方」而給定的預留高度。
|
/// - heightDelta: 為了「防止選字窗抻出螢幕下方」而給定的預留高度。
|
||||||
public func set(windowTopLeftPoint: NSPoint, bottomOutOfScreenAdjustmentHeight heightDelta: Double, useGCD: Bool) {
|
func set(windowTopLeftPoint: NSPoint, bottomOutOfScreenAdjustmentHeight heightDelta: Double, useGCD: Bool) {
|
||||||
func doSet() {
|
func doSet() {
|
||||||
guard let window = window, var screenFrame = NSScreen.main?.visibleFrame else { return }
|
guard let window = window, var screenFrame = NSScreen.main?.visibleFrame else { return }
|
||||||
let windowSize = window.frame.size
|
let windowSize = window.frame.size
|
||||||
|
@ -48,8 +48,8 @@ extension NSWindowController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension NSWindow {
|
public extension NSWindow {
|
||||||
@discardableResult public func callAlert(title: String, text: String? = nil) -> NSApplication.ModalResponse {
|
@discardableResult func callAlert(title: String, text: String? = nil) -> NSApplication.ModalResponse {
|
||||||
let alert = NSAlert()
|
let alert = NSAlert()
|
||||||
alert.messageText = title
|
alert.messageText = title
|
||||||
if let text = text { alert.informativeText = text }
|
if let text = text { alert.informativeText = text }
|
||||||
|
@ -62,7 +62,7 @@ extension NSWindow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension IMKCandidates {
|
public extension IMKCandidates {
|
||||||
/// 設定選字窗的顯示位置。
|
/// 設定選字窗的顯示位置。
|
||||||
///
|
///
|
||||||
/// 需注意:該函式會藉由設定選字窗左上角頂點的方式、使選字窗始終位於某個螢幕之內。
|
/// 需注意:該函式會藉由設定選字窗左上角頂點的方式、使選字窗始終位於某個螢幕之內。
|
||||||
|
@ -70,7 +70,7 @@ extension IMKCandidates {
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - windowTopLeftPoint: 給定的視窗顯示位置。
|
/// - windowTopLeftPoint: 給定的視窗顯示位置。
|
||||||
/// - heightDelta: 為了「防止選字窗抻出螢幕下方」而給定的預留高度。
|
/// - heightDelta: 為了「防止選字窗抻出螢幕下方」而給定的預留高度。
|
||||||
public func set(windowTopLeftPoint: NSPoint, bottomOutOfScreenAdjustmentHeight heightDelta: Double, useGCD: Bool) {
|
func set(windowTopLeftPoint: NSPoint, bottomOutOfScreenAdjustmentHeight heightDelta: Double, useGCD: Bool) {
|
||||||
func doSet() {
|
func doSet() {
|
||||||
DispatchQueue.main.async { [self] in
|
DispatchQueue.main.async { [self] in
|
||||||
guard var screenFrame = NSScreen.main?.visibleFrame else { return }
|
guard var screenFrame = NSScreen.main?.visibleFrame else { return }
|
||||||
|
|
|
@ -7,8 +7,8 @@ import SwiftUI
|
||||||
|
|
||||||
// MARK: Model
|
// MARK: Model
|
||||||
|
|
||||||
extension NSWindow {
|
public extension NSWindow {
|
||||||
public struct Position {
|
struct Position {
|
||||||
public static let defaultPadding: CGFloat = 16
|
public static let defaultPadding: CGFloat = 16
|
||||||
|
|
||||||
public var vertical: Vertical
|
public var vertical: Vertical
|
||||||
|
@ -17,28 +17,28 @@ extension NSWindow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension NSWindow.Position {
|
public extension NSWindow.Position {
|
||||||
public enum Horizontal {
|
enum Horizontal {
|
||||||
case left, center, right
|
case left, center, right
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum Vertical {
|
enum Vertical {
|
||||||
case top, center, bottom
|
case top, center, bottom
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Logic
|
// MARK: Logic
|
||||||
|
|
||||||
extension NSWindow.Position {
|
public extension NSWindow.Position {
|
||||||
public func value(forWindow windowRect: CGRect, inScreen screenRect: CGRect) -> CGPoint {
|
func value(forWindow windowRect: CGRect, inScreen screenRect: CGRect) -> CGPoint {
|
||||||
let xPosition = horizontal.valueFor(
|
let xPosition = horizontal.valueFor(
|
||||||
screenRange: screenRect.minX..<screenRect.maxX,
|
screenRange: screenRect.minX ..< screenRect.maxX,
|
||||||
width: windowRect.width,
|
width: windowRect.width,
|
||||||
padding: padding
|
padding: padding
|
||||||
)
|
)
|
||||||
|
|
||||||
let yPosition = vertical.valueFor(
|
let yPosition = vertical.valueFor(
|
||||||
screenRange: screenRect.minY..<screenRect.maxY,
|
screenRange: screenRect.minY ..< screenRect.maxY,
|
||||||
height: windowRect.height,
|
height: windowRect.height,
|
||||||
padding: padding
|
padding: padding
|
||||||
)
|
)
|
||||||
|
@ -47,8 +47,8 @@ extension NSWindow.Position {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension NSWindow.Position.Horizontal {
|
public extension NSWindow.Position.Horizontal {
|
||||||
public func valueFor(
|
func valueFor(
|
||||||
screenRange: Range<CGFloat>,
|
screenRange: Range<CGFloat>,
|
||||||
width: CGFloat,
|
width: CGFloat,
|
||||||
padding: CGFloat
|
padding: CGFloat
|
||||||
|
@ -56,15 +56,15 @@ extension NSWindow.Position.Horizontal {
|
||||||
-> CGFloat
|
-> CGFloat
|
||||||
{
|
{
|
||||||
switch self {
|
switch self {
|
||||||
case .left: return screenRange.lowerBound + padding
|
case .left: return screenRange.lowerBound + padding
|
||||||
case .center: return (screenRange.upperBound + screenRange.lowerBound - width) / 2
|
case .center: return (screenRange.upperBound + screenRange.lowerBound - width) / 2
|
||||||
case .right: return screenRange.upperBound - width - padding
|
case .right: return screenRange.upperBound - width - padding
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension NSWindow.Position.Vertical {
|
public extension NSWindow.Position.Vertical {
|
||||||
public func valueFor(
|
func valueFor(
|
||||||
screenRange: Range<CGFloat>,
|
screenRange: Range<CGFloat>,
|
||||||
height: CGFloat,
|
height: CGFloat,
|
||||||
padding: CGFloat
|
padding: CGFloat
|
||||||
|
@ -72,23 +72,23 @@ extension NSWindow.Position.Vertical {
|
||||||
-> CGFloat
|
-> CGFloat
|
||||||
{
|
{
|
||||||
switch self {
|
switch self {
|
||||||
case .top: return screenRange.upperBound - height - padding
|
case .top: return screenRange.upperBound - height - padding
|
||||||
case .center: return (screenRange.upperBound + screenRange.lowerBound - height) / 2
|
case .center: return (screenRange.upperBound + screenRange.lowerBound - height) / 2
|
||||||
case .bottom: return screenRange.lowerBound + padding
|
case .bottom: return screenRange.lowerBound + padding
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - AppKit extension
|
// MARK: - AppKit extension
|
||||||
|
|
||||||
extension NSWindow {
|
public extension NSWindow {
|
||||||
public func setPosition(_ position: Position, in screen: NSScreen?) {
|
func setPosition(_ position: Position, in screen: NSScreen?) {
|
||||||
guard let visibleFrame = (screen ?? self.screen)?.visibleFrame else { return }
|
guard let visibleFrame = (screen ?? self.screen)?.visibleFrame else { return }
|
||||||
let origin = position.value(forWindow: frame, inScreen: visibleFrame)
|
let origin = position.value(forWindow: frame, inScreen: visibleFrame)
|
||||||
setFrameOrigin(origin)
|
setFrameOrigin(origin)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func setPosition(
|
func setPosition(
|
||||||
vertical: Position.Vertical,
|
vertical: Position.Vertical,
|
||||||
horizontal: Position.Horizontal,
|
horizontal: Position.Horizontal,
|
||||||
padding: CGFloat = Position.defaultPadding,
|
padding: CGFloat = Position.defaultPadding,
|
||||||
|
@ -136,8 +136,8 @@ extension NSWindow {
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(macOS 10.15, *)
|
@available(macOS 10.15, *)
|
||||||
extension View {
|
public extension View {
|
||||||
public func hostingWindowPosition(
|
func hostingWindowPosition(
|
||||||
vertical: NSWindow.Position.Vertical,
|
vertical: NSWindow.Position.Vertical,
|
||||||
horizontal: NSWindow.Position.Horizontal,
|
horizontal: NSWindow.Position.Horizontal,
|
||||||
padding: CGFloat = NSWindow.Position.defaultPadding,
|
padding: CGFloat = NSWindow.Position.defaultPadding,
|
||||||
|
|
|
@ -4,13 +4,13 @@ import PackageDescription
|
||||||
let package = Package(
|
let package = Package(
|
||||||
name: "Hotenka",
|
name: "Hotenka",
|
||||||
platforms: [
|
platforms: [
|
||||||
.macOS(.v10_11)
|
.macOS(.v10_11),
|
||||||
],
|
],
|
||||||
products: [
|
products: [
|
||||||
.library(
|
.library(
|
||||||
name: "Hotenka",
|
name: "Hotenka",
|
||||||
targets: ["Hotenka"]
|
targets: ["Hotenka"]
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
dependencies: [],
|
dependencies: [],
|
||||||
targets: [
|
targets: [
|
||||||
|
|
|
@ -127,18 +127,18 @@ public class HotenkaChineseConverter {
|
||||||
var dictTypeKey: String
|
var dictTypeKey: String
|
||||||
|
|
||||||
switch dictType {
|
switch dictType {
|
||||||
case .zhHantTW:
|
case .zhHantTW:
|
||||||
dictTypeKey = "zh2TW"
|
dictTypeKey = "zh2TW"
|
||||||
case .zhHantHK:
|
case .zhHantHK:
|
||||||
dictTypeKey = "zh2HK"
|
dictTypeKey = "zh2HK"
|
||||||
case .zhHansSG:
|
case .zhHansSG:
|
||||||
dictTypeKey = "zh2SG"
|
dictTypeKey = "zh2SG"
|
||||||
case .zhHansJP:
|
case .zhHansJP:
|
||||||
dictTypeKey = "zh2JP"
|
dictTypeKey = "zh2JP"
|
||||||
case .zhHantKX:
|
case .zhHantKX:
|
||||||
dictTypeKey = "zh2KX"
|
dictTypeKey = "zh2KX"
|
||||||
case .zhHansCN:
|
case .zhHansCN:
|
||||||
dictTypeKey = "zh2CN"
|
dictTypeKey = "zh2CN"
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = ""
|
var result = ""
|
||||||
|
@ -153,7 +153,7 @@ public class HotenkaChineseConverter {
|
||||||
innerloop: while j > 0 {
|
innerloop: while j > 0 {
|
||||||
let start = target.index(target.startIndex, offsetBy: i)
|
let start = target.index(target.startIndex, offsetBy: i)
|
||||||
let end = target.index(target.startIndex, offsetBy: i + j)
|
let end = target.index(target.startIndex, offsetBy: i + j)
|
||||||
guard let useDictSubStr = useDict[String(target[start..<end])] else {
|
guard let useDictSubStr = useDict[String(target[start ..< end])] else {
|
||||||
j -= 1
|
j -= 1
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -164,7 +164,7 @@ public class HotenkaChineseConverter {
|
||||||
if j == 0 {
|
if j == 0 {
|
||||||
let start = target.index(target.startIndex, offsetBy: i)
|
let start = target.index(target.startIndex, offsetBy: i)
|
||||||
let end = target.index(target.startIndex, offsetBy: i + 1)
|
let end = target.index(target.startIndex, offsetBy: i + 1)
|
||||||
result = result + String(target[start..<end])
|
result = result + String(target[start ..< end])
|
||||||
i += 1
|
i += 1
|
||||||
} else {
|
} else {
|
||||||
i += j
|
i += j
|
||||||
|
@ -177,18 +177,18 @@ public class HotenkaChineseConverter {
|
||||||
|
|
||||||
// MARK: - String extensions
|
// MARK: - String extensions
|
||||||
|
|
||||||
extension String {
|
private extension String {
|
||||||
fileprivate func range(of str: String) -> Range<Int> {
|
func range(of str: String) -> Range<Int> {
|
||||||
var start = -1
|
var start = -1
|
||||||
withCString { bytes in
|
withCString { bytes in
|
||||||
str.withCString { sbytes in
|
str.withCString { sbytes in
|
||||||
start = strstr(bytes, sbytes) - UnsafeMutablePointer<Int8>(mutating: bytes)
|
start = strstr(bytes, sbytes) - UnsafeMutablePointer<Int8>(mutating: bytes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return start < 0 ? 0..<0 : start..<start + str.utf8.count
|
return start < 0 ? 0 ..< 0 : start ..< start + str.utf8.count
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func substring(to index: Int) -> String {
|
func substring(to index: Int) -> String {
|
||||||
var out = self
|
var out = self
|
||||||
withCString { bytes in
|
withCString { bytes in
|
||||||
let bytes = UnsafeMutablePointer<Int8>(mutating: bytes)
|
let bytes = UnsafeMutablePointer<Int8>(mutating: bytes)
|
||||||
|
@ -198,7 +198,7 @@ extension String {
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func substring(from index: Int) -> String {
|
func substring(from index: Int) -> String {
|
||||||
var out = self
|
var out = self
|
||||||
withCString { bytes in
|
withCString { bytes in
|
||||||
out = String(cString: bytes + index)
|
out = String(cString: bytes + index)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
.PHONY: format
|
.PHONY: lint format
|
||||||
|
|
||||||
format:
|
format:
|
||||||
swiftformat ./ --swiftversion 5.5
|
@swiftformat --swiftversion 5.5 --indent 2 ./
|
||||||
@git ls-files --exclude-standard | grep -E '\.swift$$' | xargs swift-format format --in-place --configuration ./.clang-format-swift.json --parallel
|
|
||||||
@git ls-files --exclude-standard | grep -E '\.swift$$' | xargs swift-format lint --configuration ./.clang-format-swift.json --parallel
|
lint:
|
||||||
|
@git ls-files --exclude-standard | grep -E '\.swift$$' | swiftlint --fix --autocorrect
|
|
@ -4,19 +4,19 @@ import PackageDescription
|
||||||
let package = Package(
|
let package = Package(
|
||||||
name: "IMKUtils",
|
name: "IMKUtils",
|
||||||
platforms: [
|
platforms: [
|
||||||
.macOS(.v10_11)
|
.macOS(.v10_11),
|
||||||
],
|
],
|
||||||
products: [
|
products: [
|
||||||
.library(
|
.library(
|
||||||
name: "IMKUtils",
|
name: "IMKUtils",
|
||||||
targets: ["IMKUtils"]
|
targets: ["IMKUtils"]
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
dependencies: [],
|
dependencies: [],
|
||||||
targets: [
|
targets: [
|
||||||
.target(
|
.target(
|
||||||
name: "IMKUtils",
|
name: "IMKUtils",
|
||||||
dependencies: []
|
dependencies: []
|
||||||
)
|
),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
|
@ -92,8 +92,8 @@ public enum IMKHelper {
|
||||||
|
|
||||||
// MARK: - 與輸入法的具體的安裝過程有關的命令
|
// MARK: - 與輸入法的具體的安裝過程有關的命令
|
||||||
|
|
||||||
extension IMKHelper {
|
public extension IMKHelper {
|
||||||
@discardableResult public static func registerInputMethod() -> Int32 {
|
@discardableResult static func registerInputMethod() -> Int32 {
|
||||||
TISInputSource.registerInputMethod() ? 0 : -1
|
TISInputSource.registerInputMethod() ? 0 : -1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,21 +11,21 @@ import InputMethodKit
|
||||||
|
|
||||||
// MARK: - TISInputSource Extension by The vChewing Project (MIT-NTL License).
|
// MARK: - TISInputSource Extension by The vChewing Project (MIT-NTL License).
|
||||||
|
|
||||||
extension TISInputSource {
|
public extension TISInputSource {
|
||||||
public static var allRegisteredInstancesOfThisInputMethod: [TISInputSource] {
|
static var allRegisteredInstancesOfThisInputMethod: [TISInputSource] {
|
||||||
TISInputSource.modes.compactMap { TISInputSource.generate(from: $0) }
|
TISInputSource.modes.compactMap { TISInputSource.generate(from: $0) }
|
||||||
}
|
}
|
||||||
|
|
||||||
public static var modes: [String] {
|
static var modes: [String] {
|
||||||
guard let components = Bundle.main.infoDictionary?["ComponentInputModeDict"] as? [String: Any],
|
guard let components = Bundle.main.infoDictionary?["ComponentInputModeDict"] as? [String: Any],
|
||||||
let tsInputModeListKey = components["tsInputModeListKey"] as? [String: Any]
|
let tsInputModeListKey = components["tsInputModeListKey"] as? [String: Any]
|
||||||
else {
|
else {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
return tsInputModeListKey.keys.map { $0 }
|
return tsInputModeListKey.keys.map { $0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
@discardableResult public static func registerInputMethod() -> Bool {
|
@discardableResult static func registerInputMethod() -> Bool {
|
||||||
let instances = TISInputSource.allRegisteredInstancesOfThisInputMethod
|
let instances = TISInputSource.allRegisteredInstancesOfThisInputMethod
|
||||||
if instances.isEmpty {
|
if instances.isEmpty {
|
||||||
// 有實例尚未登記。執行登記手續。
|
// 有實例尚未登記。執行登記手續。
|
||||||
|
@ -46,15 +46,15 @@ extension TISInputSource {
|
||||||
return succeeded
|
return succeeded
|
||||||
}
|
}
|
||||||
|
|
||||||
@discardableResult public static func registerInputSource() -> Bool {
|
@discardableResult static func registerInputSource() -> Bool {
|
||||||
TISRegisterInputSource(Bundle.main.bundleURL as CFURL) == noErr
|
TISRegisterInputSource(Bundle.main.bundleURL as CFURL) == noErr
|
||||||
}
|
}
|
||||||
|
|
||||||
@discardableResult public func activate() -> Bool {
|
@discardableResult func activate() -> Bool {
|
||||||
TISEnableInputSource(self) == noErr
|
TISEnableInputSource(self) == noErr
|
||||||
}
|
}
|
||||||
|
|
||||||
@discardableResult public func select() -> Bool {
|
@discardableResult func select() -> Bool {
|
||||||
if !isSelectable {
|
if !isSelectable {
|
||||||
NSLog("Non-selectable: \(identifier)")
|
NSLog("Non-selectable: \(identifier)")
|
||||||
return false
|
return false
|
||||||
|
@ -66,35 +66,35 @@ extension TISInputSource {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@discardableResult public func deactivate() -> Bool {
|
@discardableResult func deactivate() -> Bool {
|
||||||
TISDisableInputSource(self) == noErr
|
TISDisableInputSource(self) == noErr
|
||||||
}
|
}
|
||||||
|
|
||||||
public var isActivated: Bool {
|
var isActivated: Bool {
|
||||||
unsafeBitCast(TISGetInputSourceProperty(self, kTISPropertyInputSourceIsEnabled), to: CFBoolean.self)
|
unsafeBitCast(TISGetInputSourceProperty(self, kTISPropertyInputSourceIsEnabled), to: CFBoolean.self)
|
||||||
== kCFBooleanTrue
|
== kCFBooleanTrue
|
||||||
}
|
}
|
||||||
|
|
||||||
public var isSelectable: Bool {
|
var isSelectable: Bool {
|
||||||
unsafeBitCast(TISGetInputSourceProperty(self, kTISPropertyInputSourceIsSelectCapable), to: CFBoolean.self)
|
unsafeBitCast(TISGetInputSourceProperty(self, kTISPropertyInputSourceIsSelectCapable), to: CFBoolean.self)
|
||||||
== kCFBooleanTrue
|
== kCFBooleanTrue
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func generate(from identifier: String) -> TISInputSource? {
|
static func generate(from identifier: String) -> TISInputSource? {
|
||||||
TISInputSource.rawTISInputSources(onlyASCII: false)[identifier]
|
TISInputSource.rawTISInputSources(onlyASCII: false)[identifier]
|
||||||
}
|
}
|
||||||
|
|
||||||
public var inputModeID: String {
|
var inputModeID: String {
|
||||||
unsafeBitCast(TISGetInputSourceProperty(self, kTISPropertyInputModeID), to: NSString.self) as String? ?? ""
|
unsafeBitCast(TISGetInputSourceProperty(self, kTISPropertyInputModeID), to: NSString.self) as String? ?? ""
|
||||||
}
|
}
|
||||||
|
|
||||||
public var vChewingLocalizedName: String {
|
var vChewingLocalizedName: String {
|
||||||
switch identifier {
|
switch identifier {
|
||||||
case "com.apple.keylayout.ZhuyinBopomofo":
|
case "com.apple.keylayout.ZhuyinBopomofo":
|
||||||
return NSLocalizedString("Apple Zhuyin Bopomofo (Dachen)", comment: "")
|
return NSLocalizedString("Apple Zhuyin Bopomofo (Dachen)", comment: "")
|
||||||
case "com.apple.keylayout.ZhuyinEten":
|
case "com.apple.keylayout.ZhuyinEten":
|
||||||
return NSLocalizedString("Apple Zhuyin Eten (Traditional)", comment: "")
|
return NSLocalizedString("Apple Zhuyin Eten (Traditional)", comment: "")
|
||||||
default: return localizedName
|
default: return localizedName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -104,23 +104,23 @@ extension TISInputSource {
|
||||||
// Ref: Original source codes are written in Swift 4 from Mzp's InputMethodKit textbook.
|
// Ref: Original source codes are written in Swift 4 from Mzp's InputMethodKit textbook.
|
||||||
// Note: Slightly modified by vChewing Project: Using Dictionaries when necessary.
|
// Note: Slightly modified by vChewing Project: Using Dictionaries when necessary.
|
||||||
|
|
||||||
extension TISInputSource {
|
public extension TISInputSource {
|
||||||
public var localizedName: String {
|
var localizedName: String {
|
||||||
unsafeBitCast(TISGetInputSourceProperty(self, kTISPropertyLocalizedName), to: NSString.self) as String? ?? ""
|
unsafeBitCast(TISGetInputSourceProperty(self, kTISPropertyLocalizedName), to: NSString.self) as String? ?? ""
|
||||||
}
|
}
|
||||||
|
|
||||||
public var identifier: String {
|
var identifier: String {
|
||||||
unsafeBitCast(TISGetInputSourceProperty(self, kTISPropertyInputSourceID), to: NSString.self) as String? ?? ""
|
unsafeBitCast(TISGetInputSourceProperty(self, kTISPropertyInputSourceID), to: NSString.self) as String? ?? ""
|
||||||
}
|
}
|
||||||
|
|
||||||
public var scriptCode: Int {
|
var scriptCode: Int {
|
||||||
// Shiki's note: There is no "kTISPropertyScriptCode" in TextInputSources.h file.
|
// Shiki's note: There is no "kTISPropertyScriptCode" in TextInputSources.h file.
|
||||||
// Using Mzp's latest solution in his blog: https://mzp.hatenablog.com/entry/2018/07/16/212026
|
// Using Mzp's latest solution in his blog: https://mzp.hatenablog.com/entry/2018/07/16/212026
|
||||||
let r = TISGetInputSourceProperty(self, "TSMInputSourcePropertyScriptCode" as CFString)
|
let r = TISGetInputSourceProperty(self, "TSMInputSourcePropertyScriptCode" as CFString)
|
||||||
return unsafeBitCast(r, to: NSString.self).integerValue as Int? ?? 0
|
return unsafeBitCast(r, to: NSString.self).integerValue as Int? ?? 0
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func rawTISInputSources(onlyASCII: Bool = false) -> [String: TISInputSource] {
|
static func rawTISInputSources(onlyASCII: Bool = false) -> [String: TISInputSource] {
|
||||||
// 為了指定檢索條件,先構築 CFDictionary 辭典。
|
// 為了指定檢索條件,先構築 CFDictionary 辭典。
|
||||||
// 第二項代指辭典容量。
|
// 第二項代指辭典容量。
|
||||||
let conditions = CFDictionaryCreateMutable(nil, 2, nil, nil)
|
let conditions = CFDictionaryCreateMutable(nil, 2, nil, nil)
|
||||||
|
|
|
@ -4,13 +4,13 @@ import PackageDescription
|
||||||
let package = Package(
|
let package = Package(
|
||||||
name: "LangModelAssembly",
|
name: "LangModelAssembly",
|
||||||
platforms: [
|
platforms: [
|
||||||
.macOS(.v10_11)
|
.macOS(.v10_11),
|
||||||
],
|
],
|
||||||
products: [
|
products: [
|
||||||
.library(
|
.library(
|
||||||
name: "LangModelAssembly",
|
name: "LangModelAssembly",
|
||||||
targets: ["LangModelAssembly"]
|
targets: ["LangModelAssembly"]
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
.package(path: "../RMJay_LineReader"),
|
.package(path: "../RMJay_LineReader"),
|
||||||
|
|
|
@ -10,8 +10,8 @@ import Foundation
|
||||||
import LineReader
|
import LineReader
|
||||||
import Shared
|
import Shared
|
||||||
|
|
||||||
extension vChewingLM {
|
public extension vChewingLM {
|
||||||
public enum LMConsolidator {
|
enum LMConsolidator {
|
||||||
public static let kPragmaHeader = "# 𝙵𝙾𝚁𝙼𝙰𝚃 𝚘𝚛𝚐.𝚊𝚝𝚎𝚕𝚒𝚎𝚛𝙸𝚗𝚖𝚞.𝚟𝚌𝚑𝚎𝚠𝚒𝚗𝚐.𝚞𝚜𝚎𝚛𝙻𝚊𝚗𝚐𝚞𝚊𝚐𝚎𝙼𝚘𝚍𝚎𝚕𝙳𝚊𝚝𝚊.𝚏𝚘𝚛𝚖𝚊𝚝𝚝𝚎𝚍"
|
public static let kPragmaHeader = "# 𝙵𝙾𝚁𝙼𝙰𝚃 𝚘𝚛𝚐.𝚊𝚝𝚎𝚕𝚒𝚎𝚛𝙸𝚗𝚖𝚞.𝚟𝚌𝚑𝚎𝚠𝚒𝚗𝚐.𝚞𝚜𝚎𝚛𝙻𝚊𝚗𝚐𝚞𝚊𝚐𝚎𝙼𝚘𝚍𝚎𝚕𝙳𝚊𝚝𝚊.𝚏𝚘𝚛𝚖𝚊𝚝𝚝𝚎𝚍"
|
||||||
|
|
||||||
/// 檢查給定檔案的標頭是否正常。
|
/// 檢查給定檔案的標頭是否正常。
|
||||||
|
@ -24,7 +24,7 @@ extension vChewingLM {
|
||||||
throw FileErrors.fileHandleError("")
|
throw FileErrors.fileHandleError("")
|
||||||
}
|
}
|
||||||
let lineReader = try LineReader(file: fileHandle)
|
let lineReader = try LineReader(file: fileHandle)
|
||||||
for strLine in lineReader { // 不需要 i=0,因為第一遍迴圈就出結果。
|
for strLine in lineReader { // 不需要 i=0,因為第一遍迴圈就出結果。
|
||||||
if strLine != kPragmaHeader {
|
if strLine != kPragmaHeader {
|
||||||
vCLog("Header Mismatch, Starting In-Place Consolidation.")
|
vCLog("Header Mismatch, Starting In-Place Consolidation.")
|
||||||
return false
|
return false
|
||||||
|
@ -56,7 +56,7 @@ extension vChewingLM {
|
||||||
if !strIncoming.hasSuffix("\n") {
|
if !strIncoming.hasSuffix("\n") {
|
||||||
vCLog("EOF Fix Necessity Confirmed, Start Fixing.")
|
vCLog("EOF Fix Necessity Confirmed, Start Fixing.")
|
||||||
if let writeFile = FileHandle(forUpdatingAtPath: path),
|
if let writeFile = FileHandle(forUpdatingAtPath: path),
|
||||||
let endl = "\n".data(using: .utf8)
|
let endl = "\n".data(using: .utf8)
|
||||||
{
|
{
|
||||||
writeFile.seekToEndOfFile()
|
writeFile.seekToEndOfFile()
|
||||||
writeFile.write(endl)
|
writeFile.write(endl)
|
||||||
|
@ -85,7 +85,7 @@ extension vChewingLM {
|
||||||
var pragmaResult: Bool {
|
var pragmaResult: Bool {
|
||||||
let realPragmaHeader = kPragmaHeader + "\n"
|
let realPragmaHeader = kPragmaHeader + "\n"
|
||||||
if strProcessed.count <= kPragmaHeader.count { return false }
|
if strProcessed.count <= kPragmaHeader.count { return false }
|
||||||
let range = 0..<(realPragmaHeader.count)
|
let range = 0 ..< (realPragmaHeader.count)
|
||||||
let fetchedPragma = ContiguousArray(strProcessed.utf8CString[range])
|
let fetchedPragma = ContiguousArray(strProcessed.utf8CString[range])
|
||||||
return fetchedPragma == realPragmaHeader.utf8CString
|
return fetchedPragma == realPragmaHeader.utf8CString
|
||||||
}
|
}
|
||||||
|
@ -104,14 +104,14 @@ extension vChewingLM {
|
||||||
strProcessed.regReplace(pattern: #"(\n | \n)"#, replaceWith: "\n")
|
strProcessed.regReplace(pattern: #"(\n | \n)"#, replaceWith: "\n")
|
||||||
// CR & FF to LF, 且去除重複行
|
// CR & FF to LF, 且去除重複行
|
||||||
strProcessed.regReplace(pattern: #"(\f+|\r+|\n+)+"#, replaceWith: "\n")
|
strProcessed.regReplace(pattern: #"(\f+|\r+|\n+)+"#, replaceWith: "\n")
|
||||||
if strProcessed.prefix(1) == " " { // 去除檔案開頭空格
|
if strProcessed.prefix(1) == " " { // 去除檔案開頭空格
|
||||||
strProcessed.removeFirst()
|
strProcessed.removeFirst()
|
||||||
}
|
}
|
||||||
if strProcessed.suffix(1) == " " { // 去除檔案結尾空格
|
if strProcessed.suffix(1) == " " { // 去除檔案結尾空格
|
||||||
strProcessed.removeLast()
|
strProcessed.removeLast()
|
||||||
}
|
}
|
||||||
|
|
||||||
strProcessed = kPragmaHeader + "\n" + strProcessed // Add Pragma Header
|
strProcessed = kPragmaHeader + "\n" + strProcessed // Add Pragma Header
|
||||||
|
|
||||||
// Step 3: Deduplication.
|
// Step 3: Deduplication.
|
||||||
let arrData = strProcessed.split(separator: "\n")
|
let arrData = strProcessed.split(separator: "\n")
|
||||||
|
|
|
@ -10,7 +10,7 @@ import Foundation
|
||||||
import Megrez
|
import Megrez
|
||||||
import Shared
|
import Shared
|
||||||
|
|
||||||
extension vChewingLM {
|
public extension vChewingLM {
|
||||||
/// 語言模組副本化模組(LMInstantiator,下稱「LMI」)自身為符合天權星組字引擎內
|
/// 語言模組副本化模組(LMInstantiator,下稱「LMI」)自身為符合天權星組字引擎內
|
||||||
/// 的 LangModelProtocol 協定的模組、統籌且整理來自其它子模組的資料(包括使
|
/// 的 LangModelProtocol 協定的模組、統籌且整理來自其它子模組的資料(包括使
|
||||||
/// 用者語彙、繪文字模組、語彙濾除表、原廠語言模組等)。
|
/// 用者語彙、繪文字模組、語彙濾除表、原廠語言模組等)。
|
||||||
|
@ -28,7 +28,7 @@ extension vChewingLM {
|
||||||
///
|
///
|
||||||
/// LMI 會根據需要分別載入原廠語言模組和其他個別的子語言模組。LMI 本身不會記錄這些
|
/// LMI 會根據需要分別載入原廠語言模組和其他個別的子語言模組。LMI 本身不會記錄這些
|
||||||
/// 語言模組的相關資料的存放位置,僅藉由參數來讀取相關訊息。
|
/// 語言模組的相關資料的存放位置,僅藉由參數來讀取相關訊息。
|
||||||
public class LMInstantiator: LangModelProtocol {
|
class LMInstantiator: LangModelProtocol {
|
||||||
// 在函式內部用以記錄狀態的開關。
|
// 在函式內部用以記錄狀態的開關。
|
||||||
public var isCassetteEnabled = false
|
public var isCassetteEnabled = false
|
||||||
public var isPhraseReplacementEnabled = false
|
public var isPhraseReplacementEnabled = false
|
||||||
|
@ -242,8 +242,8 @@ extension vChewingLM {
|
||||||
let keyChain = keyArray.joined(separator: "-")
|
let keyChain = keyArray.joined(separator: "-")
|
||||||
_ =
|
_ =
|
||||||
isFiltering
|
isFiltering
|
||||||
? lmFiltered.temporaryMap[keyChain, default: []].append(unigram)
|
? lmFiltered.temporaryMap[keyChain, default: []].append(unigram)
|
||||||
: lmUserPhrases.temporaryMap[keyChain, default: []].append(unigram)
|
: lmUserPhrases.temporaryMap[keyChain, default: []].append(unigram)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 自當前記憶體取得指定使用者子語言模組內的原始資料體。
|
/// 自當前記憶體取得指定使用者子語言模組內的原始資料體。
|
||||||
|
@ -251,11 +251,11 @@ extension vChewingLM {
|
||||||
/// - targetType: 操作對象。
|
/// - targetType: 操作對象。
|
||||||
public func retrieveData(from targetType: ReplacableUserDataType) -> String {
|
public func retrieveData(from targetType: ReplacableUserDataType) -> String {
|
||||||
switch targetType {
|
switch targetType {
|
||||||
case .thePhrases: return lmUserPhrases.strData
|
case .thePhrases: return lmUserPhrases.strData
|
||||||
case .theFilter: return lmFiltered.strData
|
case .theFilter: return lmFiltered.strData
|
||||||
case .theReplacements: return lmReplacements.strData
|
case .theReplacements: return lmReplacements.strData
|
||||||
case .theAssociates: return lmAssociates.strData
|
case .theAssociates: return lmAssociates.strData
|
||||||
case .theSymbols: return lmUserSymbols.strData
|
case .theSymbols: return lmUserSymbols.strData
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -267,21 +267,21 @@ extension vChewingLM {
|
||||||
var rawText = rawStrData
|
var rawText = rawStrData
|
||||||
LMConsolidator.consolidate(text: &rawText, pragma: true)
|
LMConsolidator.consolidate(text: &rawText, pragma: true)
|
||||||
switch targetType {
|
switch targetType {
|
||||||
case .theAssociates:
|
case .theAssociates:
|
||||||
lmAssociates.replaceData(textData: rawText)
|
lmAssociates.replaceData(textData: rawText)
|
||||||
if save { lmAssociates.saveData() }
|
if save { lmAssociates.saveData() }
|
||||||
case .theFilter:
|
case .theFilter:
|
||||||
lmFiltered.replaceData(textData: rawText)
|
lmFiltered.replaceData(textData: rawText)
|
||||||
if save { lmAssociates.saveData() }
|
if save { lmAssociates.saveData() }
|
||||||
case .theReplacements:
|
case .theReplacements:
|
||||||
lmReplacements.replaceData(textData: rawText)
|
lmReplacements.replaceData(textData: rawText)
|
||||||
if save { lmAssociates.saveData() }
|
if save { lmAssociates.saveData() }
|
||||||
case .thePhrases:
|
case .thePhrases:
|
||||||
lmUserPhrases.replaceData(textData: rawText)
|
lmUserPhrases.replaceData(textData: rawText)
|
||||||
if save { lmAssociates.saveData() }
|
if save { lmAssociates.saveData() }
|
||||||
case .theSymbols:
|
case .theSymbols:
|
||||||
lmUserSymbols.replaceData(textData: rawText)
|
lmUserSymbols.replaceData(textData: rawText)
|
||||||
if save { lmAssociates.saveData() }
|
if save { lmAssociates.saveData() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -348,7 +348,7 @@ extension vChewingLM {
|
||||||
|
|
||||||
// 提前處理語彙置換
|
// 提前處理語彙置換
|
||||||
if isPhraseReplacementEnabled {
|
if isPhraseReplacementEnabled {
|
||||||
for i in 0..<rawAllUnigrams.count {
|
for i in 0 ..< rawAllUnigrams.count {
|
||||||
let newValue = lmReplacements.valuesFor(key: rawAllUnigrams[i].value)
|
let newValue = lmReplacements.valuesFor(key: rawAllUnigrams[i].value)
|
||||||
guard !newValue.isEmpty else { continue }
|
guard !newValue.isEmpty else { continue }
|
||||||
rawAllUnigrams[i].value = newValue
|
rawAllUnigrams[i].value = newValue
|
||||||
|
|
|
@ -10,37 +10,37 @@ import Foundation
|
||||||
import Megrez
|
import Megrez
|
||||||
import Shared
|
import Shared
|
||||||
|
|
||||||
extension vChewingLM.LMInstantiator {
|
public extension vChewingLM.LMInstantiator {
|
||||||
/// 當前磁帶所規定的花牌鍵。
|
/// 當前磁帶所規定的花牌鍵。
|
||||||
public var cassetteWildcardKey: String { Self.lmCassette.wildcardKey }
|
var cassetteWildcardKey: String { Self.lmCassette.wildcardKey }
|
||||||
/// 當前磁帶規定的最大碼長。
|
/// 當前磁帶規定的最大碼長。
|
||||||
public var maxCassetteKeyLength: Int { Self.lmCassette.maxKeyLength }
|
var maxCassetteKeyLength: Int { Self.lmCassette.maxKeyLength }
|
||||||
|
|
||||||
/// 將當前的按鍵轉換成磁帶內定義了的字根。
|
/// 將當前的按鍵轉換成磁帶內定義了的字根。
|
||||||
/// - Parameter char: 按鍵字元。
|
/// - Parameter char: 按鍵字元。
|
||||||
/// - Returns: 轉換結果。如果轉換失敗,則返回原始按鍵字元。
|
/// - Returns: 轉換結果。如果轉換失敗,則返回原始按鍵字元。
|
||||||
public func convertCassetteKeyToDisplay(char: String) -> String {
|
func convertCassetteKeyToDisplay(char: String) -> String {
|
||||||
Self.lmCassette.convertKeyToDisplay(char: char)
|
Self.lmCassette.convertKeyToDisplay(char: char)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 檢查當前的按鍵是否屬於目前的磁帶規定的允許的字根按鍵。
|
/// 檢查當前的按鍵是否屬於目前的磁帶規定的允許的字根按鍵。
|
||||||
/// - Parameter key: 按鍵字元。
|
/// - Parameter key: 按鍵字元。
|
||||||
/// - Returns: 檢查結果。
|
/// - Returns: 檢查結果。
|
||||||
public func isThisCassetteKeyAllowed(key: String) -> Bool {
|
func isThisCassetteKeyAllowed(key: String) -> Bool {
|
||||||
Self.lmCassette.allowedKeys.contains(key)
|
Self.lmCassette.allowedKeys.contains(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 檢查給定的索引鍵在搭上花牌鍵之後是否有匹配結果。
|
/// 檢查給定的索引鍵在搭上花牌鍵之後是否有匹配結果。
|
||||||
/// - Parameter key: 給定的索引鍵。
|
/// - Parameter key: 給定的索引鍵。
|
||||||
/// - Returns: 是否有批配結果。
|
/// - Returns: 是否有批配結果。
|
||||||
public func hasCassetteWildcardResultsFor(key: String) -> Bool {
|
func hasCassetteWildcardResultsFor(key: String) -> Bool {
|
||||||
Self.lmCassette.hasUnigramsFor(key: key + Self.lmCassette.wildcard)
|
Self.lmCassette.hasUnigramsFor(key: key + Self.lmCassette.wildcard)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 提供磁帶反查結果。
|
/// 提供磁帶反查結果。
|
||||||
/// - Parameter value: 要拿來反查的字詞。
|
/// - Parameter value: 要拿來反查的字詞。
|
||||||
/// - Returns: 反查結果字串陣列。
|
/// - Returns: 反查結果字串陣列。
|
||||||
public func cassetteReverseLookup(for value: String) -> [String] {
|
func cassetteReverseLookup(for value: String) -> [String] {
|
||||||
var lookupResult = Self.lmCassette.reverseLookupMap[value] ?? []
|
var lookupResult = Self.lmCassette.reverseLookupMap[value] ?? []
|
||||||
guard !lookupResult.isEmpty else { return [] }
|
guard !lookupResult.isEmpty else { return [] }
|
||||||
lookupResult = lookupResult.map { $0.trimmingCharacters(in: .newlines) }
|
lookupResult = lookupResult.map { $0.trimmingCharacters(in: .newlines) }
|
||||||
|
|
|
@ -22,56 +22,56 @@ extension vChewingLM.LMInstantiator {
|
||||||
delta.year = max(min(deltaOfCalendarYears, 0), thisYear * -1)
|
delta.year = max(min(deltaOfCalendarYears, 0), thisYear * -1)
|
||||||
let currentDateShortened = Calendar.current.date(byAdding: delta, to: currentDate)
|
let currentDateShortened = Calendar.current.date(byAdding: delta, to: currentDate)
|
||||||
switch key {
|
switch key {
|
||||||
case "ㄖˋ-ㄑㄧ", "ㄖˋ-ㄑㄧˊ":
|
case "ㄖˋ-ㄑㄧ", "ㄖˋ-ㄑㄧˊ":
|
||||||
let formatterDate1 = DateFormatter()
|
let formatterDate1 = DateFormatter()
|
||||||
let formatterDate2 = DateFormatter()
|
let formatterDate2 = DateFormatter()
|
||||||
formatterDate1.dateFormat = "yyyy-MM-dd"
|
formatterDate1.dateFormat = "yyyy-MM-dd"
|
||||||
formatterDate2.dateFormat = "yyyy年MM月dd日"
|
formatterDate2.dateFormat = "yyyy年MM月dd日"
|
||||||
let date1 = formatterDate1.string(from: currentDate)
|
let date1 = formatterDate1.string(from: currentDate)
|
||||||
let date2 = formatterDate2.string(from: currentDate)
|
let date2 = formatterDate2.string(from: currentDate)
|
||||||
var date3 = date2.convertArabicNumeralsToChinese
|
var date3 = date2.convertArabicNumeralsToChinese
|
||||||
date3 = date3.replacingOccurrences(of: "年〇", with: "年")
|
date3 = date3.replacingOccurrences(of: "年〇", with: "年")
|
||||||
date3 = date3.replacingOccurrences(of: "月〇", with: "月")
|
date3 = date3.replacingOccurrences(of: "月〇", with: "月")
|
||||||
results.append(.init(value: date1, score: -94))
|
results.append(.init(value: date1, score: -94))
|
||||||
results.append(.init(value: date2, score: -95))
|
results.append(.init(value: date2, score: -95))
|
||||||
results.append(.init(value: date3, score: -96))
|
results.append(.init(value: date3, score: -96))
|
||||||
if let currentDateShortened = currentDateShortened, delta.year != 0 {
|
if let currentDateShortened = currentDateShortened, delta.year != 0 {
|
||||||
var dateAlt1: String = formatterDate1.string(from: currentDateShortened)
|
var dateAlt1: String = formatterDate1.string(from: currentDateShortened)
|
||||||
dateAlt1.regReplace(pattern: #"^0+"#)
|
dateAlt1.regReplace(pattern: #"^0+"#)
|
||||||
var dateAlt2: String = formatterDate2.string(from: currentDateShortened)
|
var dateAlt2: String = formatterDate2.string(from: currentDateShortened)
|
||||||
dateAlt2.regReplace(pattern: #"^0+"#)
|
dateAlt2.regReplace(pattern: #"^0+"#)
|
||||||
var dateAlt3 = dateAlt2.convertArabicNumeralsToChinese
|
var dateAlt3 = dateAlt2.convertArabicNumeralsToChinese
|
||||||
dateAlt3 = dateAlt3.replacingOccurrences(of: "年〇", with: "年")
|
dateAlt3 = dateAlt3.replacingOccurrences(of: "年〇", with: "年")
|
||||||
dateAlt3 = dateAlt3.replacingOccurrences(of: "月〇", with: "月")
|
dateAlt3 = dateAlt3.replacingOccurrences(of: "月〇", with: "月")
|
||||||
results.append(.init(value: dateAlt1, score: -97))
|
results.append(.init(value: dateAlt1, score: -97))
|
||||||
results.append(.init(value: dateAlt2, score: -98))
|
results.append(.init(value: dateAlt2, score: -98))
|
||||||
results.append(.init(value: dateAlt3, score: -99))
|
results.append(.init(value: dateAlt3, score: -99))
|
||||||
}
|
}
|
||||||
case "ㄕˊ-ㄐㄧㄢ":
|
case "ㄕˊ-ㄐㄧㄢ":
|
||||||
let formatterTime1 = DateFormatter()
|
let formatterTime1 = DateFormatter()
|
||||||
let formatterTime2 = DateFormatter()
|
let formatterTime2 = DateFormatter()
|
||||||
let formatterTime3 = DateFormatter()
|
let formatterTime3 = DateFormatter()
|
||||||
formatterTime1.dateFormat = "HH:mm"
|
formatterTime1.dateFormat = "HH:mm"
|
||||||
formatterTime2.dateFormat = isCHS ? "HH点mm分" : "HH點mm分"
|
formatterTime2.dateFormat = isCHS ? "HH点mm分" : "HH點mm分"
|
||||||
formatterTime3.dateFormat = isCHS ? "HH时mm分" : "HH時mm分"
|
formatterTime3.dateFormat = isCHS ? "HH时mm分" : "HH時mm分"
|
||||||
let time1 = formatterTime1.string(from: currentDate)
|
let time1 = formatterTime1.string(from: currentDate)
|
||||||
let time2 = formatterTime2.string(from: currentDate)
|
let time2 = formatterTime2.string(from: currentDate)
|
||||||
let time3 = formatterTime3.string(from: currentDate)
|
let time3 = formatterTime3.string(from: currentDate)
|
||||||
results.append(.init(value: time1, score: -97))
|
results.append(.init(value: time1, score: -97))
|
||||||
results.append(.init(value: time2, score: -98))
|
results.append(.init(value: time2, score: -98))
|
||||||
results.append(.init(value: time3, score: -99))
|
results.append(.init(value: time3, score: -99))
|
||||||
case "ㄒㄧㄥ-ㄑㄧ", "ㄒㄧㄥ-ㄑㄧˊ":
|
case "ㄒㄧㄥ-ㄑㄧ", "ㄒㄧㄥ-ㄑㄧˊ":
|
||||||
let formatterWeek1 = DateFormatter()
|
let formatterWeek1 = DateFormatter()
|
||||||
let formatterWeek2 = DateFormatter()
|
let formatterWeek2 = DateFormatter()
|
||||||
formatterWeek1.dateFormat = "EEEE"
|
formatterWeek1.dateFormat = "EEEE"
|
||||||
formatterWeek2.dateFormat = "EE"
|
formatterWeek2.dateFormat = "EE"
|
||||||
formatterWeek1.locale = theLocale
|
formatterWeek1.locale = theLocale
|
||||||
formatterWeek2.locale = theLocale
|
formatterWeek2.locale = theLocale
|
||||||
let week1 = formatterWeek1.string(from: currentDate)
|
let week1 = formatterWeek1.string(from: currentDate)
|
||||||
let week2 = formatterWeek2.string(from: currentDate)
|
let week2 = formatterWeek2.string(from: currentDate)
|
||||||
results.append(.init(value: week1, score: -98))
|
results.append(.init(value: week1, score: -98))
|
||||||
results.append(.init(value: week2, score: -99))
|
results.append(.init(value: week2, score: -99))
|
||||||
default: return .init()
|
default: return .init()
|
||||||
}
|
}
|
||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
@ -83,10 +83,10 @@ private let tableMappingArabicNumeralsToChinese: [String: String] = [
|
||||||
"0": "〇", "1": "一", "2": "二", "3": "三", "4": "四", "5": "五", "6": "六", "7": "七", "8": "八", "9": "九",
|
"0": "〇", "1": "一", "2": "二", "3": "三", "4": "四", "5": "五", "6": "六", "7": "七", "8": "八", "9": "九",
|
||||||
]
|
]
|
||||||
|
|
||||||
extension String {
|
private extension String {
|
||||||
/// 將給定的字串當中的阿拉伯數字轉為漢語小寫,逐字轉換。
|
/// 將給定的字串當中的阿拉伯數字轉為漢語小寫,逐字轉換。
|
||||||
/// - Parameter target: 要進行轉換操作的對象,會直接修改該對象。
|
/// - Parameter target: 要進行轉換操作的對象,會直接修改該對象。
|
||||||
fileprivate var convertArabicNumeralsToChinese: String {
|
var convertArabicNumeralsToChinese: String {
|
||||||
var target = self
|
var target = self
|
||||||
for key in tableMappingArabicNumeralsToChinese.keys {
|
for key in tableMappingArabicNumeralsToChinese.keys {
|
||||||
guard let result = tableMappingArabicNumeralsToChinese[key] else { continue }
|
guard let result = tableMappingArabicNumeralsToChinese[key] else { continue }
|
||||||
|
|
|
@ -24,7 +24,7 @@ extension String {
|
||||||
) {
|
) {
|
||||||
var startIndex = startIndex
|
var startIndex = startIndex
|
||||||
split(separator: separator).forEach { substring in
|
split(separator: separator).forEach { substring in
|
||||||
let theRange = range(of: substring, range: startIndex..<endIndex)
|
let theRange = range(of: substring, range: startIndex ..< endIndex)
|
||||||
guard let theRange = theRange else { return }
|
guard let theRange = theRange else { return }
|
||||||
task(theRange)
|
task(theRange)
|
||||||
startIndex = theRange.upperBound
|
startIndex = theRange.upperBound
|
||||||
|
@ -36,11 +36,11 @@ extension String {
|
||||||
|
|
||||||
// This is only for reference and is not used in this assembly.
|
// This is only for reference and is not used in this assembly.
|
||||||
|
|
||||||
extension String {
|
private extension String {
|
||||||
fileprivate func ranges(splitBy separator: Element) -> [Range<String.Index>] {
|
func ranges(splitBy separator: Element) -> [Range<String.Index>] {
|
||||||
var startIndex = startIndex
|
var startIndex = startIndex
|
||||||
return split(separator: separator).reduce(into: []) { ranges, substring in
|
return split(separator: separator).reduce(into: []) { ranges, substring in
|
||||||
_ = range(of: substring, range: startIndex..<endIndex).map { range in
|
_ = range(of: substring, range: startIndex ..< endIndex).map { range in
|
||||||
ranges.append(range)
|
ranges.append(range)
|
||||||
startIndex = range.upperBound
|
startIndex = range.upperBound
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,8 +10,8 @@ import Megrez
|
||||||
import PinyinPhonaConverter
|
import PinyinPhonaConverter
|
||||||
import Shared
|
import Shared
|
||||||
|
|
||||||
extension vChewingLM {
|
public extension vChewingLM {
|
||||||
@frozen public struct LMAssociates {
|
@frozen struct LMAssociates {
|
||||||
public private(set) var filePath: String?
|
public private(set) var filePath: String?
|
||||||
var rangeMap: [String: [(Range<String.Index>, Int)]] = [:]
|
var rangeMap: [String: [(Range<String.Index>, Int)]] = [:]
|
||||||
var strData: String = ""
|
var strData: String = ""
|
||||||
|
|
|
@ -12,9 +12,9 @@ import LineReader
|
||||||
import Megrez
|
import Megrez
|
||||||
import Shared
|
import Shared
|
||||||
|
|
||||||
extension vChewingLM {
|
public extension vChewingLM {
|
||||||
/// 磁帶模組,用來方便使用者自行擴充字根輸入法。
|
/// 磁帶模組,用來方便使用者自行擴充字根輸入法。
|
||||||
@frozen public struct LMCassette {
|
@frozen struct LMCassette {
|
||||||
public private(set) var filePath: String?
|
public private(set) var filePath: String?
|
||||||
public private(set) var nameShort: String = ""
|
public private(set) var nameShort: String = ""
|
||||||
public private(set) var nameENG: String = ""
|
public private(set) var nameENG: String = ""
|
||||||
|
@ -115,9 +115,9 @@ extension vChewingLM {
|
||||||
} else if loadingOctagramData, !strLine.contains("%octagram") {
|
} else if loadingOctagramData, !strLine.contains("%octagram") {
|
||||||
guard let countValue = Int(cells[1]) else { continue }
|
guard let countValue = Int(cells[1]) else { continue }
|
||||||
switch cells.count {
|
switch cells.count {
|
||||||
case 2: octagramMap[strFirstCell] = countValue
|
case 2: octagramMap[strFirstCell] = countValue
|
||||||
case 3: octagramDividedMap[strFirstCell] = (countValue, cells[2].trimmingCharacters(in: .newlines))
|
case 3: octagramDividedMap[strFirstCell] = (countValue, cells[2].trimmingCharacters(in: .newlines))
|
||||||
default: break
|
default: break
|
||||||
}
|
}
|
||||||
norm += Self.fscale ** (Double(cells[0].count) / 3.0 - 1.0) * Double(countValue)
|
norm += Self.fscale ** (Double(cells[0].count) / 3.0 - 1.0) * Double(countValue)
|
||||||
}
|
}
|
||||||
|
@ -187,7 +187,7 @@ extension vChewingLM {
|
||||||
let arrRaw = charDefMap[key]?.deduplicated ?? []
|
let arrRaw = charDefMap[key]?.deduplicated ?? []
|
||||||
var arrRawWildcard: [String] = []
|
var arrRawWildcard: [String] = []
|
||||||
if let arrRawWildcardValues = charDefWildcardMap[key]?.deduplicated,
|
if let arrRawWildcardValues = charDefWildcardMap[key]?.deduplicated,
|
||||||
key.contains(wildcard), key.first?.description != wildcard
|
key.contains(wildcard), key.first?.description != wildcard
|
||||||
{
|
{
|
||||||
arrRawWildcard.append(contentsOf: arrRawWildcardValues)
|
arrRawWildcard.append(contentsOf: arrRawWildcardValues)
|
||||||
}
|
}
|
||||||
|
@ -236,18 +236,18 @@ extension vChewingLM {
|
||||||
private func calculateWeight(count theCount: Int, phraseLength: Int) -> Double {
|
private func calculateWeight(count theCount: Int, phraseLength: Int) -> Double {
|
||||||
var weight: Double = 0
|
var weight: Double = 0
|
||||||
switch theCount {
|
switch theCount {
|
||||||
case -2: // 拗音假名
|
case -2: // 拗音假名
|
||||||
weight = -13
|
weight = -13
|
||||||
case -1: // 單個假名
|
case -1: // 單個假名
|
||||||
weight = -13
|
weight = -13
|
||||||
case 0: // 墊底低頻漢字與詞語
|
case 0: // 墊底低頻漢字與詞語
|
||||||
weight = log10(
|
weight = log10(
|
||||||
Self.fscale ** (Double(phraseLength) / 3.0 - 1.0) * 0.25 / norm)
|
Self.fscale ** (Double(phraseLength) / 3.0 - 1.0) * 0.25 / norm)
|
||||||
default:
|
default:
|
||||||
weight = log10(
|
weight = log10(
|
||||||
Self.fscale ** (Double(phraseLength) / 3.0 - 1.0)
|
Self.fscale ** (Double(phraseLength) / 3.0 - 1.0)
|
||||||
* Double(theCount) / norm
|
* Double(theCount) / norm
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return weight
|
return weight
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,12 +10,12 @@ import Megrez
|
||||||
import PinyinPhonaConverter
|
import PinyinPhonaConverter
|
||||||
import Shared
|
import Shared
|
||||||
|
|
||||||
extension vChewingLM {
|
public extension vChewingLM {
|
||||||
/// 與之前的 LMCore 不同,LMCoreEX 不在辭典內記錄實體,而是記錄 range 範圍。
|
/// 與之前的 LMCore 不同,LMCoreEX 不在辭典內記錄實體,而是記錄 range 範圍。
|
||||||
/// 需要資料的時候,直接拿 range 去 strData 取資料。
|
/// 需要資料的時候,直接拿 range 去 strData 取資料。
|
||||||
/// 資料記錄原理與上游 C++ 的 ParselessLM 差不多,但用的是 Swift 原生手段。
|
/// 資料記錄原理與上游 C++ 的 ParselessLM 差不多,但用的是 Swift 原生手段。
|
||||||
/// 主要時間消耗仍在 For 迴圈,但這個算法可以顯著減少記憶體佔用。
|
/// 主要時間消耗仍在 For 迴圈,但這個算法可以顯著減少記憶體佔用。
|
||||||
@frozen public struct LMCoreEX {
|
@frozen struct LMCoreEX {
|
||||||
public private(set) var filePath: String?
|
public private(set) var filePath: String?
|
||||||
/// 資料庫辭典。索引內容為注音字串,資料內容則為字串首尾範圍、方便自 strData 取資料。
|
/// 資料庫辭典。索引內容為注音字串,資料內容則為字串首尾範圍、方便自 strData 取資料。
|
||||||
var rangeMap: [String: [Range<String.Index>]] = [:]
|
var rangeMap: [String: [Range<String.Index>]] = [:]
|
||||||
|
@ -97,7 +97,7 @@ extension vChewingLM {
|
||||||
if strData == rawStrData { return }
|
if strData == rawStrData { return }
|
||||||
strData = rawStrData
|
strData = rawStrData
|
||||||
var newMap: [String: [Range<String.Index>]] = [:]
|
var newMap: [String: [Range<String.Index>]] = [:]
|
||||||
let shouldReverse = shouldReverse // 必需,否則下文的 closure 會出錯。
|
let shouldReverse = shouldReverse // 必需,否則下文的 closure 會出錯。
|
||||||
strData.parse(splitee: "\n") { theRange in
|
strData.parse(splitee: "\n") { theRange in
|
||||||
let theCells = rawStrData[theRange].split(separator: " ")
|
let theCells = rawStrData[theRange].split(separator: " ")
|
||||||
if theCells.count >= 2, theCells[0].description.first != "#" {
|
if theCells.count >= 2, theCells[0].description.first != "#" {
|
||||||
|
@ -167,7 +167,7 @@ extension vChewingLM {
|
||||||
theScore = .init(String(neta[2])) ?? defaultScore
|
theScore = .init(String(neta[2])) ?? defaultScore
|
||||||
}
|
}
|
||||||
if theScore > 0 {
|
if theScore > 0 {
|
||||||
theScore *= -1 // 應對可能忘記寫負號的情形
|
theScore *= -1 // 應對可能忘記寫負號的情形
|
||||||
}
|
}
|
||||||
grams.append(Megrez.Unigram(value: theValue, score: theScore))
|
grams.append(Megrez.Unigram(value: theValue, score: theScore))
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,11 +10,11 @@ import Foundation
|
||||||
import Megrez
|
import Megrez
|
||||||
import Shared
|
import Shared
|
||||||
|
|
||||||
extension vChewingLM {
|
public extension vChewingLM {
|
||||||
/// 與之前的 LMCore 不同,LMCoreNS 直接讀取 plist。
|
/// 與之前的 LMCore 不同,LMCoreNS 直接讀取 plist。
|
||||||
/// 這樣一來可以節省在舊 mac 機種內的資料讀入速度。
|
/// 這樣一來可以節省在舊 mac 機種內的資料讀入速度。
|
||||||
/// 目前僅針對輸入法原廠語彙資料檔案使用 plist 格式。
|
/// 目前僅針對輸入法原廠語彙資料檔案使用 plist 格式。
|
||||||
@frozen public struct LMCoreNS {
|
@frozen struct LMCoreNS {
|
||||||
public private(set) var filePath: String?
|
public private(set) var filePath: String?
|
||||||
/// 資料庫辭典。索引內容為經過加密的注音字串,資料內容則為 UTF8 資料陣列。
|
/// 資料庫辭典。索引內容為經過加密的注音字串,資料內容則為 UTF8 資料陣列。
|
||||||
var dataMap: [String: [Data]] = [:]
|
var dataMap: [String: [Data]] = [:]
|
||||||
|
@ -144,7 +144,7 @@ extension vChewingLM {
|
||||||
theScore = .init(String(neta[1])) ?? defaultScore
|
theScore = .init(String(neta[1])) ?? defaultScore
|
||||||
}
|
}
|
||||||
if theScore > 0 {
|
if theScore > 0 {
|
||||||
theScore *= -1 // 應對可能忘記寫負號的情形
|
theScore *= -1 // 應對可能忘記寫負號的情形
|
||||||
}
|
}
|
||||||
grams.append(Megrez.Unigram(value: theValue, score: theScore))
|
grams.append(Megrez.Unigram(value: theValue, score: theScore))
|
||||||
if !key.contains("_punctuation") { continue }
|
if !key.contains("_punctuation") { continue }
|
||||||
|
|
|
@ -9,8 +9,8 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import Shared
|
import Shared
|
||||||
|
|
||||||
extension vChewingLM {
|
public extension vChewingLM {
|
||||||
@frozen public struct LMPlainBopomofo {
|
@frozen struct LMPlainBopomofo {
|
||||||
public private(set) var filePath: String?
|
public private(set) var filePath: String?
|
||||||
var dataMap: [String: String] = [:]
|
var dataMap: [String: String] = [:]
|
||||||
|
|
||||||
|
|
|
@ -8,8 +8,8 @@
|
||||||
|
|
||||||
import Shared
|
import Shared
|
||||||
|
|
||||||
extension vChewingLM {
|
public extension vChewingLM {
|
||||||
@frozen public struct LMReplacements {
|
@frozen struct LMReplacements {
|
||||||
public private(set) var filePath: String?
|
public private(set) var filePath: String?
|
||||||
var rangeMap: [String: Range<String.Index>] = [:]
|
var rangeMap: [String: Range<String.Index>] = [:]
|
||||||
var strData: String = ""
|
var strData: String = ""
|
||||||
|
|
|
@ -9,8 +9,8 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import Shared
|
import Shared
|
||||||
|
|
||||||
extension vChewingLM {
|
public extension vChewingLM {
|
||||||
@frozen public struct LMRevLookup {
|
@frozen struct LMRevLookup {
|
||||||
public private(set) var dataMap: [String: [Data]] = [:]
|
public private(set) var dataMap: [String: [Data]] = [:]
|
||||||
public private(set) var filePath: String = ""
|
public private(set) var filePath: String = ""
|
||||||
|
|
||||||
|
|
|
@ -11,21 +11,21 @@ import Foundation
|
||||||
import Megrez
|
import Megrez
|
||||||
import Shared
|
import Shared
|
||||||
|
|
||||||
extension vChewingLM {
|
public extension vChewingLM {
|
||||||
public class LMUserOverride {
|
class LMUserOverride {
|
||||||
// MARK: - Main
|
// MARK: - Main
|
||||||
|
|
||||||
var mutCapacity: Int
|
var mutCapacity: Int
|
||||||
var mutDecayExponent: Double
|
var mutDecayExponent: Double
|
||||||
var mutLRUList: [KeyObservationPair] = []
|
var mutLRUList: [KeyObservationPair] = []
|
||||||
var mutLRUMap: [String: KeyObservationPair] = [:]
|
var mutLRUMap: [String: KeyObservationPair] = [:]
|
||||||
let kDecayThreshold: Double = 1.0 / 1_048_576.0 // 衰減二十次之後差不多就失效了。
|
let kDecayThreshold: Double = 1.0 / 1_048_576.0 // 衰減二十次之後差不多就失效了。
|
||||||
var fileSaveLocationURL: URL
|
var fileSaveLocationURL: URL
|
||||||
|
|
||||||
public static let kObservedOverrideHalfLife: Double = 3600.0 * 6 // 6 小時半衰一次,能持續不到六天的記憶。
|
public static let kObservedOverrideHalfLife: Double = 3600.0 * 6 // 6 小時半衰一次,能持續不到六天的記憶。
|
||||||
|
|
||||||
public init(capacity: Int = 500, decayConstant: Double = LMUserOverride.kObservedOverrideHalfLife, dataURL: URL) {
|
public init(capacity: Int = 500, decayConstant: Double = LMUserOverride.kObservedOverrideHalfLife, dataURL: URL) {
|
||||||
mutCapacity = max(capacity, 1) // Ensures that this integer value is always > 0.
|
mutCapacity = max(capacity, 1) // Ensures that this integer value is always > 0.
|
||||||
mutDecayExponent = log(0.5) / decayConstant
|
mutDecayExponent = log(0.5) / decayConstant
|
||||||
fileSaveLocationURL = dataURL
|
fileSaveLocationURL = dataURL
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,7 @@ extension vChewingLM {
|
||||||
// 當前節點超過三個字的話,就不記憶了。在這種情形下,使用者可以考慮新增自訂語彙。
|
// 當前節點超過三個字的話,就不記憶了。在這種情形下,使用者可以考慮新增自訂語彙。
|
||||||
guard currentNode.spanLength <= 3 else { return }
|
guard currentNode.spanLength <= 3 else { return }
|
||||||
// 前一個節點得從前一次爬軌結果當中來找。
|
// 前一個節點得從前一次爬軌結果當中來找。
|
||||||
guard actualCursor > 0 else { return } // 該情況應該不會出現。
|
guard actualCursor > 0 else { return } // 該情況應該不會出現。
|
||||||
let currentNodeIndex = actualCursor
|
let currentNodeIndex = actualCursor
|
||||||
actualCursor -= 1
|
actualCursor -= 1
|
||||||
var prevNodeIndex = 0
|
var prevNodeIndex = 0
|
||||||
|
@ -153,8 +153,8 @@ extension vChewingLM.LMUserOverride {
|
||||||
|
|
||||||
// MARK: - Hash and Dehash the entire UOM data, etc.
|
// MARK: - Hash and Dehash the entire UOM data, etc.
|
||||||
|
|
||||||
extension vChewingLM.LMUserOverride {
|
public extension vChewingLM.LMUserOverride {
|
||||||
public func bleachSpecifiedSuggestions(targets: [String], saveCallback: @escaping () -> Void) {
|
func bleachSpecifiedSuggestions(targets: [String], saveCallback: @escaping () -> Void) {
|
||||||
if targets.isEmpty { return }
|
if targets.isEmpty { return }
|
||||||
for neta in mutLRUMap {
|
for neta in mutLRUMap {
|
||||||
for target in targets {
|
for target in targets {
|
||||||
|
@ -168,7 +168,7 @@ extension vChewingLM.LMUserOverride {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 自 LRU 辭典內移除所有的單元圖。
|
/// 自 LRU 辭典內移除所有的單元圖。
|
||||||
public func bleachUnigrams(saveCallback: @escaping () -> Void) {
|
func bleachUnigrams(saveCallback: @escaping () -> Void) {
|
||||||
for key in mutLRUMap.keys {
|
for key in mutLRUMap.keys {
|
||||||
if !key.contains("(),()") { continue }
|
if !key.contains("(),()") { continue }
|
||||||
mutLRUMap.removeValue(forKey: key)
|
mutLRUMap.removeValue(forKey: key)
|
||||||
|
@ -184,7 +184,7 @@ extension vChewingLM.LMUserOverride {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func clearData(withURL fileURL: URL) {
|
func clearData(withURL fileURL: URL) {
|
||||||
mutLRUMap = .init()
|
mutLRUMap = .init()
|
||||||
mutLRUList = .init()
|
mutLRUList = .init()
|
||||||
do {
|
do {
|
||||||
|
@ -196,7 +196,7 @@ extension vChewingLM.LMUserOverride {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func saveData(toURL fileURL: URL? = nil) {
|
func saveData(toURL fileURL: URL? = nil) {
|
||||||
let encoder = JSONEncoder()
|
let encoder = JSONEncoder()
|
||||||
do {
|
do {
|
||||||
guard let jsonData = try? encoder.encode(mutLRUMap) else { return }
|
guard let jsonData = try? encoder.encode(mutLRUMap) else { return }
|
||||||
|
@ -208,7 +208,7 @@ extension vChewingLM.LMUserOverride {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func loadData(fromURL fileURL: URL) {
|
func loadData(fromURL fileURL: URL) {
|
||||||
let decoder = JSONDecoder()
|
let decoder = JSONDecoder()
|
||||||
do {
|
do {
|
||||||
let data = try Data(contentsOf: fileURL, options: .mappedIfSafe)
|
let data = try Data(contentsOf: fileURL, options: .mappedIfSafe)
|
||||||
|
@ -225,7 +225,7 @@ extension vChewingLM.LMUserOverride {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct Suggestion {
|
struct Suggestion {
|
||||||
public var candidates = [(String, Megrez.Unigram)]()
|
public var candidates = [(String, Megrez.Unigram)]()
|
||||||
public var forceHighScoreOverride = false
|
public var forceHighScoreOverride = false
|
||||||
public var isEmpty: Bool { candidates.isEmpty }
|
public var isEmpty: Bool { candidates.isEmpty }
|
||||||
|
@ -282,7 +282,7 @@ extension vChewingLM.LMUserOverride {
|
||||||
eventCount: theObservation.count, totalCount: observation.count,
|
eventCount: theObservation.count, totalCount: observation.count,
|
||||||
eventTimestamp: theObservation.timestamp, timestamp: timestamp, lambda: decayExp
|
eventTimestamp: theObservation.timestamp, timestamp: timestamp, lambda: decayExp
|
||||||
)
|
)
|
||||||
if (0...currentHighScore).contains(overrideScore) { continue }
|
if (0 ... currentHighScore).contains(overrideScore) { continue }
|
||||||
|
|
||||||
candidates.append((headReading, .init(value: i, score: overrideScore)))
|
candidates.append((headReading, .init(value: i, score: overrideScore)))
|
||||||
forceHighScoreOverride = theObservation.forceHighScoreOverride
|
forceHighScoreOverride = theObservation.forceHighScoreOverride
|
||||||
|
@ -356,16 +356,16 @@ extension vChewingLM.LMUserOverride {
|
||||||
}
|
}
|
||||||
|
|
||||||
if arrNodes.count >= 2,
|
if arrNodes.count >= 2,
|
||||||
!kvPrevious.joinedKey().contains("_"),
|
!kvPrevious.joinedKey().contains("_"),
|
||||||
kvPrevious.joinedKey().split(separator: "-").count == kvPrevious.value.count
|
kvPrevious.joinedKey().split(separator: "-").count == kvPrevious.value.count
|
||||||
{
|
{
|
||||||
kvPrevious = arrNodes[1].currentPair
|
kvPrevious = arrNodes[1].currentPair
|
||||||
readingStack = kvPrevious.joinedKey() + readingStack
|
readingStack = kvPrevious.joinedKey() + readingStack
|
||||||
}
|
}
|
||||||
|
|
||||||
if arrNodes.count >= 3,
|
if arrNodes.count >= 3,
|
||||||
!kvAnterior.joinedKey().contains("_"),
|
!kvAnterior.joinedKey().contains("_"),
|
||||||
kvAnterior.joinedKey().split(separator: "-").count == kvAnterior.value.count
|
kvAnterior.joinedKey().split(separator: "-").count == kvAnterior.value.count
|
||||||
{
|
{
|
||||||
kvAnterior = arrNodes[2].currentPair
|
kvAnterior = arrNodes[2].currentPair
|
||||||
readingStack = kvAnterior.joinedKey() + readingStack
|
readingStack = kvAnterior.joinedKey() + readingStack
|
||||||
|
|
|
@ -4,13 +4,13 @@ import PackageDescription
|
||||||
let package = Package(
|
let package = Package(
|
||||||
name: "Megrez",
|
name: "Megrez",
|
||||||
platforms: [
|
platforms: [
|
||||||
.macOS(.v10_11)
|
.macOS(.v10_11),
|
||||||
],
|
],
|
||||||
products: [
|
products: [
|
||||||
.library(
|
.library(
|
||||||
name: "Megrez",
|
name: "Megrez",
|
||||||
targets: ["Megrez"]
|
targets: ["Megrez"]
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
dependencies: [],
|
dependencies: [],
|
||||||
targets: [
|
targets: [
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
extension Megrez {
|
public extension Megrez {
|
||||||
/// 一個組字器用來在給定一系列的索引鍵的情況下(藉由一系列的觀測行為)返回一套資料值。
|
/// 一個組字器用來在給定一系列的索引鍵的情況下(藉由一系列的觀測行為)返回一套資料值。
|
||||||
///
|
///
|
||||||
/// 用於輸入法的話,給定的索引鍵可以是注音、且返回的資料值都是漢語字詞組合。該組字器
|
/// 用於輸入法的話,給定的索引鍵可以是注音、且返回的資料值都是漢語字詞組合。該組字器
|
||||||
|
@ -15,7 +15,7 @@ extension Megrez {
|
||||||
/// 簡單的貝氏推論:因為底層的語言模組只會提供單元圖資料。一旦將所有可以組字的單元圖
|
/// 簡單的貝氏推論:因為底層的語言模組只會提供單元圖資料。一旦將所有可以組字的單元圖
|
||||||
/// 作為節點塞到組字器內,就可以用一個簡單的有向無環圖爬軌過程、來利用這些隱性資料值
|
/// 作為節點塞到組字器內,就可以用一個簡單的有向無環圖爬軌過程、來利用這些隱性資料值
|
||||||
/// 算出最大相似估算結果。
|
/// 算出最大相似估算結果。
|
||||||
public struct Compositor {
|
struct Compositor {
|
||||||
/// 就文字輸入方向而言的方向。
|
/// 就文字輸入方向而言的方向。
|
||||||
public enum TypingDirection { case front, rear }
|
public enum TypingDirection { case front, rear }
|
||||||
/// 軌格增減行為。
|
/// 軌格增減行為。
|
||||||
|
@ -92,7 +92,7 @@ extension Megrez {
|
||||||
spans = gridBackup
|
spans = gridBackup
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
cursor += 1 // 游標必須得在執行 update() 之後才可以變動。
|
cursor += 1 // 游標必須得在執行 update() 之後才可以變動。
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,7 +106,7 @@ extension Megrez {
|
||||||
let isBackSpace: Bool = direction == .rear ? true : false
|
let isBackSpace: Bool = direction == .rear ? true : false
|
||||||
guard cursor != (isBackSpace ? 0 : keys.count) else { return false }
|
guard cursor != (isBackSpace ? 0 : keys.count) else { return false }
|
||||||
keys.remove(at: cursor - (isBackSpace ? 1 : 0))
|
keys.remove(at: cursor - (isBackSpace ? 1 : 0))
|
||||||
cursor -= isBackSpace ? 1 : 0 // 在縮節之前。
|
cursor -= isBackSpace ? 1 : 0 // 在縮節之前。
|
||||||
resizeGrid(at: cursor, do: .shrink)
|
resizeGrid(at: cursor, do: .shrink)
|
||||||
update()
|
update()
|
||||||
return true
|
return true
|
||||||
|
@ -129,36 +129,36 @@ extension Megrez {
|
||||||
{
|
{
|
||||||
var target = isMarker ? marker : cursor
|
var target = isMarker ? marker : cursor
|
||||||
switch direction {
|
switch direction {
|
||||||
case .front:
|
case .front:
|
||||||
if target == length { return false }
|
if target == length { return false }
|
||||||
case .rear:
|
case .rear:
|
||||||
if target == 0 { return false }
|
if target == 0 { return false }
|
||||||
}
|
}
|
||||||
guard let currentRegion = walkedNodes.cursorRegionMap[target] else { return false }
|
guard let currentRegion = walkedNodes.cursorRegionMap[target] else { return false }
|
||||||
|
|
||||||
let aRegionForward = max(currentRegion - 1, 0)
|
let aRegionForward = max(currentRegion - 1, 0)
|
||||||
let currentRegionBorderRear: Int = walkedNodes[0..<currentRegion].map(\.spanLength).reduce(0, +)
|
let currentRegionBorderRear: Int = walkedNodes[0 ..< currentRegion].map(\.spanLength).reduce(0, +)
|
||||||
switch target {
|
switch target {
|
||||||
case currentRegionBorderRear:
|
case currentRegionBorderRear:
|
||||||
switch direction {
|
switch direction {
|
||||||
case .front:
|
case .front:
|
||||||
target =
|
target =
|
||||||
(currentRegion > walkedNodes.count)
|
(currentRegion > walkedNodes.count)
|
||||||
? keys.count : walkedNodes[0...currentRegion].map(\.spanLength).reduce(0, +)
|
? keys.count : walkedNodes[0 ... currentRegion].map(\.spanLength).reduce(0, +)
|
||||||
case .rear:
|
case .rear:
|
||||||
target = walkedNodes[0..<aRegionForward].map(\.spanLength).reduce(0, +)
|
target = walkedNodes[0 ..< aRegionForward].map(\.spanLength).reduce(0, +)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
switch direction {
|
switch direction {
|
||||||
case .front:
|
case .front:
|
||||||
target = currentRegionBorderRear + walkedNodes[currentRegion].spanLength
|
target = currentRegionBorderRear + walkedNodes[currentRegion].spanLength
|
||||||
case .rear:
|
case .rear:
|
||||||
target = currentRegionBorderRear
|
target = currentRegionBorderRear
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
switch isMarker {
|
switch isMarker {
|
||||||
case false: cursor = target
|
case false: cursor = target
|
||||||
case true: marker = target
|
case true: marker = target
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -168,7 +168,7 @@ extension Megrez {
|
||||||
// C# StringBuilder 與 Swift NSMutableString 能提供爆發性的效能。
|
// C# StringBuilder 與 Swift NSMutableString 能提供爆發性的效能。
|
||||||
let strOutput: NSMutableString = .init(string: "digraph {\ngraph [ rankdir=LR ];\nBOS;\n")
|
let strOutput: NSMutableString = .init(string: "digraph {\ngraph [ rankdir=LR ];\nBOS;\n")
|
||||||
for (p, span) in spans.enumerated() {
|
for (p, span) in spans.enumerated() {
|
||||||
for ni in 0...(span.maxLength) {
|
for ni in 0 ... (span.maxLength) {
|
||||||
guard let np = span.nodeOf(length: ni) else { continue }
|
guard let np = span.nodeOf(length: ni) else { continue }
|
||||||
if p == 0 {
|
if p == 0 {
|
||||||
strOutput.append("BOS -> \(np.value);\n")
|
strOutput.append("BOS -> \(np.value);\n")
|
||||||
|
@ -176,7 +176,7 @@ extension Megrez {
|
||||||
strOutput.append("\(np.value);\n")
|
strOutput.append("\(np.value);\n")
|
||||||
if (p + ni) < spans.count {
|
if (p + ni) < spans.count {
|
||||||
let destinationSpan = spans[p + ni]
|
let destinationSpan = spans[p + ni]
|
||||||
for q in 0...(destinationSpan.maxLength) {
|
for q in 0 ... (destinationSpan.maxLength) {
|
||||||
guard let dn = destinationSpan.nodeOf(length: q) else { continue }
|
guard let dn = destinationSpan.nodeOf(length: q) else { continue }
|
||||||
strOutput.append(np.value + " -> " + dn.value + ";\n")
|
strOutput.append(np.value + " -> " + dn.value + ";\n")
|
||||||
}
|
}
|
||||||
|
@ -199,14 +199,14 @@ extension Megrez.Compositor {
|
||||||
/// - location: 給定的幅位座標。
|
/// - location: 給定的幅位座標。
|
||||||
/// - action: 指定是擴張還是縮減一個幅位。
|
/// - action: 指定是擴張還是縮減一個幅位。
|
||||||
mutating func resizeGrid(at location: Int, do action: ResizeBehavior) {
|
mutating func resizeGrid(at location: Int, do action: ResizeBehavior) {
|
||||||
let location = max(min(location, spans.count), 0) // 防呆
|
let location = max(min(location, spans.count), 0) // 防呆
|
||||||
switch action {
|
switch action {
|
||||||
case .expand:
|
case .expand:
|
||||||
spans.insert(SpanUnit(), at: location)
|
spans.insert(SpanUnit(), at: location)
|
||||||
if [0, spans.count].contains(location) { return }
|
if [0, spans.count].contains(location) { return }
|
||||||
case .shrink:
|
case .shrink:
|
||||||
if spans.count == location { return }
|
if spans.count == location { return }
|
||||||
spans.remove(at: location)
|
spans.remove(at: location)
|
||||||
}
|
}
|
||||||
dropWreckedNodes(at: location)
|
dropWreckedNodes(at: location)
|
||||||
}
|
}
|
||||||
|
@ -243,12 +243,12 @@ extension Megrez.Compositor {
|
||||||
/// ```
|
/// ```
|
||||||
/// - Parameter location: 給定的幅位座標。
|
/// - Parameter location: 給定的幅位座標。
|
||||||
mutating func dropWreckedNodes(at location: Int) {
|
mutating func dropWreckedNodes(at location: Int) {
|
||||||
let location = max(min(location, spans.count), 0) // 防呆
|
let location = max(min(location, spans.count), 0) // 防呆
|
||||||
guard !spans.isEmpty else { return }
|
guard !spans.isEmpty else { return }
|
||||||
let affectedLength = Megrez.Compositor.maxSpanLength - 1
|
let affectedLength = Megrez.Compositor.maxSpanLength - 1
|
||||||
let begin = max(0, location - affectedLength)
|
let begin = max(0, location - affectedLength)
|
||||||
guard location >= begin else { return }
|
guard location >= begin else { return }
|
||||||
for i in begin..<location {
|
for i in begin ..< location {
|
||||||
spans[i].dropNodesOfOrBeyond(length: location - i + 1)
|
spans[i].dropNodesOfOrBeyond(length: location - i + 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -269,7 +269,7 @@ extension Megrez.Compositor {
|
||||||
/// - keyArray: 指定索引鍵陣列。
|
/// - keyArray: 指定索引鍵陣列。
|
||||||
/// - Returns: 拿取的節點。拿不到的話就會是 nil。
|
/// - Returns: 拿取的節點。拿不到的話就會是 nil。
|
||||||
func getNode(at location: Int, length: Int, keyArray: [String]) -> Node? {
|
func getNode(at location: Int, length: Int, keyArray: [String]) -> Node? {
|
||||||
let location = max(min(location, spans.count - 1), 0) // 防呆
|
let location = max(min(location, spans.count - 1), 0) // 防呆
|
||||||
guard let node = spans[location].nodeOf(length: length) else { return nil }
|
guard let node = spans[location].nodeOf(length: length) else { return nil }
|
||||||
return keyArray == node.keyArray ? node : nil
|
return keyArray == node.keyArray ? node : nil
|
||||||
}
|
}
|
||||||
|
@ -280,11 +280,11 @@ extension Megrez.Compositor {
|
||||||
/// - Returns: 新增或影響了多少個節點。如果返回「0」則表示可能發生了錯誤。
|
/// - Returns: 新增或影響了多少個節點。如果返回「0」則表示可能發生了錯誤。
|
||||||
@discardableResult public mutating func update(updateExisting: Bool = false) -> Int {
|
@discardableResult public mutating func update(updateExisting: Bool = false) -> Int {
|
||||||
let maxSpanLength = Megrez.Compositor.maxSpanLength
|
let maxSpanLength = Megrez.Compositor.maxSpanLength
|
||||||
let range = max(0, cursor - maxSpanLength)..<min(cursor + maxSpanLength, keys.count)
|
let range = max(0, cursor - maxSpanLength) ..< min(cursor + maxSpanLength, keys.count)
|
||||||
var nodesChanged = 0
|
var nodesChanged = 0
|
||||||
for position in range {
|
for position in range {
|
||||||
for theLength in 1...min(maxSpanLength, range.upperBound - position) {
|
for theLength in 1 ... min(maxSpanLength, range.upperBound - position) {
|
||||||
let joinedKeyArray = getJoinedKeyArray(range: position..<(position + theLength))
|
let joinedKeyArray = getJoinedKeyArray(range: position ..< (position + theLength))
|
||||||
if let theNode = getNode(at: position, length: theLength, keyArray: joinedKeyArray) {
|
if let theNode = getNode(at: position, length: theLength, keyArray: joinedKeyArray) {
|
||||||
if !updateExisting { continue }
|
if !updateExisting { continue }
|
||||||
let unigrams = langModel.unigramsFor(keyArray: joinedKeyArray)
|
let unigrams = langModel.unigramsFor(keyArray: joinedKeyArray)
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
// ====================
|
// ====================
|
||||||
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
|
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
|
||||||
|
|
||||||
extension Megrez.Compositor {
|
public extension Megrez.Compositor {
|
||||||
/// 爬軌函式,會更新當前組字器的 walkedNodes。
|
/// 爬軌函式,會更新當前組字器的 walkedNodes。
|
||||||
///
|
///
|
||||||
/// 找到軌格陣圖內權重最大的路徑。該路徑代表了可被觀測到的最可能的隱藏事件鏈。
|
/// 找到軌格陣圖內權重最大的路徑。該路徑代表了可被觀測到的最可能的隱藏事件鏈。
|
||||||
|
@ -13,7 +13,7 @@ extension Megrez.Compositor {
|
||||||
/// 對於 `G = (V, E)`,該算法的運行次數為 `O(|V|+|E|)`,其中 `G` 是一個有向無環圖。
|
/// 對於 `G = (V, E)`,該算法的運行次數為 `O(|V|+|E|)`,其中 `G` 是一個有向無環圖。
|
||||||
/// 這意味著,即使軌格很大,也可以用很少的算力就可以爬軌。
|
/// 這意味著,即使軌格很大,也可以用很少的算力就可以爬軌。
|
||||||
/// - Returns: 爬軌結果+該過程是否順利執行。
|
/// - Returns: 爬軌結果+該過程是否順利執行。
|
||||||
@discardableResult public mutating func walk() -> (walkedNode: [Node], succeeded: Bool) {
|
@discardableResult mutating func walk() -> (walkedNode: [Node], succeeded: Bool) {
|
||||||
var result = [Node]()
|
var result = [Node]()
|
||||||
defer { walkedNodes = result }
|
defer { walkedNodes = result }
|
||||||
guard !spans.isEmpty else { return (result, true) }
|
guard !spans.isEmpty else { return (result, true) }
|
||||||
|
@ -24,7 +24,7 @@ extension Megrez.Compositor {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i, span) in spans.enumerated() {
|
for (i, span) in spans.enumerated() {
|
||||||
for j in 1...span.maxLength {
|
for j in 1 ... span.maxLength {
|
||||||
if let theNode = span.nodeOf(length: j) {
|
if let theNode = span.nodeOf(length: j) {
|
||||||
vertexSpans[i].append(.init(node: theNode))
|
vertexSpans[i].append(.init(node: theNode))
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,9 +5,9 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
extension Megrez.Compositor {
|
public extension Megrez.Compositor {
|
||||||
/// 鍵值配對,乃索引鍵陣列與讀音的配對單元。
|
/// 鍵值配對,乃索引鍵陣列與讀音的配對單元。
|
||||||
public struct KeyValuePaired: Equatable, Hashable, Comparable, CustomStringConvertible {
|
struct KeyValuePaired: Equatable, Hashable, Comparable, CustomStringConvertible {
|
||||||
/// 索引鍵陣列。一般情況下用來放置讀音等可以用來作為索引的內容。
|
/// 索引鍵陣列。一般情況下用來放置讀音等可以用來作為索引的內容。
|
||||||
public var keyArray: [String]
|
public var keyArray: [String]
|
||||||
/// 資料值。
|
/// 資料值。
|
||||||
|
@ -77,17 +77,17 @@ extension Megrez.Compositor {
|
||||||
/// - all: 不只包含其它兩類結果,還允許游標穿插候選字。
|
/// - all: 不只包含其它兩類結果,還允許游標穿插候選字。
|
||||||
/// - beginAt: 僅獲取從當前游標位置開始的節點內的候選字。
|
/// - beginAt: 僅獲取從當前游標位置開始的節點內的候選字。
|
||||||
/// - endAt 僅獲取在當前游標位置結束的節點內的候選字。
|
/// - endAt 僅獲取在當前游標位置結束的節點內的候選字。
|
||||||
public enum CandidateFetchFilter { case all, beginAt, endAt }
|
enum CandidateFetchFilter { case all, beginAt, endAt }
|
||||||
|
|
||||||
/// 返回在當前位置的所有候選字詞(以詞音配對的形式)。如果組字器內有幅位、且游標
|
/// 返回在當前位置的所有候選字詞(以詞音配對的形式)。如果組字器內有幅位、且游標
|
||||||
/// 位於組字器的(文字輸入順序的)最前方(也就是游標位置的數值是最大合規數值)的
|
/// 位於組字器的(文字輸入順序的)最前方(也就是游標位置的數值是最大合規數值)的
|
||||||
/// 話,那麼這裡會用到 location - 1、以免去在呼叫該函式後再處理的麻煩。
|
/// 話,那麼這裡會用到 location - 1、以免去在呼叫該函式後再處理的麻煩。
|
||||||
/// - Parameter location: 游標位置。
|
/// - Parameter location: 游標位置。
|
||||||
/// - Returns: 候選字音配對陣列。
|
/// - Returns: 候選字音配對陣列。
|
||||||
public func fetchCandidates(at location: Int, filter: CandidateFetchFilter = .all) -> [KeyValuePaired] {
|
func fetchCandidates(at location: Int, filter: CandidateFetchFilter = .all) -> [KeyValuePaired] {
|
||||||
var result = [KeyValuePaired]()
|
var result = [KeyValuePaired]()
|
||||||
guard !keys.isEmpty else { return result }
|
guard !keys.isEmpty else { return result }
|
||||||
let location = max(min(location, keys.count - 1), 0) // 防呆
|
let location = max(min(location, keys.count - 1), 0) // 防呆
|
||||||
let anchors: [NodeAnchor] = fetchOverlappingNodes(at: location).stableSorted {
|
let anchors: [NodeAnchor] = fetchOverlappingNodes(at: location).stableSorted {
|
||||||
// 按照讀音的長度(幅位長度)來給節點排序。
|
// 按照讀音的長度(幅位長度)來給節點排序。
|
||||||
$0.spanLength > $1.spanLength
|
$0.spanLength > $1.spanLength
|
||||||
|
@ -97,13 +97,13 @@ extension Megrez.Compositor {
|
||||||
if theNode.keyArray.isEmpty { continue }
|
if theNode.keyArray.isEmpty { continue }
|
||||||
for gram in theNode.unigrams {
|
for gram in theNode.unigrams {
|
||||||
switch filter {
|
switch filter {
|
||||||
case .all:
|
case .all:
|
||||||
// 得加上這道篩選,所以會出現很多無效結果。
|
// 得加上這道篩選,所以會出現很多無效結果。
|
||||||
if !theNode.keyArray.contains(keyAtCursor) { continue }
|
if !theNode.keyArray.contains(keyAtCursor) { continue }
|
||||||
case .beginAt:
|
case .beginAt:
|
||||||
if theNode.keyArray[0] != keyAtCursor { continue }
|
if theNode.keyArray[0] != keyAtCursor { continue }
|
||||||
case .endAt:
|
case .endAt:
|
||||||
if theNode.keyArray.reversed()[0] != keyAtCursor { continue }
|
if theNode.keyArray.reversed()[0] != keyAtCursor { continue }
|
||||||
}
|
}
|
||||||
result.append(.init(keyArray: theNode.keyArray, value: gram.value))
|
result.append(.init(keyArray: theNode.keyArray, value: gram.value))
|
||||||
}
|
}
|
||||||
|
@ -119,7 +119,7 @@ extension Megrez.Compositor {
|
||||||
/// - location: 游標位置。
|
/// - location: 游標位置。
|
||||||
/// - overrideType: 指定覆寫行為。
|
/// - overrideType: 指定覆寫行為。
|
||||||
/// - Returns: 該操作是否成功執行。
|
/// - Returns: 該操作是否成功執行。
|
||||||
@discardableResult public func overrideCandidate(
|
@discardableResult func overrideCandidate(
|
||||||
_ candidate: KeyValuePaired, at location: Int, overrideType: Node.OverrideType = .withHighScore
|
_ candidate: KeyValuePaired, at location: Int, overrideType: Node.OverrideType = .withHighScore
|
||||||
)
|
)
|
||||||
-> Bool
|
-> Bool
|
||||||
|
@ -135,7 +135,7 @@ extension Megrez.Compositor {
|
||||||
/// - location: 游標位置。
|
/// - location: 游標位置。
|
||||||
/// - overrideType: 指定覆寫行為。
|
/// - overrideType: 指定覆寫行為。
|
||||||
/// - Returns: 該操作是否成功執行。
|
/// - Returns: 該操作是否成功執行。
|
||||||
@discardableResult public func overrideCandidateLiteral(
|
@discardableResult func overrideCandidateLiteral(
|
||||||
_ candidate: String,
|
_ candidate: String,
|
||||||
at location: Int, overrideType: Node.OverrideType = .withHighScore
|
at location: Int, overrideType: Node.OverrideType = .withHighScore
|
||||||
) -> Bool {
|
) -> Bool {
|
||||||
|
@ -154,7 +154,7 @@ extension Megrez.Compositor {
|
||||||
internal func overrideCandidateAgainst(keyArray: [String]?, at location: Int, value: String, type: Node.OverrideType)
|
internal func overrideCandidateAgainst(keyArray: [String]?, at location: Int, value: String, type: Node.OverrideType)
|
||||||
-> Bool
|
-> Bool
|
||||||
{
|
{
|
||||||
let location = max(min(location, keys.count), 0) // 防呆
|
let location = max(min(location, keys.count), 0) // 防呆
|
||||||
var arrOverlappedNodes: [NodeAnchor] = fetchOverlappingNodes(at: min(keys.count - 1, location))
|
var arrOverlappedNodes: [NodeAnchor] = fetchOverlappingNodes(at: min(keys.count - 1, location))
|
||||||
var overridden: NodeAnchor?
|
var overridden: NodeAnchor?
|
||||||
for anchor in arrOverlappedNodes {
|
for anchor in arrOverlappedNodes {
|
||||||
|
@ -164,9 +164,9 @@ extension Megrez.Compositor {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let overridden = overridden else { return false } // 啥也不覆寫。
|
guard let overridden = overridden else { return false } // 啥也不覆寫。
|
||||||
|
|
||||||
for i in overridden.spanIndex..<min(spans.count, overridden.spanIndex + overridden.node.spanLength) {
|
for i in overridden.spanIndex ..< min(spans.count, overridden.spanIndex + overridden.node.spanLength) {
|
||||||
/// 咱們還得弱化所有在相同的幅位座標的節點的複寫權重。舉例說之前爬軌的結果是「A BC」
|
/// 咱們還得弱化所有在相同的幅位座標的節點的複寫權重。舉例說之前爬軌的結果是「A BC」
|
||||||
/// 且 A 與 BC 都是被覆寫的結果,然後使用者現在在與 A 相同的幅位座標位置
|
/// 且 A 與 BC 都是被覆寫的結果,然後使用者現在在與 A 相同的幅位座標位置
|
||||||
/// 選了「DEF」,那麼 BC 的覆寫狀態就有必要重設(但 A 不用重設)。
|
/// 選了「DEF」,那麼 BC 的覆寫狀態就有必要重設(但 A 不用重設)。
|
||||||
|
@ -190,12 +190,12 @@ extension Megrez.Compositor {
|
||||||
|
|
||||||
// Reference: https://stackoverflow.com/a/50545761/4162914
|
// Reference: https://stackoverflow.com/a/50545761/4162914
|
||||||
|
|
||||||
extension Sequence {
|
private extension Sequence {
|
||||||
/// Return a stable-sorted collection.
|
/// Return a stable-sorted collection.
|
||||||
///
|
///
|
||||||
/// - Parameter areInIncreasingOrder: Return nil when two element are equal.
|
/// - Parameter areInIncreasingOrder: Return nil when two element are equal.
|
||||||
/// - Returns: The sorted collection.
|
/// - Returns: The sorted collection.
|
||||||
fileprivate func stableSorted(
|
func stableSorted(
|
||||||
by areInIncreasingOrder: (Element, Element) throws -> Bool
|
by areInIncreasingOrder: (Element, Element) throws -> Bool
|
||||||
)
|
)
|
||||||
rethrows -> [Element]
|
rethrows -> [Element]
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue