textinput_darwin.go 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. // Copyright 2023 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. //go:build !ios
  15. package textinput
  16. // #cgo CFLAGS: -x objective-c
  17. // #cgo LDFLAGS: -framework Cocoa
  18. import "C"
  19. import (
  20. "github.com/ebitengine/purego/objc"
  21. "github.com/hajimehoshi/ebiten/v2/internal/ui"
  22. )
  23. type textInput struct {
  24. // session must be accessed from the main thread.
  25. session *session
  26. }
  27. var theTextInput textInput
  28. func (t *textInput) Start(x, y int) (chan State, func()) {
  29. var session *session
  30. ui.Get().RunOnMainThread(func() {
  31. t.end()
  32. start(x, y)
  33. session = newSession()
  34. t.session = session
  35. })
  36. return session.ch, session.end
  37. }
  38. //export ebitengine_textinput_update
  39. func ebitengine_textinput_update(text *C.char, start, end C.int, committed C.int) {
  40. theTextInput.update(C.GoString(text), int(start), int(end), committed != 0)
  41. }
  42. func (t *textInput) update(text string, start, end int, committed bool) {
  43. if t.session != nil {
  44. startInBytes := convertUTF16CountToByteCount(text, start)
  45. endInBytes := convertUTF16CountToByteCount(text, end)
  46. t.session.trySend(State{
  47. Text: text,
  48. CompositionSelectionStartInBytes: startInBytes,
  49. CompositionSelectionEndInBytes: endInBytes,
  50. Committed: committed,
  51. })
  52. }
  53. if committed {
  54. t.end()
  55. }
  56. }
  57. //export ebitengine_textinput_end
  58. func ebitengine_textinput_end() {
  59. theTextInput.end()
  60. }
  61. func (t *textInput) end() {
  62. if t.session != nil {
  63. t.session.end()
  64. t.session = nil
  65. }
  66. }
  67. var (
  68. selAddSubview = objc.RegisterName("addSubview:")
  69. selAlloc = objc.RegisterName("alloc")
  70. selContentView = objc.RegisterName("contentView")
  71. selFrame = objc.RegisterName("frame")
  72. selInit = objc.RegisterName("init")
  73. selMainWindow = objc.RegisterName("mainWindow")
  74. selMakeFirstResponder = objc.RegisterName("makeFirstResponder:")
  75. selSetFrame = objc.RegisterName("setFrame:")
  76. selSharedApplication = objc.RegisterName("sharedApplication")
  77. idNSApplication = objc.ID(objc.GetClass("NSApplication"))
  78. )
  79. var theTextInputClient objc.ID
  80. func getTextInputClient() objc.ID {
  81. if theTextInputClient == 0 {
  82. class := objc.ID(objc.GetClass("TextInputClient"))
  83. theTextInputClient = class.Send(selAlloc).Send(selInit)
  84. }
  85. return theTextInputClient
  86. }
  87. type nsPoint struct {
  88. x float64
  89. y float64
  90. }
  91. type nsSize struct {
  92. width float64
  93. height float64
  94. }
  95. type nsRect struct {
  96. origin nsPoint
  97. size nsSize
  98. }
  99. func start(x, y int) {
  100. t := getTextInputClient()
  101. window := idNSApplication.Send(selSharedApplication).Send(selMainWindow)
  102. contentView := window.Send(selContentView)
  103. contentView.Send(selAddSubview, t)
  104. window.Send(selMakeFirstResponder, t)
  105. r := objc.Send[nsRect](contentView, selFrame)
  106. y = int(r.size.height) - y - 4
  107. t.Send(selSetFrame, nsRect{
  108. origin: nsPoint{float64(x), float64(y)},
  109. size: nsSize{1, 1},
  110. })
  111. }