TDKCandidates // Optimize the horizontal layout.

This commit is contained in:
ShikiSuen 2023-02-05 14:59:15 +08:00
parent 0c50046426
commit f8d79b0e2e
7 changed files with 40 additions and 32 deletions

View File

@ -140,6 +140,10 @@ public class CandidateCellData: Hashable {
} }
return result.charDescriptions.joined(separator: "\n") return result.charDescriptions.joined(separator: "\n")
} }
public var minWidthToDrawInSwiftUI: Double {
Double(cellLength) + ((displayedText.count > 2) ? 0 : fontSizeKey + 0) + ceil(fontSizeCandidate * 0.4)
}
} }
// MARK: - Contents specifically made for macOS 12 and newer. // MARK: - Contents specifically made for macOS 12 and newer.
@ -165,7 +169,7 @@ extension CandidateCellData {
.foregroundColor(.init(nsColor: fontColorCandidate)).lineLimit(1) .foregroundColor(.init(nsColor: fontColorCandidate)).lineLimit(1)
} }
}.padding(3) }.padding(3)
} }.frame(minWidth: minWidthToDrawInSwiftUI, alignment: .leading)
}.fixedSize(horizontal: false, vertical: true) }.fixedSize(horizontal: false, vertical: true)
} }
return result return result
@ -202,7 +206,7 @@ extension CandidateCellData {
.font(.init(CTFontCreateUIFontForLanguage(.system, fontSizeCandidate, locale as CFString)!)) .font(.init(CTFontCreateUIFontForLanguage(.system, fontSizeCandidate, locale as CFString)!))
.foregroundColor(Color(white: 1)).lineLimit(1) .foregroundColor(Color(white: 1)).lineLimit(1)
}.padding(3).foregroundColor(Color(white: 0.9)) }.padding(3).foregroundColor(Color(white: 0.9))
} }.frame(minWidth: minWidthToDrawInSwiftUI, alignment: .leading)
} else { } else {
VStack(spacing: 0) { VStack(spacing: 0) {
HStack(spacing: 4) { HStack(spacing: 4) {
@ -212,7 +216,7 @@ extension CandidateCellData {
.font(.init(CTFontCreateUIFontForLanguage(.system, fontSizeCandidate, locale as CFString)!)) .font(.init(CTFontCreateUIFontForLanguage(.system, fontSizeCandidate, locale as CFString)!))
.foregroundColor(Color.primary).lineLimit(1) .foregroundColor(Color.primary).lineLimit(1)
}.padding(3).foregroundColor(Color(white: 0.9)) }.padding(3).foregroundColor(Color(white: 0.9))
} }.frame(minWidth: minWidthToDrawInSwiftUI, alignment: .leading)
} }
}.fixedSize(horizontal: false, vertical: true) }.fixedSize(horizontal: false, vertical: true)
} }

View File

@ -33,7 +33,8 @@ public struct CandidatePool {
public var maxRowWidth: Int { Int(Double(maxRowCapacity + 3) * 2) * Int(ceil(CandidateCellData.unifiedSize)) } public var maxRowWidth: Int { Int(Double(maxRowCapacity + 3) * 2) * Int(ceil(CandidateCellData.unifiedSize)) }
public var maxWindowWidth: Double { public var maxWindowWidth: Double {
ceil(Double(maxRowCapacity + 3) * 2.7 * ceil(CandidateCellData.unifiedSize) * 1.2) let testCell = CandidateCellData(key: "XX", displayedText: "XX", isSelected: false)
return Double(maxRowCapacity) * (testCell.minWidthToDrawInSwiftUI + ceil(CandidateCellData.unifiedSize * 0.5))
} }
public var currentLineNumber: Int { public var currentLineNumber: Int {
@ -158,7 +159,7 @@ public struct CandidatePool {
for (i, candidate) in candidateDataAll.enumerated() { for (i, candidate) in candidateDataAll.enumerated() {
candidate.index = i candidate.index = i
candidate.whichRow = candidateRows.count candidate.whichRow = candidateRows.count
let isOverflown: Bool = currentRow.map(\.cellLength).reduce(0, +) + candidate.cellLength > maxRowWidth let isOverflown: Bool = currentRow.map(\.cellLength).reduce(0, +) > maxRowWidth
if isOverflown || currentRow.count == maxRowCapacity, !currentRow.isEmpty { if isOverflown || currentRow.count == maxRowCapacity, !currentRow.isEmpty {
candidateRows.append(currentRow) candidateRows.append(currentRow)
currentRow.removeAll() currentRow.removeAll()

View File

@ -15,11 +15,11 @@ import SwiftUI
@available(macOS 12, *) @available(macOS 12, *)
struct CandidatePoolViewUIHorizontal_Previews: PreviewProvider { struct CandidatePoolViewUIHorizontal_Previews: PreviewProvider {
@State static var testCandidates: [String] = [ @State static var testCandidates: [String] = [
"八月中秋山林涼", "八月中秋", "風吹大地", "山林涼", "草枝擺", "八月", "中秋", "二十四歲是學生", "二十四歲", "昏睡紅茶", "食雪漢", "意味深", "學生", "便乗",
"🐂🍺🐂🍺", "🐃🍺", "🐂🍺", "🐃🐂🍺🍺", "🐂🍺", "🐃🍺", "🐂🍺", "🐃🍺", "🐂🍺", "🐃🍺", "🐂🍺🐂🍺", "🐃🍺", "🐂🍺", "🐃🐂🍺🍺", "🐂🍺", "🐃🍺", "🐂🍺", "🐃🍺", "🐂🍺", "🐃🍺",
"山林", "風吹", "大地", "草枝", "", "", "", "", "", "", "", "", "迫真", "驚愕", "論證", "正論", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "便", "",
"", "", "", "", "", "", "", "", "",
] ]
static var thePool: CandidatePool { static var thePool: CandidatePool {
var result = CandidatePool(candidates: testCandidates, rowCapacity: 6) var result = CandidatePool(candidates: testCandidates, rowCapacity: 6)
@ -62,14 +62,14 @@ public struct VwrCandidateHorizontal: View {
ScrollView(.vertical, showsIndicators: false) { ScrollView(.vertical, showsIndicators: false) {
VStack(alignment: .leading, spacing: 1.6) { VStack(alignment: .leading, spacing: 1.6) {
ForEach(thePool.rangeForCurrentPage, id: \.self) { rowIndex in ForEach(thePool.rangeForCurrentPage, id: \.self) { rowIndex in
HStack(spacing: 10) { HStack(spacing: ceil(CandidateCellData.unifiedSize * 0.35)) {
ForEach(Array(thePool.candidateLines[rowIndex]), id: \.self) { currentCandidate in ForEach(Array(thePool.candidateLines[rowIndex]), id: \.self) { currentCandidate in
currentCandidate.attributedStringForSwiftUI.fixedSize() currentCandidate.attributedStringForSwiftUI.fixedSize()
.contentShape(Rectangle())
.frame( .frame(
maxWidth: .infinity, maxWidth: .infinity,
alignment: .topLeading alignment: .topLeading
) )
.contentShape(Rectangle())
.onTapGesture { didSelectCandidateAt(currentCandidate.index) } .onTapGesture { didSelectCandidateAt(currentCandidate.index) }
.contextMenu { .contextMenu {
if controller?.delegate?.isCandidateContextMenuEnabled ?? false { if controller?.delegate?.isCandidateContextMenuEnabled ?? false {
@ -91,7 +91,7 @@ public struct VwrCandidateHorizontal: View {
} }
} }
} }
Spacer() Spacer(minLength: Double.infinity)
}.frame( }.frame(
minWidth: 0, minWidth: 0,
maxWidth: .infinity, maxWidth: .infinity,
@ -103,8 +103,8 @@ public struct VwrCandidateHorizontal: View {
ForEach(thePool.rangeForLastPageBlanked, id: \.self) { _ in ForEach(thePool.rangeForLastPageBlanked, id: \.self) { _ in
HStack(spacing: 0) { HStack(spacing: 0) {
thePool.blankCell.attributedStringForSwiftUI thePool.blankCell.attributedStringForSwiftUI
.frame(maxWidth: .infinity, alignment: .topLeading)
.contentShape(Rectangle()) .contentShape(Rectangle())
.frame(maxWidth: .infinity, alignment: .topLeading)
Spacer() Spacer()
}.frame( }.frame(
minWidth: 0, minWidth: 0,

View File

@ -15,10 +15,11 @@ import SwiftUI
@available(macOS 12, *) @available(macOS 12, *)
struct CandidatePoolViewUIVertical_Previews: PreviewProvider { struct CandidatePoolViewUIVertical_Previews: PreviewProvider {
@State static var testCandidates: [String] = [ @State static var testCandidates: [String] = [
"八月中秋山林涼", "八月中秋", "風吹大地", "山林涼", "草枝擺", "🐂🍺", "🐃🍺", "八月", "中秋", "二十四歲是學生", "二十四歲", "昏睡紅茶", "食雪漢", "意味深", "學生", "便乗",
"山林", "風吹", "大地", "草枝", "", "", "", "", "", "", "", "", "🐂🍺🐂🍺", "🐃🍺", "🐂🍺", "🐃🐂🍺🍺", "🐂🍺", "🐃🍺", "🐂🍺", "🐃🍺", "🐂🍺", "🐃🍺",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "迫真", "驚愕", "論證", "正論", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "便", "",
"", "", "",
] ]
static var thePool: CandidatePool { static var thePool: CandidatePool {
var result = CandidatePool(candidates: testCandidates, columnCapacity: 6, selectionKeys: "123456789") var result = CandidatePool(candidates: testCandidates, columnCapacity: 6, selectionKeys: "123456789")

View File

@ -16,11 +16,11 @@ import SwiftUIBackports
@available(macOS 10.15, *) @available(macOS 10.15, *)
struct CandidatePoolViewUIHorizontalBackports_Previews: PreviewProvider { struct CandidatePoolViewUIHorizontalBackports_Previews: PreviewProvider {
@State static var testCandidates: [String] = [ @State static var testCandidates: [String] = [
"八月中秋山林涼", "八月中秋", "風吹大地", "山林涼", "草枝擺", "八月", "中秋", "二十四歲是學生", "二十四歲", "昏睡紅茶", "食雪漢", "意味深", "學生", "便乗",
"🐂🍺🐂🍺", "🐃🍺", "🐂🍺", "🐃🐂🍺🍺", "🐂🍺", "🐃🍺", "🐂🍺", "🐃🍺", "🐂🍺", "🐃🍺", "🐂🍺🐂🍺", "🐃🍺", "🐂🍺", "🐃🐂🍺🍺", "🐂🍺", "🐃🍺", "🐂🍺", "🐃🍺", "🐂🍺", "🐃🍺",
"山林", "風吹", "大地", "草枝", "", "", "", "", "", "", "", "", "迫真", "驚愕", "論證", "正論", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "便", "",
"", "", "", "", "", "", "", "", "",
] ]
static var thePool: CandidatePool { static var thePool: CandidatePool {
var result = CandidatePool(candidates: testCandidates, rowCapacity: 6) var result = CandidatePool(candidates: testCandidates, rowCapacity: 6)
@ -63,14 +63,14 @@ public struct VwrCandidateHorizontalBackports: View {
ScrollView(.vertical, showsIndicators: false) { ScrollView(.vertical, showsIndicators: false) {
VStack(alignment: .leading, spacing: 1.6) { VStack(alignment: .leading, spacing: 1.6) {
ForEach(thePool.rangeForCurrentPage, id: \.self) { rowIndex in ForEach(thePool.rangeForCurrentPage, id: \.self) { rowIndex in
HStack(spacing: 10) { HStack(spacing: ceil(CandidateCellData.unifiedSize * 0.35)) {
ForEach(Array(thePool.candidateLines[rowIndex]), id: \.self) { currentCandidate in ForEach(Array(thePool.candidateLines[rowIndex]), id: \.self) { currentCandidate in
currentCandidate.attributedStringForSwiftUIBackports.fixedSize() currentCandidate.attributedStringForSwiftUIBackports.fixedSize()
.contentShape(Rectangle())
.frame( .frame(
maxWidth: .infinity, maxWidth: .infinity,
alignment: .topLeading alignment: .topLeading
) )
.contentShape(Rectangle())
.onTapGesture { didSelectCandidateAt(currentCandidate.index) } .onTapGesture { didSelectCandidateAt(currentCandidate.index) }
.contextMenu { .contextMenu {
if controller?.delegate?.isCandidateContextMenuEnabled ?? false { if controller?.delegate?.isCandidateContextMenuEnabled ?? false {
@ -92,7 +92,7 @@ public struct VwrCandidateHorizontalBackports: View {
} }
} }
} }
Spacer() Spacer(minLength: Double.infinity)
}.frame( }.frame(
minWidth: 0, minWidth: 0,
maxWidth: .infinity, maxWidth: .infinity,
@ -104,8 +104,8 @@ public struct VwrCandidateHorizontalBackports: View {
ForEach(thePool.rangeForLastPageBlanked, id: \.self) { _ in ForEach(thePool.rangeForLastPageBlanked, id: \.self) { _ in
HStack(spacing: 0) { HStack(spacing: 0) {
thePool.blankCell.attributedStringForSwiftUIBackports thePool.blankCell.attributedStringForSwiftUIBackports
.frame(maxWidth: .infinity, alignment: .topLeading)
.contentShape(Rectangle()) .contentShape(Rectangle())
.frame(maxWidth: .infinity, alignment: .topLeading)
Spacer() Spacer()
}.frame( }.frame(
minWidth: 0, minWidth: 0,

View File

@ -16,10 +16,11 @@ import SwiftUIBackports
@available(macOS 10.15, *) @available(macOS 10.15, *)
struct CandidatePoolViewUIVerticalBackports_Previews: PreviewProvider { struct CandidatePoolViewUIVerticalBackports_Previews: PreviewProvider {
@State static var testCandidates: [String] = [ @State static var testCandidates: [String] = [
"八月中秋山林涼", "八月中秋", "風吹大地", "山林涼", "草枝擺", "🐂🍺", "🐃🍺", "八月", "中秋", "二十四歲是學生", "二十四歲", "昏睡紅茶", "食雪漢", "意味深", "學生", "便乗",
"山林", "風吹", "大地", "草枝", "", "", "", "", "", "", "", "", "🐂🍺🐂🍺", "🐃🍺", "🐂🍺", "🐃🐂🍺🍺", "🐂🍺", "🐃🍺", "🐂🍺", "🐃🍺", "🐂🍺", "🐃🍺",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "迫真", "驚愕", "論證", "正論", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "便", "",
"", "", "",
] ]
static var thePool: CandidatePool { static var thePool: CandidatePool {
var result = CandidatePool(candidates: testCandidates, columnCapacity: 6, selectionKeys: "123456789") var result = CandidatePool(candidates: testCandidates, columnCapacity: 6, selectionKeys: "123456789")

View File

@ -13,9 +13,10 @@ import XCTest
final class CandidatePoolTests: XCTestCase { final class CandidatePoolTests: XCTestCase {
let testCandidates: [String] = [ let testCandidates: [String] = [
"八月中秋山林涼", "八月中秋", "風吹大地", "山林涼", "草枝擺", "八月", "中秋", "二十四歲是學生", "二十四歲", "昏睡紅茶", "食雪漢", "意味深", "學生", "便乗",
"山林", "風吹", "大地", "草枝", "", "", "", "", "", "", "", "", "迫真", "驚愕", "論證", "正論", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "便", "",
"", "", "",
] ]
func testPoolHorizontal() throws { func testPoolHorizontal() throws {