Move messeji into etk
This commit is contained in:
parent
087a0faa19
commit
6e0f6f1ee3
19 changed files with 1707 additions and 20 deletions
|
@ -3,7 +3,7 @@ package etk
|
|||
import (
|
||||
"image"
|
||||
|
||||
"code.rocketnine.space/tslocum/messeji"
|
||||
"code.rocket9labs.com/tslocum/etk/messeji"
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
)
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
"log"
|
||||
|
||||
"code.rocket9labs.com/tslocum/etk"
|
||||
"code.rocketnine.space/tslocum/messeji"
|
||||
"code.rocket9labs.com/tslocum/etk/messeji"
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
)
|
||||
|
||||
|
|
5
go.mod
5
go.mod
|
@ -3,7 +3,6 @@ module code.rocket9labs.com/tslocum/etk
|
|||
go 1.18
|
||||
|
||||
require (
|
||||
code.rocketnine.space/tslocum/messeji v1.0.6-0.20240109205105-4ffeffdd2441
|
||||
github.com/hajimehoshi/ebiten/v2 v2.6.3
|
||||
github.com/llgcode/draw2d v0.0.0-20231212091825-f55e0c776b44
|
||||
golang.org/x/image v0.15.0
|
||||
|
@ -13,8 +12,8 @@ require (
|
|||
github.com/ebitengine/purego v0.5.1 // indirect
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
||||
github.com/jezek/xgb v1.1.1 // indirect
|
||||
golang.org/x/exp/shiny v0.0.0-20240110193028-0dcbfd608b1e // indirect
|
||||
golang.org/x/mobile v0.0.0-20231127183840-76ac6878050a // indirect
|
||||
golang.org/x/exp/shiny v0.0.0-20240112132812-db7319d0e0e3 // indirect
|
||||
golang.org/x/mobile v0.0.0-20240112133503-c713f31d574b // indirect
|
||||
golang.org/x/sync v0.6.0 // indirect
|
||||
golang.org/x/sys v0.16.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
|
|
10
go.sum
10
go.sum
|
@ -1,5 +1,3 @@
|
|||
code.rocketnine.space/tslocum/messeji v1.0.6-0.20240109205105-4ffeffdd2441 h1:pe5QsaN6Tvil0Y+jodbMLfdMWaL5MBb2vyorBIfQwqk=
|
||||
code.rocketnine.space/tslocum/messeji v1.0.6-0.20240109205105-4ffeffdd2441/go.mod h1:cznUGfvC7BKbc5sx4I36XpLsF0ar3TPJYZlrND0IlDQ=
|
||||
github.com/ebitengine/purego v0.5.1 h1:hNunhThpOf1vzKl49v6YxIsXLhl92vbBEv1/2Ez3ZrY=
|
||||
github.com/ebitengine/purego v0.5.1/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||
|
@ -12,12 +10,12 @@ github.com/jezek/xgb v1.1.1/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFl
|
|||
github.com/llgcode/draw2d v0.0.0-20231212091825-f55e0c776b44 h1:1ad70i0s40IpMtRm2ST+Nvr03X7mlHWtdALYkFNrlxk=
|
||||
github.com/llgcode/draw2d v0.0.0-20231212091825-f55e0c776b44/go.mod h1:muweRyJCZ1mZSMiCgYbAicfnwZFoeHpNr6A6QBu+rBg=
|
||||
github.com/llgcode/ps v0.0.0-20210114104736-f4b0c5d1e02e h1:ZAvbj5hI/G/EbAYAcj4yCXUNiFKefEhH0qfImDDD0/8=
|
||||
golang.org/x/exp/shiny v0.0.0-20240110193028-0dcbfd608b1e h1:4LE5KLCSN2kM0m9kpTjPEbOT9I1f6Yxfx9rKlYtPu0Y=
|
||||
golang.org/x/exp/shiny v0.0.0-20240110193028-0dcbfd608b1e/go.mod h1:3F+MieQB7dRYLTmnncoFbb1crS5lfQoTfDgQy6K4N0o=
|
||||
golang.org/x/exp/shiny v0.0.0-20240112132812-db7319d0e0e3 h1:NezsOJwoBjJ5AXH5QQCdxe+WsqLw+f/t8eo1Tacfhqs=
|
||||
golang.org/x/exp/shiny v0.0.0-20240112132812-db7319d0e0e3/go.mod h1:3F+MieQB7dRYLTmnncoFbb1crS5lfQoTfDgQy6K4N0o=
|
||||
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
|
||||
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
|
||||
golang.org/x/mobile v0.0.0-20231127183840-76ac6878050a h1:sYbmY3FwUWCBTodZL1S3JUuOvaW6kM2o+clDzzDNBWg=
|
||||
golang.org/x/mobile v0.0.0-20231127183840-76ac6878050a/go.mod h1:Ede7gF0KGoHlj822RtphAHK1jLdrcuRBZg0sF1Q+SPc=
|
||||
golang.org/x/mobile v0.0.0-20240112133503-c713f31d574b h1:kfWLZgb8iUBHdE9WydD5V5dHIS/F6HjlBZNyJfn2bs4=
|
||||
golang.org/x/mobile v0.0.0-20240112133503-c713f31d574b/go.mod h1:4efzQnuA1nICq6h4kmZRMGzbPiP06lZvgADUu1VpJCE=
|
||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
||||
|
|
2
input.go
2
input.go
|
@ -3,7 +3,7 @@ package etk
|
|||
import (
|
||||
"image"
|
||||
|
||||
"code.rocketnine.space/tslocum/messeji"
|
||||
"code.rocket9labs.com/tslocum/etk/messeji"
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
)
|
||||
|
||||
|
|
|
@ -1,22 +1,22 @@
|
|||
# kibodo
|
||||
[![GoDoc](https://code.rocketnine.space/tslocum/godoc-static/raw/branch/master/badge.svg)](https://docs.rocketnine.space/code.rocketnine.space/tslocum/etk/kibodo)
|
||||
[![Donate via LiberaPay](https://img.shields.io/liberapay/receives/rocketnine.space.svg?logo=liberapay)](https://liberapay.com/rocketnine.space)
|
||||
[![GoDoc](https://code.rocket9labs.com/tslocum/godoc-static/raw/branch/master/badge.svg)](https://docs.rocket9labs.com/code.rocket9labs.com/tslocum/etk/kibodo)
|
||||
[![Donate via LiberaPay](https://img.shields.io/liberapay/receives/rocket9labs.com.svg?logo=liberapay)](https://liberapay.com/rocket9labs.com)
|
||||
[![Donate via Patreon](https://img.shields.io/badge/dynamic/json?color=%23e85b46&label=Patreon&query=data.attributes.patron_count&suffix=%20patrons&url=https%3A%2F%2Fwww.patreon.com%2Fapi%2Fcampaigns%2F5252223)](https://www.patreon.com/rocketnine)
|
||||
|
||||
On-screen keyboard widget for [Ebitengine](https://github.com/hajimehoshi/ebiten)
|
||||
|
||||
## Demo
|
||||
|
||||
[**Try kibodo**](https://kibodo.rocketnine.space)
|
||||
[**Try kibodo**](https://kibodo.rocket9labs.com)
|
||||
|
||||
## Documentation
|
||||
|
||||
Documentation is available via [godoc](https://docs.rocketnine.space/code.rocketnine.space/tslocum/etk/kibodo).
|
||||
Documentation is available via [godoc](https://docs.rocket9labs.com/code.rocket9labs.com/tslocum/etk/kibodo).
|
||||
|
||||
## Support
|
||||
|
||||
Please share issues and suggestions [here](https://code.rocketnine.space/tslocum/etk/issues).
|
||||
Please share issues and suggestions [here](https://code.rocket9labs.com/tslocum/etk/issues).
|
||||
|
||||
## Screenshot
|
||||
|
||||
[![Screenshot](https://code.rocketnine.space/tslocum/etk/raw/branch/main/kibodo/screenshot.png)](https://code.rocketnine.space/tslocum/etk/src/branch/main/etk/screenshot.png)
|
||||
[![Screenshot](https://code.rocket9labs.com/tslocum/etk/raw/branch/main/kibodo/screenshot.png)](https://code.rocket9labs.com/tslocum/etk/src/branch/main/etk/screenshot.png)
|
||||
|
|
26
messeji/README.md
Normal file
26
messeji/README.md
Normal file
|
@ -0,0 +1,26 @@
|
|||
# messeji
|
||||
[![GoDoc](https://code.rocket9labs.com/tslocum/godoc-static/raw/branch/master/badge.svg)](https://docs.rocket9labs.com/code.rocket9labs.com/tslocum/etk/messeji)
|
||||
[![Donate via LiberaPay](https://img.shields.io/liberapay/receives/rocket9labs.com.svg?logo=liberapay)](https://liberapay.com/rocket9labs.com)
|
||||
[![Donate via Patreon](https://img.shields.io/badge/dynamic/json?color=%23e85b46&label=Patreon&query=data.attributes.patron_count&suffix=%20patrons&url=https%3A%2F%2Fwww.patreon.com%2Fapi%2Fcampaigns%2F5252223)](https://www.patreon.com/rocketnine)
|
||||
|
||||
Text widgets for [Ebitengine](https://github.com/hajimehoshi/ebiten)
|
||||
|
||||
**Note:** [IME](https://en.wikipedia.org/wiki/Input_method) is not yet supported.
|
||||
|
||||
## Demo
|
||||
|
||||
[**Try messeji**](https://messeji.rocket9labs.com)
|
||||
|
||||
The code for this demo is located in `examples/messeji`.
|
||||
|
||||
## Documentation
|
||||
|
||||
Documentation is available via [godoc](https://docs.rocket9labs.com/code.rocket9labs.com/tslocum/etk/messeji).
|
||||
|
||||
## Support
|
||||
|
||||
Please share issues and suggestions [here](https://code.rocket9labs.com/tslocum/etk/issues).
|
||||
|
||||
## Screenshot
|
||||
|
||||
[![Screenshot](https://code.rocket9labs.com/tslocum/etk/raw/branch/main/messeji/screenshot.png)](https://code.rocket9labs.com/tslocum/etk/raw/branch/main/messeji/screenshot.png)
|
4
messeji/doc.go
Normal file
4
messeji/doc.go
Normal file
|
@ -0,0 +1,4 @@
|
|||
/*
|
||||
Package messeji provides text widgets for Ebitengine.
|
||||
*/
|
||||
package messeji
|
158
messeji/examples/messeji/game/game.go
Normal file
158
messeji/examples/messeji/game/game.go
Normal file
|
@ -0,0 +1,158 @@
|
|||
//go:build example
|
||||
|
||||
package game
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"log"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"code.rocket9labs.com/tslocum/etk/messeji"
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"github.com/hajimehoshi/ebiten/v2/examples/resources/fonts"
|
||||
"github.com/hajimehoshi/ebiten/v2/inpututil"
|
||||
"golang.org/x/image/font"
|
||||
"golang.org/x/image/font/opentype"
|
||||
)
|
||||
|
||||
const initialText = `
|
||||
Welcome to the メッセージ (messeji) text widgets demo.
|
||||
This is a TextField, which can be used to display text.
|
||||
Below is an InputField, which accepts keyboard input.
|
||||
<Tab> to cycle horizontal alignment.
|
||||
<Enter> to append input text to buffer.
|
||||
<Ctrl+Tab> to toggle word wrap.
|
||||
<Ctrl+Enter> to toggle multi-line input.
|
||||
`
|
||||
|
||||
var (
|
||||
mplusNormalFont font.Face
|
||||
fontMutex *sync.Mutex
|
||||
)
|
||||
|
||||
func init() {
|
||||
tt, err := opentype.Parse(fonts.MPlus1pRegular_ttf)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
const dpi = 72
|
||||
mplusNormalFont, err = opentype.NewFace(tt, &opentype.FaceOptions{
|
||||
Size: 28,
|
||||
DPI: dpi,
|
||||
Hinting: font.HintingFull,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
type game struct {
|
||||
w, h int
|
||||
|
||||
buffer *messeji.TextField
|
||||
|
||||
input *messeji.InputField
|
||||
|
||||
singleLine bool
|
||||
|
||||
op *ebiten.DrawImageOptions
|
||||
|
||||
spinnerIndex int
|
||||
|
||||
horizontal messeji.Alignment
|
||||
}
|
||||
|
||||
// NewDemoGame returns a new messeji demo game.
|
||||
func NewDemoGame() *game {
|
||||
g := &game{
|
||||
buffer: messeji.NewTextField(mplusNormalFont, fontMutex),
|
||||
input: messeji.NewInputField(mplusNormalFont, fontMutex),
|
||||
op: &ebiten.DrawImageOptions{
|
||||
Filter: ebiten.FilterNearest,
|
||||
},
|
||||
}
|
||||
|
||||
g.buffer.SetText(strings.TrimSpace(initialText))
|
||||
g.buffer.SetPadding(7)
|
||||
|
||||
g.input.SetHandleKeyboard(true)
|
||||
g.input.SetSelectedFunc(func() (accept bool) {
|
||||
log.Printf("Input: %s", g.input.Text())
|
||||
|
||||
g.buffer.Write([]byte(fmt.Sprintf("\nInput: %s", g.input.Text())))
|
||||
|
||||
return true
|
||||
})
|
||||
g.input.SetPadding(7)
|
||||
|
||||
return g
|
||||
}
|
||||
|
||||
func (g *game) Layout(outsideWidth, outsideHeight int) (int, int) {
|
||||
if outsideWidth == g.w && outsideHeight == g.h {
|
||||
return outsideWidth, outsideHeight
|
||||
}
|
||||
|
||||
padding := 10
|
||||
|
||||
w, h := outsideWidth-padding*2, g.input.LineHeight()*3+g.input.Padding()*2
|
||||
if h > outsideHeight-padding {
|
||||
h = outsideHeight - padding
|
||||
}
|
||||
|
||||
x, y := outsideWidth/2-w/2, outsideHeight-h-padding
|
||||
|
||||
g.buffer.SetRect(image.Rect(padding, padding, outsideWidth-padding, y-padding))
|
||||
|
||||
g.input.SetRect(image.Rect(x, y, x+w, y+h))
|
||||
|
||||
g.w, g.h = outsideWidth, outsideHeight
|
||||
return outsideWidth, outsideHeight
|
||||
}
|
||||
|
||||
func (g *game) Update() error {
|
||||
if (inpututil.IsKeyJustPressed(ebiten.KeyEnter) || inpututil.IsKeyJustPressed(ebiten.KeyKPEnter)) && ebiten.IsKeyPressed(ebiten.KeyControl) {
|
||||
g.singleLine = !g.singleLine
|
||||
g.input.SetSingleLine(g.singleLine)
|
||||
return nil
|
||||
} else if (inpututil.IsKeyJustPressed(ebiten.KeyEnter) || inpututil.IsKeyJustPressed(ebiten.KeyKPEnter)) && !g.input.Visible() {
|
||||
g.input.SetVisible(true)
|
||||
return nil
|
||||
}
|
||||
|
||||
if inpututil.IsKeyJustPressed(ebiten.KeyTab) && ebiten.IsKeyPressed(ebiten.KeyControl) {
|
||||
wrap := g.buffer.WordWrap()
|
||||
g.buffer.SetWordWrap(!wrap)
|
||||
g.input.SetWordWrap(!wrap)
|
||||
return nil
|
||||
} else if inpututil.IsKeyJustPressed(ebiten.KeyTab) {
|
||||
g.horizontal++
|
||||
if g.horizontal > messeji.AlignEnd {
|
||||
g.horizontal = messeji.AlignStart
|
||||
}
|
||||
g.buffer.SetHorizontal(g.horizontal)
|
||||
return nil
|
||||
}
|
||||
|
||||
err := g.buffer.Update()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update buffer: %s", err)
|
||||
}
|
||||
|
||||
err = g.input.Update()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update input field: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *game) Draw(screen *ebiten.Image) {
|
||||
// Draw display field.
|
||||
g.buffer.Draw(screen)
|
||||
|
||||
// Draw input field.
|
||||
g.input.Draw(screen)
|
||||
}
|
18
messeji/examples/messeji/game/mobile/mobile.go
Normal file
18
messeji/examples/messeji/game/mobile/mobile.go
Normal file
|
@ -0,0 +1,18 @@
|
|||
//go:build example
|
||||
|
||||
package mobile
|
||||
|
||||
import (
|
||||
"code.rocket9labs.com/tslocum/etk/messeji/examples/messeji/game"
|
||||
"github.com/hajimehoshi/ebiten/v2/mobile"
|
||||
)
|
||||
|
||||
func init() {
|
||||
mobile.SetGame(game.NewDemoGame())
|
||||
}
|
||||
|
||||
// Dummy is a dummy exported function.
|
||||
//
|
||||
// gomobile will only compile packages that include at least one exported function.
|
||||
// Dummy forces gomobile to compile this package.
|
||||
func Dummy() {}
|
23
messeji/examples/messeji/main.go
Normal file
23
messeji/examples/messeji/main.go
Normal file
|
@ -0,0 +1,23 @@
|
|||
//go:build example
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"code.rocket9labs.com/tslocum/etk/messeji/examples/messeji/game"
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ebiten.SetWindowTitle("メッセージ")
|
||||
ebiten.SetWindowResizable(true)
|
||||
ebiten.SetWindowSize(640, 480)
|
||||
ebiten.SetMaxTPS(144)
|
||||
ebiten.SetFPSMode(ebiten.FPSModeVsyncOffMinimum)
|
||||
|
||||
g := game.NewDemoGame()
|
||||
if err := ebiten.RunGame(g); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
214
messeji/inputfield.go
Normal file
214
messeji/inputfield.go
Normal file
|
@ -0,0 +1,214 @@
|
|||
package messeji
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"github.com/hajimehoshi/ebiten/v2/inpututil"
|
||||
"golang.org/x/image/font"
|
||||
)
|
||||
|
||||
// InputField is a text input field. Call Update and Draw when your Game's
|
||||
// Update and Draw methods are called.
|
||||
//
|
||||
// Note: A position and size must be set via SetRect before the field will appear.
|
||||
// Keyboard events are not handled by default, and may be enabled via SetHandleKeyboard.
|
||||
type InputField struct {
|
||||
*TextField
|
||||
|
||||
// changedFunc is a function which is called when the text buffer is changed.
|
||||
// The function may return false to skip adding the rune to the text buffer.
|
||||
changedFunc func(r rune) (accept bool)
|
||||
|
||||
// selectedFunc is a function which is called when the enter key is pressed. The
|
||||
// function may return true to clear the text buffer.
|
||||
selectedFunc func() (accept bool)
|
||||
|
||||
// readBuffer is where incoming runes are stored before being added to the input buffer.
|
||||
readBuffer []rune
|
||||
|
||||
// keyBuffer is where incoming keys are stored before being added to the input buffer.
|
||||
keyBuffer []ebiten.Key
|
||||
|
||||
// rawRuneBuffer is where incoming raw runes are stored before being added to the input buffer.
|
||||
rawRuneBuffer []rune
|
||||
|
||||
// rawKeyBuffer is where incoming raw keys are stored before being added to the input buffer.
|
||||
rawKeyBuffer []ebiten.Key
|
||||
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
// NewInputField returns a new InputField. See type documentation for more info.
|
||||
func NewInputField(face font.Face, faceMutex *sync.Mutex) *InputField {
|
||||
f := &InputField{
|
||||
TextField: NewTextField(face, faceMutex),
|
||||
}
|
||||
f.TextField.suffix = "_"
|
||||
return f
|
||||
}
|
||||
|
||||
// SetHandleKeyboard sets a flag controlling whether keyboard input should be handled
|
||||
// by the field. This can be used to facilitate focus changes between multiple inputs.
|
||||
func (f *InputField) SetHandleKeyboard(handle bool) {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
f.handleKeyboard = handle
|
||||
}
|
||||
|
||||
// SetChangedFunc sets a handler which is called when the text buffer is changed.
|
||||
// The handler may return true to add the rune to the text buffer.
|
||||
func (f *InputField) SetChangedFunc(changedFunc func(r rune) (accept bool)) {
|
||||
f.changedFunc = changedFunc
|
||||
}
|
||||
|
||||
// SetSelectedFunc sets a handler which is called when the enter key is pressed.
|
||||
// Providing a nil function value will remove the existing handler (if set).
|
||||
// The handler may return true to clear the text buffer.
|
||||
func (f *InputField) SetSelectedFunc(selectedFunc func() (accept bool)) {
|
||||
f.selectedFunc = selectedFunc
|
||||
}
|
||||
|
||||
// HandleKeyboardEvent passes the provided key or rune to the Inputfield.
|
||||
func (f *InputField) HandleKeyboardEvent(key ebiten.Key, r rune) (handled bool, err error) {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
if !f.visible || rectIsZero(f.r) {
|
||||
return
|
||||
}
|
||||
|
||||
if !f.handleKeyboard {
|
||||
return
|
||||
}
|
||||
|
||||
// Handle rune event.
|
||||
if r > 0 {
|
||||
f.handleRunes([]rune{r})
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Handle key event.
|
||||
f.handleKeys([]ebiten.Key{key})
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (f *InputField) handleRunes(runes []rune) bool {
|
||||
var redraw bool
|
||||
for _, r := range runes {
|
||||
if f.changedFunc != nil {
|
||||
f.Unlock()
|
||||
accept := f.changedFunc(r)
|
||||
f.Lock()
|
||||
|
||||
if !accept {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
f.TextField._write([]byte(string(r)))
|
||||
redraw = true
|
||||
}
|
||||
|
||||
return redraw
|
||||
}
|
||||
|
||||
func (f *InputField) handleKeys(keys []ebiten.Key) bool {
|
||||
var redraw bool
|
||||
for _, key := range keys {
|
||||
switch key {
|
||||
case ebiten.KeyBackspace:
|
||||
l := len(f.buffer)
|
||||
if l > 0 {
|
||||
var rewrap bool
|
||||
if len(f.incoming) != 0 {
|
||||
line := string(f.incoming)
|
||||
f.incoming = append(f.incoming, []byte(line[:len(line)-1])...)
|
||||
} else if len(f.buffer[l-1]) == 0 {
|
||||
f.buffer = f.buffer[:l-1]
|
||||
rewrap = true
|
||||
} else {
|
||||
line := string(f.buffer[l-1])
|
||||
f.buffer[l-1] = []byte(line[:len(line)-1])
|
||||
rewrap = true
|
||||
}
|
||||
if rewrap && (f.needWrap == -1 || f.needWrap > l-1) {
|
||||
f.needWrap = l - 1
|
||||
}
|
||||
redraw = true
|
||||
f.modified = true
|
||||
f.redraw = true
|
||||
}
|
||||
case ebiten.KeyEnter, ebiten.KeyKPEnter:
|
||||
if f.selectedFunc != nil {
|
||||
f.Unlock()
|
||||
accept := f.selectedFunc()
|
||||
f.Lock()
|
||||
|
||||
// Clear input buffer.
|
||||
if accept {
|
||||
f.incoming = f.incoming[:0]
|
||||
f.buffer = f.buffer[:0]
|
||||
f.bufferWrapped = f.bufferWrapped[:0]
|
||||
f.lineWidths = f.lineWidths[:0]
|
||||
f.needWrap = 0
|
||||
f.wrapStart = 0
|
||||
f.modified = true
|
||||
f.redraw = true
|
||||
redraw = true
|
||||
}
|
||||
} else if !f.singleLine {
|
||||
// Append newline.
|
||||
f.incoming = append(f.incoming, '\n')
|
||||
f.modified = true
|
||||
f.redraw = true
|
||||
redraw = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return redraw
|
||||
}
|
||||
|
||||
// Update updates the input field. This function should be called when
|
||||
// Game.Update is called.
|
||||
func (f *InputField) Update() error {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
if !f.visible || rectIsZero(f.r) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !f.handleKeyboard {
|
||||
return f.TextField.Update()
|
||||
}
|
||||
|
||||
var redraw bool
|
||||
|
||||
// Handler rune input.
|
||||
f.readBuffer = ebiten.AppendInputChars(f.readBuffer[:0])
|
||||
if f.handleRunes(f.readBuffer) {
|
||||
redraw = true
|
||||
}
|
||||
if f.handleRunes(f.rawRuneBuffer) {
|
||||
redraw = true
|
||||
}
|
||||
f.rawRuneBuffer = f.rawRuneBuffer[:0]
|
||||
|
||||
// Handle key input.
|
||||
f.keyBuffer = inpututil.AppendJustPressedKeys(f.keyBuffer[:0])
|
||||
if f.handleKeys(f.keyBuffer) {
|
||||
redraw = true
|
||||
}
|
||||
if f.handleKeys(f.rawKeyBuffer) {
|
||||
redraw = true
|
||||
}
|
||||
f.rawKeyBuffer = f.rawKeyBuffer[:0]
|
||||
|
||||
if redraw {
|
||||
f.bufferModified()
|
||||
}
|
||||
|
||||
return f.TextField.Update()
|
||||
}
|
BIN
messeji/screenshot.png
Normal file
BIN
messeji/screenshot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 43 KiB |
5
messeji/testdata/long.txt
vendored
Normal file
5
messeji/testdata/long.txt
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
7
messeji/testdata/short.txt
vendored
Normal file
7
messeji/testdata/short.txt
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
|
||||
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
|
||||
|
||||
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
|
||||
|
||||
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
|
1092
messeji/textfield.go
Normal file
1092
messeji/textfield.go
Normal file
File diff suppressed because it is too large
Load diff
143
messeji/textfield_test.go
Normal file
143
messeji/textfield_test.go
Normal file
|
@ -0,0 +1,143 @@
|
|||
package messeji
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"image"
|
||||
"log"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/examples/resources/fonts"
|
||||
"golang.org/x/image/font"
|
||||
"golang.org/x/image/font/opentype"
|
||||
)
|
||||
|
||||
//go:embed testdata
|
||||
var testDataFS embed.FS
|
||||
|
||||
var testTextField *TextField
|
||||
|
||||
func TestWrapContent(t *testing.T) {
|
||||
testCases := []struct {
|
||||
long bool // Short or long text.
|
||||
wordWrap bool // Enable wordwrap.
|
||||
|
||||
}{
|
||||
{false, false},
|
||||
{false, true},
|
||||
{true, false},
|
||||
{true, true},
|
||||
}
|
||||
|
||||
tt, err := opentype.Parse(fonts.MPlus1pRegular_ttf)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
const size = 24
|
||||
const dpi = 72
|
||||
face, err := opentype.NewFace(tt, &opentype.FaceOptions{
|
||||
Size: size,
|
||||
DPI: dpi,
|
||||
Hinting: font.HintingFull,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
testRect := image.Rect(0, 0, 200, 400)
|
||||
|
||||
for _, c := range testCases {
|
||||
var name string
|
||||
if !c.long {
|
||||
name = "short"
|
||||
} else {
|
||||
name = "long"
|
||||
}
|
||||
|
||||
content, err := testDataFS.ReadFile(fmt.Sprintf("testdata/%s.txt", name))
|
||||
if err != nil {
|
||||
t.Errorf("failed to open testdata: %s", err)
|
||||
}
|
||||
|
||||
if !c.wordWrap {
|
||||
name += "/wrapChar"
|
||||
} else {
|
||||
name += "/wrapWord"
|
||||
}
|
||||
|
||||
t.Run(name, func(t *testing.T) {
|
||||
textField := NewTextField(face, &sync.Mutex{})
|
||||
testTextField = textField
|
||||
textField.SetRect(testRect)
|
||||
textField.SetWordWrap(c.wordWrap)
|
||||
textField.Write(content)
|
||||
textField.bufferModified()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkWrapContent(b *testing.B) {
|
||||
testCases := []struct {
|
||||
long bool // Short or long text.
|
||||
wordWrap bool // Enable wordwrap.
|
||||
|
||||
}{
|
||||
{false, false},
|
||||
{false, true},
|
||||
{true, false},
|
||||
{true, true},
|
||||
}
|
||||
|
||||
tt, err := opentype.Parse(fonts.MPlus1pRegular_ttf)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
const size = 24
|
||||
const dpi = 72
|
||||
face, err := opentype.NewFace(tt, &opentype.FaceOptions{
|
||||
Size: size,
|
||||
DPI: dpi,
|
||||
Hinting: font.HintingFull,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
testRect := image.Rect(0, 0, 200, 400)
|
||||
|
||||
for _, c := range testCases {
|
||||
var name string
|
||||
if !c.long {
|
||||
name = "short"
|
||||
} else {
|
||||
name = "long"
|
||||
}
|
||||
|
||||
content, err := testDataFS.ReadFile(fmt.Sprintf("testdata/%s.txt", name))
|
||||
if err != nil {
|
||||
b.Errorf("failed to open testdata: %s", err)
|
||||
}
|
||||
|
||||
if !c.wordWrap {
|
||||
name += "/wrapChar"
|
||||
} else {
|
||||
name += "/wrapWord"
|
||||
}
|
||||
|
||||
textField := NewTextField(face, &sync.Mutex{})
|
||||
testTextField = textField
|
||||
textField.SetRect(testRect)
|
||||
textField.SetWordWrap(c.wordWrap)
|
||||
|
||||
b.Run(name, func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
textField.SetText("")
|
||||
textField.Write(content)
|
||||
textField.bufferModified()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@ import (
|
|||
"image"
|
||||
"image/color"
|
||||
|
||||
"code.rocketnine.space/tslocum/messeji"
|
||||
"code.rocket9labs.com/tslocum/etk/messeji"
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
)
|
||||
|
||||
|
|
2
text.go
2
text.go
|
@ -4,7 +4,7 @@ import (
|
|||
"image"
|
||||
"image/color"
|
||||
|
||||
"code.rocketnine.space/tslocum/messeji"
|
||||
"code.rocket9labs.com/tslocum/etk/messeji"
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
)
|
||||
|
||||
|
|
Loading…
Reference in a new issue