Allow fonts to be specified directly

This will enable etk to auto-scale text to fit within a bounded area.

Relates to #5.
This commit is contained in:
Trevor Slocum 2024-04-23 13:05:39 -07:00
parent d17310185c
commit 416576f940
8 changed files with 102 additions and 75 deletions

View file

@ -3,11 +3,10 @@ package etk
import (
"image"
"image/color"
"sync"
"code.rocket9labs.com/tslocum/etk/messeji"
"github.com/hajimehoshi/ebiten/v2"
"golang.org/x/image/font"
"golang.org/x/image/font/sfnt"
)
// Button is a clickable button.
@ -96,12 +95,12 @@ func (b *Button) SetText(text string) {
b.field.SetText(text)
}
// SetFont sets the font face of the text within the field.
func (b *Button) SetFont(face font.Face, mutex *sync.Mutex) {
// SetFont sets the font and text size of button label. Scaling is not applied.
func (b *Button) SetFont(fnt *sfnt.Font, size int) {
b.Lock()
defer b.Unlock()
b.field.SetFont(face, mutex)
b.field.SetFont(FontFace(fnt, size), fontMutex)
}
// HandleKeyboard is called when a keyboard event occurs.

39
game.go
View file

@ -4,15 +4,19 @@ import (
"fmt"
"image"
"image/color"
"log"
"math"
"runtime/debug"
"strings"
"sync"
"time"
"code.rocket9labs.com/tslocum/etk/messeji"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/inpututil"
"golang.org/x/image/font"
"golang.org/x/image/font/opentype"
"golang.org/x/image/font/sfnt"
"golang.org/x/image/math/fixed"
)
@ -50,6 +54,8 @@ var (
keyBuffer []ebiten.Key
runeBuffer []rune
fontMutex = &sync.Mutex{}
)
var debugColor = color.RGBA{0, 0, 255, 255}
@ -80,6 +86,37 @@ func Scale(v int) int {
return int(float64(v) * deviceScale)
}
var (
fontCache = make(map[string]font.Face)
fontCacheLock sync.Mutex
)
// 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
}
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
// be drawn on the screen and receive user input. The root widget will also be
// focused. To temporarily disable etk, set a nil root widget.
@ -412,7 +449,7 @@ func draw(w Widget, screen *ebiten.Image) error {
}
func newText() *messeji.TextField {
f := messeji.NewTextField(Style.TextFont, Style.TextFontMutex)
f := messeji.NewTextField(FontFace(Style.TextFont, Scale(Style.TextSize)), fontMutex)
f.SetForegroundColor(Style.TextColorLight)
f.SetBackgroundColor(transparent)
f.SetScrollBarColors(Style.ScrollAreaColor, Style.ScrollHandleColor)

14
go.mod
View file

@ -3,18 +3,18 @@ module code.rocket9labs.com/tslocum/etk
go 1.18
require (
github.com/hajimehoshi/ebiten/v2 v2.6.5
github.com/llgcode/draw2d v0.0.0-20231212091825-f55e0c776b44
github.com/hajimehoshi/ebiten/v2 v2.7.2
github.com/llgcode/draw2d v0.0.0-20240322162412-ee6987bd01dc
golang.org/x/image v0.15.0
)
require (
github.com/ebitengine/purego v0.6.0 // indirect
github.com/ebitengine/gomobile v0.0.0-20240329170434-1771503ff0a8 // indirect
github.com/ebitengine/hideconsole v1.0.0 // indirect
github.com/ebitengine/purego v0.7.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-20240205201215-2c58cdc269a3 // indirect
golang.org/x/mobile v0.0.0-20240112133503-c713f31d574b // indirect
golang.org/x/sync v0.6.0 // indirect
golang.org/x/sys v0.17.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.19.0 // indirect
golang.org/x/text v0.14.0 // indirect
)

28
go.sum
View file

@ -1,24 +1,24 @@
github.com/ebitengine/purego v0.6.0 h1:Yo9uBc1x+ETQbfEaf6wcBsjrQfCEnh/gaGUg7lguEJY=
github.com/ebitengine/purego v0.6.0/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ=
github.com/ebitengine/gomobile v0.0.0-20240329170434-1771503ff0a8 h1:5e8X7WEdOWrjrKvgaWF6PRnDvJicfrkEnwAkWtMN74g=
github.com/ebitengine/gomobile v0.0.0-20240329170434-1771503ff0a8/go.mod h1:tWboRRNagZwwwis4QIgEFG1ZNFwBJ3LAhSLAXAAxobQ=
github.com/ebitengine/hideconsole v1.0.0 h1:5J4U0kXF+pv/DhiXt5/lTz0eO5ogJ1iXb8Yj1yReDqE=
github.com/ebitengine/hideconsole v1.0.0/go.mod h1:hTTBTvVYWKBuxPr7peweneWdkUwEuHuB3C1R/ielR1A=
github.com/ebitengine/purego v0.7.1 h1:6/55d26lG3o9VCZX8lping+bZcmShseiqlh2bnUDiPA=
github.com/ebitengine/purego v0.7.1/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ=
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.0.0 h1:r2+6gYK38nfztS/et50gHAswb9hXgxXECYgE8Nczmi4=
github.com/hajimehoshi/ebiten/v2 v2.6.5 h1:lALv+qhEK3CBWViyiGpz4YcR6slVJEjCiS7sExKZ9OE=
github.com/hajimehoshi/ebiten/v2 v2.6.5/go.mod h1:TZtorL713an00UW4LyvMeKD8uXWnuIuCPtlH11b0pgI=
github.com/hajimehoshi/ebiten/v2 v2.7.2 h1:5HcWAjxhGMBocJh0jH/61Kx4QJ91HkzYtSeSucvVg7o=
github.com/hajimehoshi/ebiten/v2 v2.7.2/go.mod h1:1vjyPw+h3n30rfTOpIsbWRXSxZ0Oz1cYc6Tq/2DKoQg=
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-20231212091825-f55e0c776b44 h1:1ad70i0s40IpMtRm2ST+Nvr03X7mlHWtdALYkFNrlxk=
github.com/llgcode/draw2d v0.0.0-20231212091825-f55e0c776b44/go.mod h1:muweRyJCZ1mZSMiCgYbAicfnwZFoeHpNr6A6QBu+rBg=
github.com/llgcode/draw2d v0.0.0-20240322162412-ee6987bd01dc h1:lorg2FaIDdlahOHekjnQjItP2oCtkVlYc0QNekubCLk=
github.com/llgcode/draw2d v0.0.0-20240322162412-ee6987bd01dc/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-20240205201215-2c58cdc269a3 h1:tImqKNm/Iclm3Rqb6GffLiURSp3m1iRx/C4mturH8Ys=
golang.org/x/exp/shiny v0.0.0-20240205201215-2c58cdc269a3/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-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.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=

View file

@ -3,11 +3,10 @@ package etk
import (
"image"
"image/color"
"sync"
"code.rocket9labs.com/tslocum/etk/messeji"
"github.com/hajimehoshi/ebiten/v2"
"golang.org/x/image/font"
"golang.org/x/image/font/sfnt"
)
// Input is a text input widget. The Input widget is simply a Text widget that
@ -26,7 +25,7 @@ func NewInput(text string, onSelected func(text string) (handled bool)) *Input {
textColor = Style.InputColor
}*/
f := messeji.NewInputField(Style.TextFont, Style.TextFontMutex)
f := messeji.NewInputField(FontFace(Style.TextFont, Scale(Style.TextSize)), fontMutex)
f.SetForegroundColor(textColor)
f.SetBackgroundColor(transparent)
f.SetScrollBarColors(Style.ScrollAreaColor, Style.ScrollHandleColor)
@ -169,12 +168,12 @@ func (i *Input) SetAutoHideScrollBar(autoHide bool) {
i.field.SetAutoHideScrollBar(autoHide)
}
// SetFont sets the font face of the text within the field.
func (i *Input) SetFont(face font.Face, mutex *sync.Mutex) {
i.Lock()
defer i.Unlock()
// SetFont sets the font and text size of the field. Scaling is not applied.
func (t *Input) SetFont(fnt *sfnt.Font, size int) {
t.Lock()
defer t.Unlock()
i.field.SetFont(face, mutex)
t.field.SetFont(FontFace(fnt, size), fontMutex)
}
// Padding returns the amount of padding around the text within the field.

View file

@ -3,43 +3,34 @@ package etk
import (
"image/color"
"log"
"sync"
"github.com/hajimehoshi/ebiten/v2/examples/resources/fonts"
"golang.org/x/image/font"
"golang.org/x/image/font/opentype"
"golang.org/x/image/font/sfnt"
)
var transparent = color.RGBA{0, 0, 0, 0}
func defaultFont() font.Face {
tt, err := opentype.Parse(fonts.MPlus1pRegular_ttf)
func defaultFont() *sfnt.Font {
f, err := opentype.Parse(fonts.MPlus1pRegular_ttf)
if err != nil {
log.Fatal(err)
}
const dpi = 72
defaultFont, err := opentype.NewFace(tt, &opentype.FaceOptions{
Size: 32,
DPI: dpi,
Hinting: font.HintingFull,
})
if err != nil {
log.Fatal(err)
}
return defaultFont
return f
}
// Attributes represents a default attribute configuration. Integer values will be scaled.
type Attributes struct {
TextFont font.Face
TextFontMutex *sync.Mutex
TextFont *sfnt.Font
TextSize int
TextColorLight color.RGBA
TextColorDark color.RGBA
TextBgColor color.RGBA
BorderSize int
BorderSize int
BorderColorTop color.RGBA
BorderColorRight color.RGBA
BorderColorBottom color.RGBA
@ -48,7 +39,8 @@ type Attributes struct {
ScrollAreaColor color.RGBA
ScrollHandleColor color.RGBA
ScrollBorderSize int
ScrollBorderSize int
ScrollBorderColorTop color.RGBA
ScrollBorderColorRight color.RGBA
ScrollBorderColorBottom color.RGBA
@ -63,15 +55,16 @@ type Attributes struct {
// Style is the current default attribute configuration. Integer values will be scaled.
var Style = &Attributes{
TextFont: defaultFont(),
TextFontMutex: &sync.Mutex{},
TextFont: defaultFont(),
TextSize: 32,
TextColorLight: color.RGBA{255, 255, 255, 255},
TextColorDark: color.RGBA{0, 0, 0, 255},
TextBgColor: transparent,
BorderSize: 4,
BorderSize: 4,
BorderColorTop: color.RGBA{220, 220, 220, 255},
BorderColorRight: color.RGBA{0, 0, 0, 255},
BorderColorBottom: color.RGBA{0, 0, 0, 255},
@ -80,7 +73,8 @@ var Style = &Attributes{
ScrollAreaColor: color.RGBA{200, 200, 200, 255},
ScrollHandleColor: color.RGBA{108, 108, 108, 255},
ScrollBorderSize: 2,
ScrollBorderSize: 2,
ScrollBorderColorTop: color.RGBA{240, 240, 240, 255},
ScrollBorderColorRight: color.RGBA{0, 0, 0, 255},
ScrollBorderColorBottom: color.RGBA{0, 0, 0, 255},

View file

@ -3,11 +3,10 @@ package etk
import (
"image"
"image/color"
"sync"
"code.rocket9labs.com/tslocum/etk/messeji"
"github.com/hajimehoshi/ebiten/v2"
"golang.org/x/image/font"
"golang.org/x/image/font/sfnt"
)
// Text is a text display widget.
@ -155,12 +154,12 @@ func (t *Text) SetAutoHideScrollBar(autoHide bool) {
t.field.SetAutoHideScrollBar(autoHide)
}
// SetFont sets the font face of the text within the field.
func (t *Text) SetFont(face font.Face, mutex *sync.Mutex) {
// SetFont sets the font and text size of the field. Scaling is not applied.
func (t *Text) SetFont(fnt *sfnt.Font, size int) {
t.Lock()
defer t.Unlock()
t.field.SetFont(face, mutex)
t.field.SetFont(FontFace(fnt, size), fontMutex)
}
// Padding returns the amount of padding around the text within the field.

View file

@ -3,17 +3,16 @@ package etk
import (
"image"
"image/color"
"sync"
"github.com/hajimehoshi/ebiten/v2"
"golang.org/x/image/font"
"golang.org/x/image/font/sfnt"
)
// Window displays child widgets in floating or maximized windows.
type Window struct {
*Box
fontFace font.Face
fontMutex *sync.Mutex
font *sfnt.Font
fontSize int
frameSize int
titleSize int
titles []*Text
@ -26,8 +25,8 @@ type Window struct {
func NewWindow() *Window {
return &Window{
Box: NewBox(),
fontFace: Style.TextFont,
fontMutex: Style.TextFontMutex,
font: Style.TextFont,
fontSize: Scale(Style.TextSize),
frameSize: Scale(4),
titleSize: Scale(40),
}
@ -42,16 +41,16 @@ func (w *Window) SetRect(r image.Rectangle) {
w.modified = true
}
// SetFont sets the font face of the window titles.
func (w *Window) SetFont(face font.Face, mutex *sync.Mutex) {
// SetFont sets the font and text size of the window titles. Scaling is not applied.
func (w *Window) SetFont(fnt *sfnt.Font, size int) {
w.Lock()
defer w.Unlock()
w.fontFace = face
w.fontMutex = mutex
w.font = fnt
w.fontSize = size
for _, title := range w.titles {
title.SetFont(w.fontFace, w.fontMutex)
title.SetFont(w.font, w.fontSize)
}
}
@ -158,7 +157,7 @@ func (w *Window) AddChild(wgt ...Widget) {
for _, widget := range wgt {
t := NewText("")
t.SetFont(w.fontFace, w.fontMutex)
t.SetFont(w.font, w.fontSize)
w.children = append(w.children, &windowWidget{NewBox(), t, widget, w})
w.titles = append(w.titles, t)
@ -173,7 +172,7 @@ func (w *Window) AddChildWithTitle(wgt Widget, title string) int {
defer w.Unlock()
t := NewText(title)
t.SetFont(w.fontFace, w.fontMutex)
t.SetFont(w.font, w.fontSize)
w.children = append(w.children, &windowWidget{NewBox(), t, wgt, w})
w.titles = append(w.titles, t)