Fix automatically resizing non-English text
This commit is contained in:
parent
baf88f4b7b
commit
b9cc08d3af
10 changed files with 202 additions and 187 deletions
37
button.go
37
button.go
|
@ -16,7 +16,6 @@ type Button struct {
|
|||
btnBackground color.RGBA
|
||||
textFont *text.GoTextFaceSource
|
||||
textSize int
|
||||
textAutoSize int
|
||||
borderSize int
|
||||
borderTop color.RGBA
|
||||
borderRight color.RGBA
|
||||
|
@ -37,7 +36,7 @@ func NewButton(label string, onSelected func() error) *Button {
|
|||
f.SetForegroundColor(textColor)
|
||||
f.SetHorizontal(messeji.AlignCenter)
|
||||
f.SetVertical(messeji.AlignCenter)
|
||||
f.SetScrollBarVisible(false)
|
||||
f.SetAutoResize(true)
|
||||
|
||||
b := &Button{
|
||||
Box: NewBox(),
|
||||
|
@ -53,7 +52,6 @@ func NewButton(label string, onSelected func() error) *Button {
|
|||
borderLeft: Style.ButtonBorderLeft,
|
||||
}
|
||||
b.SetBackground(Style.ButtonBgColor)
|
||||
b.resizeFont()
|
||||
return b
|
||||
}
|
||||
|
||||
|
@ -62,7 +60,6 @@ func (b *Button) SetRect(r image.Rectangle) {
|
|||
b.Box.rect = r
|
||||
|
||||
b.field.SetRect(r)
|
||||
b.resizeFont()
|
||||
|
||||
for _, w := range b.children {
|
||||
w.SetRect(r)
|
||||
|
@ -119,7 +116,6 @@ func (b *Button) SetText(text string) {
|
|||
defer b.Unlock()
|
||||
|
||||
b.field.SetText(text)
|
||||
b.resizeFont()
|
||||
}
|
||||
|
||||
// SetFont sets the font and text size of button label. Scaling is not applied.
|
||||
|
@ -128,36 +124,7 @@ func (b *Button) SetFont(fnt *text.GoTextFaceSource, size int) {
|
|||
defer b.Unlock()
|
||||
|
||||
b.textFont, b.textSize = fnt, size
|
||||
b.resizeFont()
|
||||
}
|
||||
|
||||
func (b *Button) resizeFont() {
|
||||
w, h := b.rect.Dx()-b.field.Padding()*2, b.rect.Dy()-b.field.Padding()*2
|
||||
if w == 0 || h == 0 {
|
||||
if b.textAutoSize == b.textSize {
|
||||
return
|
||||
}
|
||||
b.textAutoSize = b.textSize
|
||||
ff := FontFace(b.textFont, b.textSize)
|
||||
b.field.SetFont(ff, fontMutex)
|
||||
return
|
||||
}
|
||||
|
||||
var autoSize int
|
||||
var ff *text.GoTextFace
|
||||
for autoSize = b.textSize; autoSize > 0; autoSize-- {
|
||||
ff = FontFace(b.textFont, autoSize)
|
||||
bounds := BoundString(ff, b.field.Text())
|
||||
if bounds.Dx() <= w && bounds.Dy() <= h {
|
||||
break
|
||||
}
|
||||
}
|
||||
if b.textAutoSize == autoSize {
|
||||
return
|
||||
}
|
||||
|
||||
b.field.SetFont(ff, fontMutex)
|
||||
b.textAutoSize = autoSize
|
||||
b.field.SetFont(b.textFont, b.textSize, fontMutex)
|
||||
}
|
||||
|
||||
// SetHorizontal sets the horizontal alignment of the button label.
|
||||
|
|
2
game.go
2
game.go
|
@ -463,7 +463,7 @@ func draw(w Widget, screen *ebiten.Image) error {
|
|||
}
|
||||
|
||||
func newText() *messeji.TextField {
|
||||
f := messeji.NewTextField(FontFace(Style.TextFont, Scale(Style.TextSize)), fontMutex)
|
||||
f := messeji.NewTextField(Style.TextFont, Scale(Style.TextSize), fontMutex)
|
||||
f.SetForegroundColor(Style.TextColorLight)
|
||||
f.SetBackgroundColor(transparent)
|
||||
f.SetScrollBarColors(Style.ScrollAreaColor, Style.ScrollHandleColor)
|
||||
|
|
4
go.mod
4
go.mod
|
@ -18,8 +18,8 @@ require (
|
|||
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/exp/shiny v0.0.0-20241108190413-2d47ceb2692f // indirect
|
||||
golang.org/x/mobile v0.0.0-20241108191957-fa514ef75a0f // 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
|
||||
|
|
8
go.sum
8
go.sum
|
@ -24,12 +24,12 @@ github.com/llgcode/ps v0.0.0-20210114104736-f4b0c5d1e02e h1:ZAvbj5hI/G/EbAYAcj4y
|
|||
github.com/llgcode/ps v0.0.0-20210114104736-f4b0c5d1e02e/go.mod h1:1l8ky+Ew27CMX29uG+a2hNOKpeNYEQjjtiALiBlFQbY=
|
||||
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/exp/shiny v0.0.0-20241108190413-2d47ceb2692f h1:vic/+xALPgqKNmH4tJ2tsJh9M51ULiNg5hoQd84DVM4=
|
||||
golang.org/x/exp/shiny v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:3F+MieQB7dRYLTmnncoFbb1crS5lfQoTfDgQy6K4N0o=
|
||||
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/mobile v0.0.0-20241108191957-fa514ef75a0f h1:23H/YlmTHfmmvpZ+ajKZL0qLz0+IwFOIqQA0mQbmLeM=
|
||||
golang.org/x/mobile v0.0.0-20241108191957-fa514ef75a0f/go.mod h1:UbSUP4uu/C9hw9R2CkojhXlAxvayHjBdU9aRvE+c1To=
|
||||
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=
|
||||
|
|
4
input.go
4
input.go
|
@ -23,7 +23,7 @@ type Input struct {
|
|||
|
||||
// NewInput returns a new Input widget.
|
||||
func NewInput(text string, onSelected func(text string) (handled bool)) *Input {
|
||||
f := messeji.NewInputField(FontFace(Style.TextFont, Scale(Style.TextSize)), fontMutex)
|
||||
f := messeji.NewInputField(Style.TextFont, Scale(Style.TextSize), fontMutex)
|
||||
f.SetForegroundColor(Style.TextColorLight)
|
||||
f.SetBackgroundColor(transparent)
|
||||
f.SetScrollBarColors(Style.ScrollAreaColor, Style.ScrollHandleColor)
|
||||
|
@ -191,7 +191,7 @@ func (t *Input) SetFont(fnt *text.GoTextFaceSource, size int) {
|
|||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
t.field.SetFont(FontFace(fnt, size), fontMutex)
|
||||
t.field.SetFont(fnt, size, fontMutex)
|
||||
}
|
||||
|
||||
// Padding returns the amount of padding around the text within the field.
|
||||
|
|
|
@ -28,20 +28,17 @@ Below is an InputField, which accepts keyboard input.
|
|||
`
|
||||
|
||||
var (
|
||||
mplusNormalFont *text.GoTextFace
|
||||
fontMutex *sync.Mutex
|
||||
fontSource *text.GoTextFaceSource
|
||||
fontSize = 24
|
||||
fontMutex *sync.Mutex
|
||||
)
|
||||
|
||||
func init() {
|
||||
source, err := text.NewGoTextFaceSource(bytes.NewReader(fonts.MPlus1pRegular_ttf))
|
||||
var err error
|
||||
fontSource, err = text.NewGoTextFaceSource(bytes.NewReader(fonts.MPlus1pRegular_ttf))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
mplusNormalFont = &text.GoTextFace{
|
||||
Source: source,
|
||||
Size: 24,
|
||||
}
|
||||
}
|
||||
|
||||
type game struct {
|
||||
|
@ -63,8 +60,8 @@ type game struct {
|
|||
// NewDemoGame returns a new messeji demo game.
|
||||
func NewDemoGame() *game {
|
||||
g := &game{
|
||||
buffer: messeji.NewTextField(mplusNormalFont, fontMutex),
|
||||
input: messeji.NewInputField(mplusNormalFont, fontMutex),
|
||||
buffer: messeji.NewTextField(fontSource, fontSize, fontMutex),
|
||||
input: messeji.NewInputField(fontSource, fontSize, fontMutex),
|
||||
op: &ebiten.DrawImageOptions{
|
||||
Filter: ebiten.FilterNearest,
|
||||
},
|
||||
|
|
|
@ -40,9 +40,9 @@ type InputField struct {
|
|||
}
|
||||
|
||||
// NewInputField returns a new InputField. See type documentation for more info.
|
||||
func NewInputField(face *text.GoTextFace, faceMutex *sync.Mutex) *InputField {
|
||||
func NewInputField(fontSource *text.GoTextFaceSource, fontSize int, fontMutex *sync.Mutex) *InputField {
|
||||
f := &InputField{
|
||||
TextField: NewTextField(face, faceMutex),
|
||||
TextField: NewTextField(fontSource, fontSize, fontMutex),
|
||||
}
|
||||
f.TextField.suffix = "_"
|
||||
return f
|
||||
|
|
|
@ -94,11 +94,22 @@ type TextField struct {
|
|||
// vertical is the vertical alignment of the text within field.
|
||||
vertical Alignment
|
||||
|
||||
// face is the font face of the text within the field.
|
||||
face *text.GoTextFace
|
||||
autoResize bool
|
||||
|
||||
// faceMutex is the lock which is held whenever utilizing the font face.
|
||||
faceMutex *sync.Mutex
|
||||
// fontSource is the font face source of the text within the field.
|
||||
fontSource *text.GoTextFaceSource
|
||||
|
||||
// fontFace is the font face of the text within the field.
|
||||
fontFace *text.GoTextFace
|
||||
|
||||
// fontSize is the maximum font size of the text within the field.
|
||||
fontSize int
|
||||
|
||||
// overrideFontSize is the actual font size of the text within the field.
|
||||
overrideFontSize int
|
||||
|
||||
// fontMutex is the lock which is held whenever utilizing the font.
|
||||
fontMutex *sync.Mutex
|
||||
|
||||
// lineHeight is the height of a single line of text.
|
||||
lineHeight int
|
||||
|
@ -188,14 +199,15 @@ type TextField struct {
|
|||
}
|
||||
|
||||
// NewTextField returns a new TextField. See type documentation for more info.
|
||||
func NewTextField(face *text.GoTextFace, faceMutex *sync.Mutex) *TextField {
|
||||
if faceMutex == nil {
|
||||
faceMutex = &sync.Mutex{}
|
||||
func NewTextField(fontSource *text.GoTextFaceSource, fontSize int, fontMutex *sync.Mutex) *TextField {
|
||||
if fontMutex == nil {
|
||||
fontMutex = &sync.Mutex{}
|
||||
}
|
||||
|
||||
f := &TextField{
|
||||
face: face,
|
||||
faceMutex: faceMutex,
|
||||
fontSource: fontSource,
|
||||
fontSize: fontSize,
|
||||
fontMutex: fontMutex,
|
||||
textColor: initialForeground,
|
||||
backgroundColor: initialBackground,
|
||||
padding: initialPadding,
|
||||
|
@ -210,10 +222,10 @@ func NewTextField(face *text.GoTextFace, faceMutex *sync.Mutex) *TextField {
|
|||
redraw: true,
|
||||
}
|
||||
|
||||
f.faceMutex.Lock()
|
||||
defer f.faceMutex.Unlock()
|
||||
f.fontMutex.Lock()
|
||||
defer f.fontMutex.Unlock()
|
||||
|
||||
f.fontUpdated()
|
||||
f.resizeFont()
|
||||
return f
|
||||
}
|
||||
|
||||
|
@ -243,6 +255,12 @@ func (f *TextField) SetRect(r image.Rectangle) {
|
|||
}
|
||||
|
||||
f.r = r
|
||||
f.resizeFont()
|
||||
}
|
||||
|
||||
func (f *TextField) text() string {
|
||||
f.processIncoming()
|
||||
return string(bytes.Join(f.buffer, []byte("\n")))
|
||||
}
|
||||
|
||||
// Text returns the text in the field.
|
||||
|
@ -250,9 +268,7 @@ func (f *TextField) Text() string {
|
|||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
f.processIncoming()
|
||||
|
||||
return string(bytes.Join(f.buffer, []byte("\n")))
|
||||
return f.text()
|
||||
}
|
||||
|
||||
// SetText sets the text in the field.
|
||||
|
@ -268,6 +284,7 @@ func (f *TextField) SetText(text string) {
|
|||
f.incoming = append(f.incoming[:0], []byte(text)...)
|
||||
f.modified = true
|
||||
f.redraw = true
|
||||
f.resizeFont()
|
||||
}
|
||||
|
||||
// SetLast sets the text of the last line of the field. Newline characters are
|
||||
|
@ -292,6 +309,7 @@ func (f *TextField) SetLast(text string) {
|
|||
|
||||
f.modified = true
|
||||
f.redraw = true
|
||||
f.resizeFont()
|
||||
}
|
||||
|
||||
// SetPrefix sets the text shown before the content of the field.
|
||||
|
@ -303,6 +321,7 @@ func (f *TextField) SetPrefix(text string) {
|
|||
f.needWrap = 0
|
||||
f.wrapStart = 0
|
||||
f.modified = true
|
||||
f.resizeFont()
|
||||
}
|
||||
|
||||
// SetSuffix sets the text shown after the content of the field.
|
||||
|
@ -314,6 +333,7 @@ func (f *TextField) SetSuffix(text string) {
|
|||
f.needWrap = 0
|
||||
f.wrapStart = 0
|
||||
f.modified = true
|
||||
f.resizeFont()
|
||||
}
|
||||
|
||||
// SetFollow sets whether the field should automatically scroll to the end when
|
||||
|
@ -339,6 +359,7 @@ func (f *TextField) SetSingleLine(single bool) {
|
|||
f.needWrap = 0
|
||||
f.wrapStart = 0
|
||||
f.modified = true
|
||||
f.resizeFont()
|
||||
}
|
||||
|
||||
// SetHorizontal sets the horizontal alignment of the text within the field.
|
||||
|
@ -392,6 +413,7 @@ func (f *TextField) SetLineHeight(height int) {
|
|||
f.needWrap = 0
|
||||
f.wrapStart = 0
|
||||
f.modified = true
|
||||
f.resizeFont()
|
||||
}
|
||||
|
||||
// ForegroundColor returns the color of the text within the field.
|
||||
|
@ -421,7 +443,7 @@ func (f *TextField) SetBackgroundColor(c color.RGBA) {
|
|||
}
|
||||
|
||||
// SetFont sets the font face of the text within the field.
|
||||
func (f *TextField) SetFont(face *text.GoTextFace, mutex *sync.Mutex) {
|
||||
func (f *TextField) SetFont(faceSource *text.GoTextFaceSource, size int, mutex *sync.Mutex) {
|
||||
if mutex == nil {
|
||||
mutex = &sync.Mutex{}
|
||||
}
|
||||
|
@ -432,13 +454,25 @@ func (f *TextField) SetFont(face *text.GoTextFace, mutex *sync.Mutex) {
|
|||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
|
||||
f.face = face
|
||||
f.faceMutex = mutex
|
||||
f.fontUpdated()
|
||||
f.fontSource = faceSource
|
||||
f.fontSize = size
|
||||
f.fontMutex = mutex
|
||||
f.overrideFontSize = 0
|
||||
|
||||
f.needWrap = 0
|
||||
f.wrapStart = 0
|
||||
f.modified = true
|
||||
f.resizeFont()
|
||||
}
|
||||
|
||||
// SetAutoResize sets whether the font is automatically scaled down when it is
|
||||
// too large to fit the entire text buffer on one line.
|
||||
func (f *TextField) SetAutoResize(resize bool) {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
f.autoResize = resize
|
||||
f.resizeFont()
|
||||
}
|
||||
|
||||
// Padding returns the amount of padding around the text within the field.
|
||||
|
@ -458,6 +492,7 @@ func (f *TextField) SetPadding(padding int) {
|
|||
f.needWrap = 0
|
||||
f.wrapStart = 0
|
||||
f.modified = true
|
||||
f.resizeFont()
|
||||
}
|
||||
|
||||
// Visible returns whether the field is currently visible on the screen.
|
||||
|
@ -493,6 +528,7 @@ func (f *TextField) SetScrollBarWidth(width int) {
|
|||
f.needWrap = 0
|
||||
f.wrapStart = 0
|
||||
f.modified = true
|
||||
f.resizeFont()
|
||||
}
|
||||
|
||||
// SetScrollBarColors sets the color of the scroll bar area and handle.
|
||||
|
@ -580,6 +616,53 @@ func (f *TextField) SetWordWrap(wrap bool) {
|
|||
f.modified = true
|
||||
}
|
||||
|
||||
func (f *TextField) resizeFont() {
|
||||
if !f.autoResize {
|
||||
if f.overrideFontSize == f.fontSize {
|
||||
return
|
||||
}
|
||||
f.overrideFontSize = f.fontSize
|
||||
f.fontFace = fontFace(f.fontSource, f.overrideFontSize)
|
||||
f.fontUpdated()
|
||||
f.bufferModified()
|
||||
return
|
||||
}
|
||||
|
||||
w, h := f.r.Dx()-f.padding*2, f.r.Dy()-f.padding*2
|
||||
if w <= 0 || h <= 0 {
|
||||
if f.overrideFontSize == f.fontSize {
|
||||
return
|
||||
}
|
||||
f.overrideFontSize = f.fontSize
|
||||
f.fontFace = fontFace(f.fontSource, f.overrideFontSize)
|
||||
f.fontUpdated()
|
||||
f.bufferModified()
|
||||
return
|
||||
}
|
||||
|
||||
buffer := f.text()
|
||||
|
||||
var size int
|
||||
var ff *text.GoTextFace
|
||||
var m text.Metrics
|
||||
for size = f.fontSize; size > 1; size-- {
|
||||
ff = fontFace(f.fontSource, size)
|
||||
m = ff.Metrics()
|
||||
bw, bh := text.Measure(buffer, ff, m.HAscent+m.HDescent)
|
||||
if int(bw) <= w && int(bh) <= h {
|
||||
break
|
||||
}
|
||||
}
|
||||
if f.overrideFontSize == size {
|
||||
return
|
||||
}
|
||||
|
||||
f.overrideFontSize = size
|
||||
f.fontFace = ff
|
||||
f.fontUpdated()
|
||||
f.bufferModified()
|
||||
}
|
||||
|
||||
// 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 *TextField) SetHandleKeyboard(handle bool) {
|
||||
|
@ -600,6 +683,7 @@ func (f *TextField) SetMask(r rune) {
|
|||
|
||||
f.maskRune = r
|
||||
f.modified = true
|
||||
f.resizeFont()
|
||||
}
|
||||
|
||||
// Write writes to the field's buffer.
|
||||
|
@ -763,12 +847,12 @@ func (f *TextField) Draw(screen *ebiten.Image) {
|
|||
defer f.Unlock()
|
||||
|
||||
if f.modified {
|
||||
f.faceMutex.Lock()
|
||||
f.fontMutex.Lock()
|
||||
|
||||
f.bufferModified()
|
||||
f.modified = false
|
||||
|
||||
f.faceMutex.Unlock()
|
||||
f.fontMutex.Unlock()
|
||||
}
|
||||
|
||||
if !f.visible || rectIsZero(f.r) {
|
||||
|
@ -776,12 +860,12 @@ func (f *TextField) Draw(screen *ebiten.Image) {
|
|||
}
|
||||
|
||||
if f.redraw {
|
||||
f.faceMutex.Lock()
|
||||
f.fontMutex.Lock()
|
||||
|
||||
f.drawImage()
|
||||
f.redraw = false
|
||||
|
||||
f.faceMutex.Unlock()
|
||||
f.fontMutex.Unlock()
|
||||
}
|
||||
|
||||
op := &ebiten.DrawImageOptions{}
|
||||
|
@ -790,7 +874,7 @@ func (f *TextField) Draw(screen *ebiten.Image) {
|
|||
}
|
||||
|
||||
func (f *TextField) fontUpdated() {
|
||||
m := f.face.Metrics()
|
||||
m := f.fontFace.Metrics()
|
||||
f.lineHeight = int(m.HAscent + m.HDescent)
|
||||
f.lineOffset = int(m.HLineGap)
|
||||
if f.lineOffset < 0 {
|
||||
|
@ -807,9 +891,9 @@ func (f *TextField) wrapContent(withScrollBar bool) {
|
|||
}
|
||||
f.wrapScrollBar = withScrollBar
|
||||
|
||||
if f.singleLine || len(f.buffer) == 0 {
|
||||
if len(f.buffer) == 0 || (f.singleLine && !f.autoResize) {
|
||||
buffer := f.prefix + string(bytes.Join(f.buffer, nil)) + f.suffix
|
||||
w, _ := text.Measure(buffer, f.face, float64(f.lineHeight))
|
||||
w, _ := text.Measure(buffer, f.fontFace, float64(f.lineHeight))
|
||||
|
||||
f.bufferWrapped = []string{buffer}
|
||||
f.wrapStart = 0
|
||||
|
@ -836,7 +920,7 @@ func (f *TextField) wrapContent(withScrollBar bool) {
|
|||
line += f.suffix
|
||||
}
|
||||
l := len(line)
|
||||
availableWidth := w - (f.padding * 2) - 5
|
||||
availableWidth := w - (f.padding * 2) - 15
|
||||
|
||||
f.wrapStart = j
|
||||
|
||||
|
@ -864,29 +948,31 @@ func (f *TextField) wrapContent(withScrollBar bool) {
|
|||
WRAPTEXT:
|
||||
for start < l {
|
||||
lastSpace = -1
|
||||
for e, r := range line[start:] {
|
||||
if unicode.IsSpace(r) {
|
||||
lastSpace = start + e + 1
|
||||
var e int
|
||||
for _, r := range line[start:] {
|
||||
if e > l-start {
|
||||
e = l - start
|
||||
}
|
||||
w, _ := text.Measure(line[start:start+e]+string(r), f.face, float64(f.lineHeight))
|
||||
runeLength := len(string(r))
|
||||
if unicode.IsSpace(r) {
|
||||
lastSpace = start + e
|
||||
}
|
||||
w, _ := text.Measure(line[start:start+e], f.fontFace, float64(f.lineHeight))
|
||||
boundsWidth = int(w)
|
||||
if boundsWidth > availableWidth {
|
||||
original := e
|
||||
if e != l-start-1 && e > 0 {
|
||||
e--
|
||||
if e > 0 {
|
||||
e -= runeLength
|
||||
}
|
||||
if f.wordWrap && lastSpace != -1 && start+e+1 < l {
|
||||
e = lastSpace - start - 1
|
||||
}
|
||||
if e != original {
|
||||
w, _ := text.Measure(line[start:start+e+1], f.face, float64(f.lineHeight))
|
||||
boundsWidth = int(w)
|
||||
if f.wordWrap && lastSpace != -1 && start+e+runeLength < l {
|
||||
e = lastSpace - start
|
||||
}
|
||||
w, _ := text.Measure(line[start:start+e], f.fontFace, float64(f.lineHeight))
|
||||
boundsWidth = int(w)
|
||||
|
||||
if len(f.bufferWrapped) <= j {
|
||||
f.bufferWrapped = append(f.bufferWrapped, line[start:start+e+1])
|
||||
f.bufferWrapped = append(f.bufferWrapped, line[start:start+e])
|
||||
} else {
|
||||
f.bufferWrapped[j] = line[start : start+e+1]
|
||||
f.bufferWrapped[j] = line[start : start+e]
|
||||
}
|
||||
if len(f.lineWidths) <= j {
|
||||
f.lineWidths = append(f.lineWidths, boundsWidth)
|
||||
|
@ -895,11 +981,14 @@ func (f *TextField) wrapContent(withScrollBar bool) {
|
|||
}
|
||||
j++
|
||||
|
||||
start = start + e + 1
|
||||
start += e + runeLength
|
||||
continue WRAPTEXT
|
||||
}
|
||||
e += runeLength
|
||||
}
|
||||
|
||||
w, _ := text.Measure(line[start:], f.fontFace, float64(f.lineHeight))
|
||||
boundsWidth = int(w)
|
||||
if len(f.bufferWrapped) <= j {
|
||||
f.bufferWrapped = append(f.bufferWrapped, line[start:])
|
||||
} else {
|
||||
|
@ -954,7 +1043,7 @@ func (f *TextField) drawContent() (overflow bool) {
|
|||
numVisible := lastVisible - firstVisible
|
||||
// Calculate buffer size (width for single-line fields or height for multi-line fields).
|
||||
if f.singleLine {
|
||||
w, _ := text.Measure(f.bufferWrapped[firstVisible], f.face, float64(f.lineHeight))
|
||||
w, _ := text.Measure(f.bufferWrapped[firstVisible], f.fontFace, float64(f.lineHeight))
|
||||
f.bufferSize = int(w)
|
||||
if f.bufferSize > fieldWidth-f.padding*2 {
|
||||
overflow = true
|
||||
|
@ -1003,9 +1092,9 @@ func (f *TextField) drawContent() (overflow bool) {
|
|||
|
||||
// Align vertically.
|
||||
totalHeight := f.lineOffset + lineHeight*(lines)
|
||||
if f.vertical == AlignCenter && totalHeight <= h {
|
||||
if f.vertical == AlignCenter && (f.autoResize || totalHeight <= h) {
|
||||
lineY = fieldHeight/2 - totalHeight/2 + f.lineOffset + (lineHeight * (i)) - 2
|
||||
} else if f.vertical == AlignEnd && totalHeight <= h {
|
||||
} else if f.vertical == AlignEnd && (f.autoResize || totalHeight <= h) {
|
||||
lineY = fieldHeight - lineHeight*(numVisible+1-i) - f.padding
|
||||
}
|
||||
|
||||
|
@ -1013,7 +1102,7 @@ func (f *TextField) drawContent() (overflow bool) {
|
|||
op := &text.DrawOptions{}
|
||||
op.GeoM.Translate(float64(lineX), float64(lineY))
|
||||
op.ColorScale.ScaleWithColor(f.textColor)
|
||||
text.Draw(f.img, line, f.face, op)
|
||||
text.Draw(f.img, line, f.fontFace, op)
|
||||
}
|
||||
|
||||
return overflow
|
||||
|
@ -1040,10 +1129,24 @@ func (f *TextField) clampOffset() {
|
|||
}
|
||||
|
||||
func (f *TextField) showScrollBar() bool {
|
||||
return !f.singleLine && f.scrollVisible && (f.overflow || !f.scrollAutoHide)
|
||||
return !f.autoResize && !f.singleLine && f.scrollVisible && (f.overflow || !f.scrollAutoHide)
|
||||
}
|
||||
|
||||
func (f *TextField) wrap() {
|
||||
w, h := f.r.Dx(), f.r.Dy()
|
||||
|
||||
var newImage bool
|
||||
if f.img == nil {
|
||||
newImage = true
|
||||
} else {
|
||||
imgRect := f.img.Bounds()
|
||||
imgW, imgH := imgRect.Dx(), imgRect.Dy()
|
||||
newImage = imgW != w || imgH != h
|
||||
}
|
||||
if newImage {
|
||||
f.img = ebiten.NewImage(w, h)
|
||||
}
|
||||
|
||||
showScrollBar := f.showScrollBar()
|
||||
f.wrapContent(showScrollBar)
|
||||
f.overflow = f.drawContent()
|
||||
|
@ -1060,24 +1163,12 @@ func (f *TextField) drawImage() {
|
|||
return
|
||||
}
|
||||
|
||||
w, h := f.r.Dx(), f.r.Dy()
|
||||
|
||||
var newImage bool
|
||||
if f.img == nil {
|
||||
newImage = true
|
||||
} else {
|
||||
imgRect := f.img.Bounds()
|
||||
imgW, imgH := imgRect.Dx(), imgRect.Dy()
|
||||
newImage = imgW != w || imgH != h
|
||||
}
|
||||
if newImage {
|
||||
f.img = ebiten.NewImage(w, h)
|
||||
}
|
||||
|
||||
f.wrap()
|
||||
|
||||
// Draw scrollbar.
|
||||
if f.showScrollBar() {
|
||||
w, h := f.r.Dx(), f.r.Dy()
|
||||
|
||||
scrollAreaX, scrollAreaY := w-f.scrollWidth, 0
|
||||
f.scrollRect = image.Rect(scrollAreaX, scrollAreaY, scrollAreaX+f.scrollWidth, h)
|
||||
|
||||
|
@ -1152,3 +1243,10 @@ func (f *TextField) bufferModified() {
|
|||
func rectIsZero(r image.Rectangle) bool {
|
||||
return r.Dx() == 0 || r.Dy() == 0
|
||||
}
|
||||
|
||||
func fontFace(source *text.GoTextFaceSource, size int) *text.GoTextFace {
|
||||
return &text.GoTextFace{
|
||||
Source: source,
|
||||
Size: float64(size),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,10 +31,8 @@ func testFace() (*text.GoTextFace, error) {
|
|||
}
|
||||
|
||||
func TestWrapContent(t *testing.T) {
|
||||
face, err := testFace()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
const fontSize = 24
|
||||
fontSource := defaultFont()
|
||||
|
||||
testCases := []struct {
|
||||
long bool // Test data type.
|
||||
|
@ -69,7 +67,7 @@ func TestWrapContent(t *testing.T) {
|
|||
}
|
||||
|
||||
t.Run(name, func(t *testing.T) {
|
||||
textField := NewTextField(face, &sync.Mutex{})
|
||||
textField := NewTextField(fontSource, fontSize, &sync.Mutex{})
|
||||
testTextField = textField
|
||||
textField.SetRect(testRect)
|
||||
textField.SetWordWrap(c.wordWrap)
|
||||
|
@ -80,10 +78,8 @@ func TestWrapContent(t *testing.T) {
|
|||
}
|
||||
|
||||
func BenchmarkWrapContent(b *testing.B) {
|
||||
face, err := testFace()
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
}
|
||||
const fontSize = 24
|
||||
fontSource := defaultFont()
|
||||
|
||||
testCases := []struct {
|
||||
long bool // Test data type.
|
||||
|
@ -117,7 +113,7 @@ func BenchmarkWrapContent(b *testing.B) {
|
|||
name += "/wrapWord"
|
||||
}
|
||||
|
||||
textField := NewTextField(face, &sync.Mutex{})
|
||||
textField := NewTextField(fontSource, fontSize, &sync.Mutex{})
|
||||
testTextField = textField
|
||||
textField.SetRect(testRect)
|
||||
textField.SetWordWrap(c.wordWrap)
|
||||
|
@ -131,3 +127,11 @@ func BenchmarkWrapContent(b *testing.B) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func defaultFont() *text.GoTextFaceSource {
|
||||
source, err := text.NewGoTextFaceSource(bytes.NewReader(fonts.MPlus1pRegular_ttf))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return source
|
||||
}
|
||||
|
|
67
text.go
67
text.go
|
@ -15,8 +15,6 @@ type Text struct {
|
|||
field *messeji.TextField
|
||||
textFont *text.GoTextFaceSource
|
||||
textSize int
|
||||
textResize bool
|
||||
textAutoSize int
|
||||
scrollVisible bool
|
||||
children []Widget
|
||||
}
|
||||
|
@ -35,7 +33,6 @@ func NewText(text string) *Text {
|
|||
textSize: Scale(Style.TextSize),
|
||||
scrollVisible: true,
|
||||
}
|
||||
t.resizeFont()
|
||||
return t
|
||||
}
|
||||
|
||||
|
@ -46,7 +43,6 @@ func (t *Text) SetRect(r image.Rectangle) {
|
|||
|
||||
t.rect = r
|
||||
t.field.SetRect(r)
|
||||
t.resizeFont()
|
||||
}
|
||||
|
||||
// Foreground return the color of the text within the field.
|
||||
|
@ -139,7 +135,6 @@ func (t *Text) Write(p []byte) (n int, err error) {
|
|||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
t.resizeFont()
|
||||
return n, err
|
||||
}
|
||||
|
||||
|
@ -157,7 +152,6 @@ func (t *Text) SetText(text string) {
|
|||
defer t.Unlock()
|
||||
|
||||
t.field.SetText(text)
|
||||
t.resizeFont()
|
||||
}
|
||||
|
||||
// SetLast sets the text of the last line of the field.
|
||||
|
@ -166,52 +160,18 @@ func (t *Text) SetLast(text string) {
|
|||
defer t.Unlock()
|
||||
|
||||
t.field.SetLast(text)
|
||||
t.resizeFont()
|
||||
}
|
||||
|
||||
func (t *Text) resizeFont() {
|
||||
if !t.textResize {
|
||||
if t.textAutoSize == t.textSize {
|
||||
return
|
||||
}
|
||||
t.textAutoSize = t.textSize
|
||||
ff := FontFace(t.textFont, t.textSize)
|
||||
t.field.SetFont(ff, fontMutex)
|
||||
return
|
||||
}
|
||||
// SetAutoResize sets whether the font is automatically scaled down when it is
|
||||
// too large to fit the entire text buffer on one line.
|
||||
func (t *Text) SetAutoResize(resize bool) {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
w, h := t.rect.Dx()-t.field.Padding()*2, t.rect.Dy()-t.field.Padding()*2
|
||||
if w == 0 || h == 0 {
|
||||
if t.textAutoSize == t.textSize {
|
||||
return
|
||||
}
|
||||
t.textAutoSize = t.textSize
|
||||
ff := FontFace(t.textFont, t.textSize)
|
||||
t.field.SetFont(ff, fontMutex)
|
||||
return
|
||||
}
|
||||
|
||||
var autoSize int
|
||||
var ff *text.GoTextFace
|
||||
for autoSize = t.textSize; autoSize > 0; autoSize-- {
|
||||
ff = FontFace(t.textFont, autoSize)
|
||||
bounds := BoundString(ff, t.field.Text())
|
||||
if bounds.Dx() <= w && bounds.Dy() <= h {
|
||||
break
|
||||
}
|
||||
}
|
||||
if t.textAutoSize == autoSize {
|
||||
return
|
||||
}
|
||||
|
||||
t.field.SetFont(ff, fontMutex)
|
||||
t.textAutoSize = autoSize
|
||||
t.field.SetAutoResize(resize)
|
||||
}
|
||||
|
||||
func (t *Text) scrollBarVisible() bool {
|
||||
if t.textResize {
|
||||
return false
|
||||
}
|
||||
return t.scrollVisible
|
||||
}
|
||||
|
||||
|
@ -238,7 +198,7 @@ func (t *Text) FontSize() int {
|
|||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
return t.textAutoSize
|
||||
return t.textSize
|
||||
}
|
||||
|
||||
// SetFont sets the font and text size of the field. Scaling is not applied.
|
||||
|
@ -247,18 +207,7 @@ func (t *Text) SetFont(fnt *text.GoTextFaceSource, size int) {
|
|||
defer t.Unlock()
|
||||
|
||||
t.textFont, t.textSize = fnt, size
|
||||
t.resizeFont()
|
||||
}
|
||||
|
||||
// SetAutoResize sets whether the font is automatically scaled down when it is
|
||||
// too large to fit the entire text buffer on one line.
|
||||
func (t *Text) SetAutoResize(resize bool) {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
t.textResize = resize
|
||||
t.resizeFont()
|
||||
t.field.SetScrollBarVisible(t.scrollBarVisible())
|
||||
t.field.SetFont(t.textFont, t.textSize, fontMutex)
|
||||
}
|
||||
|
||||
// Padding returns the amount of padding around the text within the field.
|
||||
|
|
Loading…
Reference in a new issue