Skip to content

Commit 6d35387

Browse files
committed
render keyboard based on JSON def
1 parent 5cf5db9 commit 6d35387

File tree

12 files changed

+171
-32
lines changed

12 files changed

+171
-32
lines changed

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,6 @@
2525
[submodule "ios-cmake"]
2626
path = ios-cmake
2727
url = https://github.com/leetal/ios-cmake
28+
[submodule "fcitx5-keyboard-layouts"]
29+
path = fcitx5-keyboard-layouts
30+
url = https://github.com/fcitx-contrib/fcitx5-keyboard-layouts

common/util.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,16 @@ public func removeFile(_ file: URL) -> Bool {
9292
}
9393
}
9494

95+
public func readJSON(_ file: URL) -> Any? {
96+
guard let stream = InputStream(url: file) else {
97+
return nil
98+
}
99+
stream.open()
100+
let j = try? JSONSerialization.jsonObject(with: stream)
101+
stream.close()
102+
return j
103+
}
104+
95105
// Call on both app and keyboard to initialize input method list after install.
96106
public func initProfile() {
97107
mkdirP(appGroupConfig.path)

fcitx5-keyboard-layouts

Submodule fcitx5-keyboard-layouts added at bcbbeb8

src/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,8 @@ if (RIME)
131131
copy_share_to_keyboard(copy_directory "rime-data")
132132
endif()
133133

134+
copy_to_keyboard(copy_directory "${PROJECT_SOURCE_DIR}/fcitx5-keyboard-layouts/layout" "share/layout")
135+
134136
# Embed keyboard.appex in app.
135137
copy_to_app(copy_directory "${PROJECT_BINARY_DIR}/keyboard/$<CONFIG>-${SDK_NAME}/keyboard.appex" "PlugIns/keyboard.appex")
136138
# Copy share directory, icon and profile.

uipanel/CMakeLists.txt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,17 @@ set_target_properties(KeyboardUI PROPERTIES Swift_MODULE_NAME KeyboardUI)
1515
target_compile_options(KeyboardUI PUBLIC "$<$<COMPILE_LANGUAGE:Swift>:-cxx-interoperability-mode=default>")
1616
target_include_directories(KeyboardUI PUBLIC
1717
"${CMAKE_CURRENT_SOURCE_DIR}"
18+
"${PROJECT_SOURCE_DIR}/common"
1819
"${PROJECT_BINARY_DIR}/protocol/$<CONFIG>-${SDK_NAME}"
20+
"${PROJECT_BINARY_DIR}/common/$<CONFIG>-${SDK_NAME}"
1921
)
20-
target_link_libraries(KeyboardUI FcitxProtocol)
22+
target_link_libraries(KeyboardUI FcitxProtocol FcitxCommon SwiftUtil)
2123

2224
_swift_generate_cxx_header(
2325
KeyboardUI
2426
"${CMAKE_CURRENT_BINARY_DIR}/include/keyboardui-swift.h"
2527
SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/keyboardui.swift"
26-
SEARCH_PATHS "${CMAKE_CURRENT_SOURCE_DIR};${PROJECT_BINARY_DIR}/protocol/$<CONFIG>-${SDK_NAME}"
28+
SEARCH_PATHS "${CMAKE_CURRENT_SOURCE_DIR};${PROJECT_SOURCE_DIR}/common;${PROJECT_BINARY_DIR}/protocol/$<CONFIG>-${SDK_NAME};${PROJECT_BINARY_DIR}/common/$<CONFIG>-${SDK_NAME}"
2729
)
2830

2931
add_fcitx5_addon(uipanel uipanel.cpp)

uipanel/Edit.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ struct EditView: View {
111111
.foregroundColor(.black)
112112
.cornerRadius(r)
113113
}.buttonStyle(PlainButtonStyle())
114-
.shadow(color: Color.black.opacity(0.3), radius: 1, x: 0, y: 1)
114+
.shadow(color: Color.gray, radius: 0, x: 0, y: 1)
115115
.frame(width: w, height: h)
116116
.overlay(
117117
RoundedRectangle(cornerRadius: r)

uipanel/Key.swift

Lines changed: 60 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,71 @@
11
import SwiftUI
22

3+
extension View {
4+
func commonContentStyle(width: CGFloat, height: CGFloat, background: Color) -> some View {
5+
self.frame(width: width - columnGap, height: height - rowGap)
6+
.background(background)
7+
.cornerRadius(keyCornerRadius)
8+
.foregroundColor(.black)
9+
.overlay(
10+
RoundedRectangle(cornerRadius: keyCornerRadius)
11+
.stroke(Color.clear, lineWidth: 0)
12+
)
13+
}
14+
15+
func commonContainerStyle(width: CGFloat, height: CGFloat) -> some View {
16+
self.shadow(color: Color.gray, radius: 0, x: 0, y: 1)
17+
.frame(width: width, height: height)
18+
}
19+
}
20+
321
struct KeyView: View {
422
let label: String
23+
let key: String
24+
let width: CGFloat
25+
let height: CGFloat
526

627
var body: some View {
728
Button {
8-
client.keyPressed(label, "")
29+
client.keyPressed(key, "")
930
} label: {
1031
Text(label)
11-
.frame(width: label == " " ? 100 : 35, height: 40)
12-
.background(Color.white)
13-
.cornerRadius(5)
14-
.overlay(
15-
RoundedRectangle(cornerRadius: 5)
16-
.stroke(Color.gray, lineWidth: 1)
17-
)
18-
}
32+
.font(.system(size: height * 0.5).weight(.light))
33+
.commonContentStyle(width: width, height: height, background: normalBackground)
34+
}.commonContainerStyle(width: width, height: height)
35+
}
36+
}
37+
38+
struct SpaceView: View {
39+
let label: String
40+
let width: CGFloat
41+
let height: CGFloat
42+
43+
var body: some View {
44+
Button {
45+
client.keyPressed(" ", "")
46+
} label: {
47+
Text(label)
48+
.font(.system(size: height * 0.4))
49+
.commonContentStyle(width: width, height: height, background: normalBackground)
50+
}.commonContainerStyle(width: width, height: height)
51+
}
52+
}
53+
54+
struct BackspaceView: View {
55+
let width: CGFloat
56+
let height: CGFloat
57+
58+
var body: some View {
59+
Button {
60+
client.keyPressed("", "Backspace")
61+
} label: {
62+
VStack {
63+
Image(systemName: "delete.left")
64+
.resizable()
65+
.aspectRatio(contentMode: .fit)
66+
.frame(height: height * 0.4)
67+
}
68+
.commonContentStyle(width: width, height: height, background: functionBackground)
69+
}.commonContainerStyle(width: width, height: height)
1970
}
2071
}

uipanel/Keyboard.swift

Lines changed: 65 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,75 @@
11
import SwiftUI
2+
import SwiftUtil
3+
4+
private func getFlexes(_ keys: [[String: Any]]) -> [CGFloat] {
5+
return keys.map({ key in
6+
if let flex = key["flex"] as? String,
7+
let value = Double(flex)
8+
{
9+
return CGFloat(value)
10+
}
11+
return 1
12+
})
13+
}
214

315
struct KeyboardView: View {
4-
let keys: [[String]] = [
5-
["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"],
6-
["q", "w", "e", "r", "t", "y", "u", "i", "o", "p"],
7-
["a", "s", "d", "f", "g", "h", "j", "k", "l"],
8-
["`", "z", "x", "c", "v", "b", "n", "m"],
9-
[",", " ", "."],
10-
]
16+
@Binding var spaceLabel: String
17+
@State private var rows: [[String: Any]] = []
1118

1219
var body: some View {
13-
VStack(spacing: 8) {
14-
ForEach(keys, id: \.self) { row in
15-
HStack(spacing: 6) {
16-
ForEach(row, id: \.self) { key in
17-
KeyView(label: key)
18-
}
20+
GeometryReader { geometry in
21+
let width = geometry.size.width
22+
let height = keyboardHeight / CGFloat(rows.count)
23+
VStack(spacing: 0) {
24+
ForEach(Array(rows.enumerated()), id: \.offset) { _, row in
25+
renderRow(row, width, height)
1926
}
2027
}
28+
.onAppear {
29+
setLayout()
30+
}
2131
}.frame(height: keyboardHeight)
2232
}
33+
34+
func setLayout() {
35+
let layoutUrl = Bundle.main.bundleURL.appendingPathComponent("share/layout/qwerty.json")
36+
guard let content = readJSON(layoutUrl) as? [String: Any],
37+
let layers = content["layers"] as? [[String: Any]],
38+
let defaultLayer = layers.first,
39+
let rows = defaultLayer["rows"] as? [[String: Any]]
40+
else {
41+
return
42+
}
43+
self.rows = rows
44+
}
45+
46+
func renderRow(_ row: [String: Any], _ width: CGFloat, _ height: CGFloat) -> some View {
47+
guard let keys = row["keys"] as? [[String: Any]] else {
48+
return AnyView(EmptyView())
49+
}
50+
let flexes = getFlexes(keys)
51+
let unit = width / flexes.reduce(0, +)
52+
return AnyView(
53+
HStack(spacing: 0) {
54+
ForEach(Array(keys.enumerated()), id: \.offset) { i, key in
55+
let keyWidth = flexes[i] * unit
56+
if let type = key["type"] as? String {
57+
switch type {
58+
case "key":
59+
if let label = key["label"] as? String,
60+
let k = key["key"] as? String
61+
{
62+
KeyView(label: label, key: k, width: keyWidth, height: height)
63+
}
64+
case "space":
65+
SpaceView(label: spaceLabel, width: keyWidth, height: height)
66+
case "backspace":
67+
BackspaceView(width: keyWidth, height: height)
68+
default:
69+
VStack {}.frame(width: keyWidth, height: height)
70+
}
71+
}
72+
}
73+
}.frame(width: width, height: height))
74+
}
2375
}

uipanel/VirtualKeyboard.swift

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import FcitxProtocol
22
import SwiftUI
3+
import SwiftUtil
34

45
public enum DisplayMode {
56
case initial
@@ -12,6 +13,7 @@ private class ViewModel: ObservableObject {
1213
@Published var mode: DisplayMode = .initial
1314
@Published var candidates: [String] = []
1415
@Published var actions = [StatusAreaAction]()
16+
@Published var spaceLabel = ""
1517
}
1618

1719
public struct VirtualKeyboardView: View {
@@ -29,7 +31,7 @@ public struct VirtualKeyboardView: View {
2931
} else if viewModel.mode == .edit {
3032
EditView()
3133
} else {
32-
KeyboardView()
34+
KeyboardView(spaceLabel: $viewModel.spaceLabel)
3335
}
3436
}.background(lightBackground)
3537
}
@@ -47,8 +49,16 @@ public struct VirtualKeyboardView: View {
4749
viewModel.candidates = candidates
4850
}
4951

50-
public func setActions(_ actions: [StatusAreaAction]) {
52+
public func setStatusArea(
53+
_ actions: [StatusAreaAction], _ currentInputMethod: String, _ inputMethods: [InputMethod]
54+
) {
5155
viewModel.actions = actions
56+
for inputMethod in inputMethods {
57+
if inputMethod.name == currentInputMethod {
58+
viewModel.spaceLabel = inputMethod.displayName
59+
break
60+
}
61+
}
5262
}
5363
}
5464

uipanel/keyboardui.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
import FcitxCommon
12
import FcitxProtocol
23
import SwiftUI
4+
import SwiftUtil
35

46
var client: FcitxProtocol!
57

@@ -34,8 +36,9 @@ public struct StatusAreaAction: Identifiable {
3436
}
3537
}
3638

37-
public func setStatusAreaActionsAsync(_ actions: [StatusAreaAction]) {
39+
public func setStatusAreaAsync(_ actions: [StatusAreaAction], _ currentInputMethod: String) {
3840
DispatchQueue.main.async {
39-
virtualKeyboardView.setActions(actions)
41+
virtualKeyboardView.setStatusArea(
42+
actions, currentInputMethod, deserialize([InputMethod].self, String(getInputMethods())))
4043
}
4144
}

0 commit comments

Comments
 (0)