exp/textinput: unexport State and Start

macOS requires the information of the current selection to implement
IME correctly, then the user must use Field. Field is no longer just
a wrapper API.

As this package is experimental, it is OK to have a breaking change,
though this is not ideal.

Updates #3233
This commit is contained in:
Hajime Hoshi
2025-05-12 00:57:02 +09:00
parent d6f95c4e8c
commit 0e6572443f
6 changed files with 30 additions and 30 deletions
+8 -8
View File
@@ -75,9 +75,9 @@ type Field struct {
selectionStartInBytes int
selectionEndInBytes int
ch <-chan State
ch <-chan textInputState
end func()
state State
state textInputState
err error
}
@@ -103,7 +103,7 @@ func (f *Field) HandleInput(x, y int) (handled bool, err error) {
if f.ch == nil {
// TODO: On iOS Safari, Start doesn't work as expected (#2898).
// Handle a click event and focus the textarea there.
f.ch, f.end = Start(x, y)
f.ch, f.end = start(x, y)
// Start returns nil for non-supported envrionments, or when unable to start text inputting for some reasons.
if f.ch == nil {
return handled, nil
@@ -121,12 +121,12 @@ func (f *Field) HandleInput(x, y int) (handled bool, err error) {
if !ok {
f.ch = nil
f.end = nil
f.state = State{}
f.state = textInputState{}
break readchar
}
if state.Committed && state.Text == "\x7f" {
// DEL should not modify the text (#3212).
f.state = State{}
f.state = textInputState{}
continue
}
handled = true
@@ -134,7 +134,7 @@ func (f *Field) HandleInput(x, y int) (handled bool, err error) {
f.text = f.text[:f.selectionStartInBytes] + state.Text + f.text[f.selectionEndInBytes:]
f.selectionStartInBytes += len(state.Text)
f.selectionEndInBytes = f.selectionStartInBytes
f.state = State{}
f.state = textInputState{}
continue
}
f.state = state
@@ -189,7 +189,7 @@ func (f *Field) cleanUp() {
f.text = f.text[:f.selectionStartInBytes] + state.Text + f.text[f.selectionEndInBytes:]
f.selectionStartInBytes += len(state.Text)
f.selectionEndInBytes = f.selectionStartInBytes
f.state = State{}
f.state = textInputState{}
}
f.state = state
default:
@@ -201,7 +201,7 @@ func (f *Field) cleanUp() {
f.end()
f.ch = nil
f.end = nil
f.state = State{}
f.state = textInputState{}
}
}
+11 -11
View File
@@ -24,10 +24,10 @@ import (
"github.com/hajimehoshi/ebiten/v2/internal/ui"
)
// State represents the current state of text inputting.
// textInputState represents the current state of text inputting.
//
// State is the low-level API. For most use cases, Field is easier to use.
type State struct {
// textInputState is the low-level API. For most use cases, Field is easier to use.
type textInputState struct {
// Text represents the current inputting text.
Text string
@@ -44,13 +44,13 @@ type State struct {
Error error
}
// Start starts text inputting.
// Start returns a channel to send the state repeatedly, and a function to end the text inputting.
// start starts text inputting.
// start returns a channel to send the state repeatedly, and a function to end the text inputting.
//
// Start is the low-level API. For most use cases, Field is easier to use.
// start is the low-level API. For most use cases, Field is easier to use.
//
// Start returns nil and nil if the current environment doesn't support this package.
func Start(x, y int) (states <-chan State, close func()) {
// start returns nil and nil if the current environment doesn't support this package.
func start(x, y int) (states <-chan textInputState, close func()) {
cx, cy := ui.Get().LogicalPositionToClientPositionInNativePixels(float64(x), float64(y))
return theTextInput.Start(int(cx), int(cy))
}
@@ -60,13 +60,13 @@ func convertUTF16CountToByteCount(text string, c int) int {
}
type session struct {
ch chan State
ch chan textInputState
done chan struct{}
}
func newSession() *session {
return &session{
ch: make(chan State, 1),
ch: make(chan textInputState, 1),
done: make(chan struct{}),
}
}
@@ -80,7 +80,7 @@ func (s *session) end() {
close(s.done)
}
func (s *session) trySend(state State) {
func (s *session) trySend(state textInputState) {
for {
select {
case s.ch <- state:
+2 -2
View File
@@ -33,7 +33,7 @@ type textInput struct {
var theTextInput textInput
func (t *textInput) Start(x, y int) (<-chan State, func()) {
func (t *textInput) Start(x, y int) (<-chan textInputState, func()) {
ui.Get().RunOnMainThread(func() {
t.start(x, y)
})
@@ -49,7 +49,7 @@ func (t *textInput) update(text string, start, end int, committed bool) {
if t.session != nil {
startInBytes := convertUTF16CountToByteCount(text, start)
endInBytes := convertUTF16CountToByteCount(text, end)
t.session.trySend(State{
t.session.trySend(textInputState{
Text: text,
CompositionSelectionStartInBytes: startInBytes,
CompositionSelectionEndInBytes: endInBytes,
+2 -2
View File
@@ -156,7 +156,7 @@ body.addEventListener("keyup", handler);`)
// TODO: What about other events like wheel?
}
func (t *textInput) Start(x, y int) (<-chan State, func()) {
func (t *textInput) Start(x, y int) (<-chan textInputState, func()) {
if !t.textareaElement.Truthy() {
return nil, nil
}
@@ -220,7 +220,7 @@ func (t *textInput) trySend(committed bool) {
startInBytes := convertUTF16CountToByteCount(textareaValue, start)
endInBytes := convertUTF16CountToByteCount(textareaValue, end)
t.session.trySend(State{
t.session.trySend(textInputState{
Text: textareaValue,
CompositionSelectionStartInBytes: startInBytes,
CompositionSelectionEndInBytes: endInBytes,
+2 -2
View File
@@ -27,7 +27,7 @@ type textInput struct {
var theTextInput textInput
func (t *textInput) Start(x, y int) (<-chan State, func()) {
func (t *textInput) Start(x, y int) (<-chan textInputState, func()) {
// AppendInputChars is updated only when the tick is updated.
// If the tick is not updated, return nil immediately.
tick := ebiten.Tick()
@@ -46,7 +46,7 @@ func (t *textInput) Start(x, y int) (<-chan State, func()) {
if len(t.rs) == 0 {
return nil, nil
}
s.ch <- State{
s.ch <- textInputState{
Text: string(t.rs),
Committed: true,
}
+5 -5
View File
@@ -41,7 +41,7 @@ type textInput struct {
var theTextInput textInput
func (t *textInput) Start(x, y int) (<-chan State, func()) {
func (t *textInput) Start(x, y int) (<-chan textInputState, func()) {
if microsoftgdk.IsXbox() {
return nil, nil
}
@@ -55,7 +55,7 @@ func (t *textInput) Start(x, y int) (<-chan State, func()) {
t.session = session
})
if err != nil {
session.ch <- State{Error: err}
session.ch <- textInputState{Error: err}
session.end()
}
return session.ch, func() {
@@ -144,13 +144,13 @@ func (t *textInput) wndProc(hWnd uintptr, uMsg uint32, wParam, lParam uintptr) u
if lParam&(_GCS_RESULTSTR|_GCS_COMPSTR) != 0 {
if lParam&_GCS_RESULTSTR != 0 {
if err := t.commit(); err != nil {
t.session.ch <- State{Error: err}
t.session.ch <- textInputState{Error: err}
t.end()
}
}
if lParam&_GCS_COMPSTR != 0 {
if err := t.update(); err != nil {
t.session.ch <- State{Error: err}
t.session.ch <- textInputState{Error: err}
t.end()
}
}
@@ -194,7 +194,7 @@ func (t *textInput) wndProc(hWnd uintptr, uMsg uint32, wParam, lParam uintptr) u
// send must be called from the main thread.
func (t *textInput) send(text string, startInBytes, endInBytes int, committed bool) {
if t.session != nil {
t.session.trySend(State{
t.session.trySend(textInputState{
Text: text,
CompositionSelectionStartInBytes: startInBytes,
CompositionSelectionEndInBytes: endInBytes,