diff --git a/app/internal/wm/GioView.java b/app/internal/wm/GioView.java index 42e5c531..4f3fd990 100644 --- a/app/internal/wm/GioView.java +++ b/app/internal/wm/GioView.java @@ -36,6 +36,7 @@ import android.view.inputmethod.BaseInputConnection; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.EditorInfo; +import android.text.InputType; import java.io.UnsupportedEncodingException; @@ -47,6 +48,7 @@ public final class GioView extends SurfaceView implements Choreographer.FrameCal private final InputMethodManager imm; private final float scrollXScale; private final float scrollYScale; + private int keyboardHint; 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); } + void setInputHint(int hint) { + if (hint == this.keyboardHint) { + return; + } + this.keyboardHint = hint; + imm.restartInput(this); + } + void showTextInput() { GioView.this.requestFocus(); imm.showSoftInput(GioView.this, 0); diff --git a/app/internal/wm/os_android.go b/app/internal/wm/os_android.go index e4a36124..2e3b3233 100644 --- a/app/internal/wm/os_android.go +++ b/app/internal/wm/os_android.go @@ -152,6 +152,7 @@ var gioView struct { getFontScale C.jmethodID showTextInput C.jmethodID hideTextInput C.jmethodID + setInputHint C.jmethodID postFrameCallback C.jmethodID setCursor 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.showTextInput = getMethodID(env, class, "showTextInput", "()V") m.hideTextInput = getMethodID(env, class, "hideTextInput", "()V") + m.setInputHint = getMethodID(env, class, "setInputHint", "(I)V") m.postFrameCallback = getMethodID(env, class, "postFrameCallback", "()V") m.setCursor = getMethodID(env, class, "setCursor", "(I)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 { if str == "" { return 0 diff --git a/app/internal/wm/os_ios.go b/app/internal/wm/os_ios.go index 63dc8e0d..aa298396 100644 --- a/app/internal/wm/os_ios.go +++ b/app/internal/wm/os_ios.go @@ -297,6 +297,8 @@ func (w *window) ShowTextInput(show bool) { } } +func (w *window) SetInputHint(_ key.InputHint) {} + // Close the window. Not implemented for iOS. func (w *window) Close() {} diff --git a/app/internal/wm/os_js.go b/app/internal/wm/os_js.go index fb466b6e..d088a04b 100644 --- a/app/internal/wm/os_js.go +++ b/app/internal/wm/os_js.go @@ -313,6 +313,27 @@ func (w *window) focus() { 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) { k := e.Get("key").String() 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. func (w *window) Close() {} diff --git a/app/internal/wm/os_macos.go b/app/internal/wm/os_macos.go index fa4d023f..103b92e1 100644 --- a/app/internal/wm/os_macos.go +++ b/app/internal/wm/os_macos.go @@ -187,6 +187,8 @@ func (w *window) SetCursor(name pointer.CursorName) { func (w *window) ShowTextInput(show bool) {} +func (w *window) SetInputHint(_ key.InputHint) {} + func (w *window) SetAnimating(anim bool) { if anim { w.displayLink.Start() diff --git a/app/internal/wm/os_wayland.go b/app/internal/wm/os_wayland.go index ef162d4c..6fa69526 100644 --- a/app/internal/wm/os_wayland.go +++ b/app/internal/wm/os_wayland.go @@ -1462,6 +1462,8 @@ func (w *window) surface() (*C.struct_wl_surface, int, int) { func (w *window) ShowTextInput(show bool) {} +func (w *window) SetInputHint(_ key.InputHint) {} + // Close the window. Not implemented for Wayland. func (w *window) Close() {} diff --git a/app/internal/wm/os_windows.go b/app/internal/wm/os_windows.go index ceebbe64..5c0cfaf6 100644 --- a/app/internal/wm/os_windows.go +++ b/app/internal/wm/os_windows.go @@ -665,6 +665,8 @@ func loadCursor(name pointer.CursorName) (syscall.Handle, error) { func (w *window) ShowTextInput(show bool) {} +func (w *window) SetInputHint(_ key.InputHint) {} + func (w *window) HDC() syscall.Handle { return w.hdc } diff --git a/app/internal/wm/os_x11.go b/app/internal/wm/os_x11.go index f664de02..5b7e4635 100644 --- a/app/internal/wm/os_x11.go +++ b/app/internal/wm/os_x11.go @@ -223,6 +223,8 @@ func (w *x11Window) SetWindowMode(mode WindowMode) { func (w *x11Window) ShowTextInput(show bool) {} +func (w *x11Window) SetInputHint(_ key.InputHint) {} + // Close the window. func (w *x11Window) Close() { var xev C.XEvent diff --git a/app/internal/wm/window.go b/app/internal/wm/window.go index 475d7a4c..7e5b250d 100644 --- a/app/internal/wm/window.go +++ b/app/internal/wm/window.go @@ -6,6 +6,7 @@ package wm import ( "errors" + "gioui.org/io/key" "image/color" "gioui.org/gpu" @@ -84,8 +85,12 @@ type Driver interface { // SetAnimating sets the animation flag. When the window is animating, // FrameEvents are delivered as fast as the display can handle them. SetAnimating(anim bool) + // ShowTextInput updates the virtual keyboard state. ShowTextInput(show bool) + + SetInputHint(mode key.InputHint) + NewContext() (Context, error) // ReadClipboard requests the clipboard content. diff --git a/app/window.go b/app/window.go index fd91427b..51dc5ef8 100644 --- a/app/window.go +++ b/app/window.go @@ -184,6 +184,9 @@ func (w *Window) processFrame(frameStart time.Time, size image.Point, frame *op. case router.TextInputClose: 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 { go w.WriteClipboard(txt) } diff --git a/internal/opconst/ops.go b/internal/opconst/ops.go index db9dd8dc..13c4ddf2 100644 --- a/internal/opconst/ops.go +++ b/internal/opconst/ops.go @@ -50,8 +50,8 @@ const ( TypePassLen = 1 + 1 TypeClipboardReadLen = 1 TypeClipboardWriteLen = 1 - TypeKeyInputLen = 1 - TypeKeyFocusLen = 1 + TypeKeyInputLen = 1 + 1 + TypeKeyFocusLen = 1 + 1 TypeKeySoftKeyboardLen = 1 + 1 TypeSaveLen = 1 + 4 TypeLoadLen = 1 + 1 + 4 diff --git a/io/key/key.go b/io/key/key.go index c19338ab..c2043283 100644 --- a/io/key/key.go +++ b/io/key/key.go @@ -22,7 +22,8 @@ import ( // Key events are in general only delivered to the // focused key handler. type InputOp struct { - Tag event.Tag + Tag event.Tag + Hint InputHint } // SoftKeyboardOp shows or hide the on-screen keyboard, if available. @@ -64,6 +65,25 @@ type EditEvent struct { 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. type State uint8 @@ -127,6 +147,7 @@ func (h InputOp) Add(o *op.Ops) { } data := o.Write1(opconst.TypeKeyInputLen, h.Tag) data[0] = byte(opconst.TypeKeyInput) + data[1] = byte(h.Hint) } func (h SoftKeyboardOp) Add(o *op.Ops) { diff --git a/io/router/key.go b/io/router/key.go index d1fbb665..af545162 100644 --- a/io/router/key.go +++ b/io/router/key.go @@ -17,6 +17,7 @@ type keyQueue struct { handlers map[event.Tag]*keyHandler reader ops.Reader state TextInputState + hint key.InputHint } type keyHandler struct { @@ -24,6 +25,7 @@ type keyHandler struct { // in the current frame. visible bool new bool + hint key.InputHint } const ( @@ -38,6 +40,20 @@ func (q *keyQueue) InputState() TextInputState { 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) { if q.handlers == nil { 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 } h.visible = true + h.hint = op.Hint } } return @@ -118,7 +135,8 @@ func decodeKeyInputOp(d []byte, refs []interface{}) key.InputOp { panic("invalid op") } return key.InputOp{ - Tag: refs[0].(event.Tag), + Tag: refs[0].(event.Tag), + Hint: key.InputHint(d[1]), } } diff --git a/io/router/router.go b/io/router/router.go index a605ecac..4a3df741 100644 --- a/io/router/router.go +++ b/io/router/router.go @@ -102,6 +102,11 @@ func (q *Router) TextInputState() TextInputState { 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 // to the clipboard, if any. func (q *Router) WriteClipboard() (string, bool) {