1
0

field.go 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. // Copyright 2024 The Ebitengine Authors
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package textinput
  15. import (
  16. "sync"
  17. )
  18. var (
  19. theFocusedField *Field
  20. theFocusedFieldM sync.Mutex
  21. )
  22. func focusField(f *Field) {
  23. var origField *Field
  24. defer func() {
  25. if origField != nil {
  26. origField.cleanUp()
  27. }
  28. }()
  29. theFocusedFieldM.Lock()
  30. defer theFocusedFieldM.Unlock()
  31. if theFocusedField == f {
  32. return
  33. }
  34. origField = theFocusedField
  35. theFocusedField = f
  36. }
  37. func blurField(f *Field) {
  38. var origField *Field
  39. defer func() {
  40. if origField != nil {
  41. origField.cleanUp()
  42. }
  43. }()
  44. theFocusedFieldM.Lock()
  45. defer theFocusedFieldM.Unlock()
  46. if theFocusedField != f {
  47. return
  48. }
  49. origField = theFocusedField
  50. theFocusedField = nil
  51. }
  52. func isFieldFocused(f *Field) bool {
  53. theFocusedFieldM.Lock()
  54. defer theFocusedFieldM.Unlock()
  55. return theFocusedField == f
  56. }
  57. // Field is a region accepting text inputting with IME.
  58. //
  59. // Field is not focused by default. You have to call Focus when you start text inputting.
  60. //
  61. // Field is a wrapper of the low-level API like Start.
  62. //
  63. // For an actual usage, see the examples "textinput".
  64. type Field struct {
  65. text string
  66. selectionStart int
  67. selectionEnd int
  68. ch chan State
  69. end func()
  70. state State
  71. err error
  72. }
  73. // HandleInput updates the field state.
  74. // HandleInput must be called every tick, i.e., every Update, when Field is focused.
  75. // HandleInput takes a position where an IME window is shown if needed.
  76. //
  77. // HandleInput returns whether the text inputting is handled or not.
  78. // If HandleInput returns true, a Field user should not handle further input events.
  79. //
  80. // HandleInput returns an error when handling input causes an error.
  81. func (f *Field) HandleInput(x, y int) (handled bool, err error) {
  82. if f.err != nil {
  83. return false, f.err
  84. }
  85. if !f.IsFocused() {
  86. return false, nil
  87. }
  88. // Text inputting can happen multiple times in one tick (1/60[s] by default).
  89. // Handle all of them.
  90. for {
  91. if f.ch == nil {
  92. // TODO: On iOS Safari, Start doesn't work as expected (#2898).
  93. // Handle a click event and focus the textarea there.
  94. f.ch, f.end = Start(x, y)
  95. // Start returns nil for non-supported envrionments.
  96. if f.ch == nil {
  97. return false, nil
  98. }
  99. }
  100. readchar:
  101. for {
  102. select {
  103. case state, ok := <-f.ch:
  104. if state.Error != nil {
  105. f.err = state.Error
  106. return false, f.err
  107. }
  108. handled = true
  109. if !ok {
  110. f.ch = nil
  111. f.end = nil
  112. f.state = State{}
  113. break readchar
  114. }
  115. if state.Committed {
  116. f.text = f.text[:f.selectionStart] + state.Text + f.text[f.selectionEnd:]
  117. f.selectionStart += len(state.Text)
  118. f.selectionEnd = f.selectionStart
  119. f.state = State{}
  120. continue
  121. }
  122. f.state = state
  123. default:
  124. break readchar
  125. }
  126. }
  127. if f.ch == nil {
  128. continue
  129. }
  130. break
  131. }
  132. return
  133. }
  134. // Focus focuses the field.
  135. // A Field has to be focused to start text inputting.
  136. //
  137. // There can be only one Field that is focused at the same time.
  138. // When Focus is called and there is already a focused field, Focus removes the focus of that.
  139. func (f *Field) Focus() {
  140. focusField(f)
  141. }
  142. // Blur removes the focus from the field.
  143. func (f *Field) Blur() {
  144. blurField(f)
  145. }
  146. // IsFocused reports whether the field is focused or not.
  147. func (f *Field) IsFocused() bool {
  148. return isFieldFocused(f)
  149. }
  150. func (f *Field) cleanUp() {
  151. if f.err != nil {
  152. return
  153. }
  154. // If the text field still has a session, read the last state and process it just in case.
  155. if f.ch != nil {
  156. select {
  157. case state, ok := <-f.ch:
  158. if state.Error != nil {
  159. f.err = state.Error
  160. return
  161. }
  162. if ok && state.Committed {
  163. f.text = f.text[:f.selectionStart] + state.Text + f.text[f.selectionEnd:]
  164. f.selectionStart += len(state.Text)
  165. f.selectionEnd = f.selectionStart
  166. f.state = State{}
  167. }
  168. f.state = state
  169. default:
  170. break
  171. }
  172. }
  173. if f.end != nil {
  174. f.end()
  175. f.ch = nil
  176. f.end = nil
  177. f.state = State{}
  178. }
  179. }
  180. // Selection returns the current selection range in bytes.
  181. func (f *Field) Selection() (start, end int) {
  182. return f.selectionStart, f.selectionEnd
  183. }
  184. // CompositionSelection returns the current composition selection in bytes if a text is composited.
  185. // If a text is not composited, this returns 0s and false.
  186. // The returned values indicate relative positions in bytes where the current composition text's start is 0.
  187. func (f *Field) CompositionSelection() (start, end int, ok bool) {
  188. if f.IsFocused() && f.state.Text != "" {
  189. return f.state.CompositionSelectionStartInBytes, f.state.CompositionSelectionEndInBytes, true
  190. }
  191. return 0, 0, false
  192. }
  193. // SetSelection sets the selection range.
  194. func (f *Field) SetSelection(start, end int) {
  195. f.cleanUp()
  196. f.selectionStart = start
  197. f.selectionEnd = end
  198. }
  199. // Text returns the current text.
  200. // The returned value doesn't include compositing texts.
  201. func (f *Field) Text() string {
  202. return f.text
  203. }
  204. // TextForRendering returns the text for rendering.
  205. // The returned value includes compositing texts.
  206. func (f *Field) TextForRendering() string {
  207. if f.IsFocused() && f.state.Text != "" {
  208. return f.text[:f.selectionStart] + f.state.Text + f.text[f.selectionEnd:]
  209. }
  210. return f.text
  211. }
  212. // SetTextAndSelection sets the text and the selection range.
  213. func (f *Field) SetTextAndSelection(text string, selectionStart, selectionEnd int) {
  214. f.cleanUp()
  215. f.text = text
  216. f.selectionStart = selectionStart
  217. f.selectionEnd = selectionEnd
  218. }