mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-07 02:15:34 +00:00
app, io: [wasm, android] add support for numeric/email keyboard mode
Previously, the on-screen keyboard always displays the text keyboard, (QWERTY or equivalent). For optimal user experience, it's possible to specify the keyboard type using `InputHint`. The on-screen keyboard will provide shortcuts or restrict what the user can input. Due to some limitations (gio#116), only numeric and text keyboards are supported on Android. Signed-off-by: Inkeliz <inkeliz@inkeliz.com>
This commit is contained in:
@@ -36,6 +36,7 @@ import android.view.inputmethod.BaseInputConnection;
|
|||||||
import android.view.inputmethod.InputConnection;
|
import android.view.inputmethod.InputConnection;
|
||||||
import android.view.inputmethod.InputMethodManager;
|
import android.view.inputmethod.InputMethodManager;
|
||||||
import android.view.inputmethod.EditorInfo;
|
import android.view.inputmethod.EditorInfo;
|
||||||
|
import android.text.InputType;
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
|
|
||||||
@@ -47,6 +48,7 @@ public final class GioView extends SurfaceView implements Choreographer.FrameCal
|
|||||||
private final InputMethodManager imm;
|
private final InputMethodManager imm;
|
||||||
private final float scrollXScale;
|
private final float scrollXScale;
|
||||||
private final float scrollYScale;
|
private final float scrollYScale;
|
||||||
|
private int keyboardHint;
|
||||||
|
|
||||||
private long nhandle;
|
private long nhandle;
|
||||||
|
|
||||||
@@ -247,10 +249,20 @@ public final class GioView extends SurfaceView implements Choreographer.FrameCal
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
|
@Override public InputConnection onCreateInputConnection(EditorInfo editor) {
|
||||||
|
editor.inputType = this.keyboardHint;
|
||||||
|
editor.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN | EditorInfo.IME_FLAG_NO_EXTRACT_UI;
|
||||||
return new InputConnection(this);
|
return new InputConnection(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setInputHint(int hint) {
|
||||||
|
if (hint == this.keyboardHint) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.keyboardHint = hint;
|
||||||
|
imm.restartInput(this);
|
||||||
|
}
|
||||||
|
|
||||||
void showTextInput() {
|
void showTextInput() {
|
||||||
GioView.this.requestFocus();
|
GioView.this.requestFocus();
|
||||||
imm.showSoftInput(GioView.this, 0);
|
imm.showSoftInput(GioView.this, 0);
|
||||||
|
|||||||
@@ -152,6 +152,7 @@ var gioView struct {
|
|||||||
getFontScale C.jmethodID
|
getFontScale C.jmethodID
|
||||||
showTextInput C.jmethodID
|
showTextInput C.jmethodID
|
||||||
hideTextInput C.jmethodID
|
hideTextInput C.jmethodID
|
||||||
|
setInputHint C.jmethodID
|
||||||
postFrameCallback C.jmethodID
|
postFrameCallback C.jmethodID
|
||||||
setCursor C.jmethodID
|
setCursor C.jmethodID
|
||||||
setOrientation C.jmethodID
|
setOrientation C.jmethodID
|
||||||
@@ -281,6 +282,7 @@ func Java_org_gioui_GioView_onCreateView(env *C.JNIEnv, class C.jclass, view C.j
|
|||||||
m.getFontScale = getMethodID(env, class, "getFontScale", "()F")
|
m.getFontScale = getMethodID(env, class, "getFontScale", "()F")
|
||||||
m.showTextInput = getMethodID(env, class, "showTextInput", "()V")
|
m.showTextInput = getMethodID(env, class, "showTextInput", "()V")
|
||||||
m.hideTextInput = getMethodID(env, class, "hideTextInput", "()V")
|
m.hideTextInput = getMethodID(env, class, "hideTextInput", "()V")
|
||||||
|
m.setInputHint = getMethodID(env, class, "setInputHint", "(I)V")
|
||||||
m.postFrameCallback = getMethodID(env, class, "postFrameCallback", "()V")
|
m.postFrameCallback = getMethodID(env, class, "postFrameCallback", "()V")
|
||||||
m.setCursor = getMethodID(env, class, "setCursor", "(I)V")
|
m.setCursor = getMethodID(env, class, "setCursor", "(I)V")
|
||||||
m.setOrientation = getMethodID(env, class, "setOrientation", "(II)V")
|
m.setOrientation = getMethodID(env, class, "setOrientation", "(II)V")
|
||||||
@@ -609,6 +611,28 @@ func (w *window) ShowTextInput(show bool) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *window) SetInputHint(mode key.InputHint) {
|
||||||
|
// Constants defined at https://developer.android.com/reference/android/text/InputType.
|
||||||
|
const (
|
||||||
|
TYPE_NULL = 0
|
||||||
|
TYPE_CLASS_NUMBER = 2
|
||||||
|
TYPE_NUMBER_FLAG_DECIMAL = 8192
|
||||||
|
TYPE_NUMBER_FLAG_SIGNED = 4096
|
||||||
|
)
|
||||||
|
|
||||||
|
runInJVM(javaVM(), func(env *C.JNIEnv) {
|
||||||
|
var m jvalue
|
||||||
|
switch mode {
|
||||||
|
case key.HintNumeric:
|
||||||
|
m = TYPE_CLASS_NUMBER | TYPE_NUMBER_FLAG_DECIMAL | TYPE_NUMBER_FLAG_SIGNED
|
||||||
|
default:
|
||||||
|
// TYPE_NULL, since TYPE_CLASS_TEXT isn't currently supported (gio#116), so TYPE_NULL is used instead.
|
||||||
|
m = TYPE_NULL
|
||||||
|
}
|
||||||
|
callVoidMethod(env, w.view, gioView.setInputHint, m)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func javaString(env *C.JNIEnv, str string) C.jstring {
|
func javaString(env *C.JNIEnv, str string) C.jstring {
|
||||||
if str == "" {
|
if str == "" {
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
@@ -297,6 +297,8 @@ func (w *window) ShowTextInput(show bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *window) SetInputHint(_ key.InputHint) {}
|
||||||
|
|
||||||
// Close the window. Not implemented for iOS.
|
// Close the window. Not implemented for iOS.
|
||||||
func (w *window) Close() {}
|
func (w *window) Close() {}
|
||||||
|
|
||||||
|
|||||||
@@ -313,6 +313,27 @@ func (w *window) focus() {
|
|||||||
w.requestFocus = true
|
w.requestFocus = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *window) keyboard(hint key.InputHint) {
|
||||||
|
var m string
|
||||||
|
switch hint {
|
||||||
|
case key.HintAny:
|
||||||
|
m = "text"
|
||||||
|
case key.HintText:
|
||||||
|
m = "text"
|
||||||
|
case key.HintNumeric:
|
||||||
|
m = "decimal"
|
||||||
|
case key.HintEmail:
|
||||||
|
m = "email"
|
||||||
|
case key.HintURL:
|
||||||
|
m = "url"
|
||||||
|
case key.HintTelephone:
|
||||||
|
m = "tel"
|
||||||
|
default:
|
||||||
|
m = "text"
|
||||||
|
}
|
||||||
|
w.tarea.Set("inputMode", m)
|
||||||
|
}
|
||||||
|
|
||||||
func (w *window) keyEvent(e js.Value, ks key.State) {
|
func (w *window) keyEvent(e js.Value, ks key.State) {
|
||||||
k := e.Get("key").String()
|
k := e.Get("key").String()
|
||||||
if n, ok := translateKey(k); ok {
|
if n, ok := translateKey(k); ok {
|
||||||
@@ -524,6 +545,10 @@ func (w *window) ShowTextInput(show bool) {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *window) SetInputHint(mode key.InputHint) {
|
||||||
|
w.keyboard(mode)
|
||||||
|
}
|
||||||
|
|
||||||
// Close the window. Not implemented for js.
|
// Close the window. Not implemented for js.
|
||||||
func (w *window) Close() {}
|
func (w *window) Close() {}
|
||||||
|
|
||||||
|
|||||||
@@ -187,6 +187,8 @@ func (w *window) SetCursor(name pointer.CursorName) {
|
|||||||
|
|
||||||
func (w *window) ShowTextInput(show bool) {}
|
func (w *window) ShowTextInput(show bool) {}
|
||||||
|
|
||||||
|
func (w *window) SetInputHint(_ key.InputHint) {}
|
||||||
|
|
||||||
func (w *window) SetAnimating(anim bool) {
|
func (w *window) SetAnimating(anim bool) {
|
||||||
if anim {
|
if anim {
|
||||||
w.displayLink.Start()
|
w.displayLink.Start()
|
||||||
|
|||||||
@@ -1462,6 +1462,8 @@ func (w *window) surface() (*C.struct_wl_surface, int, int) {
|
|||||||
|
|
||||||
func (w *window) ShowTextInput(show bool) {}
|
func (w *window) ShowTextInput(show bool) {}
|
||||||
|
|
||||||
|
func (w *window) SetInputHint(_ key.InputHint) {}
|
||||||
|
|
||||||
// Close the window. Not implemented for Wayland.
|
// Close the window. Not implemented for Wayland.
|
||||||
func (w *window) Close() {}
|
func (w *window) Close() {}
|
||||||
|
|
||||||
|
|||||||
@@ -665,6 +665,8 @@ func loadCursor(name pointer.CursorName) (syscall.Handle, error) {
|
|||||||
|
|
||||||
func (w *window) ShowTextInput(show bool) {}
|
func (w *window) ShowTextInput(show bool) {}
|
||||||
|
|
||||||
|
func (w *window) SetInputHint(_ key.InputHint) {}
|
||||||
|
|
||||||
func (w *window) HDC() syscall.Handle {
|
func (w *window) HDC() syscall.Handle {
|
||||||
return w.hdc
|
return w.hdc
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -223,6 +223,8 @@ func (w *x11Window) SetWindowMode(mode WindowMode) {
|
|||||||
|
|
||||||
func (w *x11Window) ShowTextInput(show bool) {}
|
func (w *x11Window) ShowTextInput(show bool) {}
|
||||||
|
|
||||||
|
func (w *x11Window) SetInputHint(_ key.InputHint) {}
|
||||||
|
|
||||||
// Close the window.
|
// Close the window.
|
||||||
func (w *x11Window) Close() {
|
func (w *x11Window) Close() {
|
||||||
var xev C.XEvent
|
var xev C.XEvent
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ package wm
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"gioui.org/io/key"
|
||||||
"image/color"
|
"image/color"
|
||||||
|
|
||||||
"gioui.org/gpu"
|
"gioui.org/gpu"
|
||||||
@@ -84,8 +85,12 @@ type Driver interface {
|
|||||||
// SetAnimating sets the animation flag. When the window is animating,
|
// SetAnimating sets the animation flag. When the window is animating,
|
||||||
// FrameEvents are delivered as fast as the display can handle them.
|
// FrameEvents are delivered as fast as the display can handle them.
|
||||||
SetAnimating(anim bool)
|
SetAnimating(anim bool)
|
||||||
|
|
||||||
// ShowTextInput updates the virtual keyboard state.
|
// ShowTextInput updates the virtual keyboard state.
|
||||||
ShowTextInput(show bool)
|
ShowTextInput(show bool)
|
||||||
|
|
||||||
|
SetInputHint(mode key.InputHint)
|
||||||
|
|
||||||
NewContext() (Context, error)
|
NewContext() (Context, error)
|
||||||
|
|
||||||
// ReadClipboard requests the clipboard content.
|
// ReadClipboard requests the clipboard content.
|
||||||
|
|||||||
@@ -184,6 +184,9 @@ func (w *Window) processFrame(frameStart time.Time, size image.Point, frame *op.
|
|||||||
case router.TextInputClose:
|
case router.TextInputClose:
|
||||||
go w.driverRun(func(d wm.Driver) { d.ShowTextInput(false) })
|
go w.driverRun(func(d wm.Driver) { d.ShowTextInput(false) })
|
||||||
}
|
}
|
||||||
|
if hint, ok := w.queue.q.TextInputHint(); ok {
|
||||||
|
go w.driverRun(func(d wm.Driver) { d.SetInputHint(hint) })
|
||||||
|
}
|
||||||
if txt, ok := w.queue.q.WriteClipboard(); ok {
|
if txt, ok := w.queue.q.WriteClipboard(); ok {
|
||||||
go w.WriteClipboard(txt)
|
go w.WriteClipboard(txt)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,8 +50,8 @@ const (
|
|||||||
TypePassLen = 1 + 1
|
TypePassLen = 1 + 1
|
||||||
TypeClipboardReadLen = 1
|
TypeClipboardReadLen = 1
|
||||||
TypeClipboardWriteLen = 1
|
TypeClipboardWriteLen = 1
|
||||||
TypeKeyInputLen = 1
|
TypeKeyInputLen = 1 + 1
|
||||||
TypeKeyFocusLen = 1
|
TypeKeyFocusLen = 1 + 1
|
||||||
TypeKeySoftKeyboardLen = 1 + 1
|
TypeKeySoftKeyboardLen = 1 + 1
|
||||||
TypeSaveLen = 1 + 4
|
TypeSaveLen = 1 + 4
|
||||||
TypeLoadLen = 1 + 1 + 4
|
TypeLoadLen = 1 + 1 + 4
|
||||||
|
|||||||
+22
-1
@@ -22,7 +22,8 @@ import (
|
|||||||
// Key events are in general only delivered to the
|
// Key events are in general only delivered to the
|
||||||
// focused key handler.
|
// focused key handler.
|
||||||
type InputOp struct {
|
type InputOp struct {
|
||||||
Tag event.Tag
|
Tag event.Tag
|
||||||
|
Hint InputHint
|
||||||
}
|
}
|
||||||
|
|
||||||
// SoftKeyboardOp shows or hide the on-screen keyboard, if available.
|
// SoftKeyboardOp shows or hide the on-screen keyboard, if available.
|
||||||
@@ -64,6 +65,25 @@ type EditEvent struct {
|
|||||||
Text string
|
Text string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InputHint changes the on-screen-keyboard type. That hints the
|
||||||
|
// type of data that might be entered by the user.
|
||||||
|
type InputHint uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
// HintAny hints that any input is expected.
|
||||||
|
HintAny InputHint = iota
|
||||||
|
// HintText hints that text input is expected. It may activate auto-correction and suggestions.
|
||||||
|
HintText
|
||||||
|
// HintNumeric hints that numeric input is expected. It may activate shortcuts for 0-9, "." and ",".
|
||||||
|
HintNumeric
|
||||||
|
// HintEmail hints that email input is expected. It may activate shortcuts for common email characters, such as "@" and ".com".
|
||||||
|
HintEmail
|
||||||
|
// HintURL hints that URL input is expected. It may activate shortcuts for common URL fragments such as "/" and ".com".
|
||||||
|
HintURL
|
||||||
|
// HintTelephone hints that telephone number input is expected. It may activate shortcuts for 0-9, "#" and "*".
|
||||||
|
HintTelephone
|
||||||
|
)
|
||||||
|
|
||||||
// State is the state of a key during an event.
|
// State is the state of a key during an event.
|
||||||
type State uint8
|
type State uint8
|
||||||
|
|
||||||
@@ -127,6 +147,7 @@ func (h InputOp) Add(o *op.Ops) {
|
|||||||
}
|
}
|
||||||
data := o.Write1(opconst.TypeKeyInputLen, h.Tag)
|
data := o.Write1(opconst.TypeKeyInputLen, h.Tag)
|
||||||
data[0] = byte(opconst.TypeKeyInput)
|
data[0] = byte(opconst.TypeKeyInput)
|
||||||
|
data[1] = byte(h.Hint)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h SoftKeyboardOp) Add(o *op.Ops) {
|
func (h SoftKeyboardOp) Add(o *op.Ops) {
|
||||||
|
|||||||
+19
-1
@@ -17,6 +17,7 @@ type keyQueue struct {
|
|||||||
handlers map[event.Tag]*keyHandler
|
handlers map[event.Tag]*keyHandler
|
||||||
reader ops.Reader
|
reader ops.Reader
|
||||||
state TextInputState
|
state TextInputState
|
||||||
|
hint key.InputHint
|
||||||
}
|
}
|
||||||
|
|
||||||
type keyHandler struct {
|
type keyHandler struct {
|
||||||
@@ -24,6 +25,7 @@ type keyHandler struct {
|
|||||||
// in the current frame.
|
// in the current frame.
|
||||||
visible bool
|
visible bool
|
||||||
new bool
|
new bool
|
||||||
|
hint key.InputHint
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -38,6 +40,20 @@ func (q *keyQueue) InputState() TextInputState {
|
|||||||
return q.state
|
return q.state
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InputHint returns the input mode from the most recent key.InputOp.
|
||||||
|
func (q *keyQueue) InputHint() (key.InputHint, bool) {
|
||||||
|
if q.focus == nil {
|
||||||
|
return q.hint, false
|
||||||
|
}
|
||||||
|
focused, ok := q.handlers[q.focus]
|
||||||
|
if !ok {
|
||||||
|
return q.hint, false
|
||||||
|
}
|
||||||
|
old := q.hint
|
||||||
|
q.hint = focused.hint
|
||||||
|
return q.hint, old != q.hint
|
||||||
|
}
|
||||||
|
|
||||||
func (q *keyQueue) Frame(root *op.Ops, events *handlerEvents) {
|
func (q *keyQueue) Frame(root *op.Ops, events *handlerEvents) {
|
||||||
if q.handlers == nil {
|
if q.handlers == nil {
|
||||||
q.handlers = make(map[event.Tag]*keyHandler)
|
q.handlers = make(map[event.Tag]*keyHandler)
|
||||||
@@ -108,6 +124,7 @@ func (q *keyQueue) resolveFocus(events *handlerEvents) (focus event.Tag, changed
|
|||||||
q.handlers[op.Tag] = h
|
q.handlers[op.Tag] = h
|
||||||
}
|
}
|
||||||
h.visible = true
|
h.visible = true
|
||||||
|
h.hint = op.Hint
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@@ -118,7 +135,8 @@ func decodeKeyInputOp(d []byte, refs []interface{}) key.InputOp {
|
|||||||
panic("invalid op")
|
panic("invalid op")
|
||||||
}
|
}
|
||||||
return key.InputOp{
|
return key.InputOp{
|
||||||
Tag: refs[0].(event.Tag),
|
Tag: refs[0].(event.Tag),
|
||||||
|
Hint: key.InputHint(d[1]),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -102,6 +102,11 @@ func (q *Router) TextInputState() TextInputState {
|
|||||||
return q.kqueue.InputState()
|
return q.kqueue.InputState()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TextInputHint returns the input mode from the most recent key.InputOp.
|
||||||
|
func (q *Router) TextInputHint() (key.InputHint, bool) {
|
||||||
|
return q.kqueue.InputHint()
|
||||||
|
}
|
||||||
|
|
||||||
// WriteClipboard returns the most recent text to be copied
|
// WriteClipboard returns the most recent text to be copied
|
||||||
// to the clipboard, if any.
|
// to the clipboard, if any.
|
||||||
func (q *Router) WriteClipboard() (string, bool) {
|
func (q *Router) WriteClipboard() (string, bool) {
|
||||||
|
|||||||
Reference in New Issue
Block a user