Move messeji into etk

This commit is contained in:
Trevor Slocum 2024-01-16 13:00:20 -08:00
parent 087a0faa19
commit 6e0f6f1ee3
19 changed files with 1707 additions and 20 deletions

View file

@ -3,7 +3,7 @@ package etk
import (
"image"
"code.rocketnine.space/tslocum/messeji"
"code.rocket9labs.com/tslocum/etk/messeji"
"github.com/hajimehoshi/ebiten/v2"
)

View file

@ -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
View file

@ -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
View file

@ -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=

View file

@ -3,7 +3,7 @@ package etk
import (
"image"
"code.rocketnine.space/tslocum/messeji"
"code.rocket9labs.com/tslocum/etk/messeji"
"github.com/hajimehoshi/ebiten/v2"
)

View file

@ -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
View 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
View file

@ -0,0 +1,4 @@
/*
Package messeji provides text widgets for Ebitengine.
*/
package messeji

View 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)
}

View 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() {}

View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

5
messeji/testdata/long.txt vendored Normal file
View file

@ -0,0 +1,5 @@
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

7
messeji/testdata/short.txt vendored Normal file
View 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

File diff suppressed because it is too large Load diff

143
messeji/textfield_test.go Normal file
View 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()
}
})
}
}

View file

@ -4,7 +4,7 @@ import (
"image"
"image/color"
"code.rocketnine.space/tslocum/messeji"
"code.rocket9labs.com/tslocum/etk/messeji"
"github.com/hajimehoshi/ebiten/v2"
)

View file

@ -4,7 +4,7 @@ import (
"image"
"image/color"
"code.rocketnine.space/tslocum/messeji"
"code.rocket9labs.com/tslocum/etk/messeji"
"github.com/hajimehoshi/ebiten/v2"
)