Add SetHorizontal and SetVertical
These methods control how text is aligned within the field.
This commit is contained in:
parent
ed816e4129
commit
b37820227a
5 changed files with 115 additions and 23 deletions
|
@ -1,8 +1,9 @@
|
|||
# messeji
|
||||
[](https://docs.rocketnine.space/code.rocketnine.space/tslocum/messeji)
|
||||
[](https://liberapay.com/rocketnine.space)
|
||||
[](https://liberapay.com/rocketnine.space)
|
||||
[](https://www.patreon.com/rocketnine)
|
||||
|
||||
Text widgets for [Ebiten](https://github.com/hajimehoshi/ebiten)
|
||||
Text widgets for [Ebitengine](https://github.com/hajimehoshi/ebiten)
|
||||
|
||||
**Note:** [IME](https://en.wikipedia.org/wiki/Input_method) is not yet supported.
|
||||
|
||||
|
|
2
doc.go
2
doc.go
|
@ -1,4 +1,4 @@
|
|||
/*
|
||||
Package messeji provides text widgets for Ebiten.
|
||||
Package messeji provides text widgets for Ebitengine.
|
||||
*/
|
||||
package messeji
|
||||
|
|
|
@ -21,9 +21,10 @@ 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.
|
||||
Press <Tab> to toggle word wrap.
|
||||
Press <Enter> to append input text.
|
||||
Press <Ctrl+M> to toggle multi-line.
|
||||
<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
|
||||
|
@ -36,7 +37,7 @@ func init() {
|
|||
|
||||
const dpi = 72
|
||||
mplusNormalFont, err = opentype.NewFace(tt, &opentype.FaceOptions{
|
||||
Size: 32,
|
||||
Size: 28,
|
||||
DPI: dpi,
|
||||
Hinting: font.HintingFull,
|
||||
})
|
||||
|
@ -57,6 +58,8 @@ type game struct {
|
|||
op *ebiten.DrawImageOptions
|
||||
|
||||
spinnerIndex int
|
||||
|
||||
horizontal messeji.Alignment
|
||||
}
|
||||
|
||||
// NewDemoGame returns a new messeji demo game.
|
||||
|
@ -109,21 +112,26 @@ func (g *game) Layout(outsideWidth, outsideHeight int) (int, int) {
|
|||
}
|
||||
|
||||
func (g *game) Update() error {
|
||||
if inpututil.IsKeyJustPressed(ebiten.KeyEnter) && !g.input.Visible() {
|
||||
if inpututil.IsKeyJustPressed(ebiten.KeyEnter) && ebiten.IsKeyPressed(ebiten.KeyControl) {
|
||||
g.singleLine = !g.singleLine
|
||||
g.input.SetSingleLine(g.singleLine)
|
||||
return nil
|
||||
} else if inpututil.IsKeyJustPressed(ebiten.KeyEnter) && !g.input.Visible() {
|
||||
g.input.SetVisible(true)
|
||||
return nil
|
||||
}
|
||||
|
||||
if inpututil.IsKeyJustPressed(ebiten.KeyTab) {
|
||||
if inpututil.IsKeyJustPressed(ebiten.KeyTab) && ebiten.IsKeyPressed(ebiten.KeyControl) {
|
||||
wrap := g.buffer.WordWrap()
|
||||
g.buffer.SetWordWrap(!wrap)
|
||||
g.input.SetWordWrap(!wrap)
|
||||
return nil
|
||||
}
|
||||
|
||||
if inpututil.IsKeyJustPressed(ebiten.KeyM) && ebiten.IsKeyPressed(ebiten.KeyControl) {
|
||||
g.singleLine = !g.singleLine
|
||||
g.input.SetSingleLine(g.singleLine)
|
||||
} else if inpututil.IsKeyJustPressed(ebiten.KeyTab) {
|
||||
g.horizontal++
|
||||
if g.horizontal > messeji.AlignEnd {
|
||||
g.horizontal = messeji.AlignStart
|
||||
}
|
||||
g.buffer.SetHorizontal(g.horizontal)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
BIN
screenshot.png
BIN
screenshot.png
Binary file not shown.
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 71 KiB |
101
textfield.go
101
textfield.go
|
@ -13,8 +13,22 @@ import (
|
|||
"golang.org/x/image/font"
|
||||
)
|
||||
|
||||
// Alignment specifies how text is aligned within the field.
|
||||
type Alignment int
|
||||
|
||||
const (
|
||||
initialPadding = 2
|
||||
// AlignStart aligns text at the start of the field.
|
||||
AlignStart Alignment = 0
|
||||
|
||||
// AlignCenter aligns text at the center of the field.
|
||||
AlignCenter Alignment = 1
|
||||
|
||||
// AlignEnd aligns text at the end of the field.
|
||||
AlignEnd Alignment = 2
|
||||
)
|
||||
|
||||
const (
|
||||
initialPadding = 5
|
||||
initialScrollWidth = 32
|
||||
)
|
||||
|
||||
|
@ -51,9 +65,18 @@ type TextField struct {
|
|||
// line mode is enabled,
|
||||
bufferSize int
|
||||
|
||||
// lineWidths is the size (in pixels) of each line as it appears on the screen.
|
||||
lineWidths []int
|
||||
|
||||
// singleLine is whether the field displays all text on a single line.
|
||||
singleLine bool
|
||||
|
||||
// horizontal is the horizontal alignment of the text within field.
|
||||
horizontal Alignment
|
||||
|
||||
// vertical is the vertical alignment of the text within field.
|
||||
vertical Alignment
|
||||
|
||||
// face is the font face of the text within the field.
|
||||
face font.Face
|
||||
|
||||
|
@ -207,6 +230,32 @@ func (f *TextField) SetSingleLine(single bool) {
|
|||
f.bufferModified()
|
||||
}
|
||||
|
||||
// SetHorizontal sets the horizontal alignment of the text within the field.
|
||||
func (f *TextField) SetHorizontal(h Alignment) {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
if f.horizontal == h {
|
||||
return
|
||||
}
|
||||
|
||||
f.horizontal = h
|
||||
f.bufferModified()
|
||||
}
|
||||
|
||||
// SetVertical sets the veritcal alignment of the text within the field.
|
||||
func (f *TextField) SetVertical(v Alignment) {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
if f.vertical == v {
|
||||
return
|
||||
}
|
||||
|
||||
f.vertical = v
|
||||
f.bufferModified()
|
||||
}
|
||||
|
||||
// LineHeight returns the line height for the field.
|
||||
func (f *TextField) LineHeight() int {
|
||||
f.Lock()
|
||||
|
@ -467,10 +516,15 @@ func (f *TextField) setDefaultLineHeight() {
|
|||
}
|
||||
|
||||
func (f *TextField) wrapContent(withScrollBar bool) {
|
||||
f.lineWidths = f.lineWidths[:0]
|
||||
buffer := f.prefix + f.buffer + f.suffix
|
||||
|
||||
if f.singleLine {
|
||||
f.bufferWrapped = []string{strings.ReplaceAll(buffer, "\n", "")}
|
||||
buffer = strings.ReplaceAll(buffer, "\n", "")
|
||||
bounds := text.BoundString(f.face, buffer)
|
||||
|
||||
f.bufferWrapped = []string{buffer}
|
||||
f.lineWidths = append(f.lineWidths, bounds.Dx())
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -483,8 +537,11 @@ func (f *TextField) wrapContent(withScrollBar bool) {
|
|||
l := len(line)
|
||||
var start int
|
||||
var end int
|
||||
var initialEnd int
|
||||
for start < l {
|
||||
for end = l; end > start; end-- {
|
||||
initialEnd = end
|
||||
|
||||
bounds := text.BoundString(f.face, line[start:end])
|
||||
if bounds.Dx() < w-(f.padding*2)-2 {
|
||||
if f.wordWrap {
|
||||
|
@ -492,12 +549,21 @@ func (f *TextField) wrapContent(withScrollBar bool) {
|
|||
for endOffset := 0; endOffset < end-start; endOffset++ {
|
||||
if unicode.IsSpace(rune(line[end-endOffset])) {
|
||||
end = end - endOffset
|
||||
if end < l-1 {
|
||||
end++
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if end != initialEnd && f.horizontal != AlignStart {
|
||||
bounds = text.BoundString(f.face, line[start:end])
|
||||
}
|
||||
|
||||
f.bufferWrapped = append(f.bufferWrapped, line[start:end])
|
||||
f.lineWidths = append(f.lineWidths, bounds.Dx())
|
||||
break
|
||||
}
|
||||
}
|
||||
|
@ -510,12 +576,19 @@ func (f *TextField) wrapContent(withScrollBar bool) {
|
|||
func (f *TextField) drawContent() (overflow bool) {
|
||||
f.img.Fill(f.backgroundColor)
|
||||
|
||||
fieldWidth := f.r.Dx()
|
||||
fieldHeight := f.r.Dy()
|
||||
if f.showScrollBar() {
|
||||
fieldWidth -= f.scrollWidth
|
||||
}
|
||||
lines := len(f.bufferWrapped)
|
||||
|
||||
h := f.r.Dy()
|
||||
lineHeight := f.overrideLineHeight
|
||||
if lineHeight == 0 {
|
||||
lineHeight = f.lineHeight
|
||||
}
|
||||
lineOffset := 7
|
||||
lineOffset := lineHeight / 3
|
||||
f.bufferSize = 0
|
||||
for i, line := range f.bufferWrapped {
|
||||
lineX := f.padding
|
||||
|
@ -545,6 +618,18 @@ func (f *TextField) drawContent() (overflow bool) {
|
|||
lineY -= f.offset
|
||||
}
|
||||
|
||||
if f.horizontal == AlignCenter {
|
||||
lineX = (fieldWidth - f.lineWidths[i]) / 2
|
||||
} else if f.horizontal == AlignEnd {
|
||||
lineX = (fieldWidth - f.lineWidths[i]) - f.padding*2
|
||||
}
|
||||
|
||||
if f.vertical == AlignCenter && lineHeight*lines <= h {
|
||||
lineY = (fieldHeight-(lineHeight*lines))/2 + lineHeight*(i+1) - lineOffset
|
||||
} else if f.vertical == AlignEnd && lineHeight*lines <= h {
|
||||
lineY = (fieldHeight - lineHeight*i) - f.padding*2
|
||||
}
|
||||
|
||||
text.Draw(f.img, line, f.face, lineX, lineY, f.textColor)
|
||||
}
|
||||
|
||||
|
@ -596,22 +681,20 @@ func (f *TextField) drawImage() {
|
|||
f.drawContent()
|
||||
}
|
||||
|
||||
scrollBarW := 32
|
||||
|
||||
// Draw scrollbar.
|
||||
if f.showScrollBar() {
|
||||
scrollAreaX, scrollAreaY := w-scrollBarW, 0
|
||||
f.scrollRect = image.Rect(scrollAreaX, scrollAreaY, scrollAreaX+scrollBarW, h)
|
||||
scrollAreaX, scrollAreaY := w-f.scrollWidth, 0
|
||||
f.scrollRect = image.Rect(scrollAreaX, scrollAreaY, scrollAreaX+f.scrollWidth, h)
|
||||
|
||||
scrollBarH := f.scrollWidth / 2
|
||||
if scrollBarH < 4 {
|
||||
scrollBarH = 4
|
||||
}
|
||||
|
||||
scrollX, scrollY := w-scrollBarW, 0
|
||||
scrollX, scrollY := w-f.scrollWidth, 0
|
||||
pct := float64(f.offset) / float64(f.bufferSize-h)
|
||||
scrollY += int(float64(h-scrollBarH) * pct)
|
||||
scrollBarRect := image.Rect(scrollX, scrollY, scrollX+scrollBarW, scrollY+scrollBarH)
|
||||
scrollBarRect := image.Rect(scrollX, scrollY, scrollX+f.scrollWidth, scrollY+scrollBarH)
|
||||
|
||||
f.img.SubImage(f.scrollRect).(*ebiten.Image).Fill(color.RGBA{200, 200, 200, 255})
|
||||
f.img.SubImage(scrollBarRect).(*ebiten.Image).Fill(color.RGBA{108, 108, 108, 255})
|
||||
|
|
Reference in a new issue