Shiki: Swiftify // Voltaire UI Revamp - Horizontal

This commit is contained in:
ShikiSuen 2022-01-14 22:52:32 +08:00
parent c66f2c10db
commit c4d7007f29
1 changed files with 115 additions and 98 deletions

View File

@ -1,11 +1,16 @@
// //
// HorizontalCandidateController.swift // HorizontalCandidateController.swift
// //
// Copyright (c) 2011 The McBopomofo Project. // Voltaire IME Candidate Controller Module
//
// Copyright (c) 2011-2022 The OpenVanilla Project.
// Beautified by (c) 2021-2022 The vChewing Project.
// //
// Contributors: // Contributors:
// Mengjuei Hsieh (@mjhsieh) // Lukhnos Liu (@lukhnos) @ OpenVanilla // Original Developer
// Weizhong Yang (@zonble) // Weizhong Yang (@zonble) @ OpenVanilla // Rewriter to Swift
// Shiki Suen (ShikiSuen) @ vChewing // Beautification (objC)
// //
// Based on the Syrup Project and the Formosana Library // Based on the Syrup Project and the Formosana Library
// by Lukhnos Liu (@lukhnos). // by Lukhnos Liu (@lukhnos).
@ -41,11 +46,14 @@ fileprivate class HorizontalCandidateView: NSView {
private var keyLabels: [String] = [] private var keyLabels: [String] = []
private var displayedCandidates: [String] = [] private var displayedCandidates: [String] = []
private var dispCandidatesWithLabels: [String] = []
private var keyLabelHeight: CGFloat = 0 private var keyLabelHeight: CGFloat = 0
private var keyLabelWidth: CGFloat = 0
private var candidateTextHeight: CGFloat = 0 private var candidateTextHeight: CGFloat = 0
private var cellPadding: CGFloat = 0 private var cellPadding: CGFloat = 0
private var keyLabelAttrDict: [NSAttributedString.Key: AnyObject] = [:] private var keyLabelAttrDict: [NSAttributedString.Key: AnyObject] = [:]
private var candidateAttrDict: [NSAttributedString.Key: AnyObject] = [:] private var candidateAttrDict: [NSAttributedString.Key: AnyObject] = [:]
private var candidateWithLabelAttrDict: [NSAttributedString.Key: AnyObject] = [:]
private var elementWidths: [CGFloat] = [] private var elementWidths: [CGFloat] = []
private var trackingHighlightedIndex: UInt = UInt.max private var trackingHighlightedIndex: UInt = UInt.max
@ -59,7 +67,8 @@ fileprivate class HorizontalCandidateView: NSView {
if !elementWidths.isEmpty { if !elementWidths.isEmpty {
result.width = elementWidths.reduce(0, +) result.width = elementWidths.reduce(0, +)
result.width += CGFloat(elementWidths.count) result.width += CGFloat(elementWidths.count)
result.height = keyLabelHeight + candidateTextHeight + 1.0 // result.height = keyLabelHeight + candidateTextHeight + 1.0
result.height = candidateTextHeight + cellPadding;
} }
return result return result
} }
@ -69,13 +78,14 @@ fileprivate class HorizontalCandidateView: NSView {
let count = min(labels.count, candidates.count) let count = min(labels.count, candidates.count)
keyLabels = Array(labels[0..<count]) keyLabels = Array(labels[0..<count])
displayedCandidates = Array(candidates[0..<count]) displayedCandidates = Array(candidates[0..<count])
// dispCandidatesWithLabels = keyLabels + displayedCandidates
dispCandidatesWithLabels = zip(keyLabels,displayedCandidates).map() {$0 + $1}
var newWidths = [CGFloat]() var newWidths = [CGFloat]()
let baseSize = NSSize(width: 10240.0, height: 10240.0) let baseSize = NSSize(width: 10240.0, height: 10240.0)
for index in 0..<count { for index in 0..<count {
let labelRect = (keyLabels[index] as NSString).boundingRect(with: baseSize, options: .usesLineFragmentOrigin, attributes: keyLabelAttrDict) let rctCandidate = (dispCandidatesWithLabels[index] as NSString).boundingRect(with: baseSize, options: .usesLineFragmentOrigin, attributes: candidateWithLabelAttrDict)
let candidateRect = (displayedCandidates[index] as NSString).boundingRect(with: baseSize, options: .usesLineFragmentOrigin, attributes: candidateAttrDict) let cellWidth = rctCandidate.size.width + cellPadding;
let cellWidth = max(labelRect.size.width, candidateRect.size.width) + cellPadding;
newWidths.append(cellWidth) newWidths.append(cellWidth)
} }
elementWidths = newWidths elementWidths = newWidths
@ -87,29 +97,35 @@ fileprivate class HorizontalCandidateView: NSView {
paraStyle.setParagraphStyle(NSParagraphStyle.default) paraStyle.setParagraphStyle(NSParagraphStyle.default)
paraStyle.alignment = .center paraStyle.alignment = .center
candidateWithLabelAttrDict = [.font: candidateFont,
.paragraphStyle: paraStyle,
.foregroundColor: NSColor.labelColor] // We still need this dummy section to make sure the space occupations of the candidates are correct.
keyLabelAttrDict = [.font: labelFont, keyLabelAttrDict = [.font: labelFont,
.paragraphStyle: paraStyle, .paragraphStyle: paraStyle,
.foregroundColor: NSColor.black] .foregroundColor: NSColor.secondaryLabelColor] // Candidate phrase text color
candidateAttrDict = [.font: candidateFont, candidateAttrDict = [.font: candidateFont,
.paragraphStyle: paraStyle, .paragraphStyle: paraStyle,
.foregroundColor: NSColor.textColor] .foregroundColor: NSColor.labelColor] // Candidate index text color
let labelFontSize = labelFont.pointSize let labelFontSize = labelFont.pointSize
let candidateFontSize = candidateFont.pointSize let candidateFontSize = candidateFont.pointSize
let biggestSize = max(labelFontSize, candidateFontSize) let biggestSize = max(labelFontSize, candidateFontSize)
keyLabelWidth = ceil(labelFontSize)
keyLabelHeight = ceil(labelFontSize * 1.20) keyLabelHeight = ceil(labelFontSize * 1.20)
candidateTextHeight = ceil(candidateFontSize * 1.20) candidateTextHeight = ceil(candidateFontSize * 1.20)
cellPadding = ceil(biggestSize / 2.0) cellPadding = ceil(biggestSize / 2.0)
} }
override func draw(_ dirtyRect: NSRect) { override func draw(_ dirtyRect: NSRect) {
let backgroundColor = NSColor.controlBackgroundColor
let darkGray = NSColor(deviceWhite: 0.7, alpha: 1.0) // Give a standalone layer to the candidate list panel
let lightGray = NSColor(deviceWhite: 0.8, alpha: 1.0) self.wantsLayer = true
self.layer?.borderColor = NSColor.selectedMenuItemTextColor.withAlphaComponent(0.30).cgColor
self.layer?.borderWidth = 1.0
self.layer?.cornerRadius = 6.0
let bounds = self.bounds let bounds = self.bounds
backgroundColor.setFill() NSColor.controlBackgroundColor.setFill() // Candidate list panel base background
NSBezierPath.fill(bounds) NSBezierPath.fill(bounds)
if #available(macOS 10.14, *) { if #available(macOS 10.14, *) {
@ -123,23 +139,22 @@ fileprivate class HorizontalCandidateView: NSView {
var accuWidth: CGFloat = 0 var accuWidth: CGFloat = 0
for index in 0..<elementWidths.count { for index in 0..<elementWidths.count {
let currentWidth = elementWidths[index] let currentWidth = elementWidths[index]
let labelRect = NSRect(x: accuWidth, y: 0.0, width: currentWidth, height: keyLabelHeight) let rctCandidateArea = NSRect(x: accuWidth, y: 0.0, width: currentWidth + 1.0, height: candidateTextHeight + cellPadding)
let candidateRect = NSRect(x: accuWidth, y: keyLabelHeight + 1.0, width: currentWidth, height: candidateTextHeight) let rctLabel = NSRect(x: accuWidth + cellPadding / 2 - 1, y: cellPadding / 2 , width: keyLabelWidth, height: candidateTextHeight)
(index == highlightedIndex ? darkGray : lightGray).setFill() let rctCandidatePhrase = NSRect(x: accuWidth + keyLabelWidth - 1, y: cellPadding / 2 , width: currentWidth - keyLabelWidth, height: candidateTextHeight)
NSBezierPath.fill(labelRect)
(keyLabels[index] as NSString).draw(in: labelRect, withAttributes: keyLabelAttrDict)
var activeCandidateIndexAttr = keyLabelAttrDict
var activeCandidateAttr = candidateAttrDict var activeCandidateAttr = candidateAttrDict
if index == highlightedIndex { if index == highlightedIndex {
NSColor.selectedTextBackgroundColor.setFill() NSColor.alternateSelectedControlColor.setFill() // The background color of the highlightened candidate
activeCandidateAttr = candidateAttrDict activeCandidateIndexAttr[.foregroundColor] = NSColor.selectedMenuItemTextColor.withAlphaComponent(0.84) // The index text color of the highlightened candidate
activeCandidateAttr[.foregroundColor] = NSColor.selectedTextColor activeCandidateAttr[.foregroundColor] = NSColor.selectedMenuItemTextColor // The phrase text color of the highlightened candidate
} else { } else {
backgroundColor.setFill() NSColor.controlBackgroundColor.setFill()
} }
NSBezierPath.fill(rctCandidateArea)
NSBezierPath.fill(candidateRect) (keyLabels[index] as NSString).draw(in: rctLabel, withAttributes: activeCandidateIndexAttr)
(displayedCandidates[index] as NSString).draw(in: candidateRect, withAttributes: activeCandidateAttr) (displayedCandidates[index] as NSString).draw(in: rctCandidatePhrase, withAttributes: activeCandidateAttr)
accuWidth += currentWidth + 1.0 accuWidth += currentWidth + 1.0
} }
} }
@ -203,24 +218,34 @@ public class HorizontalCandidateController: CandidateController {
var contentRect = NSRect(x: 128.0, y: 128.0, width: 0.0, height: 0.0) var contentRect = NSRect(x: 128.0, y: 128.0, width: 0.0, height: 0.0)
let styleMask: NSWindow.StyleMask = [.borderless, .nonactivatingPanel] let styleMask: NSWindow.StyleMask = [.borderless, .nonactivatingPanel]
let panel = NSPanel(contentRect: contentRect, styleMask: styleMask, backing: .buffered, defer: false) let panel = NSPanel(contentRect: contentRect, styleMask: styleMask, backing: .buffered, defer: false)
panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel)) panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel) + 1)
panel.hasShadow = true panel.hasShadow = true
panel.isOpaque = false
panel.backgroundColor = NSColor.clear // Transparentify everything outside of the candidate list panel
contentRect.origin = NSPoint.zero contentRect.origin = NSPoint.zero
candidateView = HorizontalCandidateView(frame: contentRect) candidateView = HorizontalCandidateView(frame: contentRect)
panel.contentView?.addSubview(candidateView) panel.contentView?.addSubview(candidateView)
contentRect.size = NSSize(width: 36.0, height: 20.0) contentRect.size = NSSize(width: 20.0, height: 15.0) // Reduce the button width
nextPageButton = NSButton(frame: contentRect) nextPageButton = NSButton(frame: contentRect)
nextPageButton.setButtonType(.momentaryLight) nextPageButton.setButtonType(.momentaryLight)
nextPageButton.bezelStyle = .smallSquare nextPageButton.bezelStyle = .shadowlessSquare
nextPageButton.title = "»" nextPageButton.wantsLayer = true
nextPageButton.layer?.masksToBounds = true
nextPageButton.layer?.borderColor = NSColor.clear.cgColor // Attempt to remove the system default layer border color - step 1
nextPageButton.layer?.borderWidth = 0.0 // Attempt to remove the system default layer border color - step 2
nextPageButton.layer?.backgroundColor = NSColor.black.cgColor // Button Background Color. Otherwise the button will be half-transparent in macOS Monterey Dark Mode.
nextPageButton.attributedTitle = NSMutableAttributedString(string: "⬇︎") // Next Page Arrow
prevPageButton = NSButton(frame: contentRect) prevPageButton = NSButton(frame: contentRect)
prevPageButton.setButtonType(.momentaryLight) prevPageButton.setButtonType(.momentaryLight)
prevPageButton.bezelStyle = .smallSquare prevPageButton.bezelStyle = .shadowlessSquare
prevPageButton.title = "«" prevPageButton.wantsLayer = true
prevPageButton.layer?.masksToBounds = true
prevPageButton.layer?.borderColor = NSColor.clear.cgColor // Attempt to remove the system default layer border color - step 1
prevPageButton.layer?.borderWidth = 0.0 // Attempt to remove the system default layer border color - step 2
prevPageButton.layer?.backgroundColor = NSColor.black.cgColor // Button Background Color. Otherwise the button will be half-transparent in macOS Monterey Dark Mode.
prevPageButton.attributedTitle = NSMutableAttributedString(string: "⬆︎") // Previous Page Arrow
panel.contentView?.addSubview(nextPageButton) panel.contentView?.addSubview(nextPageButton)
panel.contentView?.addSubview(prevPageButton) panel.contentView?.addSubview(prevPageButton)
@ -364,17 +389,9 @@ extension HorizontalCandidateController {
if pageCount > 1 { if pageCount > 1 {
var buttonRect = nextPageButton.frame var buttonRect = nextPageButton.frame
var spacing = 0.0 let spacing:CGFloat = 0.0
if newSize.height < 40.0 { buttonRect.size.height = floor(newSize.height / 2)
buttonRect.size.height = floor(newSize.height / 2)
} else {
buttonRect.size.height = 20.0
}
if newSize.height >= 60.0 {
spacing = ceil(newSize.height * 0.1)
}
let buttonOriginY = (newSize.height - (buttonRect.size.height * 2.0 + spacing)) / 2.0 let buttonOriginY = (newSize.height - (buttonRect.size.height * 2.0 + spacing)) / 2.0
buttonRect.origin = NSPoint(x: newSize.width + 8.0, y: buttonOriginY) buttonRect.origin = NSPoint(x: newSize.width + 8.0, y: buttonOriginY)