Skip to content

Commit 2ed3847

Browse files
authored
long press globe; rework status area/input methods update (#29)
1 parent 346cd00 commit 2ed3847

7 files changed

Lines changed: 98 additions & 31 deletions

File tree

src/api.d.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,10 @@ export type SystemEvent = {
4545
}
4646
} | {
4747
type: 'STATUS_AREA'
48+
data: StatusAreaAction[]
49+
} | {
50+
type: 'INPUT_METHODS'
4851
data: {
49-
actions: StatusAreaAction[]
5052
currentInputMethod: string
5153
inputMethods: InputMethod[]
5254
}
@@ -59,7 +61,7 @@ export type VirtualKeyboardEvent = {
5961
code: string
6062
}
6163
} | {
62-
type: 'COMMIT'
64+
type: 'COMMIT' | 'SET_INPUT_METHOD'
6365
data: string
6466
} | {
6567
type: 'UNDO' | 'REDO' | 'CUT' | 'COPY' | 'PASTE' | 'COLLAPSE' |

src/keyboard.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { renderStatusArea, setStatusArea } from './statusArea'
1212
import { renderSymbolSelector } from './symbol'
1313
import { enableRedo, enableUndo, renderToolbar } from './toolbar'
1414
import { div, hide } from './util'
15-
import { onTouchEnd, onTouchMove, onTouchStart, setEnterKeyType, setLayer, setLayout as setLayout_ } from './ux'
15+
import { onTouchEnd, onTouchMove, onTouchStart, setEnterKeyType, setInputMethods, setLayer, setLayout as setLayout_ } from './ux'
1616

1717
const builtInLayoutMap = { qwerty } as { [key: string]: Layout }
1818

@@ -108,7 +108,10 @@ export function onMessage(message: string) {
108108
setCandidateActions(event.data.index, event.data.actions)
109109
break
110110
case 'STATUS_AREA':
111-
setStatusArea(event.data.actions, event.data.currentInputMethod, event.data.inputMethods)
111+
setStatusArea(event.data)
112+
break
113+
case 'INPUT_METHODS':
114+
setInputMethods(event.data.inputMethods, event.data.currentInputMethod)
112115
break
113116
case 'SELECT':
114117
return select()

src/statusArea.ts

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import type { InputMethod, StatusAreaAction } from './api'
1+
import type { StatusAreaAction } from './api'
22
import FullPunc from 'bundle-text:../svg/full-punc.svg'
33
import FullWidth from 'bundle-text:../svg/full-width.svg'
44
import HalfPunc from 'bundle-text:../svg/half-punc.svg'
55
import HalfWidth from 'bundle-text:../svg/half-width.svg'
66
import LightbulbOutline from 'bundle-text:../svg/lightbulb-outline.svg'
77
import Lightbulb from 'bundle-text:../svg/lightbulb.svg'
88
import { div, getStatusArea } from './util'
9-
import { sendEvent, setSpaceKeyLabel } from './ux'
9+
import { sendEvent } from './ux'
1010

1111
export function renderStatusArea() {
1212
return div('fcitx-keyboard-status-area')
@@ -35,7 +35,7 @@ function getLabel(icon: string) {
3535
}
3636
}
3737

38-
export function setStatusArea(actions: StatusAreaAction[], currentInputMethod: string, inputMethods: InputMethod[]) {
38+
export function setStatusArea(actions: StatusAreaAction[]) {
3939
const statusArea = getStatusArea()
4040
statusArea.innerHTML = ''
4141
for (const action of actions) {
@@ -56,10 +56,4 @@ export function setStatusArea(actions: StatusAreaAction[], currentInputMethod: s
5656
button.appendChild(text)
5757
statusArea.appendChild(button)
5858
}
59-
for (const inputMethod of inputMethods) {
60-
if (inputMethod.name === currentInputMethod) {
61-
setSpaceKeyLabel(inputMethod.displayName)
62-
break
63-
}
64-
}
6559
}

src/ux.ts

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import type { Key, Layout } from '../src/layout'
2-
import type { VirtualKeyboardClient, VirtualKeyboardEvent } from './api'
2+
import type { InputMethod, VirtualKeyboardClient, VirtualKeyboardEvent } from './api'
33
import ArrowLeft from 'bundle-text:../svg/arrow-left.svg'
44
import ArrowRight from 'bundle-text:../svg/arrow-right.svg'
55
import CheckMark from 'bundle-text:../svg/checkmark.svg'
66
import Enter from 'bundle-text:../svg/enter.svg'
77
import Search from 'bundle-text:../svg/search.svg'
88
import Send from 'bundle-text:../svg/send.svg'
9+
import { showContextmenu } from './contextmenu'
910
import { setDisplayMode } from './display'
1011
import { renderRow } from './key'
1112
import { getContainer, getKey, press, release } from './util'
@@ -19,9 +20,12 @@ let shiftReleased = true
1920
let keyPressedWithShiftPressed = false
2021
let enterKeyType = ''
2122
let spaceKeyLabel = ''
23+
let inputMethods_: InputMethod[] = []
2224
let pendingTouch: Touch | null = null
2325
const touches: { [key: number]: Touch } = {}
2426
let startX = 0
27+
let startY = 0
28+
let longPressId: number | null = null
2529
let lastX = 0
2630
// We assume only one key can slide at a time, and another touch suspends it.
2731
let slidingKey: Key | null = null
@@ -30,6 +34,22 @@ let completedSteps = 0
3034
const slideStep = 10
3135

3236
const DOUBLE_TAP_INTERVAL = 300 // Same with f5a.
37+
const LONG_PRESS_THRESHOLD = 500
38+
const DRAG_THRESHOLD = 10
39+
40+
function dragged(touch: Touch) {
41+
const { clientX, clientY } = touch
42+
const dX = clientX - startX
43+
const dY = clientY - startY
44+
return dX * dX + dY * dY > DRAG_THRESHOLD
45+
}
46+
47+
function cancelLongPress() {
48+
if (longPressId) {
49+
clearTimeout(longPressId)
50+
longPressId = null
51+
}
52+
}
3353

3454
export function setLayout(layout: Layout) {
3555
layout_ = layout
@@ -143,9 +163,11 @@ function touchUp(touch: Touch) {
143163
export function onTouchStart(event: TouchEvent) {
144164
const touch = event.changedTouches[0]
145165
startX = touch.clientX
166+
startY = touch.clientY
146167
lastX = startX
147168
resetSlide()
148-
const key = getKey(getContainer(touch))
169+
let container = getContainer(touch)
170+
const key = getKey(container)
149171
if (key) {
150172
slidingKey = key
151173
if (pendingTouch) {
@@ -158,15 +180,30 @@ export function onTouchStart(event: TouchEvent) {
158180
else {
159181
pendingTouch = touch
160182
}
183+
if (key.type === 'globe') {
184+
longPressId = window.setTimeout(() => {
185+
longPressId = null
186+
pendingTouch = null
187+
showContextmenu(container!, inputMethods_.map(inputMethod => ({
188+
text: inputMethod.displayName,
189+
callback() { sendEvent({ type: 'SET_INPUT_METHOD', data: inputMethod.name }) },
190+
})))
191+
}, LONG_PRESS_THRESHOLD)
192+
}
161193
}
162194
// Must recalculate container as layer may have been changed.
163-
const container = getContainer(touch)
195+
container = getContainer(touch)
164196
container && press(container)
165197
touches[touch.identifier] = touch
166198
}
167199

168200
export function onTouchMove(event: TouchEvent) {
169201
const touch = event.changedTouches[0]
202+
// If same touch of startX/Y, great.
203+
// If comparing with a more recent touch, we don't care if it's cancelled or not.
204+
if (dragged(touch)) {
205+
cancelLongPress()
206+
}
170207
const { clientX } = touch
171208
if (['space', 'backspace'].includes(slidingKey?.type ?? '')) {
172209
if ((clientX - lastX) * (clientX - startX) < 0) { // turn around
@@ -194,6 +231,7 @@ export function onTouchMove(event: TouchEvent) {
194231
}
195232

196233
export function onTouchEnd(event: TouchEvent) {
234+
cancelLongPress()
197235
if (slidingKey) {
198236
if (slidingKey.type === 'backspace' && slid) {
199237
sendEvent({ type: 'BACKSPACE_SLIDE', data: 'RELEASE' })
@@ -266,3 +304,13 @@ export function setSpaceKeyLabel(label: string) {
266304
space.innerHTML = spaceKeyLabel
267305
}
268306
}
307+
308+
export function setInputMethods(inputMethods: InputMethod[], currentInputMethod: string) {
309+
inputMethods_ = inputMethods
310+
for (const inputMethod of inputMethods) {
311+
if (inputMethod.name === currentInputMethod) {
312+
setSpaceKeyLabel(inputMethod.displayName)
313+
break
314+
}
315+
}
316+
}

tests/test-globe.spec.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,33 @@
11
import { expect, test } from '@playwright/test'
2-
import { getSentEvents, init, tap } from './util'
2+
import { getSentEvents, init, longPress, sendSystemEvent, tap } from './util'
33

44
test('Tap', async ({ page }) => {
55
await init(page)
6+
67
const globe = page.locator('.fcitx-keyboard-globe')
78
await tap(globe)
89
expect(await getSentEvents(page)).toEqual([{ type: 'GLOBE' }])
910
})
11+
12+
test('Long press', async ({ page }) => {
13+
await init(page)
14+
15+
await sendSystemEvent(page, { type: 'INPUT_METHODS', data: {
16+
currentInputMethod: 'pinyin',
17+
inputMethods: [
18+
{ name: 'keyboard-us', displayName: 'English' },
19+
{ name: 'pinyin', displayName: '拼音' },
20+
],
21+
} })
22+
const globe = page.locator('.fcitx-keyboard-globe')
23+
await longPress(globe)
24+
const contextmenu = page.locator('.fcitx-keyboard-contextmenu')
25+
const english = contextmenu.getByText('English')
26+
const pinyin = contextmenu.getByText('拼音')
27+
await expect(english).toBeVisible()
28+
await expect(pinyin).toBeVisible()
29+
30+
await english.tap()
31+
await expect(contextmenu).not.toBeVisible()
32+
expect(await getSentEvents(page)).toEqual([{ type: 'SET_INPUT_METHOD', data: 'keyboard-us' }])
33+
})

tests/test-space.spec.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@ test('Space', async ({ page }) => {
1212
await init(page)
1313

1414
const space = page.locator('.fcitx-keyboard-space')
15-
await sendSystemEvent(page, { type: 'STATUS_AREA', data: {
16-
actions: [],
15+
await sendSystemEvent(page, { type: 'INPUT_METHODS', data: {
1716
currentInputMethod: 'pinyin',
1817
inputMethods: [
1918
{ name: 'keyboard-us', displayName: 'English' },
@@ -37,8 +36,7 @@ test('Adjust font size', async ({ page }) => {
3736

3837
const space = page.locator('.fcitx-keyboard-space')
3938
const longLabel = 'Long Long Long Long Long'
40-
await sendSystemEvent(page, { type: 'STATUS_AREA', data: {
41-
actions: [],
39+
await sendSystemEvent(page, { type: 'INPUT_METHODS', data: {
4240
currentInputMethod: 'long',
4341
inputMethods: [
4442
{ name: 'long', displayName: longLabel },

tests/test-status-area.spec.ts

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,10 @@ function gotoStatusArea(page: Page) {
99
test('Simplified and traditional', async ({ page }) => {
1010
await init(page)
1111

12-
await sendSystemEvent(page, { type: 'STATUS_AREA', data: {
13-
actions: [{ desc: '简体中文', icon: 'fcitx-chttrans-inactive', id: 4 }],
14-
currentInputMethod: '',
15-
inputMethods: [],
16-
} })
12+
await sendSystemEvent(page, {
13+
type: 'STATUS_AREA',
14+
data: [{ desc: '简体中文', icon: 'fcitx-chttrans-inactive', id: 4 }],
15+
})
1716
await gotoStatusArea(page)
1817

1918
const button = page.locator('.fcitx-keyboard-status-area-circle')
@@ -24,11 +23,10 @@ test('Simplified and traditional', async ({ page }) => {
2423
{ type: 'STATUS_AREA_ACTION', data: 4 },
2524
])
2625

27-
await sendSystemEvent(page, { type: 'STATUS_AREA', data: {
28-
actions: [{ desc: '繁体中文', icon: 'fcitx-chttrans-active', id: 4 }],
29-
currentInputMethod: '',
30-
inputMethods: [],
31-
} })
26+
await sendSystemEvent(page, {
27+
type: 'STATUS_AREA',
28+
data: [{ desc: '繁体中文', icon: 'fcitx-chttrans-active', id: 4 }],
29+
})
3230
await expect(button).toHaveText('繁')
3331
})
3432

0 commit comments

Comments
 (0)