Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 候補WindowのNSWindowによる独自実装 #35

Merged
merged 35 commits into from
Aug 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
9804a07
CandidateViewを追加
nyanko3141592 Aug 3, 2024
bb6d4cc
resize実装
nyanko3141592 Aug 3, 2024
1347b18
最初に表示するpatch
nyanko3141592 Aug 3, 2024
30ec4cd
表示位地のpatch
nyanko3141592 Aug 3, 2024
ea8016c
ミスを修正
nyanko3141592 Aug 4, 2024
1eba187
スペースキーで選択を移動
nyanko3141592 Aug 4, 2024
1be6ef9
Enterキーで変換を確定できるように修正
nyanko3141592 Aug 4, 2024
84f0bd1
hide.patchを適用
nyanko3141592 Aug 4, 2024
669ae84
矢印のpatch
nyanko3141592 Aug 4, 2024
9e1157c
fix.patch適用
nyanko3141592 Aug 4, 2024
3da0dd2
additional patchをpush
nyanko3141592 Aug 4, 2024
8734987
文字サイズの変更
nyanko3141592 Aug 4, 2024
e03d57b
ヘッダー非表示
nyanko3141592 Aug 4, 2024
1c685a0
font size 変更
nyanko3141592 Aug 4, 2024
d00d92a
diviverを表示
nyanko3141592 Aug 4, 2024
ab61dfb
composing textを非表示
nyanko3141592 Aug 4, 2024
05f46e4
角丸の実装
nyanko3141592 Aug 4, 2024
32b4ff1
マウス無効化
nyanko3141592 Aug 4, 2024
ad70b19
numberを登録
nyanko3141592 Aug 4, 2024
338dae6
数字キーで変換を確定する
nyanko3141592 Aug 4, 2024
68e103c
現在の選択行を1番として数字を表示するように変更
nyanko3141592 Aug 4, 2024
82c8623
closeButtonが表示されるバグを修正
nyanko3141592 Aug 5, 2024
003d4ec
表示位置を上下で場合分け
nyanko3141592 Aug 5, 2024
5a71181
変数としてcursorHeightを定義
nyanko3141592 Aug 5, 2024
e649ff3
右にはみ出さない処理の追加
nyanko3141592 Aug 5, 2024
9e772fc
画面幅の最適化
nyanko3141592 Aug 5, 2024
20667e3
表示範囲の修整
nyanko3141592 Aug 5, 2024
1893f13
Windowの高さ調整
nyanko3141592 Aug 5, 2024
6f819af
チームの変更をミスってpushしたので修正
nyanko3141592 Aug 5, 2024
78624b1
swiftlint
nyanko3141592 Aug 5, 2024
90d2e6f
ClientActionの整理
nyanko3141592 Aug 5, 2024
0cca2ca
表示のチラつきのバグを修正
nyanko3141592 Aug 5, 2024
9f75113
不要な関数を削除
nyanko3141592 Aug 5, 2024
e8d9bcb
不要なIMKCandidateの削除
nyanko3141592 Aug 5, 2024
54f3d20
selectingで入力が.zeroの場合を修正
nyanko3141592 Aug 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions azooKeyMac.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
55EA62E22BD6BF900056B5BA /* ConfigWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55EA62E12BD6BF900056B5BA /* ConfigWindow.swift */; };
E916C20B2BF5F81600548B7A /* azooKeyMacInputControllerHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = E916C20A2BF5F81600548B7A /* azooKeyMacInputControllerHelper.swift */; };
E96A5BC72BF4B28400AEAB72 /* InputState.swift in Sources */ = {isa = PBXBuildFile; fileRef = E96A5BC62BF4B28400AEAB72 /* InputState.swift */; };
E97406CE2C5E546200696C66 /* CandidateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E97406CD2C5E546200696C66 /* CandidateView.swift */; };
E989B4562BF4B1130085AF6D /* UserAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = E989B4552BF4B1130085AF6D /* UserAction.swift */; };
E989B4582BF4B1550085AF6D /* ClientAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = E989B4572BF4B1550085AF6D /* ClientAction.swift */; };
E9C0C6F32BF5E8AE00E0EA50 /* InputMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9C0C6F22BF5E8AE00E0EA50 /* InputMode.swift */; };
Expand Down Expand Up @@ -85,6 +86,7 @@
55EA62E12BD6BF900056B5BA /* ConfigWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigWindow.swift; sourceTree = "<group>"; };
E916C20A2BF5F81600548B7A /* azooKeyMacInputControllerHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = azooKeyMacInputControllerHelper.swift; sourceTree = "<group>"; };
E96A5BC62BF4B28400AEAB72 /* InputState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputState.swift; sourceTree = "<group>"; };
E97406CD2C5E546200696C66 /* CandidateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CandidateView.swift; sourceTree = "<group>"; };
E989B4552BF4B1130085AF6D /* UserAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAction.swift; sourceTree = "<group>"; };
E989B4572BF4B1550085AF6D /* ClientAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientAction.swift; sourceTree = "<group>"; };
E9C0C6F22BF5E8AE00E0EA50 /* InputMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputMode.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -238,6 +240,7 @@
E96A5BC62BF4B28400AEAB72 /* InputState.swift */,
E9C0C6F22BF5E8AE00E0EA50 /* InputMode.swift */,
E916C20A2BF5F81600548B7A /* azooKeyMacInputControllerHelper.swift */,
E97406CD2C5E546200696C66 /* CandidateView.swift */,
);
path = InputController;
sourceTree = "<group>";
Expand Down Expand Up @@ -395,6 +398,7 @@
buildActionMask = 2147483647;
files = (
551398D92BDD38B700F1DB82 /* BoolConfigItem.swift in Sources */,
E97406CE2C5E546200696C66 /* CandidateView.swift in Sources */,
E916C20B2BF5F81600548B7A /* azooKeyMacInputControllerHelper.swift in Sources */,
557D35E12BB1C22100877564 /* KeyMap.swift in Sources */,
E96A5BC72BF4B28400AEAB72 /* InputState.swift in Sources */,
Expand Down
2 changes: 0 additions & 2 deletions azooKeyMac/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ class NSManualApplication: NSApplication {
@main
class AppDelegate: NSObject, NSApplicationDelegate {
var server = IMKServer()
var candidatesWindow = IMKCandidates()
weak var configWindow: NSWindow?
var configWindowController: NSWindowController?
@MainActor var kanaKanjiConverter = KanaKanjiConverter()
Expand Down Expand Up @@ -62,7 +61,6 @@ class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ notification: Notification) {
// Insert code here to initialize your application
self.server = IMKServer(name: Bundle.main.infoDictionary?["InputMethodConnectionName"] as? String, bundleIdentifier: Bundle.main.bundleIdentifier)
self.candidatesWindow = IMKCandidates(server: server, panelType: kIMKSingleColumnScrollingCandidatePanel, styleType: kIMKMain)
NSLog("tried connection")
}

Expand Down
7 changes: 5 additions & 2 deletions azooKeyMac/InputController/Actions/ClientAction.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

import InputMethodKit

indirect enum ClientAction {
Expand All @@ -12,8 +11,12 @@ indirect enum ClientAction {
case moveCursor(Int)

case commitMarkedText

case submitSelectedCandidate
case forwardToCandidateWindow(NSEvent)
case selectNextCandidate
case selectPrevCandidate
case selectNumberCandidate(Int)

case selectInputMode(InputMode)

case stopComposition
Expand Down
6 changes: 5 additions & 1 deletion azooKeyMac/InputController/Actions/UserAction.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

enum UserAction {
case input(String)
case backspace
Expand All @@ -9,8 +8,13 @@ enum UserAction {
case 英数
case かな
case navigation(NavigationDirection)
case number(Number)

enum NavigationDirection {
case up, down, right, left
}

enum Number {
case one, two, three, four, five, six, seven, eight, nine, zero
}
}
263 changes: 263 additions & 0 deletions azooKeyMac/InputController/CandidateView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
//
// CandidateView.swift
// azooKeyMac
//
// Created by 高橋直希 on 2024/08/03.
//

import Cocoa

class CandidatesViewController: NSViewController {
private var candidates: [String] = []
private var tableView: NSTableView!
weak var delegate: (any CandidatesViewControllerDelegate)?
private var currentSelectedRow: Int = -1

override func loadView() {
let scrollView = NSScrollView()
self.tableView = NonClickableTableView()
scrollView.documentView = self.tableView
scrollView.hasVerticalScroller = true

// グリッドスタイルを設定してセル間に水平線を表示
self.tableView.gridStyleMask = .solidHorizontalGridLineMask

let stackView = NSStackView(views: [scrollView])
stackView.orientation = .vertical
stackView.spacing = 10
self.view = stackView

let column = NSTableColumn(identifier: NSUserInterfaceItemIdentifier("CandidatesColumn"))
self.tableView.headerView = nil
self.tableView.addTableColumn(column)
self.tableView.delegate = self
self.tableView.dataSource = self
}

override func viewDidLoad() {
super.viewDidLoad()

// 角丸のためのウィンドウ設定
configureWindowForRoundedCorners()
}

private func configureWindowForRoundedCorners() {
guard let window = self.view.window else { return }

// ウィンドウとそのコンテンツビューがレイヤーバックされるように設定
window.contentView?.wantsLayer = true
window.contentView?.layer?.masksToBounds = true

// ウィンドウをボーダーレスに設定
window.styleMask = [.borderless, .resizable]
window.isMovable = true
window.hasShadow = true
window.titlebarAppearsTransparent = true
window.titleVisibility = .hidden

// 角丸を適用
window.contentView?.layer?.cornerRadius = 10
window.backgroundColor = .clear

// 重要:黒い背景が角に見えないようにする
window.isOpaque = false
}

override func viewDidAppear() {
super.viewDidAppear()
configureWindowForRoundedCorners()
}

func updateCandidates(_ candidates: [String], cursorLocation: CGPoint) {
self.candidates = candidates
self.currentSelectedRow = -1 // 選択をリセット
self.tableView.reloadData()
self.resizeWindowToFitContent(cursorLocation: cursorLocation)
self.selectFirstCandidate() // 最初の候補を選択
}

private func updateVisibleRows() {
let visibleRows = self.tableView.rows(in: self.tableView.visibleRect)
for row in visibleRows.lowerBound..<visibleRows.upperBound {
if let cellView = self.tableView.view(atColumn: 0, row: row, makeIfNecessary: false) as? CandidateTableCellView {
self.updateCellView(cellView, forRow: row)
}
}
}

private func updateCellView(_ cellView: CandidateTableCellView, forRow row: Int) {
let adjustedIndex = (row - self.currentSelectedRow + self.candidates.count) % self.candidates.count
var displayText = ""
if adjustedIndex == 0 {
displayText = "1. \(self.candidates[row])"
} else if adjustedIndex + 1 > 9 {
displayText = " \(self.candidates[row])"
} else {
displayText = "\(adjustedIndex + 1). \(self.candidates[row])"
}
cellView.candidateTextField.stringValue = displayText
}

func clearCandidates() {
self.candidates = []
self.tableView.reloadData()
}

private func resizeWindowToFitContent(cursorLocation: CGPoint) {
guard let window = self.view.window, let screen = window.screen else { return }

let numberOfRows = min(9, self.tableView.numberOfRows)
let rowHeight = self.tableView.rowHeight
let tableViewHeight = CGFloat(numberOfRows) * rowHeight
let stackViewSpacing = (self.view as! NSStackView).spacing
let totalHeight = stackViewSpacing + tableViewHeight

// 候補の最大幅を計算
let maxWidth = candidates.reduce(0) { maxWidth, candidate in
let attributedString = NSAttributedString(string: candidate, attributes: [.font: NSFont.systemFont(ofSize: 16)])
let width = attributedString.size().width
return max(maxWidth, width)
}

// ウィンドウの幅を設定(番号とパディングのための追加幅を考慮)
let windowWidth = min(max(maxWidth + 50, 50), 400) // 最小200px、最大400px

var newWindowFrame = window.frame
newWindowFrame.size.width = windowWidth
newWindowFrame.size.height = totalHeight

// 画面のサイズを取得
let screenRect = screen.visibleFrame
let cursorY = cursorLocation.y

// カーソルの高さを考慮してウィンドウ位置を調整
let cursorHeight: CGFloat = 16 // カーソルの高さを16ピクセルと仮定

// ウィンドウをカーソルの下に表示
if cursorY - totalHeight < screenRect.origin.y {
newWindowFrame.origin = CGPoint(x: cursorLocation.x, y: cursorLocation.y + cursorHeight)
} else {
newWindowFrame.origin = CGPoint(x: cursorLocation.x, y: cursorLocation.y - totalHeight - cursorHeight)
}

// 右端でウィンドウが画面外に出る場合は左にシフト
if newWindowFrame.maxX > screenRect.maxX {
newWindowFrame.origin.x = screenRect.maxX - newWindowFrame.width
}

window.setFrame(newWindowFrame, display: true, animate: false)
}

func selectCandidate(offset: Int) {
let selectedRow = self.tableView.selectedRow
if selectedRow + offset < 0 {
return
}
let nextRow = (selectedRow + offset + self.candidates.count) % self.candidates.count
self.tableView.selectRowIndexes(IndexSet(integer: nextRow), byExtendingSelection: false)

// 表示範囲
if offset > 0 {
self.tableView.scrollRowToVisible((selectedRow + offset + 8 + self.candidates.count) % self.candidates.count)
} else {
self.tableView.scrollRowToVisible((selectedRow + offset + self.candidates.count) % self.candidates.count)
}
let selectedCandidate = self.candidates[nextRow]
self.delegate?.candidateSelectionChanged(selectedCandidate)

// 新しい選択行を設定
self.currentSelectedRow = nextRow

// 表示を更新
self.updateVisibleRows()
}

func selectFirstCandidate() {
guard !self.candidates.isEmpty else {
return
}
let nextRow = 0
self.tableView.selectRowIndexes(IndexSet(integer: nextRow), byExtendingSelection: false)
self.tableView.scrollRowToVisible(nextRow)
let selectedCandidate = self.candidates[nextRow]
self.delegate?.candidateSelectionChanged(selectedCandidate)

// 新しい選択行を設定
self.currentSelectedRow = nextRow

// 表示を更新
self.updateVisibleRows()
}

func confirmCandidateSelection() {
let selectedRow = self.tableView.selectedRow
if selectedRow >= 0 && selectedRow < self.candidates.count {
let selectedCandidate = self.candidates[selectedRow]
delegate?.candidateSelected(selectedCandidate)
}
}
}

extension CandidatesViewController: NSTableViewDelegate, NSTableViewDataSource {
func numberOfRows(in tableView: NSTableView) -> Int {
return candidates.count
}

func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
let cellIdentifier = NSUserInterfaceItemIdentifier("CandidateCell")
var cell = tableView.makeView(withIdentifier: cellIdentifier, owner: nil) as? CandidateTableCellView
if cell == nil {
cell = CandidateTableCellView()
cell?.identifier = cellIdentifier
}

if let cell = cell {
self.updateCellView(cell, forRow: row)
}

return cell
}
}

class NonClickableTableView: NSTableView {
override func rightMouseDown(with event: NSEvent) {
// 右クリックイベントを無視
}

override func mouseDown(with event: NSEvent) {
// 左クリックイベントも無視する場合はこのメソッド内を空に
}

override func otherMouseDown(with event: NSEvent) {
// 中クリックなどその他のマウスボタンのクリックも無視
}
}

class CandidateTableCellView: NSTableCellView {
let candidateTextField: NSTextField

override init(frame frameRect: NSRect) {
self.candidateTextField = NSTextField(labelWithString: "")
// font size
self.candidateTextField.font = NSFont.systemFont(ofSize: 16)
super.init(frame: frameRect)
self.addSubview(self.candidateTextField)

self.candidateTextField.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
self.candidateTextField.leadingAnchor.constraint(equalTo: self.leadingAnchor),
self.candidateTextField.trailingAnchor.constraint(equalTo: self.trailingAnchor),
self.candidateTextField.topAnchor.constraint(equalTo: self.topAnchor),
self.candidateTextField.bottomAnchor.constraint(equalTo: self.bottomAnchor)
])
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

protocol CandidatesViewControllerDelegate: AnyObject {
func candidateSelected(_ candidate: String)
func candidateSelectionChanged(_ candidateString: String)
}
21 changes: 20 additions & 1 deletion azooKeyMac/InputController/InputMode.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

import Cocoa

enum InputMode {
Expand Down Expand Up @@ -45,6 +44,26 @@ enum InputMode {
return .navigation(.down)
case 126: // Up
return .navigation(.up)
case 18:
return .number(.one)
case 19:
return .number(.two)
case 20:
return .number(.three)
case 21:
return .number(.four)
case 23:
return .number(.five)
case 22:
return .number(.six)
case 26:
return .number(.seven)
case 28:
return .number(.eight)
case 25:
return .number(.nine)
case 29:
return .number(.zero)
default:
if let text = event.characters, isPrintable(text) {
return .input(KeyMap.h2zMap(text))
Expand Down
Loading