Shiki: Swiftify // Voltaire UI Revamp - Horizontal
This commit is contained in:
parent
c66f2c10db
commit
c4d7007f29
|
@ -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).
|
||||||
|
@ -38,112 +43,122 @@ fileprivate class HorizontalCandidateView: NSView {
|
||||||
var highlightedIndex: UInt = 0
|
var highlightedIndex: UInt = 0
|
||||||
var action: Selector?
|
var action: Selector?
|
||||||
weak var target: AnyObject?
|
weak var target: AnyObject?
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
override var isFlipped: Bool {
|
override var isFlipped: Bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
var sizeForView: NSSize {
|
var sizeForView: NSSize {
|
||||||
var result = NSSize.zero
|
var result = NSSize.zero
|
||||||
|
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc (setKeyLabels:displayedCandidates:)
|
@objc (setKeyLabels:displayedCandidates:)
|
||||||
func set(keyLabels labels: [String], displayedCandidates candidates: [String]) {
|
func set(keyLabels labels: [String], displayedCandidates candidates: [String]) {
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc (setKeyLabelFont:candidateFont:)
|
@objc (setKeyLabelFont:candidateFont:)
|
||||||
func set(keyLabelFont labelFont: NSFont, candidateFont: NSFont) {
|
func set(keyLabelFont labelFont: NSFont, candidateFont: NSFont) {
|
||||||
let paraStyle = NSMutableParagraphStyle()
|
let paraStyle = NSMutableParagraphStyle()
|
||||||
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, *) {
|
||||||
NSColor.separatorColor.setStroke()
|
NSColor.separatorColor.setStroke()
|
||||||
} else {
|
} else {
|
||||||
NSColor.darkGray.setStroke()
|
NSColor.darkGray.setStroke()
|
||||||
}
|
}
|
||||||
|
|
||||||
NSBezierPath.strokeLine(from: NSPoint(x: bounds.size.width, y: 0.0), to: NSPoint(x: bounds.size.width, y: bounds.size.height))
|
NSBezierPath.strokeLine(from: NSPoint(x: bounds.size.width, y: 0.0), to: NSPoint(x: bounds.size.width, y: bounds.size.height))
|
||||||
|
|
||||||
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func findHitIndex(event: NSEvent) -> UInt? {
|
private func findHitIndex(event: NSEvent) -> UInt? {
|
||||||
let location = convert(event.locationInWindow, to: nil)
|
let location = convert(event.locationInWindow, to: nil)
|
||||||
if !NSPointInRect(location, self.bounds) {
|
if !NSPointInRect(location, self.bounds) {
|
||||||
|
@ -152,16 +167,16 @@ fileprivate class HorizontalCandidateView: NSView {
|
||||||
var accuWidth: CGFloat = 0.0
|
var accuWidth: CGFloat = 0.0
|
||||||
for index in 0..<elementWidths.count {
|
for index in 0..<elementWidths.count {
|
||||||
let currentWidth = elementWidths[index]
|
let currentWidth = elementWidths[index]
|
||||||
|
|
||||||
if location.x >= accuWidth && location.x <= accuWidth + currentWidth {
|
if location.x >= accuWidth && location.x <= accuWidth + currentWidth {
|
||||||
return UInt(index)
|
return UInt(index)
|
||||||
}
|
}
|
||||||
accuWidth += currentWidth + 1.0
|
accuWidth += currentWidth + 1.0
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override func mouseUp(with event: NSEvent) {
|
override func mouseUp(with event: NSEvent) {
|
||||||
trackingHighlightedIndex = highlightedIndex
|
trackingHighlightedIndex = highlightedIndex
|
||||||
guard let newIndex = findHitIndex(event: event) else {
|
guard let newIndex = findHitIndex(event: event) else {
|
||||||
|
@ -170,7 +185,7 @@ fileprivate class HorizontalCandidateView: NSView {
|
||||||
highlightedIndex = newIndex
|
highlightedIndex = newIndex
|
||||||
self.setNeedsDisplay(self.bounds)
|
self.setNeedsDisplay(self.bounds)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func mouseDown(with event: NSEvent) {
|
override func mouseDown(with event: NSEvent) {
|
||||||
guard let newIndex = findHitIndex(event: event) else {
|
guard let newIndex = findHitIndex(event: event) else {
|
||||||
return
|
return
|
||||||
|
@ -181,7 +196,7 @@ fileprivate class HorizontalCandidateView: NSView {
|
||||||
} else {
|
} else {
|
||||||
highlightedIndex = trackingHighlightedIndex
|
highlightedIndex = trackingHighlightedIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
trackingHighlightedIndex = 0
|
trackingHighlightedIndex = 0
|
||||||
self.setNeedsDisplay(self.bounds)
|
self.setNeedsDisplay(self.bounds)
|
||||||
if triggerAction {
|
if triggerAction {
|
||||||
|
@ -198,89 +213,99 @@ public class HorizontalCandidateController: CandidateController {
|
||||||
private var prevPageButton: NSButton
|
private var prevPageButton: NSButton
|
||||||
private var nextPageButton: NSButton
|
private var nextPageButton: NSButton
|
||||||
private var currentPage: UInt = 0
|
private var currentPage: UInt = 0
|
||||||
|
|
||||||
public init() {
|
public init() {
|
||||||
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)
|
||||||
|
|
||||||
super.init(window: panel)
|
super.init(window: panel)
|
||||||
|
|
||||||
candidateView.target = self
|
candidateView.target = self
|
||||||
candidateView.action = #selector(candidateViewMouseDidClick(_:))
|
candidateView.action = #selector(candidateViewMouseDidClick(_:))
|
||||||
|
|
||||||
nextPageButton.target = self
|
nextPageButton.target = self
|
||||||
nextPageButton.action = #selector(pageButtonAction(_:))
|
nextPageButton.action = #selector(pageButtonAction(_:))
|
||||||
|
|
||||||
prevPageButton.target = self
|
prevPageButton.target = self
|
||||||
prevPageButton.action = #selector(pageButtonAction(_:))
|
prevPageButton.action = #selector(pageButtonAction(_:))
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
required init?(coder: NSCoder) {
|
||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
public override func reloadData() {
|
public override func reloadData() {
|
||||||
candidateView.highlightedIndex = 0
|
candidateView.highlightedIndex = 0
|
||||||
currentPage = 0
|
currentPage = 0
|
||||||
layoutCandidateView()
|
layoutCandidateView()
|
||||||
}
|
}
|
||||||
|
|
||||||
public override func showNextPage() -> Bool {
|
public override func showNextPage() -> Bool {
|
||||||
guard delegate != nil else {
|
guard delegate != nil else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if currentPage + 1 >= pageCount {
|
if currentPage + 1 >= pageCount {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
currentPage += 1
|
currentPage += 1
|
||||||
candidateView.highlightedIndex = 0
|
candidateView.highlightedIndex = 0
|
||||||
layoutCandidateView()
|
layoutCandidateView()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
public override func showPreviousPage() -> Bool {
|
public override func showPreviousPage() -> Bool {
|
||||||
guard delegate != nil else {
|
guard delegate != nil else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if currentPage == 0 {
|
if currentPage == 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
currentPage -= 1
|
currentPage -= 1
|
||||||
candidateView.highlightedIndex = 0
|
candidateView.highlightedIndex = 0
|
||||||
layoutCandidateView()
|
layoutCandidateView()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
public override func highlightNextCandidate() -> Bool {
|
public override func highlightNextCandidate() -> Bool {
|
||||||
guard let delegate = delegate else {
|
guard let delegate = delegate else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
let currentIndex = selectedCandidateIndex
|
let currentIndex = selectedCandidateIndex
|
||||||
if currentIndex + 1 >= delegate.candidateCountForController(self) {
|
if currentIndex + 1 >= delegate.candidateCountForController(self) {
|
||||||
return false
|
return false
|
||||||
|
@ -288,30 +313,30 @@ public class HorizontalCandidateController: CandidateController {
|
||||||
selectedCandidateIndex = currentIndex + 1
|
selectedCandidateIndex = currentIndex + 1
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
public override func highlightPreviousCandidate() -> Bool {
|
public override func highlightPreviousCandidate() -> Bool {
|
||||||
guard delegate != nil else {
|
guard delegate != nil else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
let currentIndex = selectedCandidateIndex
|
let currentIndex = selectedCandidateIndex
|
||||||
if currentIndex == 0 {
|
if currentIndex == 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
selectedCandidateIndex = currentIndex - 1
|
selectedCandidateIndex = currentIndex - 1
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
public override func candidateIndexAtKeyLabelIndex(_ index: UInt) -> UInt {
|
public override func candidateIndexAtKeyLabelIndex(_ index: UInt) -> UInt {
|
||||||
guard let delegate = delegate else {
|
guard let delegate = delegate else {
|
||||||
return UInt.max
|
return UInt.max
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = currentPage * UInt(keyLabels.count) + index
|
let result = currentPage * UInt(keyLabels.count) + index
|
||||||
return result < delegate.candidateCountForController(self) ? result : UInt.max
|
return result < delegate.candidateCountForController(self) ? result : UInt.max
|
||||||
}
|
}
|
||||||
|
|
||||||
public override var selectedCandidateIndex: UInt {
|
public override var selectedCandidateIndex: UInt {
|
||||||
get {
|
get {
|
||||||
currentPage * UInt(keyLabels.count) + candidateView.highlightedIndex
|
currentPage * UInt(keyLabels.count) + candidateView.highlightedIndex
|
||||||
|
@ -331,7 +356,7 @@ public class HorizontalCandidateController: CandidateController {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension HorizontalCandidateController {
|
extension HorizontalCandidateController {
|
||||||
|
|
||||||
private var pageCount: UInt {
|
private var pageCount: UInt {
|
||||||
guard let delegate = delegate else {
|
guard let delegate = delegate else {
|
||||||
return 0
|
return 0
|
||||||
|
@ -340,17 +365,17 @@ extension HorizontalCandidateController {
|
||||||
let keyLabelCount = UInt(keyLabels.count)
|
let keyLabelCount = UInt(keyLabels.count)
|
||||||
return totalCount / keyLabelCount + ((totalCount % keyLabelCount) != 0 ? 1 : 0)
|
return totalCount / keyLabelCount + ((totalCount % keyLabelCount) != 0 ? 1 : 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func layoutCandidateView() {
|
private func layoutCandidateView() {
|
||||||
guard let delegate = delegate else {
|
guard let delegate = delegate else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
candidateView.set(keyLabelFont: keyLabelFont, candidateFont: candidateFont)
|
candidateView.set(keyLabelFont: keyLabelFont, candidateFont: candidateFont)
|
||||||
var candidates = [String]()
|
var candidates = [String]()
|
||||||
let count = delegate.candidateCountForController(self)
|
let count = delegate.candidateCountForController(self)
|
||||||
let keyLabelCount = UInt(keyLabels.count)
|
let keyLabelCount = UInt(keyLabels.count)
|
||||||
|
|
||||||
let begin = currentPage * keyLabelCount
|
let begin = currentPage * keyLabelCount
|
||||||
for index in begin..<min(begin + keyLabelCount, count) {
|
for index in begin..<min(begin + keyLabelCount, count) {
|
||||||
let candidate = delegate.candidateController(self, candidateAtIndex: index)
|
let candidate = delegate.candidateController(self, candidateAtIndex: index)
|
||||||
|
@ -361,28 +386,20 @@ extension HorizontalCandidateController {
|
||||||
var frameRect = candidateView.frame
|
var frameRect = candidateView.frame
|
||||||
frameRect.size = newSize
|
frameRect.size = newSize
|
||||||
candidateView.frame = frameRect
|
candidateView.frame = frameRect
|
||||||
|
|
||||||
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)
|
||||||
nextPageButton.frame = buttonRect
|
nextPageButton.frame = buttonRect
|
||||||
|
|
||||||
buttonRect.origin = NSPoint(x: newSize.width + 8.0, y: buttonOriginY + buttonRect.size.height + spacing)
|
buttonRect.origin = NSPoint(x: newSize.width + 8.0, y: buttonOriginY + buttonRect.size.height + spacing)
|
||||||
prevPageButton.frame = buttonRect
|
prevPageButton.frame = buttonRect
|
||||||
|
|
||||||
newSize.width += 52.0
|
newSize.width += 52.0
|
||||||
nextPageButton.isHidden = false
|
nextPageButton.isHidden = false
|
||||||
prevPageButton.isHidden = false
|
prevPageButton.isHidden = false
|
||||||
|
@ -390,16 +407,16 @@ extension HorizontalCandidateController {
|
||||||
nextPageButton.isHidden = true
|
nextPageButton.isHidden = true
|
||||||
prevPageButton.isHidden = true
|
prevPageButton.isHidden = true
|
||||||
}
|
}
|
||||||
|
|
||||||
frameRect = window?.frame ?? NSRect.zero
|
frameRect = window?.frame ?? NSRect.zero
|
||||||
|
|
||||||
let topLeftPoint = NSMakePoint(frameRect.origin.x, frameRect.origin.y + frameRect.size.height)
|
let topLeftPoint = NSMakePoint(frameRect.origin.x, frameRect.origin.y + frameRect.size.height)
|
||||||
frameRect.size = newSize
|
frameRect.size = newSize
|
||||||
frameRect.origin = NSMakePoint(topLeftPoint.x, topLeftPoint.y - frameRect.size.height)
|
frameRect.origin = NSMakePoint(topLeftPoint.x, topLeftPoint.y - frameRect.size.height)
|
||||||
self.window?.setFrame(frameRect, display: false)
|
self.window?.setFrame(frameRect, display: false)
|
||||||
candidateView.setNeedsDisplay(candidateView.bounds)
|
candidateView.setNeedsDisplay(candidateView.bounds)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc fileprivate func pageButtonAction(_ sender: Any) {
|
@objc fileprivate func pageButtonAction(_ sender: Any) {
|
||||||
guard let sender = sender as? NSButton else {
|
guard let sender = sender as? NSButton else {
|
||||||
return
|
return
|
||||||
|
@ -410,9 +427,9 @@ extension HorizontalCandidateController {
|
||||||
_ = showPreviousPage()
|
_ = showPreviousPage()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc fileprivate func candidateViewMouseDidClick(_ sender: Any) {
|
@objc fileprivate func candidateViewMouseDidClick(_ sender: Any) {
|
||||||
delegate?.candidateController(self, didSelectCandidateAtIndex: selectedCandidateIndex)
|
delegate?.candidateController(self, didSelectCandidateAtIndex: selectedCandidateIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue