Zonble: Notifier UI like Yahoo Kimo IME.
- This won't get suppressed by macOS "do-not-disturb" mode. - Beautified by ShikiSuen, inheriting some message format changes from Hiraku's PR1. Co-Authored-By: Hiraku <hirakujira@users.noreply.github.com>
This commit is contained in:
parent
ea1f6d9a12
commit
9944d4ce9b
|
@ -1695,6 +1695,8 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
{
|
||||
_chineseConversionEnabled = !_chineseConversionEnabled;
|
||||
[[NSUserDefaults standardUserDefaults] setBool:_chineseConversionEnabled forKey:kChineseConversionEnabledKey];
|
||||
|
||||
[NotifierController notifyWithMessage:[NSString stringWithFormat:@"%@%@%@", NSLocalizedString(@"Chinese Conversion", @""), @"\n", _chineseConversionEnabled ? NSLocalizedString(@"NotificationSwitchON", @"") : NSLocalizedString(@"NotificationSwitchOFF", @"")] stay:NO];
|
||||
}
|
||||
|
||||
- (void)toggleHalfWidthPunctuation:(id)sender
|
||||
|
|
|
@ -0,0 +1,215 @@
|
|||
//
|
||||
// NotifierControler.swift
|
||||
//
|
||||
// Copyright (c) 2021-2022 The vChewing Project.
|
||||
// Copyright (c) 2011-2022 The OpenVanilla Project.
|
||||
//
|
||||
// Contributors:
|
||||
// Weizhong Yang (@zonble) @ OpenVanilla
|
||||
// Shiki Suen (@ShikiSuen) @ vChewing // Color tweaks only
|
||||
//
|
||||
// Based on the Syrup Project and the Formosana Library
|
||||
// by Lukhnos Liu (@lukhnos).
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person
|
||||
// obtaining a copy of this software and associated documentation
|
||||
// files (the "Software"), to deal in the Software without
|
||||
// restriction, including without limitation the rights to use,
|
||||
// copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the
|
||||
// Software is furnished to do so, subject to the following
|
||||
// conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
// OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
|
||||
private protocol NotifierWindowDelegate: AnyObject {
|
||||
func windowDidBecomeClicked(_ window: NotifierWindow)
|
||||
}
|
||||
|
||||
private class NotifierWindow: NSWindow {
|
||||
weak var clickDelegate: NotifierWindowDelegate?
|
||||
|
||||
override func mouseDown(with event: NSEvent) {
|
||||
clickDelegate?.windowDidBecomeClicked(self)
|
||||
}
|
||||
}
|
||||
|
||||
private let kWindowWidth: CGFloat = 213.0
|
||||
private let kWindowHeight: CGFloat = 60.0
|
||||
|
||||
public class NotifierController: NSWindowController, NotifierWindowDelegate {
|
||||
private var messageTextField: NSTextField
|
||||
|
||||
private var message: String = "" {
|
||||
didSet {
|
||||
let paraStyle = NSMutableParagraphStyle()
|
||||
paraStyle.setParagraphStyle(NSParagraphStyle.default)
|
||||
paraStyle.alignment = .center
|
||||
let attr: [NSAttributedString.Key: AnyObject] = [
|
||||
.foregroundColor: foregroundColor,
|
||||
.font: NSFont.boldSystemFont(ofSize: NSFont.systemFontSize(for: .regular)),
|
||||
.paragraphStyle: paraStyle
|
||||
]
|
||||
let attrString = NSAttributedString(string: message, attributes: attr)
|
||||
messageTextField.attributedStringValue = attrString
|
||||
let width = window?.frame.width ?? kWindowWidth
|
||||
let rect = attrString.boundingRect(with: NSSize(width: width, height: 1600), options: .usesLineFragmentOrigin)
|
||||
let height = rect.height
|
||||
let x = messageTextField.frame.origin.x
|
||||
let y = ((window?.frame.height ?? kWindowHeight) - height) / 2
|
||||
let newFrame = NSRect(x: x, y: y, width: width, height: height)
|
||||
messageTextField.frame = newFrame
|
||||
}
|
||||
}
|
||||
private var shouldStay: Bool = false
|
||||
private var backgroundColor: NSColor = .textBackgroundColor {
|
||||
didSet {
|
||||
self.window?.backgroundColor = backgroundColor
|
||||
}
|
||||
}
|
||||
private var foregroundColor: NSColor = .controlTextColor {
|
||||
didSet {
|
||||
self.messageTextField.textColor = foregroundColor
|
||||
}
|
||||
}
|
||||
private var waitTimer: Timer?
|
||||
private var fadeTimer: Timer?
|
||||
|
||||
private static var instanceCount = 0
|
||||
private static var lastLocation = NSPoint.zero
|
||||
|
||||
@objc public static func notify(message: String, stay: Bool = false) {
|
||||
let controller = NotifierController()
|
||||
controller.message = message
|
||||
controller.shouldStay = stay
|
||||
controller.show()
|
||||
}
|
||||
|
||||
private static func increaseInstanceCount() {
|
||||
instanceCount += 1
|
||||
}
|
||||
|
||||
private static func decreaseInstanceCount() {
|
||||
instanceCount -= 1
|
||||
if instanceCount < 0 {
|
||||
instanceCount = 0
|
||||
}
|
||||
}
|
||||
|
||||
private init() {
|
||||
let screenRect = NSScreen.main?.visibleFrame ?? NSRect.zero
|
||||
let contentRect = NSRect(x: 0, y: 0, width: kWindowWidth, height: kWindowHeight)
|
||||
var windowRect = contentRect
|
||||
windowRect.origin.x = screenRect.maxX - windowRect.width - 10
|
||||
windowRect.origin.y = screenRect.maxY - windowRect.height - 10
|
||||
let styleMask: NSWindow.StyleMask = [.fullSizeContentView, .titled]
|
||||
|
||||
|
||||
|
||||
let panel = NotifierWindow(contentRect: windowRect, styleMask: styleMask, backing: .buffered, defer: false)
|
||||
panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel))
|
||||
panel.hasShadow = true
|
||||
panel.backgroundColor = backgroundColor
|
||||
panel.title = ""
|
||||
panel.titlebarAppearsTransparent = true
|
||||
panel.titleVisibility = .hidden
|
||||
panel.showsToolbarButton = false
|
||||
panel.standardWindowButton(NSWindow.ButtonType.fullScreenButton)?.isHidden = true
|
||||
panel.standardWindowButton(NSWindow.ButtonType.miniaturizeButton)?.isHidden = true
|
||||
panel.standardWindowButton(NSWindow.ButtonType.closeButton)?.isHidden = true
|
||||
panel.standardWindowButton(NSWindow.ButtonType.zoomButton)?.isHidden = true
|
||||
|
||||
messageTextField = NSTextField()
|
||||
messageTextField.frame = contentRect
|
||||
messageTextField.isEditable = false
|
||||
messageTextField.isSelectable = false
|
||||
messageTextField.isBezeled = false
|
||||
messageTextField.textColor = foregroundColor
|
||||
messageTextField.drawsBackground = false
|
||||
messageTextField.font = .boldSystemFont(ofSize: NSFont.systemFontSize(for: .regular))
|
||||
panel.contentView?.addSubview(messageTextField)
|
||||
|
||||
super.init(window: panel)
|
||||
|
||||
panel.clickDelegate = self
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
private func show() {
|
||||
func setStartLocation() {
|
||||
if NotifierController.instanceCount == 0 {
|
||||
return
|
||||
}
|
||||
let lastLocation = NotifierController.lastLocation
|
||||
let screenRect = NSScreen.main?.visibleFrame ?? NSRect.zero
|
||||
var windowRect = self.window?.frame ?? NSRect.zero
|
||||
windowRect.origin.x = lastLocation.x
|
||||
windowRect.origin.y = lastLocation.y - 10 - windowRect.height
|
||||
|
||||
if windowRect.origin.y < screenRect.minY {
|
||||
return
|
||||
}
|
||||
|
||||
self.window?.setFrame(windowRect, display: true)
|
||||
}
|
||||
|
||||
func moveIn() {
|
||||
let afterRect = self.window?.frame ?? NSRect.zero
|
||||
NotifierController.lastLocation = afterRect.origin
|
||||
var beforeRect = afterRect
|
||||
beforeRect.origin.y += 10
|
||||
window?.setFrame(beforeRect, display: true)
|
||||
window?.orderFront(self)
|
||||
window?.setFrame(afterRect, display: true, animate: true)
|
||||
}
|
||||
|
||||
setStartLocation()
|
||||
moveIn()
|
||||
NotifierController.increaseInstanceCount()
|
||||
waitTimer = Timer.scheduledTimer(timeInterval: shouldStay ? 5 : 1, target: self, selector: #selector(fadeOut), userInfo: nil, repeats: false)
|
||||
}
|
||||
|
||||
@objc private func doFadeOut(_ timer: Timer) {
|
||||
let opacity = self.window?.alphaValue ?? 0
|
||||
if opacity <= 0 {
|
||||
self.close()
|
||||
} else {
|
||||
self.window?.alphaValue = opacity - 0.2
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func fadeOut() {
|
||||
waitTimer?.invalidate()
|
||||
waitTimer = nil
|
||||
NotifierController.decreaseInstanceCount()
|
||||
fadeTimer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(doFadeOut(_:)), userInfo: nil, repeats: true)
|
||||
}
|
||||
|
||||
public override func close() {
|
||||
waitTimer?.invalidate()
|
||||
waitTimer = nil
|
||||
fadeTimer?.invalidate()
|
||||
fadeTimer = nil
|
||||
super.close()
|
||||
}
|
||||
|
||||
fileprivate func windowDidBecomeClicked(_ window: NotifierWindow) {
|
||||
self.fadeOut()
|
||||
}
|
||||
}
|
|
@ -25,6 +25,7 @@
|
|||
5BDF2D032791C71200838ADB /* NonModalAlertWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BDF2D022791C71200838ADB /* NonModalAlertWindowController.swift */; };
|
||||
5BDF2D062791DFF200838ADB /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BDF2D052791DA6700838ADB /* AppDelegate.swift */; };
|
||||
5BE798A42792E58A00337FF9 /* TooltipController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BE798A32792E58A00337FF9 /* TooltipController.swift */; };
|
||||
5BE798A72793280C00337FF9 /* NotifierController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BE798A62793280C00337FF9 /* NotifierController.swift */; };
|
||||
5BF4A6FE27844738007DC6E7 /* frmAboutWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BF4A6FC27844738007DC6E7 /* frmAboutWindow.m */; };
|
||||
5BF4A70027844DC5007DC6E7 /* frmAboutWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5BF4A70227844DC5007DC6E7 /* frmAboutWindow.xib */; };
|
||||
6A0421A815FEF3F50061ED63 /* FastLM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6A0421A615FEF3F50061ED63 /* FastLM.cpp */; };
|
||||
|
@ -108,6 +109,7 @@
|
|||
5BDF2D022791C71200838ADB /* NonModalAlertWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonModalAlertWindowController.swift; sourceTree = "<group>"; };
|
||||
5BDF2D052791DA6700838ADB /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
5BE798A32792E58A00337FF9 /* TooltipController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TooltipController.swift; sourceTree = "<group>"; };
|
||||
5BE798A62793280C00337FF9 /* NotifierController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotifierController.swift; sourceTree = "<group>"; };
|
||||
5BF4A6FB27844738007DC6E7 /* frmAboutWindow.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = frmAboutWindow.h; sourceTree = "<group>"; };
|
||||
5BF4A6FC27844738007DC6E7 /* frmAboutWindow.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = frmAboutWindow.m; sourceTree = "<group>"; };
|
||||
5BF4A70327844DD0007DC6E7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/frmAboutWindow.xib; sourceTree = "<group>"; };
|
||||
|
@ -258,6 +260,7 @@
|
|||
5BE798A12792E50F00337FF9 /* UI */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5BE798A52793280C00337FF9 /* NotifierUI */,
|
||||
5BE798A22792E51F00337FF9 /* TooltipUI */,
|
||||
6A0D4ED815FC0DA600ABF4B3 /* CandidateUI */,
|
||||
);
|
||||
|
@ -272,6 +275,14 @@
|
|||
path = TooltipUI;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
5BE798A52793280C00337FF9 /* NotifierUI */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5BE798A62793280C00337FF9 /* NotifierController.swift */,
|
||||
);
|
||||
path = NotifierUI;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
6A0D4E9215FC0CFA00ABF4B3 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -660,6 +671,7 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5BDF2CFE2791BE4400838ADB /* InputSourceHelper.swift in Sources */,
|
||||
5BE798A72793280C00337FF9 /* NotifierController.swift in Sources */,
|
||||
5B5F4F93279294A300922DC2 /* LanguageModelManager.mm in Sources */,
|
||||
6A0D4ED215FC0D6400ABF4B3 /* InputMethodController.mm in Sources */,
|
||||
6A0D4ED315FC0D6400ABF4B3 /* main.m in Sources */,
|
||||
|
|
Loading…
Reference in New Issue