2022-07-07 21:53:14 +00:00
|
|
|
package etk
|
|
|
|
|
|
|
|
import (
|
|
|
|
"image"
|
2023-10-26 03:51:19 +00:00
|
|
|
"image/color"
|
2022-07-07 21:53:14 +00:00
|
|
|
|
2024-01-16 21:00:20 +00:00
|
|
|
"code.rocket9labs.com/tslocum/etk/messeji"
|
2023-10-21 23:09:53 +00:00
|
|
|
"github.com/hajimehoshi/ebiten/v2"
|
2024-08-22 06:43:05 +00:00
|
|
|
"golang.org/x/image/font"
|
2024-04-23 20:05:39 +00:00
|
|
|
"golang.org/x/image/font/sfnt"
|
2022-07-07 21:53:14 +00:00
|
|
|
)
|
|
|
|
|
2023-10-29 06:04:32 +00:00
|
|
|
// Text is a text display widget.
|
2022-07-07 21:53:14 +00:00
|
|
|
type Text struct {
|
2024-01-23 20:39:08 +00:00
|
|
|
*Box
|
2024-09-02 04:25:01 +00:00
|
|
|
field *messeji.TextField
|
|
|
|
textFont *sfnt.Font
|
|
|
|
textSize int
|
|
|
|
textResize bool
|
|
|
|
textAutoSize int
|
|
|
|
scrollVisible bool
|
|
|
|
children []Widget
|
2022-07-07 21:53:14 +00:00
|
|
|
}
|
|
|
|
|
2023-10-29 06:04:32 +00:00
|
|
|
// NewText returns a new Text widget.
|
2022-07-07 21:53:14 +00:00
|
|
|
func NewText(text string) *Text {
|
2024-01-23 20:39:08 +00:00
|
|
|
f := newText()
|
|
|
|
f.SetText(text)
|
|
|
|
f.SetForegroundColor(Style.TextColorLight)
|
|
|
|
f.SetHandleKeyboard(true)
|
2022-07-07 21:53:14 +00:00
|
|
|
|
2024-08-22 06:43:05 +00:00
|
|
|
t := &Text{
|
2024-09-02 04:25:01 +00:00
|
|
|
Box: NewBox(),
|
|
|
|
field: f,
|
|
|
|
textFont: Style.TextFont,
|
|
|
|
textSize: Scale(Style.TextSize),
|
|
|
|
scrollVisible: true,
|
2022-07-07 21:53:14 +00:00
|
|
|
}
|
2024-08-22 06:43:05 +00:00
|
|
|
t.resizeFont()
|
|
|
|
return t
|
2022-07-07 21:53:14 +00:00
|
|
|
}
|
|
|
|
|
2024-01-23 20:39:08 +00:00
|
|
|
// SetRect sets the position and size of the widget.
|
|
|
|
func (t *Text) SetRect(r image.Rectangle) {
|
2023-10-26 03:51:19 +00:00
|
|
|
t.Lock()
|
|
|
|
defer t.Unlock()
|
|
|
|
|
2024-01-23 20:39:08 +00:00
|
|
|
t.rect = r
|
|
|
|
t.field.SetRect(r)
|
2024-08-22 06:43:05 +00:00
|
|
|
t.resizeFont()
|
2023-10-26 03:51:19 +00:00
|
|
|
}
|
|
|
|
|
2024-01-23 20:39:08 +00:00
|
|
|
// Foreground return the color of the text within the field.
|
|
|
|
func (t *Text) Foreground() color.RGBA {
|
2023-10-26 03:51:19 +00:00
|
|
|
t.Lock()
|
|
|
|
defer t.Unlock()
|
|
|
|
|
2024-01-23 20:39:08 +00:00
|
|
|
return t.field.ForegroundColor()
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetForegroundColor sets the color of the text within the field.
|
|
|
|
func (t *Text) SetForeground(c color.RGBA) {
|
|
|
|
t.Lock()
|
|
|
|
defer t.Unlock()
|
|
|
|
|
|
|
|
t.field.SetForegroundColor(c)
|
2023-10-26 03:51:19 +00:00
|
|
|
}
|
|
|
|
|
2023-10-29 06:04:32 +00:00
|
|
|
// Focus returns the focus state of the widget.
|
2023-10-29 05:23:18 +00:00
|
|
|
func (t *Text) Focus() bool {
|
2023-10-23 07:30:39 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2023-10-29 06:04:32 +00:00
|
|
|
// SetFocus sets the focus state of the widget.
|
2023-10-29 05:23:18 +00:00
|
|
|
func (t *Text) SetFocus(focus bool) bool {
|
2023-10-23 07:30:39 +00:00
|
|
|
return false
|
2023-10-21 22:30:09 +00:00
|
|
|
}
|
|
|
|
|
2024-01-23 20:39:08 +00:00
|
|
|
// SetScrollBarWidth sets the width of the scroll bar.
|
|
|
|
func (t *Text) SetScrollBarWidth(width int) {
|
|
|
|
t.Lock()
|
|
|
|
defer t.Unlock()
|
|
|
|
|
|
|
|
t.field.SetScrollBarWidth(width)
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetScrollBarColors sets the color of the scroll bar area and handle.
|
|
|
|
func (t *Text) SetScrollBarColors(area color.RGBA, handle color.RGBA) {
|
|
|
|
t.Lock()
|
|
|
|
defer t.Unlock()
|
|
|
|
|
|
|
|
t.field.SetScrollBarColors(Style.ScrollAreaColor, Style.ScrollHandleColor)
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetScrollBorderColor sets the color of the top, right, bottom and left border
|
|
|
|
// of the scroll bar handle.
|
|
|
|
func (t *Text) SetScrollBorderColors(top color.RGBA, right color.RGBA, bottom color.RGBA, left color.RGBA) {
|
|
|
|
t.Lock()
|
|
|
|
defer t.Unlock()
|
|
|
|
|
|
|
|
t.field.SetScrollBorderColors(top, right, bottom, left)
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetWordWrap sets a flag which, when enabled, causes text to wrap without breaking words.
|
|
|
|
func (t *Text) SetWordWrap(wrap bool) {
|
|
|
|
t.Lock()
|
|
|
|
defer t.Unlock()
|
|
|
|
|
|
|
|
t.field.SetWordWrap(wrap)
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetHorizontal sets the horizontal alignment of the text within the field.
|
|
|
|
func (t *Text) SetHorizontal(h Alignment) {
|
|
|
|
t.Lock()
|
|
|
|
defer t.Unlock()
|
|
|
|
|
|
|
|
t.field.SetHorizontal(messeji.Alignment(h))
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetVertical sets the vertical alignment of the text within the field.
|
2024-09-19 02:41:36 +00:00
|
|
|
func (t *Text) SetVertical(v Alignment) {
|
2024-01-23 20:39:08 +00:00
|
|
|
t.Lock()
|
|
|
|
defer t.Unlock()
|
|
|
|
|
2024-09-19 02:41:36 +00:00
|
|
|
t.field.SetVertical(messeji.Alignment(v))
|
2023-01-03 19:37:01 +00:00
|
|
|
}
|
|
|
|
|
2024-09-12 05:05:01 +00:00
|
|
|
// Cursor returns the cursor shape shown when a mouse cursor hovers over the
|
|
|
|
// widget, or -1 to let widgets beneath determine the cursor shape.
|
|
|
|
func (t *Text) Cursor() ebiten.CursorShapeType {
|
|
|
|
return ebiten.CursorShapeDefault
|
|
|
|
}
|
|
|
|
|
2023-10-29 06:04:32 +00:00
|
|
|
// Write writes to the text buffer.
|
2022-07-07 21:53:14 +00:00
|
|
|
func (t *Text) Write(p []byte) (n int, err error) {
|
2024-01-23 20:39:08 +00:00
|
|
|
t.Lock()
|
|
|
|
defer t.Unlock()
|
|
|
|
|
2024-08-22 06:43:05 +00:00
|
|
|
n, err = t.field.Write(p)
|
|
|
|
if err != nil {
|
|
|
|
return n, err
|
|
|
|
}
|
|
|
|
t.resizeFont()
|
|
|
|
return n, err
|
2022-07-07 21:53:14 +00:00
|
|
|
}
|
|
|
|
|
2023-10-29 06:04:32 +00:00
|
|
|
// Text returns the content of the text buffer.
|
2023-06-08 04:07:08 +00:00
|
|
|
func (t *Text) Text() string {
|
2024-01-23 20:39:08 +00:00
|
|
|
t.Lock()
|
|
|
|
defer t.Unlock()
|
|
|
|
|
|
|
|
return t.field.Text()
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetText sets the text in the field.
|
|
|
|
func (t *Text) SetText(text string) {
|
|
|
|
t.Lock()
|
|
|
|
defer t.Unlock()
|
|
|
|
|
|
|
|
t.field.SetText(text)
|
2024-08-22 06:43:05 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2024-09-02 04:32:09 +00:00
|
|
|
w, h := t.rect.Dx()-t.field.Padding()*2, t.rect.Dy()-t.field.Padding()*2
|
|
|
|
if w == 0 || h == 0 {
|
2024-08-22 06:43:05 +00:00
|
|
|
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 font.Face
|
|
|
|
for autoSize = t.textSize; autoSize > 0; autoSize-- {
|
|
|
|
ff = FontFace(t.textFont, autoSize)
|
2024-09-02 04:32:09 +00:00
|
|
|
bounds := BoundString(ff, t.field.Text())
|
|
|
|
if bounds.Dx() <= w && bounds.Dy() <= h {
|
2024-08-22 06:43:05 +00:00
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if t.textAutoSize == autoSize {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
t.field.SetFont(ff, fontMutex)
|
|
|
|
t.textAutoSize = autoSize
|
2024-01-23 20:39:08 +00:00
|
|
|
}
|
|
|
|
|
2024-09-02 04:25:01 +00:00
|
|
|
func (t *Text) scrollBarVisible() bool {
|
|
|
|
if t.textResize {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return t.scrollVisible
|
|
|
|
}
|
|
|
|
|
2024-01-23 20:39:08 +00:00
|
|
|
// SetScrollBarVisible sets whether the scroll bar is visible on the screen.
|
|
|
|
func (t *Text) SetScrollBarVisible(scrollVisible bool) {
|
|
|
|
t.Lock()
|
|
|
|
defer t.Unlock()
|
|
|
|
|
2024-09-02 04:25:01 +00:00
|
|
|
t.scrollVisible = scrollVisible
|
|
|
|
t.field.SetScrollBarVisible(t.scrollBarVisible())
|
2024-01-23 20:39:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// SetAutoHideScrollBar sets whether the scroll bar is automatically hidden
|
|
|
|
// when the entire text buffer is visible.
|
|
|
|
func (t *Text) SetAutoHideScrollBar(autoHide bool) {
|
|
|
|
t.Lock()
|
|
|
|
defer t.Unlock()
|
|
|
|
|
|
|
|
t.field.SetAutoHideScrollBar(autoHide)
|
|
|
|
}
|
|
|
|
|
2024-04-23 20:05:39 +00:00
|
|
|
// SetFont sets the font and text size of the field. Scaling is not applied.
|
|
|
|
func (t *Text) SetFont(fnt *sfnt.Font, size int) {
|
2024-01-23 20:39:08 +00:00
|
|
|
t.Lock()
|
|
|
|
defer t.Unlock()
|
|
|
|
|
2024-08-22 06:43:05 +00:00
|
|
|
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()
|
2024-09-02 04:25:01 +00:00
|
|
|
t.field.SetScrollBarVisible(t.scrollBarVisible())
|
2024-01-23 20:39:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Padding returns the amount of padding around the text within the field.
|
|
|
|
func (t *Text) Padding() int {
|
|
|
|
t.Lock()
|
|
|
|
defer t.Unlock()
|
|
|
|
|
|
|
|
return t.field.Padding()
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetPadding sets the amount of padding around the text within the field.
|
|
|
|
func (t *Text) SetPadding(padding int) {
|
|
|
|
t.Lock()
|
|
|
|
defer t.Unlock()
|
|
|
|
|
|
|
|
t.field.SetPadding(padding)
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetFollow sets whether the field should automatically scroll to the end when
|
|
|
|
// content is added to the buffer.
|
|
|
|
func (t *Text) SetFollow(follow bool) {
|
|
|
|
t.Lock()
|
|
|
|
defer t.Unlock()
|
|
|
|
|
|
|
|
t.field.SetFollow(follow)
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetSingleLine sets whether the field displays all text on a single line.
|
|
|
|
// When enabled, the field scrolls horizontally. Otherwise, it scrolls vertically.
|
|
|
|
func (t *Text) SetSingleLine(single bool) {
|
|
|
|
t.Lock()
|
|
|
|
defer t.Unlock()
|
|
|
|
|
|
|
|
t.field.SetSingleLine(single)
|
2022-07-07 21:53:14 +00:00
|
|
|
}
|
|
|
|
|
2024-02-10 20:38:13 +00:00
|
|
|
// SetMask sets the rune used to mask the text buffer contents. Set to 0 to disable.
|
|
|
|
func (t *Text) SetMask(r rune) {
|
|
|
|
t.Lock()
|
|
|
|
defer t.Unlock()
|
|
|
|
|
|
|
|
t.field.SetMask(r)
|
|
|
|
}
|
|
|
|
|
2023-10-29 06:04:32 +00:00
|
|
|
// HandleKeyboard is called when a keyboard event occurs.
|
2023-10-28 05:04:50 +00:00
|
|
|
func (t *Text) HandleKeyboard(key ebiten.Key, r rune) (handled bool, err error) {
|
2024-01-23 20:39:08 +00:00
|
|
|
return t.field.HandleKeyboardEvent(key, r)
|
2022-07-07 21:53:14 +00:00
|
|
|
}
|
|
|
|
|
2023-10-29 06:04:32 +00:00
|
|
|
// HandleMouse is called when a mouse event occurs.
|
2023-10-28 05:04:50 +00:00
|
|
|
func (t *Text) HandleMouse(cursor image.Point, pressed bool, clicked bool) (handled bool, err error) {
|
2024-01-23 20:39:08 +00:00
|
|
|
return t.field.HandleMouseEvent(cursor, pressed, clicked)
|
2023-10-22 19:29:32 +00:00
|
|
|
}
|
|
|
|
|
2023-10-29 06:04:32 +00:00
|
|
|
// Draw draws the widget on the screen.
|
2022-07-07 21:53:14 +00:00
|
|
|
func (t *Text) Draw(screen *ebiten.Image) error {
|
2024-01-23 20:39:08 +00:00
|
|
|
t.field.Draw(screen)
|
2022-07-07 21:53:14 +00:00
|
|
|
return nil
|
|
|
|
}
|
2023-10-26 03:51:19 +00:00
|
|
|
|
2023-10-29 06:04:32 +00:00
|
|
|
// Children returns the children of the widget.
|
|
|
|
func (t *Text) Children() []Widget {
|
|
|
|
t.Lock()
|
|
|
|
defer t.Unlock()
|
|
|
|
|
|
|
|
return t.children
|
|
|
|
}
|
|
|
|
|
|
|
|
// AddChild adds a child to the widget.
|
|
|
|
func (t *Text) AddChild(w ...Widget) {
|
|
|
|
t.Lock()
|
|
|
|
defer t.Unlock()
|
|
|
|
|
|
|
|
t.children = append(t.children, w...)
|
|
|
|
}
|
|
|
|
|
2023-10-26 03:51:19 +00:00
|
|
|
var _ Widget = &Text{}
|