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:
parent
d17310185c
commit
416576f940
8 changed files with 102 additions and 75 deletions
|
@ -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
39
game.go
|
@ -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
14
go.mod
|
@ -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
28
go.sum
|
@ -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=
|
||||
|
|
15
input.go
15
input.go
|
@ -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.
|
||||
|
|
38
style.go
38
style.go
|
@ -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},
|
||||
|
|
9
text.go
9
text.go
|
@ -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.
|
||||
|
|
25
window.go
25
window.go
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue