Use text/v2 to draw text
This commit is contained in:
parent
eba7ce7e2f
commit
746cdb2994
17 changed files with 147 additions and 179 deletions
|
@ -6,8 +6,7 @@ import (
|
|||
|
||||
"code.rocket9labs.com/tslocum/etk/messeji"
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"golang.org/x/image/font"
|
||||
"golang.org/x/image/font/sfnt"
|
||||
"github.com/hajimehoshi/ebiten/v2/text/v2"
|
||||
)
|
||||
|
||||
// Button is a clickable button.
|
||||
|
@ -15,7 +14,7 @@ type Button struct {
|
|||
*Box
|
||||
field *messeji.TextField
|
||||
btnBackground color.RGBA
|
||||
textFont *sfnt.Font
|
||||
textFont *text.GoTextFaceSource
|
||||
textSize int
|
||||
textAutoSize int
|
||||
borderSize int
|
||||
|
@ -124,7 +123,7 @@ func (b *Button) SetText(text string) {
|
|||
}
|
||||
|
||||
// SetFont sets the font and text size of button label. Scaling is not applied.
|
||||
func (b *Button) SetFont(fnt *sfnt.Font, size int) {
|
||||
func (b *Button) SetFont(fnt *text.GoTextFaceSource, size int) {
|
||||
b.Lock()
|
||||
defer b.Unlock()
|
||||
|
||||
|
@ -145,7 +144,7 @@ func (b *Button) resizeFont() {
|
|||
}
|
||||
|
||||
var autoSize int
|
||||
var ff font.Face
|
||||
var ff *text.GoTextFace
|
||||
for autoSize = b.textSize; autoSize > 0; autoSize-- {
|
||||
ff = FontFace(b.textFont, autoSize)
|
||||
bounds := BoundString(ff, b.field.Text())
|
||||
|
|
|
@ -3,13 +3,12 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"bytes"
|
||||
|
||||
"code.rocket9labs.com/tslocum/etk"
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"github.com/hajimehoshi/ebiten/v2/examples/resources/fonts"
|
||||
"golang.org/x/image/font/opentype"
|
||||
"golang.org/x/image/font/sfnt"
|
||||
"github.com/hajimehoshi/ebiten/v2/text/v2"
|
||||
)
|
||||
|
||||
type game struct {
|
||||
|
@ -37,10 +36,10 @@ func (g *game) Draw(screen *ebiten.Image) {
|
|||
}
|
||||
}
|
||||
|
||||
func defaultFont() *sfnt.Font {
|
||||
f, err := opentype.Parse(fonts.MPlus1pRegular_ttf)
|
||||
func defaultFont() *text.GoTextFaceSource {
|
||||
source, err := text.NewGoTextFaceSource(bytes.NewReader(fonts.MPlus1pRegular_ttf))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
panic(err)
|
||||
}
|
||||
return f
|
||||
return source
|
||||
}
|
||||
|
|
36
game.go
36
game.go
|
@ -5,7 +5,6 @@ import (
|
|||
"image"
|
||||
"image/color"
|
||||
"io"
|
||||
"log"
|
||||
"math"
|
||||
"sync"
|
||||
"time"
|
||||
|
@ -13,9 +12,8 @@ import (
|
|||
"code.rocket9labs.com/tslocum/etk/messeji"
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"github.com/hajimehoshi/ebiten/v2/inpututil"
|
||||
"github.com/hajimehoshi/ebiten/v2/text/v2"
|
||||
"golang.org/x/image/font"
|
||||
"golang.org/x/image/font/opentype"
|
||||
"golang.org/x/image/font/sfnt"
|
||||
"golang.org/x/image/math/fixed"
|
||||
)
|
||||
|
||||
|
@ -117,29 +115,11 @@ var (
|
|||
)
|
||||
|
||||
// FontFace returns a face for the provided font and size. Scaling is not applied.
|
||||
func FontFace(fnt *sfnt.Font, size int) font.Face {
|
||||
id := fmt.Sprintf("%p/%d", fnt, size)
|
||||
|
||||
fontCacheLock.Lock()
|
||||
defer fontCacheLock.Unlock()
|
||||
|
||||
f := fontCache[id]
|
||||
if f != nil {
|
||||
return f
|
||||
func FontFace(source *text.GoTextFaceSource, size int) *text.GoTextFace {
|
||||
return &text.GoTextFace{
|
||||
Source: source,
|
||||
Size: float64(size),
|
||||
}
|
||||
|
||||
const dpi = 72
|
||||
f, err := opentype.NewFace(fnt, &opentype.FaceOptions{
|
||||
Size: float64(size),
|
||||
DPI: dpi,
|
||||
Hinting: font.HintingFull,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fontCache[id] = f
|
||||
return f
|
||||
}
|
||||
|
||||
// SetRoot sets the root widget. The root widget and all of its children will
|
||||
|
@ -177,12 +157,12 @@ func int26ToRect(r fixed.Rectangle26_6) image.Rectangle {
|
|||
}
|
||||
|
||||
// BoundString returns the bounds of the provided string.
|
||||
func BoundString(f font.Face, s string) image.Rectangle {
|
||||
func BoundString(f *text.GoTextFace, s string) image.Rectangle {
|
||||
fontMutex.Lock()
|
||||
defer fontMutex.Unlock()
|
||||
|
||||
bounds, _ := font.BoundString(f, s)
|
||||
return int26ToRect(bounds)
|
||||
w, h := text.Measure(s, f, 0)
|
||||
return image.Rect(0, 0, int(w), int(h))
|
||||
}
|
||||
|
||||
// SetDebug sets whether debug information is drawn on screen. When enabled,
|
||||
|
|
11
go.mod
11
go.mod
|
@ -6,20 +6,21 @@ toolchain go1.23.0
|
|||
|
||||
require (
|
||||
code.rocket9labs.com/tslocum/clipboard v0.0.0-20241012025701-2c0fb515daab
|
||||
github.com/hajimehoshi/ebiten/v2 v2.8.3
|
||||
github.com/hajimehoshi/ebiten/v2 v2.8.4
|
||||
github.com/llgcode/draw2d v0.0.0-20240627062922-0ed1ff131195
|
||||
golang.org/x/image v0.21.0
|
||||
golang.org/x/image v0.22.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/ebitengine/gomobile v0.0.0-20241016134836-cc2e38a7c0ee // indirect
|
||||
github.com/ebitengine/hideconsole v1.0.0 // indirect
|
||||
github.com/ebitengine/purego v0.8.1 // indirect
|
||||
github.com/go-text/typesetting v0.2.0 // 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-20241009180824-f66d83c29e7c // indirect
|
||||
golang.org/x/mobile v0.0.0-20241016134751-7ff83004ec2c // indirect
|
||||
golang.org/x/sync v0.8.0 // indirect
|
||||
golang.org/x/sys v0.26.0 // indirect
|
||||
golang.org/x/text v0.19.0 // indirect
|
||||
golang.org/x/sync v0.9.0 // indirect
|
||||
golang.org/x/sys v0.27.0 // indirect
|
||||
golang.org/x/text v0.20.0 // indirect
|
||||
)
|
||||
|
|
24
go.sum
24
go.sum
|
@ -6,12 +6,16 @@ github.com/ebitengine/hideconsole v1.0.0 h1:5J4U0kXF+pv/DhiXt5/lTz0eO5ogJ1iXb8Yj
|
|||
github.com/ebitengine/hideconsole v1.0.0/go.mod h1:hTTBTvVYWKBuxPr7peweneWdkUwEuHuB3C1R/ielR1A=
|
||||
github.com/ebitengine/purego v0.8.1 h1:sdRKd6plj7KYW33EH5As6YKfe8m9zbN9JMrOjNVF/BE=
|
||||
github.com/ebitengine/purego v0.8.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/go-text/typesetting v0.2.0 h1:fbzsgbmk04KiWtE+c3ZD4W2nmCRzBqrqQOvYlwAOdho=
|
||||
github.com/go-text/typesetting v0.2.0/go.mod h1:2+owI/sxa73XA581LAzVuEBZ3WEEV2pXeDswCH/3i1I=
|
||||
github.com/go-text/typesetting-utils v0.0.0-20240317173224-1986cbe96c66 h1:GUrm65PQPlhFSKjLPGOZNPNxLCybjzjYBzjfoBGaDUY=
|
||||
github.com/go-text/typesetting-utils v0.0.0-20240317173224-1986cbe96c66/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/hajimehoshi/bitmapfont/v3 v3.2.0 h1:0DISQM/rseKIJhdF29AkhvdzIULqNIIlXAGWit4ez1Q=
|
||||
github.com/hajimehoshi/bitmapfont/v3 v3.2.0/go.mod h1:8gLqGatKVu0pwcNCJguW3Igg9WQqVXF0zg/RvrGQWyg=
|
||||
github.com/hajimehoshi/ebiten/v2 v2.8.3 h1:AKHqj3QbQMzNEhK33MMJeRwXm9UzftrUUo6AWwFV258=
|
||||
github.com/hajimehoshi/ebiten/v2 v2.8.3/go.mod h1:SXx/whkvpfsavGo6lvZykprerakl+8Uo1X8d2U5aAnA=
|
||||
github.com/hajimehoshi/ebiten/v2 v2.8.4 h1:BzXkcyYX046SRZFkzF2KaCaHiBjwCaufUPCAOK59JSw=
|
||||
github.com/hajimehoshi/ebiten/v2 v2.8.4/go.mod h1:SXx/whkvpfsavGo6lvZykprerakl+8Uo1X8d2U5aAnA=
|
||||
github.com/jezek/xgb v1.1.1 h1:bE/r8ZZtSv7l9gk6nU0mYx51aXrvnyb44892TwSaqS4=
|
||||
github.com/jezek/xgb v1.1.1/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk=
|
||||
github.com/llgcode/draw2d v0.0.0-20240627062922-0ed1ff131195 h1:Vdz2cBh5Fw2MYHWi3ED2PraDQaWEUhNCr1XFHrP4N5A=
|
||||
|
@ -22,13 +26,13 @@ github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ
|
|||
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
golang.org/x/exp/shiny v0.0.0-20241009180824-f66d83c29e7c h1:jTMrjjZRcSH/BDxWhXCP6OWsfVgmnwI7J+F4/nyVXaU=
|
||||
golang.org/x/exp/shiny v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:3F+MieQB7dRYLTmnncoFbb1crS5lfQoTfDgQy6K4N0o=
|
||||
golang.org/x/image v0.21.0 h1:c5qV36ajHpdj4Qi0GnE0jUc/yuo33OLFaa0d+crTD5s=
|
||||
golang.org/x/image v0.21.0/go.mod h1:vUbsLavqK/W303ZroQQVKQ+Af3Yl6Uz1Ppu5J/cLz78=
|
||||
golang.org/x/image v0.22.0 h1:UtK5yLUzilVrkjMAZAZ34DXGpASN8i8pj8g+O+yd10g=
|
||||
golang.org/x/image v0.22.0/go.mod h1:9hPFhljd4zZ1GNSIZJ49sqbp45GKK9t6w+iXvGqZUz4=
|
||||
golang.org/x/mobile v0.0.0-20241016134751-7ff83004ec2c h1:zuNS/LWsEpPTLfrmBkis6Xofw3nieAqB4hYLn8+uswk=
|
||||
golang.org/x/mobile v0.0.0-20241016134751-7ff83004ec2c/go.mod h1:snk1Mn2ZpdKCt90JPEsDh4sL3ReK520U2t0d7RHBnSU=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
||||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
|
||||
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
|
||||
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
|
||||
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
|
||||
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
|
||||
|
|
4
input.go
4
input.go
|
@ -6,7 +6,7 @@ import (
|
|||
|
||||
"code.rocket9labs.com/tslocum/etk/messeji"
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"golang.org/x/image/font/sfnt"
|
||||
"github.com/hajimehoshi/ebiten/v2/text/v2"
|
||||
)
|
||||
|
||||
// Input is a text input widget. The Input widget is simply a Text widget that
|
||||
|
@ -187,7 +187,7 @@ func (i *Input) SetAutoHideScrollBar(autoHide bool) {
|
|||
}
|
||||
|
||||
// SetFont sets the font and text size of the field. Scaling is not applied.
|
||||
func (t *Input) SetFont(fnt *sfnt.Font, size int) {
|
||||
func (t *Input) SetFont(fnt *text.GoTextFaceSource, size int) {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
|
|
|
@ -3,16 +3,15 @@
|
|||
package game
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"code.rocket9labs.com/tslocum/etk/kibodo"
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
|
||||
"github.com/hajimehoshi/ebiten/v2/examples/resources/fonts"
|
||||
"github.com/hajimehoshi/ebiten/v2/inpututil"
|
||||
"golang.org/x/image/font/opentype"
|
||||
"golang.org/x/image/font/sfnt"
|
||||
"github.com/hajimehoshi/ebiten/v2/text/v2"
|
||||
)
|
||||
|
||||
type game struct {
|
||||
|
@ -121,10 +120,10 @@ func (g *game) Draw(screen *ebiten.Image) {
|
|||
screen.DrawImage(g.buffer, g.op)
|
||||
}
|
||||
|
||||
func defaultFont() *sfnt.Font {
|
||||
f, err := opentype.Parse(fonts.MPlus1pRegular_ttf)
|
||||
func defaultFont() *text.GoTextFaceSource {
|
||||
source, err := text.NewGoTextFaceSource(bytes.NewReader(fonts.MPlus1pRegular_ttf))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
panic(err)
|
||||
}
|
||||
return f
|
||||
return source
|
||||
}
|
||||
|
|
|
@ -3,15 +3,11 @@ package kibodo
|
|||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"github.com/hajimehoshi/ebiten/v2/inpututil"
|
||||
"github.com/hajimehoshi/ebiten/v2/text"
|
||||
"golang.org/x/image/font"
|
||||
"golang.org/x/image/font/opentype"
|
||||
"golang.org/x/image/font/sfnt"
|
||||
"github.com/hajimehoshi/ebiten/v2/text/v2"
|
||||
)
|
||||
|
||||
// Keyboard is an on-screen keyboard widget.
|
||||
|
@ -50,7 +46,7 @@ type Keyboard struct {
|
|||
|
||||
hideShortcuts []ebiten.Key
|
||||
|
||||
labelFont font.Face
|
||||
labelFont *text.GoTextFace
|
||||
lineHeight int
|
||||
lineOffset int
|
||||
|
||||
|
@ -62,12 +58,7 @@ type Keyboard struct {
|
|||
}
|
||||
|
||||
// NewKeyboard returns a new Keyboard widget.
|
||||
func NewKeyboard(f *sfnt.Font) *Keyboard {
|
||||
ff, err := fontFace(f, 64)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
func NewKeyboard(f *text.GoTextFaceSource) *Keyboard {
|
||||
k := &Keyboard{
|
||||
alpha: 1.0,
|
||||
op: &ebiten.DrawImageOptions{
|
||||
|
@ -80,7 +71,7 @@ func NewKeyboard(f *sfnt.Font) *Keyboard {
|
|||
backgroundColor: color.RGBA{0, 0, 0, 255},
|
||||
holdTouchID: -1,
|
||||
hideShortcuts: []ebiten.Key{ebiten.KeyEscape},
|
||||
labelFont: ff,
|
||||
labelFont: fontFace(f, 64),
|
||||
backspaceDelay: 500 * time.Millisecond,
|
||||
backspaceRepeat: 75 * time.Millisecond,
|
||||
}
|
||||
|
@ -88,13 +79,11 @@ func NewKeyboard(f *sfnt.Font) *Keyboard {
|
|||
return k
|
||||
}
|
||||
|
||||
func fontFace(f *sfnt.Font, size float64) (font.Face, error) {
|
||||
const dpi = 72 // TODO
|
||||
return opentype.NewFace(f, &opentype.FaceOptions{
|
||||
Size: size,
|
||||
DPI: dpi,
|
||||
Hinting: font.HintingFull,
|
||||
})
|
||||
func fontFace(source *text.GoTextFaceSource, size float64) *text.GoTextFace {
|
||||
return &text.GoTextFace{
|
||||
Source: source,
|
||||
Size: size,
|
||||
}
|
||||
}
|
||||
|
||||
// SetRect sets the position and size of the widget.
|
||||
|
@ -156,7 +145,7 @@ func (k *Keyboard) SetShowExtended(show bool) {
|
|||
}
|
||||
|
||||
// SetLabelFont sets the key label font.
|
||||
func (k *Keyboard) SetLabelFont(face font.Face) {
|
||||
func (k *Keyboard) SetLabelFont(face *text.GoTextFace) {
|
||||
k.labelFont = face
|
||||
k.fontUpdated()
|
||||
|
||||
|
@ -165,8 +154,11 @@ func (k *Keyboard) SetLabelFont(face font.Face) {
|
|||
|
||||
func (k *Keyboard) fontUpdated() {
|
||||
m := k.labelFont.Metrics()
|
||||
k.lineHeight = m.Height.Round()
|
||||
k.lineOffset = m.Ascent.Round()
|
||||
k.lineHeight = int(m.HAscent + m.HDescent)
|
||||
k.lineOffset = int(m.CapHeight)
|
||||
if k.lineOffset < 0 {
|
||||
k.lineOffset *= -1
|
||||
}
|
||||
}
|
||||
|
||||
// SetHideShortcuts sets the key shortcuts which, when pressed, will hide the
|
||||
|
@ -626,13 +618,17 @@ func (k *Keyboard) drawBackground() {
|
|||
label = key.UpperLabel
|
||||
}
|
||||
|
||||
bounds := text.BoundString(k.labelFont, label)
|
||||
x := (key.w - bounds.Dx()) / 2
|
||||
boundsW, boundsY := text.Measure(label, k.labelFont, float64(k.lineHeight))
|
||||
x := key.w/2 - int(boundsW)/2
|
||||
if x < 0 {
|
||||
x = 0
|
||||
}
|
||||
y := halfLineHeight + (key.h-halfLineHeight)/2
|
||||
text.Draw(keyImage, label, k.labelFont, key.x+x, key.y+y, color.White)
|
||||
y := key.h/2 - int(boundsY)/2
|
||||
_ = halfLineHeight
|
||||
op := &text.DrawOptions{}
|
||||
op.GeoM.Translate(float64(key.x+x), float64(key.y+y))
|
||||
op.ColorScale.ScaleWithColor(color.White)
|
||||
text.Draw(keyImage, label, k.labelFont, op)
|
||||
|
||||
// Draw border
|
||||
keyImage.SubImage(image.Rect(key.x, key.y, key.x+key.w, key.y+1)).(*ebiten.Image).Fill(lightShade)
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
package kibodo
|
||||
|
||||
import (
|
||||
"log"
|
||||
"bytes"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"github.com/hajimehoshi/ebiten/v2/examples/resources/fonts"
|
||||
"golang.org/x/image/font/opentype"
|
||||
"golang.org/x/image/font/sfnt"
|
||||
"github.com/hajimehoshi/ebiten/v2/text/v2"
|
||||
)
|
||||
|
||||
// TODO test presses registered
|
||||
|
@ -88,10 +87,10 @@ func NewDummyGame() *DummyGame {
|
|||
return &DummyGame{}
|
||||
}
|
||||
|
||||
func defaultFont() *sfnt.Font {
|
||||
f, err := opentype.Parse(fonts.MPlus1pRegular_ttf)
|
||||
func defaultFont() *text.GoTextFaceSource {
|
||||
source, err := text.NewGoTextFaceSource(bytes.NewReader(fonts.MPlus1pRegular_ttf))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
panic(err)
|
||||
}
|
||||
return f
|
||||
return source
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
package game
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"image"
|
||||
"log"
|
||||
|
@ -13,8 +14,7 @@ import (
|
|||
"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"
|
||||
"github.com/hajimehoshi/ebiten/v2/text/v2"
|
||||
)
|
||||
|
||||
const initialText = `
|
||||
|
@ -28,24 +28,19 @@ Below is an InputField, which accepts keyboard input.
|
|||
`
|
||||
|
||||
var (
|
||||
mplusNormalFont font.Face
|
||||
mplusNormalFont *text.GoTextFace
|
||||
fontMutex *sync.Mutex
|
||||
)
|
||||
|
||||
func init() {
|
||||
tt, err := opentype.Parse(fonts.MPlus1pRegular_ttf)
|
||||
source, err := text.NewGoTextFaceSource(bytes.NewReader(fonts.MPlus1pRegular_ttf))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
panic(err)
|
||||
}
|
||||
|
||||
const dpi = 72
|
||||
mplusNormalFont, err = opentype.NewFace(tt, &opentype.FaceOptions{
|
||||
Size: 28,
|
||||
DPI: dpi,
|
||||
Hinting: font.HintingFull,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
mplusNormalFont = &text.GoTextFace{
|
||||
Source: source,
|
||||
Size: 24,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"github.com/hajimehoshi/ebiten/v2/inpututil"
|
||||
"golang.org/x/image/font"
|
||||
"github.com/hajimehoshi/ebiten/v2/text/v2"
|
||||
)
|
||||
|
||||
// InputField is a text input field. Call Update and Draw when your Game's
|
||||
|
@ -40,7 +40,7 @@ type InputField struct {
|
|||
}
|
||||
|
||||
// NewInputField returns a new InputField. See type documentation for more info.
|
||||
func NewInputField(face font.Face, faceMutex *sync.Mutex) *InputField {
|
||||
func NewInputField(face *text.GoTextFace, faceMutex *sync.Mutex) *InputField {
|
||||
f := &InputField{
|
||||
TextField: NewTextField(face, faceMutex),
|
||||
}
|
||||
|
|
|
@ -11,9 +11,7 @@ import (
|
|||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"github.com/hajimehoshi/ebiten/v2/inpututil"
|
||||
"github.com/hajimehoshi/ebiten/v2/text"
|
||||
"golang.org/x/image/font"
|
||||
"golang.org/x/image/math/fixed"
|
||||
"github.com/hajimehoshi/ebiten/v2/text/v2"
|
||||
)
|
||||
|
||||
// Alignment specifies how text is aligned within the field.
|
||||
|
@ -97,7 +95,7 @@ type TextField struct {
|
|||
vertical Alignment
|
||||
|
||||
// face is the font face of the text within the field.
|
||||
face font.Face
|
||||
face *text.GoTextFace
|
||||
|
||||
// faceMutex is the lock which is held whenever utilizing the font face.
|
||||
faceMutex *sync.Mutex
|
||||
|
@ -190,7 +188,7 @@ type TextField struct {
|
|||
}
|
||||
|
||||
// NewTextField returns a new TextField. See type documentation for more info.
|
||||
func NewTextField(face font.Face, faceMutex *sync.Mutex) *TextField {
|
||||
func NewTextField(face *text.GoTextFace, faceMutex *sync.Mutex) *TextField {
|
||||
if faceMutex == nil {
|
||||
faceMutex = &sync.Mutex{}
|
||||
}
|
||||
|
@ -423,7 +421,7 @@ func (f *TextField) SetBackgroundColor(c color.RGBA) {
|
|||
}
|
||||
|
||||
// SetFont sets the font face of the text within the field.
|
||||
func (f *TextField) SetFont(face font.Face, mutex *sync.Mutex) {
|
||||
func (f *TextField) SetFont(face *text.GoTextFace, mutex *sync.Mutex) {
|
||||
if mutex == nil {
|
||||
mutex = &sync.Mutex{}
|
||||
}
|
||||
|
@ -793,8 +791,8 @@ func (f *TextField) Draw(screen *ebiten.Image) {
|
|||
|
||||
func (f *TextField) fontUpdated() {
|
||||
m := f.face.Metrics()
|
||||
f.lineHeight = m.Height.Ceil()
|
||||
f.lineOffset = m.CapHeight.Ceil()
|
||||
f.lineHeight = int(m.HAscent + m.HDescent)
|
||||
f.lineOffset = int(m.HLineGap)
|
||||
if f.lineOffset < 0 {
|
||||
f.lineOffset *= -1
|
||||
}
|
||||
|
@ -811,11 +809,11 @@ func (f *TextField) wrapContent(withScrollBar bool) {
|
|||
|
||||
if f.singleLine || len(f.buffer) == 0 {
|
||||
buffer := f.prefix + string(bytes.Join(f.buffer, nil)) + f.suffix
|
||||
bounds, _ := font.BoundString(f.face, buffer)
|
||||
w, _ := text.Measure(buffer, f.face, float64(f.lineHeight))
|
||||
|
||||
f.bufferWrapped = []string{buffer}
|
||||
f.wrapStart = 0
|
||||
f.lineWidths = append(f.lineWidths[:0], (bounds.Max.X - bounds.Min.X).Floor())
|
||||
f.lineWidths = append(f.lineWidths[:0], int(w))
|
||||
|
||||
f.needWrap = -1
|
||||
return
|
||||
|
@ -862,7 +860,6 @@ func (f *TextField) wrapContent(withScrollBar bool) {
|
|||
// wrapping is enabled, break the line at the last whitespace character.
|
||||
var start int
|
||||
var lastSpace int
|
||||
var bounds fixed.Rectangle26_6
|
||||
var boundsWidth int
|
||||
WRAPTEXT:
|
||||
for start < l {
|
||||
|
@ -871,8 +868,8 @@ func (f *TextField) wrapContent(withScrollBar bool) {
|
|||
if unicode.IsSpace(r) {
|
||||
lastSpace = start + e + 1
|
||||
}
|
||||
bounds, _ = font.BoundString(f.face, line[start:start+e]+string(r))
|
||||
boundsWidth = (bounds.Max.X - bounds.Min.X).Floor()
|
||||
w, _ := text.Measure(line[start:start+e]+string(r), f.face, float64(f.lineHeight))
|
||||
boundsWidth = int(w)
|
||||
if boundsWidth > availableWidth {
|
||||
original := e
|
||||
if e != l-start-1 && e > 0 {
|
||||
|
@ -882,8 +879,8 @@ func (f *TextField) wrapContent(withScrollBar bool) {
|
|||
e = lastSpace - start - 1
|
||||
}
|
||||
if e != original {
|
||||
bounds, _ = font.BoundString(f.face, line[start:start+e+1])
|
||||
boundsWidth = (bounds.Max.X - bounds.Min.X).Floor()
|
||||
w, _ := text.Measure(line[start:start+e+1], f.face, float64(f.lineHeight))
|
||||
boundsWidth = int(w)
|
||||
}
|
||||
|
||||
if len(f.bufferWrapped) <= j {
|
||||
|
@ -956,10 +953,16 @@ func (f *TextField) drawContent() (overflow bool) {
|
|||
}
|
||||
// Calculate buffer size (width for single-line fields or height for multi-line fields).
|
||||
if f.singleLine {
|
||||
bounds, _ := font.BoundString(f.face, f.bufferWrapped[firstVisible])
|
||||
f.bufferSize = (bounds.Max.X - bounds.Min.X).Floor()
|
||||
w, _ := text.Measure(f.bufferWrapped[firstVisible], f.face, float64(f.lineHeight))
|
||||
f.bufferSize = int(w)
|
||||
if f.bufferSize > fieldWidth-f.padding*2 {
|
||||
overflow = true
|
||||
}
|
||||
} else {
|
||||
f.bufferSize = (len(f.bufferWrapped)) * lineHeight
|
||||
if f.bufferSize > fieldHeight-f.padding*2 {
|
||||
overflow = true
|
||||
}
|
||||
}
|
||||
for i := firstVisible; i <= lastVisible; i++ {
|
||||
line := f.bufferWrapped[i]
|
||||
|
@ -970,10 +973,10 @@ func (f *TextField) drawContent() (overflow bool) {
|
|||
}
|
||||
}
|
||||
lineX := f.padding
|
||||
lineY := 1 + f.padding + f.lineOffset + lineHeight*i
|
||||
lineY := 1 + f.padding + -f.lineOffset + lineHeight*i
|
||||
|
||||
// Calculate whether the line overflows the visible area.
|
||||
lineOverflows := lineY < 0 || lineY >= h-(f.padding*2)
|
||||
lineOverflows := lineY < 0 || lineY >= h-f.padding
|
||||
if lineOverflows {
|
||||
overflow = true
|
||||
}
|
||||
|
@ -998,7 +1001,7 @@ func (f *TextField) drawContent() (overflow bool) {
|
|||
}
|
||||
|
||||
// Align vertically.
|
||||
totalHeight := f.lineOffset + lineHeight*(lines-1)
|
||||
totalHeight := f.lineOffset + lineHeight*(lines)
|
||||
if f.vertical == AlignCenter && totalHeight <= h {
|
||||
lineY = fieldHeight/2 - totalHeight/2 + f.lineOffset + (lineHeight * (i))
|
||||
} else if f.vertical == AlignEnd && totalHeight <= h {
|
||||
|
@ -1006,7 +1009,10 @@ func (f *TextField) drawContent() (overflow bool) {
|
|||
}
|
||||
|
||||
// Draw line.
|
||||
text.Draw(f.img, line, f.face, lineX, lineY, f.textColor)
|
||||
op := &text.DrawOptions{}
|
||||
op.GeoM.Translate(float64(lineX), float64(lineY))
|
||||
op.ColorScale.ScaleWithColor(f.textColor)
|
||||
text.Draw(f.img, line, f.face, op)
|
||||
}
|
||||
|
||||
return overflow
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
package messeji
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"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"
|
||||
"github.com/hajimehoshi/ebiten/v2/text/v2"
|
||||
)
|
||||
|
||||
//go:embed testdata
|
||||
|
@ -18,7 +17,25 @@ var testDataFS embed.FS
|
|||
|
||||
var testTextField *TextField
|
||||
|
||||
func testFace() (*text.GoTextFace, error) {
|
||||
source, err := text.NewGoTextFaceSource(bytes.NewReader(fonts.MPlus1pRegular_ttf))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
face := &text.GoTextFace{
|
||||
Source: source,
|
||||
Size: 24,
|
||||
}
|
||||
return face, nil
|
||||
}
|
||||
|
||||
func TestWrapContent(t *testing.T) {
|
||||
face, err := testFace()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
long bool // Test data type.
|
||||
wordWrap bool // Enable wordwrap.
|
||||
|
@ -30,22 +47,6 @@ func TestWrapContent(t *testing.T) {
|
|||
{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 {
|
||||
|
@ -79,6 +80,11 @@ func TestWrapContent(t *testing.T) {
|
|||
}
|
||||
|
||||
func BenchmarkWrapContent(b *testing.B) {
|
||||
face, err := testFace()
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
long bool // Test data type.
|
||||
wordWrap bool // Enable wordwrap.
|
||||
|
@ -90,22 +96,6 @@ func BenchmarkWrapContent(b *testing.B) {
|
|||
{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 {
|
||||
|
|
|
@ -119,6 +119,7 @@ func (s *Select) AddOption(label string) {
|
|||
t := NewText(label)
|
||||
t.SetVertical(AlignCenter)
|
||||
t.SetForeground(textColor)
|
||||
t.SetAutoResize(true)
|
||||
s.list.AddChildAt(t, 0, len(s.items)-1)
|
||||
}
|
||||
|
||||
|
|
4
style.go
4
style.go
|
@ -3,14 +3,14 @@ package etk
|
|||
import (
|
||||
"image/color"
|
||||
|
||||
"golang.org/x/image/font/sfnt"
|
||||
"github.com/hajimehoshi/ebiten/v2/text/v2"
|
||||
)
|
||||
|
||||
var transparent = color.RGBA{0, 0, 0, 0}
|
||||
|
||||
// Attributes represents a default attribute configuration. Integer values will be scaled.
|
||||
type Attributes struct {
|
||||
TextFont *sfnt.Font
|
||||
TextFont *text.GoTextFaceSource
|
||||
TextSize int
|
||||
|
||||
TextColorLight color.RGBA
|
||||
|
|
9
text.go
9
text.go
|
@ -6,15 +6,14 @@ import (
|
|||
|
||||
"code.rocket9labs.com/tslocum/etk/messeji"
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"golang.org/x/image/font"
|
||||
"golang.org/x/image/font/sfnt"
|
||||
"github.com/hajimehoshi/ebiten/v2/text/v2"
|
||||
)
|
||||
|
||||
// Text is a text display widget.
|
||||
type Text struct {
|
||||
*Box
|
||||
field *messeji.TextField
|
||||
textFont *sfnt.Font
|
||||
textFont *text.GoTextFaceSource
|
||||
textSize int
|
||||
textResize bool
|
||||
textAutoSize int
|
||||
|
@ -193,7 +192,7 @@ func (t *Text) resizeFont() {
|
|||
}
|
||||
|
||||
var autoSize int
|
||||
var ff font.Face
|
||||
var ff *text.GoTextFace
|
||||
for autoSize = t.textSize; autoSize > 0; autoSize-- {
|
||||
ff = FontFace(t.textFont, autoSize)
|
||||
bounds := BoundString(ff, t.field.Text())
|
||||
|
@ -243,7 +242,7 @@ func (t *Text) FontSize() int {
|
|||
}
|
||||
|
||||
// SetFont sets the font and text size of the field. Scaling is not applied.
|
||||
func (t *Text) SetFont(fnt *sfnt.Font, size int) {
|
||||
func (t *Text) SetFont(fnt *text.GoTextFaceSource, size int) {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
|
|
|
@ -5,14 +5,14 @@ import (
|
|||
"image"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"golang.org/x/image/font/sfnt"
|
||||
"github.com/hajimehoshi/ebiten/v2/text/v2"
|
||||
)
|
||||
|
||||
// Window displays a single child widget at a time, and includes a list to
|
||||
// view other child widgets. Window.Show must be called after adding a widget.
|
||||
type Window struct {
|
||||
*Box
|
||||
font *sfnt.Font
|
||||
font *text.GoTextFaceSource
|
||||
fontSize int
|
||||
frameSize int
|
||||
list *List
|
||||
|
@ -56,7 +56,7 @@ func (w *Window) SetRect(r image.Rectangle) {
|
|||
}
|
||||
|
||||
// SetFont sets the font and text size of the window titles. Scaling is not applied.
|
||||
func (w *Window) SetFont(fnt *sfnt.Font, size int) {
|
||||
func (w *Window) SetFont(fnt *text.GoTextFaceSource, size int) {
|
||||
w.Lock()
|
||||
defer w.Unlock()
|
||||
|
||||
|
|
Loading…
Reference in a new issue