ebiten/exp/textinput/textinput_darwin.go
2024-04-04 12:41:11 +09:00

133 lines
3.3 KiB
Go

// Copyright 2023 The Ebitengine Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build !ios
package textinput
// #cgo CFLAGS: -x objective-c
// #cgo LDFLAGS: -framework Cocoa
import "C"
import (
"github.com/ebitengine/purego/objc"
"github.com/hajimehoshi/ebiten/v2/internal/ui"
)
type textInput struct {
// session must be accessed from the main thread.
session *session
}
var theTextInput textInput
func (t *textInput) Start(x, y int) (chan State, func()) {
var session *session
ui.Get().RunOnMainThread(func() {
t.end()
start(x, y)
session = newSession()
t.session = session
})
return session.ch, session.end
}
//export ebitengine_textinput_update
func ebitengine_textinput_update(text *C.char, start, end C.int, committed C.int) {
theTextInput.update(C.GoString(text), int(start), int(end), committed != 0)
}
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{
Text: text,
CompositionSelectionStartInBytes: startInBytes,
CompositionSelectionEndInBytes: endInBytes,
Committed: committed,
})
}
if committed {
t.end()
}
}
//export ebitengine_textinput_end
func ebitengine_textinput_end() {
theTextInput.end()
}
func (t *textInput) end() {
if t.session != nil {
t.session.end()
t.session = nil
}
}
var (
selAddSubview = objc.RegisterName("addSubview:")
selAlloc = objc.RegisterName("alloc")
selContentView = objc.RegisterName("contentView")
selFrame = objc.RegisterName("frame")
selInit = objc.RegisterName("init")
selMainWindow = objc.RegisterName("mainWindow")
selMakeFirstResponder = objc.RegisterName("makeFirstResponder:")
selSetFrame = objc.RegisterName("setFrame:")
selSharedApplication = objc.RegisterName("sharedApplication")
idNSApplication = objc.ID(objc.GetClass("NSApplication"))
)
var theTextInputClient objc.ID
func getTextInputClient() objc.ID {
if theTextInputClient == 0 {
class := objc.ID(objc.GetClass("TextInputClient"))
theTextInputClient = class.Send(selAlloc).Send(selInit)
}
return theTextInputClient
}
type nsPoint struct {
x float64
y float64
}
type nsSize struct {
width float64
height float64
}
type nsRect struct {
origin nsPoint
size nsSize
}
func start(x, y int) {
t := getTextInputClient()
window := idNSApplication.Send(selSharedApplication).Send(selMainWindow)
contentView := window.Send(selContentView)
contentView.Send(selAddSubview, t)
window.Send(selMakeFirstResponder, t)
r := objc.Send[nsRect](contentView, selFrame)
y = int(r.size.height) - y - 4
t.Send(selSetFrame, nsRect{
origin: nsPoint{float64(x), float64(y)},
size: nsSize{1, 1},
})
}