Repo // Deprecating Zonble's Tooltip controller.
This commit is contained in:
parent
21c369defb
commit
3cb0872a1d
|
@ -88,7 +88,7 @@
|
||||||
- ctlInputMethod 輸入法主控制器內則採用策略設計模式來處理各種狀態。
|
- ctlInputMethod 輸入法主控制器內則採用策略設計模式來處理各種狀態。
|
||||||
- 半衰記憶模組的 C++ 原版作者是 Mengjuei Hsieh,且由 Shiki Suen 用 Swift 與 C# 分別重寫、繼續開發。
|
- 半衰記憶模組的 C++ 原版作者是 Mengjuei Hsieh,且由 Shiki Suen 用 Swift 與 C# 分別重寫、繼續開發。
|
||||||
- 僅供研發人員調試方便而使用的 App 版安裝程式 (by Zonble Yang),不對公眾使用。
|
- 僅供研發人員調試方便而使用的 App 版安裝程式 (by Zonble Yang),不對公眾使用。
|
||||||
- Voltaire MK2 選字窗、飄雲通知視窗、工具提示 (by Zonble Yang),有大幅度修改。
|
- Voltaire MK2 選字窗、飄雲通知視窗 (by Zonble Yang),有大幅度修改。
|
||||||
|
|
||||||
威注音輸入法 macOS 版以 MIT-NTL License 授權釋出 (與 MIT 相容):© 2021-2022 vChewing 專案。
|
威注音輸入法 macOS 版以 MIT-NTL License 授權釋出 (與 MIT 相容):© 2021-2022 vChewing 專案。
|
||||||
|
|
||||||
|
|
|
@ -88,7 +88,7 @@
|
||||||
- ctlInputMethod 输入法主控制器内则采用策略设计模式来处理各种状态。
|
- ctlInputMethod 输入法主控制器内则采用策略设计模式来处理各种状态。
|
||||||
- 半衰记忆模组的 C++ 原版作者是 Mengjuei Hsieh,且由 Shiki Suen 用 Swift 与 C# 分别重写、继续开发。
|
- 半衰记忆模组的 C++ 原版作者是 Mengjuei Hsieh,且由 Shiki Suen 用 Swift 与 C# 分别重写、继续开发。
|
||||||
- 仅供研发人员调试方便而使用的 App 版安装程式 (by Zonble Yang),不对公众使用。
|
- 仅供研发人员调试方便而使用的 App 版安装程式 (by Zonble Yang),不对公众使用。
|
||||||
- Voltaire MK2 选字窗、飘云通知视窗、工具提示 (by Zonble Yang),有大幅度修改。
|
- Voltaire MK2 选字窗、飘云通知视窗 (by Zonble Yang),有大幅度修改。
|
||||||
|
|
||||||
威注音输入法 macOS 版以 MIT-NTL License 授权释出 (与 MIT 相容):© 2021-2022 vChewing 专案。
|
威注音输入法 macOS 版以 MIT-NTL License 授权释出 (与 MIT 相容):© 2021-2022 vChewing 专案。
|
||||||
|
|
||||||
|
|
|
@ -179,7 +179,7 @@ extension StateData {
|
||||||
}
|
}
|
||||||
arrOutput.append(neta)
|
arrOutput.append(neta)
|
||||||
}
|
}
|
||||||
return arrOutput.joined(separator: " ")
|
return arrOutput.joined(separator: "\u{A0}")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 更新工具提示內容、以及對應配對是否在庫。
|
/// 更新工具提示內容、以及對應配對是否在庫。
|
||||||
|
@ -187,7 +187,7 @@ extension StateData {
|
||||||
public static func updateParameters(_ data: inout StateData) {
|
public static func updateParameters(_ data: inout StateData) {
|
||||||
var tooltipGenerated: String {
|
var tooltipGenerated: String {
|
||||||
if mgrPrefs.phraseReplacementEnabled {
|
if mgrPrefs.phraseReplacementEnabled {
|
||||||
ctlInputMethod.tooltipController.setColor(state: .warning)
|
ctlInputMethod.tooltipInstance.setColor(state: .warning)
|
||||||
return NSLocalizedString(
|
return NSLocalizedString(
|
||||||
"⚠︎ Phrase replacement mode enabled, interfering user phrase entry.", comment: ""
|
"⚠︎ Phrase replacement mode enabled, interfering user phrase entry.", comment: ""
|
||||||
)
|
)
|
||||||
|
@ -198,18 +198,18 @@ extension StateData {
|
||||||
|
|
||||||
let text = data.displayedText.charComponents[data.markedRange].joined()
|
let text = data.displayedText.charComponents[data.markedRange].joined()
|
||||||
if data.markedRange.count < mgrPrefs.allowedMarkRange.lowerBound {
|
if data.markedRange.count < mgrPrefs.allowedMarkRange.lowerBound {
|
||||||
ctlInputMethod.tooltipController.setColor(state: .denialInsufficiency)
|
ctlInputMethod.tooltipInstance.setColor(state: .denialInsufficiency)
|
||||||
return String(
|
return String(
|
||||||
format: NSLocalizedString(
|
format: NSLocalizedString(
|
||||||
"\"%@\" length must ≥ 2 for a user phrase.", comment: ""
|
"\"%@\" length must ≥ 2 for a user phrase.", comment: ""
|
||||||
) + "\n// " + generateReadingThread(data), text
|
) + "\n◆ " + generateReadingThread(data), text
|
||||||
)
|
)
|
||||||
} else if data.markedRange.count > mgrPrefs.allowedMarkRange.upperBound {
|
} else if data.markedRange.count > mgrPrefs.allowedMarkRange.upperBound {
|
||||||
ctlInputMethod.tooltipController.setColor(state: .denialOverflow)
|
ctlInputMethod.tooltipInstance.setColor(state: .denialOverflow)
|
||||||
return String(
|
return String(
|
||||||
format: NSLocalizedString(
|
format: NSLocalizedString(
|
||||||
"\"%@\" length should ≤ %d for a user phrase.", comment: ""
|
"\"%@\" length should ≤ %d for a user phrase.", comment: ""
|
||||||
) + "\n// " + generateReadingThread(data), text, mgrPrefs.allowedMarkRange.upperBound
|
) + "\n◆ " + generateReadingThread(data), text, mgrPrefs.allowedMarkRange.upperBound
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -219,17 +219,17 @@ extension StateData {
|
||||||
)
|
)
|
||||||
if exist {
|
if exist {
|
||||||
data.markedTargetExists = exist
|
data.markedTargetExists = exist
|
||||||
ctlInputMethod.tooltipController.setColor(state: .prompt)
|
ctlInputMethod.tooltipInstance.setColor(state: .prompt)
|
||||||
return String(
|
return String(
|
||||||
format: NSLocalizedString(
|
format: NSLocalizedString(
|
||||||
"\"%@\" already exists:\n ENTER to boost, SHIFT+COMMAND+ENTER to nerf, \n BackSpace or Delete key to exclude.",
|
"\"%@\" already exists:\n ENTER to boost, SHIFT+COMMAND+ENTER to nerf, \n BackSpace or Delete key to exclude.",
|
||||||
comment: ""
|
comment: ""
|
||||||
) + "\n// " + generateReadingThread(data), text
|
) + "\n◆ " + generateReadingThread(data), text
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
ctlInputMethod.tooltipController.resetColor()
|
ctlInputMethod.tooltipInstance.resetColor()
|
||||||
return String(
|
return String(
|
||||||
format: NSLocalizedString("\"%@\" selected. ENTER to add user phrase.", comment: "") + "\n// "
|
format: NSLocalizedString("\"%@\" selected. ENTER to add user phrase.", comment: "") + "\n◆ "
|
||||||
+ generateReadingThread(data),
|
+ generateReadingThread(data),
|
||||||
text
|
text
|
||||||
)
|
)
|
||||||
|
|
|
@ -27,8 +27,8 @@ class ctlInputMethod: IMKInputController {
|
||||||
static var ctlCandidateCurrent: ctlCandidateProtocol =
|
static var ctlCandidateCurrent: ctlCandidateProtocol =
|
||||||
mgrPrefs.useIMKCandidateWindow ? ctlCandidateIMK.init(.horizontal) : ctlCandidateUniversal.init(.horizontal)
|
mgrPrefs.useIMKCandidateWindow ? ctlCandidateIMK.init(.horizontal) : ctlCandidateUniversal.init(.horizontal)
|
||||||
|
|
||||||
/// 工具提示視窗的副本。
|
/// 工具提示視窗的副本,每次都重新初始化。
|
||||||
static let tooltipController = TooltipController()
|
static var tooltipInstance = ctlTooltip()
|
||||||
|
|
||||||
// MARK: -
|
// MARK: -
|
||||||
|
|
||||||
|
|
|
@ -27,15 +27,21 @@ extension ctlInputMethod {
|
||||||
cursor -= 1
|
cursor -= 1
|
||||||
}
|
}
|
||||||
var finalOrigin: NSPoint = lineHeightRect.origin
|
var finalOrigin: NSPoint = lineHeightRect.origin
|
||||||
|
let delta: CGFloat = lineHeightRect.size.height + 4.0 // bottomOutOfScreenAdjustmentHeight
|
||||||
if isVerticalTyping {
|
if isVerticalTyping {
|
||||||
finalOrigin = NSPoint(
|
finalOrigin = NSPoint(
|
||||||
x: lineHeightRect.origin.x + lineHeightRect.size.width + 4.0, y: lineHeightRect.origin.y - 4.0
|
x: lineHeightRect.origin.x + lineHeightRect.size.width + 5, y: lineHeightRect.origin.y
|
||||||
|
)
|
||||||
|
ctlInputMethod.tooltipInstance.show(
|
||||||
|
tooltip: tooltip, at: finalOrigin,
|
||||||
|
bottomOutOfScreenAdjustmentHeight: delta, direction: .vertical
|
||||||
)
|
)
|
||||||
ctlInputMethod.tooltipController.direction = .vertical
|
|
||||||
} else {
|
} else {
|
||||||
ctlInputMethod.tooltipController.direction = .horizontal
|
ctlInputMethod.tooltipInstance.show(
|
||||||
|
tooltip: tooltip, at: finalOrigin,
|
||||||
|
bottomOutOfScreenAdjustmentHeight: delta, direction: .horizontal
|
||||||
|
)
|
||||||
}
|
}
|
||||||
ctlInputMethod.tooltipController.show(tooltip: tooltip, at: finalOrigin)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func show(candidateWindowWith state: IMEStateProtocol) {
|
func show(candidateWindowWith state: IMEStateProtocol) {
|
||||||
|
|
|
@ -25,7 +25,7 @@ extension ctlInputMethod {
|
||||||
case .ofDeactivated:
|
case .ofDeactivated:
|
||||||
ctlInputMethod.ctlCandidateCurrent.delegate = nil
|
ctlInputMethod.ctlCandidateCurrent.delegate = nil
|
||||||
ctlInputMethod.ctlCandidateCurrent.visible = false
|
ctlInputMethod.ctlCandidateCurrent.visible = false
|
||||||
ctlInputMethod.tooltipController.hide()
|
ctlInputMethod.tooltipInstance.hide()
|
||||||
if previous.hasComposition {
|
if previous.hasComposition {
|
||||||
commit(text: previous.displayedText)
|
commit(text: previous.displayedText)
|
||||||
}
|
}
|
||||||
|
@ -39,20 +39,20 @@ extension ctlInputMethod {
|
||||||
previous = state
|
previous = state
|
||||||
}
|
}
|
||||||
ctlInputMethod.ctlCandidateCurrent.visible = false
|
ctlInputMethod.ctlCandidateCurrent.visible = false
|
||||||
ctlInputMethod.tooltipController.hide()
|
ctlInputMethod.tooltipInstance.hide()
|
||||||
// 全專案用以判斷「.Abortion」的地方僅此一處。
|
// 全專案用以判斷「.Abortion」的地方僅此一處。
|
||||||
if previous.hasComposition, state.type != .ofAbortion {
|
if previous.hasComposition, state.type != .ofAbortion {
|
||||||
commit(text: previous.displayedText)
|
commit(text: previous.displayedText)
|
||||||
}
|
}
|
||||||
// 在這裡手動再取消一次選字窗與工具提示的顯示,可謂雙重保險。
|
// 在這裡手動再取消一次選字窗與工具提示的顯示,可謂雙重保險。
|
||||||
ctlInputMethod.ctlCandidateCurrent.visible = false
|
ctlInputMethod.ctlCandidateCurrent.visible = false
|
||||||
ctlInputMethod.tooltipController.hide()
|
ctlInputMethod.tooltipInstance.hide()
|
||||||
clearInlineDisplay()
|
clearInlineDisplay()
|
||||||
// 最後一道保險
|
// 最後一道保險
|
||||||
keyHandler.clear()
|
keyHandler.clear()
|
||||||
case .ofCommitting:
|
case .ofCommitting:
|
||||||
ctlInputMethod.ctlCandidateCurrent.visible = false
|
ctlInputMethod.ctlCandidateCurrent.visible = false
|
||||||
ctlInputMethod.tooltipController.hide()
|
ctlInputMethod.tooltipInstance.hide()
|
||||||
let textToCommit = state.textToCommit
|
let textToCommit = state.textToCommit
|
||||||
if !textToCommit.isEmpty { commit(text: textToCommit) }
|
if !textToCommit.isEmpty { commit(text: textToCommit) }
|
||||||
clearInlineDisplay()
|
clearInlineDisplay()
|
||||||
|
@ -60,7 +60,7 @@ extension ctlInputMethod {
|
||||||
keyHandler.clear()
|
keyHandler.clear()
|
||||||
case .ofInputting:
|
case .ofInputting:
|
||||||
ctlInputMethod.ctlCandidateCurrent.visible = false
|
ctlInputMethod.ctlCandidateCurrent.visible = false
|
||||||
ctlInputMethod.tooltipController.hide()
|
ctlInputMethod.tooltipInstance.hide()
|
||||||
let textToCommit = state.textToCommit
|
let textToCommit = state.textToCommit
|
||||||
if !textToCommit.isEmpty { commit(text: textToCommit) }
|
if !textToCommit.isEmpty { commit(text: textToCommit) }
|
||||||
setInlineDisplayWithCursor()
|
setInlineDisplayWithCursor()
|
||||||
|
@ -74,7 +74,7 @@ extension ctlInputMethod {
|
||||||
ctlInputMethod.ctlCandidateCurrent.visible = false
|
ctlInputMethod.ctlCandidateCurrent.visible = false
|
||||||
setInlineDisplayWithCursor()
|
setInlineDisplayWithCursor()
|
||||||
if state.tooltip.isEmpty {
|
if state.tooltip.isEmpty {
|
||||||
ctlInputMethod.tooltipController.hide()
|
ctlInputMethod.tooltipInstance.hide()
|
||||||
} else {
|
} else {
|
||||||
let cursorReference: Int = {
|
let cursorReference: Int = {
|
||||||
if state.data.marker >= state.data.cursor { return state.data.u16Cursor }
|
if state.data.marker >= state.data.cursor { return state.data.u16Cursor }
|
||||||
|
@ -86,7 +86,7 @@ extension ctlInputMethod {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
case .ofCandidates, .ofAssociates, .ofSymbolTable:
|
case .ofCandidates, .ofAssociates, .ofSymbolTable:
|
||||||
ctlInputMethod.tooltipController.hide()
|
ctlInputMethod.tooltipInstance.hide()
|
||||||
setInlineDisplayWithCursor()
|
setInlineDisplayWithCursor()
|
||||||
show(candidateWindowWith: state)
|
show(candidateWindowWith: state)
|
||||||
default: break
|
default: break
|
||||||
|
|
|
@ -1,172 +0,0 @@
|
||||||
// (c) 2021 and onwards Weizhong Yang (MIT License).
|
|
||||||
// All possible vChewing-specific modifications are of:
|
|
||||||
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
|
||||||
// ====================
|
|
||||||
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
|
|
||||||
// ... with NTL restriction stating that:
|
|
||||||
// No trademark license is granted to use the trade names, trademarks, service
|
|
||||||
// marks, or product names of Contributor, except as required to fulfill notice
|
|
||||||
// requirements defined in MIT License.
|
|
||||||
|
|
||||||
import Cocoa
|
|
||||||
|
|
||||||
public class TooltipController: NSWindowController {
|
|
||||||
public enum ColorStates {
|
|
||||||
case normal
|
|
||||||
case redAlert
|
|
||||||
case warning
|
|
||||||
case denialOverflow
|
|
||||||
case denialInsufficiency
|
|
||||||
case prompt
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum displayDirection {
|
|
||||||
case horizontal
|
|
||||||
case vertical
|
|
||||||
}
|
|
||||||
|
|
||||||
private var backgroundColor = NSColor.windowBackgroundColor
|
|
||||||
private var textColor = NSColor.windowBackgroundColor
|
|
||||||
private var messageTextField: NSTextField
|
|
||||||
private var tooltip: String = "" {
|
|
||||||
didSet {
|
|
||||||
messageTextField.stringValue = tooltip
|
|
||||||
adjustSize()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public var direction: displayDirection = .horizontal
|
|
||||||
|
|
||||||
public init() {
|
|
||||||
let contentRect = NSRect(x: 128.0, y: 128.0, width: 300.0, height: 20.0)
|
|
||||||
let styleMask: NSWindow.StyleMask = [.borderless, .nonactivatingPanel]
|
|
||||||
let panel = NSPanel(
|
|
||||||
contentRect: contentRect, styleMask: styleMask, backing: .buffered, defer: false
|
|
||||||
)
|
|
||||||
panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel) + 1)
|
|
||||||
panel.hasShadow = true
|
|
||||||
|
|
||||||
messageTextField = NSTextField()
|
|
||||||
messageTextField.isEditable = false
|
|
||||||
messageTextField.isSelectable = false
|
|
||||||
messageTextField.isBezeled = false
|
|
||||||
messageTextField.textColor = textColor
|
|
||||||
messageTextField.drawsBackground = true
|
|
||||||
messageTextField.backgroundColor = backgroundColor
|
|
||||||
messageTextField.font = .systemFont(ofSize: NSFont.systemFontSize(for: .small))
|
|
||||||
panel.contentView?.addSubview(messageTextField)
|
|
||||||
super.init(window: panel)
|
|
||||||
}
|
|
||||||
|
|
||||||
@available(*, unavailable)
|
|
||||||
public required init?(coder _: NSCoder) {
|
|
||||||
fatalError("init(coder:) has not been implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
public func show(tooltip: String, at point: NSPoint) {
|
|
||||||
messageTextField.textColor = textColor
|
|
||||||
messageTextField.backgroundColor = backgroundColor
|
|
||||||
self.tooltip = tooltip
|
|
||||||
window?.orderFront(nil)
|
|
||||||
set(windowLocation: point)
|
|
||||||
}
|
|
||||||
|
|
||||||
public func setColor(state: ColorStates) {
|
|
||||||
switch state {
|
|
||||||
case .normal:
|
|
||||||
backgroundColor = NSColor(
|
|
||||||
red: 0.18, green: 0.18, blue: 0.18, alpha: 1.00
|
|
||||||
)
|
|
||||||
textColor = NSColor.white
|
|
||||||
case .redAlert:
|
|
||||||
backgroundColor = NSColor(
|
|
||||||
red: 0.55, green: 0.00, blue: 0.00, alpha: 1.00
|
|
||||||
)
|
|
||||||
textColor = NSColor.white
|
|
||||||
case .warning:
|
|
||||||
backgroundColor = NSColor.purple
|
|
||||||
textColor = NSColor.white
|
|
||||||
case .denialOverflow:
|
|
||||||
backgroundColor = NSColor(
|
|
||||||
red: 0.13, green: 0.08, blue: 0.00, alpha: 1.00
|
|
||||||
)
|
|
||||||
textColor = NSColor(
|
|
||||||
red: 1.00, green: 0.60, blue: 0.00, alpha: 1.00
|
|
||||||
)
|
|
||||||
case .denialInsufficiency:
|
|
||||||
backgroundColor = NSColor(
|
|
||||||
red: 0.18, green: 0.18, blue: 0.18, alpha: 1.00
|
|
||||||
)
|
|
||||||
textColor = NSColor(
|
|
||||||
red: 0.86, green: 0.86, blue: 0.86, alpha: 1.00
|
|
||||||
)
|
|
||||||
case .prompt:
|
|
||||||
backgroundColor = NSColor(
|
|
||||||
red: 0.00, green: 0.18, blue: 0.13, alpha: 1.00
|
|
||||||
)
|
|
||||||
textColor = NSColor(
|
|
||||||
red: 0.00, green: 1.00, blue: 0.74, alpha: 1.00
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func resetColor() {
|
|
||||||
setColor(state: .normal)
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc
|
|
||||||
public func hide() {
|
|
||||||
window?.orderOut(nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func set(windowLocation windowTopLeftPoint: NSPoint) {
|
|
||||||
var adjustedPoint = windowTopLeftPoint
|
|
||||||
adjustedPoint.y -= 5
|
|
||||||
|
|
||||||
var screenFrame = NSScreen.main?.visibleFrame ?? NSRect.zero
|
|
||||||
for screen in NSScreen.screens {
|
|
||||||
let frame = screen.visibleFrame
|
|
||||||
if windowTopLeftPoint.x >= frame.minX, windowTopLeftPoint.x <= frame.maxX,
|
|
||||||
windowTopLeftPoint.y >= frame.minY, windowTopLeftPoint.y <= frame.maxY
|
|
||||||
{
|
|
||||||
screenFrame = frame
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let windowSize = window?.frame.size ?? NSSize.zero
|
|
||||||
|
|
||||||
// bottom beneath the screen?
|
|
||||||
if adjustedPoint.y - windowSize.height < screenFrame.minY {
|
|
||||||
adjustedPoint.y = screenFrame.minY + windowSize.height
|
|
||||||
}
|
|
||||||
|
|
||||||
// top over the screen?
|
|
||||||
if adjustedPoint.y >= screenFrame.maxY {
|
|
||||||
adjustedPoint.y = screenFrame.maxY - 1.0
|
|
||||||
}
|
|
||||||
|
|
||||||
// right
|
|
||||||
if adjustedPoint.x + windowSize.width >= screenFrame.maxX {
|
|
||||||
adjustedPoint.x = screenFrame.maxX - windowSize.width
|
|
||||||
}
|
|
||||||
|
|
||||||
// left
|
|
||||||
if adjustedPoint.x < screenFrame.minX {
|
|
||||||
adjustedPoint.x = screenFrame.minX
|
|
||||||
}
|
|
||||||
|
|
||||||
window?.setFrameTopLeftPoint(adjustedPoint)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func adjustSize() {
|
|
||||||
let attrString = messageTextField.attributedStringValue
|
|
||||||
var rect = attrString.boundingRect(
|
|
||||||
with: NSSize(width: 1600.0, height: 1600.0), options: .usesLineFragmentOrigin
|
|
||||||
)
|
|
||||||
|
|
||||||
rect.size.width += 10
|
|
||||||
messageTextField.frame = rect
|
|
||||||
window?.setFrame(rect, display: true)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,176 @@
|
||||||
|
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||||
|
// ====================
|
||||||
|
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
|
||||||
|
// ... with NTL restriction stating that:
|
||||||
|
// No trademark license is granted to use the trade names, trademarks, service
|
||||||
|
// marks, or product names of Contributor, except as required to fulfill notice
|
||||||
|
// requirements defined in MIT License.
|
||||||
|
|
||||||
|
import Cocoa
|
||||||
|
|
||||||
|
public class ctlTooltip: NSWindowController {
|
||||||
|
public enum ColorStates {
|
||||||
|
case normal
|
||||||
|
case redAlert
|
||||||
|
case warning
|
||||||
|
case denialOverflow
|
||||||
|
case denialInsufficiency
|
||||||
|
case prompt
|
||||||
|
}
|
||||||
|
|
||||||
|
private var messageText: NSAttributedTextView
|
||||||
|
private var tooltip: String = "" {
|
||||||
|
didSet {
|
||||||
|
messageText.text = tooltip.isEmpty ? nil : tooltip
|
||||||
|
adjustSize()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public var direction: NSAttributedTextView.writingDirection = .horizontal {
|
||||||
|
didSet {
|
||||||
|
messageText.direction = direction
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public init() {
|
||||||
|
let contentRect = NSRect(x: 128.0, y: 128.0, width: 300.0, height: 20.0)
|
||||||
|
let styleMask: NSWindow.StyleMask = [.borderless, .nonactivatingPanel]
|
||||||
|
let panel = NSPanel(
|
||||||
|
contentRect: contentRect, styleMask: styleMask, backing: .buffered, defer: false
|
||||||
|
)
|
||||||
|
panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel) + 1)
|
||||||
|
panel.hasShadow = true
|
||||||
|
panel.backgroundColor = NSColor.controlBackgroundColor
|
||||||
|
messageText = NSAttributedTextView()
|
||||||
|
messageText.backgroundColor = NSColor.controlBackgroundColor
|
||||||
|
messageText.textColor = NSColor.textColor
|
||||||
|
panel.contentView?.addSubview(messageText)
|
||||||
|
super.init(window: panel)
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable)
|
||||||
|
public required init?(coder _: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
public func show(
|
||||||
|
tooltip: String = "", at point: NSPoint,
|
||||||
|
bottomOutOfScreenAdjustmentHeight heightDelta: CGFloat,
|
||||||
|
direction: NSAttributedTextView.writingDirection = .horizontal
|
||||||
|
) {
|
||||||
|
self.direction = direction
|
||||||
|
messageText.direction = self.direction
|
||||||
|
self.tooltip = tooltip
|
||||||
|
window?.orderFront(nil)
|
||||||
|
set(windowTopLeftPoint: point, bottomOutOfScreenAdjustmentHeight: heightDelta)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func setColor(state: ColorStates) {
|
||||||
|
var backgroundColor = NSColor.controlBackgroundColor
|
||||||
|
var textColor = NSColor.textColor
|
||||||
|
switch state {
|
||||||
|
case .normal:
|
||||||
|
backgroundColor = NSColor(
|
||||||
|
red: 0.18, green: 0.18, blue: 0.18, alpha: 1.00
|
||||||
|
)
|
||||||
|
textColor = NSColor.white
|
||||||
|
case .redAlert:
|
||||||
|
backgroundColor = NSColor(
|
||||||
|
red: 0.55, green: 0.00, blue: 0.00, alpha: 1.00
|
||||||
|
)
|
||||||
|
textColor = NSColor.white
|
||||||
|
case .warning:
|
||||||
|
backgroundColor = NSColor.purple
|
||||||
|
textColor = NSColor.white
|
||||||
|
case .denialOverflow:
|
||||||
|
backgroundColor = NSColor(
|
||||||
|
red: 0.13, green: 0.08, blue: 0.00, alpha: 1.00
|
||||||
|
)
|
||||||
|
textColor = NSColor(
|
||||||
|
red: 1.00, green: 0.60, blue: 0.00, alpha: 1.00
|
||||||
|
)
|
||||||
|
case .denialInsufficiency:
|
||||||
|
backgroundColor = NSColor.windowBackgroundColor
|
||||||
|
textColor = NSColor.labelColor
|
||||||
|
case .prompt:
|
||||||
|
backgroundColor = NSColor(
|
||||||
|
red: 0.09, green: 0.15, blue: 0.15, alpha: 1.00
|
||||||
|
)
|
||||||
|
textColor = NSColor(
|
||||||
|
red: 0.91, green: 0.95, blue: 0.92, alpha: 1.00
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if !IME.isDarkMode {
|
||||||
|
switch state {
|
||||||
|
case .denialInsufficiency: break
|
||||||
|
default:
|
||||||
|
let colorInterchange = backgroundColor
|
||||||
|
backgroundColor = textColor
|
||||||
|
textColor = colorInterchange
|
||||||
|
}
|
||||||
|
}
|
||||||
|
window?.backgroundColor = backgroundColor
|
||||||
|
messageText.backgroundColor = backgroundColor
|
||||||
|
messageText.textColor = textColor
|
||||||
|
}
|
||||||
|
|
||||||
|
public func resetColor() {
|
||||||
|
setColor(state: .normal)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func hide() {
|
||||||
|
window?.orderOut(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func set(windowTopLeftPoint: NSPoint, bottomOutOfScreenAdjustmentHeight heightDelta: CGFloat) {
|
||||||
|
guard let window = window else { return }
|
||||||
|
let windowSize = window.frame.size
|
||||||
|
|
||||||
|
var adjustedPoint = windowTopLeftPoint
|
||||||
|
var delta = heightDelta
|
||||||
|
var screenFrame = NSScreen.main?.visibleFrame ?? NSRect.zero
|
||||||
|
for frame in NSScreen.screens.map(\.visibleFrame).filter({ !$0.contains(windowTopLeftPoint) }) {
|
||||||
|
screenFrame = frame
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if delta > screenFrame.size.height / 2.0 { delta = 0.0 }
|
||||||
|
|
||||||
|
if adjustedPoint.y < screenFrame.minY + windowSize.height {
|
||||||
|
adjustedPoint.y = windowTopLeftPoint.y + windowSize.height + delta
|
||||||
|
}
|
||||||
|
adjustedPoint.y = min(adjustedPoint.y, screenFrame.maxY - 1.0)
|
||||||
|
adjustedPoint.x = min(max(adjustedPoint.x, screenFrame.minX), screenFrame.maxX - windowSize.width - 1.0)
|
||||||
|
|
||||||
|
window.setFrameTopLeftPoint(adjustedPoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func adjustSize() {
|
||||||
|
let attrString = messageText.attributedStringValue
|
||||||
|
var rect = attrString.boundingRect(
|
||||||
|
with: NSSize(width: 1600.0, height: 1600.0),
|
||||||
|
options: [.usesLineFragmentOrigin, .usesFontLeading, .usesDeviceMetrics]
|
||||||
|
)
|
||||||
|
if direction != .horizontal {
|
||||||
|
rect = .init(x: rect.minX, y: rect.minY, width: rect.height, height: rect.width)
|
||||||
|
rect.size.height += NSFont.systemFontSize
|
||||||
|
rect.size.width *= 1.03
|
||||||
|
rect.size.width = max(rect.size.width, NSFont.systemFontSize * 1.05)
|
||||||
|
rect.size.width = ceil(rect.size.width)
|
||||||
|
} else {
|
||||||
|
rect = .init(x: rect.minX, y: rect.minY, width: rect.width, height: rect.height)
|
||||||
|
rect.size.width += NSFont.systemFontSize
|
||||||
|
rect.size.height *= 1.03
|
||||||
|
rect.size.height = max(rect.size.height, NSFont.systemFontSize * 1.05)
|
||||||
|
rect.size.height = ceil(rect.size.height)
|
||||||
|
}
|
||||||
|
var bigRect = rect
|
||||||
|
bigRect.size.width += NSFont.systemFontSize
|
||||||
|
bigRect.size.height += NSFont.systemFontSize
|
||||||
|
rect.origin.x += NSFont.systemFontSize / 2
|
||||||
|
rect.origin.y += NSFont.systemFontSize / 2
|
||||||
|
messageText.frame = rect
|
||||||
|
window?.setFrame(bigRect, display: true)
|
||||||
|
messageText.draw(messageText.frame)
|
||||||
|
}
|
||||||
|
}
|
|
@ -33,11 +33,11 @@
|
||||||
5B5948CE289CC04500C85824 /* LMInstantiator_DateTimeExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B5948CD289CC04500C85824 /* LMInstantiator_DateTimeExtension.swift */; };
|
5B5948CE289CC04500C85824 /* LMInstantiator_DateTimeExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B5948CD289CC04500C85824 /* LMInstantiator_DateTimeExtension.swift */; };
|
||||||
5B5E535227EF261400C6AA1E /* IME.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B5E535127EF261400C6AA1E /* IME.swift */; };
|
5B5E535227EF261400C6AA1E /* IME.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B5E535127EF261400C6AA1E /* IME.swift */; };
|
||||||
5B5F8AEB28C86AAD007C11F1 /* NSAttributedTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B5F8AEA28C86AAD007C11F1 /* NSAttributedTextView.swift */; };
|
5B5F8AEB28C86AAD007C11F1 /* NSAttributedTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B5F8AEA28C86AAD007C11F1 /* NSAttributedTextView.swift */; };
|
||||||
|
5B5F8AEE28C8826D007C11F1 /* ctlTooltip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B5F8AED28C8826D007C11F1 /* ctlTooltip.swift */; };
|
||||||
5B62A32927AE77D100A19448 /* FSEventStreamHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B62A32827AE77D100A19448 /* FSEventStreamHelper.swift */; };
|
5B62A32927AE77D100A19448 /* FSEventStreamHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B62A32827AE77D100A19448 /* FSEventStreamHelper.swift */; };
|
||||||
5B62A33627AE795800A19448 /* mgrPrefs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B62A33527AE795800A19448 /* mgrPrefs.swift */; };
|
5B62A33627AE795800A19448 /* mgrPrefs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B62A33527AE795800A19448 /* mgrPrefs.swift */; };
|
||||||
5B62A33D27AE7CC100A19448 /* ctlAboutWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B62A33C27AE7CC100A19448 /* ctlAboutWindow.swift */; };
|
5B62A33D27AE7CC100A19448 /* ctlAboutWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B62A33C27AE7CC100A19448 /* ctlAboutWindow.swift */; };
|
||||||
5B62A34727AE7CD900A19448 /* ctlCandidate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B62A34027AE7CD900A19448 /* ctlCandidate.swift */; };
|
5B62A34727AE7CD900A19448 /* ctlCandidate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B62A34027AE7CD900A19448 /* ctlCandidate.swift */; };
|
||||||
5B62A34927AE7CD900A19448 /* TooltipController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B62A34327AE7CD900A19448 /* TooltipController.swift */; };
|
|
||||||
5B62A34A27AE7CD900A19448 /* NotifierController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B62A34527AE7CD900A19448 /* NotifierController.swift */; };
|
5B62A34A27AE7CD900A19448 /* NotifierController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B62A34527AE7CD900A19448 /* NotifierController.swift */; };
|
||||||
5B6C141228A9D4B30098ADF8 /* ctlInputMethod_Common.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6C141128A9D4B30098ADF8 /* ctlInputMethod_Common.swift */; };
|
5B6C141228A9D4B30098ADF8 /* ctlInputMethod_Common.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6C141128A9D4B30098ADF8 /* ctlInputMethod_Common.swift */; };
|
||||||
5B73FB5E27B2BE1300E9BF49 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5B73FB6027B2BE1300E9BF49 /* InfoPlist.strings */; };
|
5B73FB5E27B2BE1300E9BF49 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5B73FB6027B2BE1300E9BF49 /* InfoPlist.strings */; };
|
||||||
|
@ -248,11 +248,11 @@
|
||||||
5B5948CD289CC04500C85824 /* LMInstantiator_DateTimeExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LMInstantiator_DateTimeExtension.swift; sourceTree = "<group>"; };
|
5B5948CD289CC04500C85824 /* LMInstantiator_DateTimeExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LMInstantiator_DateTimeExtension.swift; sourceTree = "<group>"; };
|
||||||
5B5E535127EF261400C6AA1E /* IME.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = IME.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
|
5B5E535127EF261400C6AA1E /* IME.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = IME.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
|
||||||
5B5F8AEA28C86AAD007C11F1 /* NSAttributedTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSAttributedTextView.swift; sourceTree = "<group>"; };
|
5B5F8AEA28C86AAD007C11F1 /* NSAttributedTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSAttributedTextView.swift; sourceTree = "<group>"; };
|
||||||
|
5B5F8AED28C8826D007C11F1 /* ctlTooltip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ctlTooltip.swift; sourceTree = "<group>"; };
|
||||||
5B62A32827AE77D100A19448 /* FSEventStreamHelper.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = FSEventStreamHelper.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
|
5B62A32827AE77D100A19448 /* FSEventStreamHelper.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = FSEventStreamHelper.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
|
||||||
5B62A33527AE795800A19448 /* mgrPrefs.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = mgrPrefs.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
|
5B62A33527AE795800A19448 /* mgrPrefs.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = mgrPrefs.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
|
||||||
5B62A33C27AE7CC100A19448 /* ctlAboutWindow.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = ctlAboutWindow.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
|
5B62A33C27AE7CC100A19448 /* ctlAboutWindow.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = ctlAboutWindow.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
|
||||||
5B62A34027AE7CD900A19448 /* ctlCandidate.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = ctlCandidate.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
|
5B62A34027AE7CD900A19448 /* ctlCandidate.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = ctlCandidate.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
|
||||||
5B62A34327AE7CD900A19448 /* TooltipController.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = TooltipController.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
|
|
||||||
5B62A34527AE7CD900A19448 /* NotifierController.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = NotifierController.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
|
5B62A34527AE7CD900A19448 /* NotifierController.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = NotifierController.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
|
||||||
5B65B919284D0185007C558B /* README-CHT.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "README-CHT.md"; sourceTree = "<group>"; };
|
5B65B919284D0185007C558B /* README-CHT.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "README-CHT.md"; sourceTree = "<group>"; };
|
||||||
5B6C141128A9D4B30098ADF8 /* ctlInputMethod_Common.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ctlInputMethod_Common.swift; sourceTree = "<group>"; };
|
5B6C141128A9D4B30098ADF8 /* ctlInputMethod_Common.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ctlInputMethod_Common.swift; sourceTree = "<group>"; };
|
||||||
|
@ -621,7 +621,7 @@
|
||||||
5B62A34227AE7CD900A19448 /* TooltipUI */ = {
|
5B62A34227AE7CD900A19448 /* TooltipUI */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
5B62A34327AE7CD900A19448 /* TooltipController.swift */,
|
5B5F8AED28C8826D007C11F1 /* ctlTooltip.swift */,
|
||||||
);
|
);
|
||||||
path = TooltipUI;
|
path = TooltipUI;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -1214,6 +1214,7 @@
|
||||||
D47B92C027972AD100458394 /* main.swift in Sources */,
|
D47B92C027972AD100458394 /* main.swift in Sources */,
|
||||||
D4A13D5A27A59F0B003BE359 /* ctlInputMethod_Core.swift in Sources */,
|
D4A13D5A27A59F0B003BE359 /* ctlInputMethod_Core.swift in Sources */,
|
||||||
5BA9FD4827FEF3C9002DE248 /* PreferencesWindowController.swift in Sources */,
|
5BA9FD4827FEF3C9002DE248 /* PreferencesWindowController.swift in Sources */,
|
||||||
|
5B5F8AEE28C8826D007C11F1 /* ctlTooltip.swift in Sources */,
|
||||||
5BD0113B28180D6100609769 /* LMInstantiator.swift in Sources */,
|
5BD0113B28180D6100609769 /* LMInstantiator.swift in Sources */,
|
||||||
5B2170E7289FACAD00BE7304 /* 1_Compositor.swift in Sources */,
|
5B2170E7289FACAD00BE7304 /* 1_Compositor.swift in Sources */,
|
||||||
5B21177028753B9D000443A9 /* ctlInputMethod_Delegates.swift in Sources */,
|
5B21177028753B9D000443A9 /* ctlInputMethod_Delegates.swift in Sources */,
|
||||||
|
@ -1263,7 +1264,6 @@
|
||||||
5B40730D281672610023DFFF /* lmReplacements.swift in Sources */,
|
5B40730D281672610023DFFF /* lmReplacements.swift in Sources */,
|
||||||
5B5E535227EF261400C6AA1E /* IME.swift in Sources */,
|
5B5E535227EF261400C6AA1E /* IME.swift in Sources */,
|
||||||
5B2170E0289FACAD00BE7304 /* 7_LangModel.swift in Sources */,
|
5B2170E0289FACAD00BE7304 /* 7_LangModel.swift in Sources */,
|
||||||
5B62A34927AE7CD900A19448 /* TooltipController.swift in Sources */,
|
|
||||||
5BA9FD4027FEF3C8002DE248 /* Localization.swift in Sources */,
|
5BA9FD4027FEF3C8002DE248 /* Localization.swift in Sources */,
|
||||||
5BF0B84C28C070B000795FC6 /* NSEventExtension.swift in Sources */,
|
5BF0B84C28C070B000795FC6 /* NSEventExtension.swift in Sources */,
|
||||||
5BAA8FBE282CAF380066C406 /* SyllableComposer.swift in Sources */,
|
5BAA8FBE282CAF380066C406 /* SyllableComposer.swift in Sources */,
|
||||||
|
|
Loading…
Reference in New Issue