...

Source file src/code.rocket9labs.com/tslocum/etk/button.go

Documentation: code.rocket9labs.com/tslocum/etk

     1  package etk
     2  
     3  import (
     4  	"image"
     5  	"image/color"
     6  
     7  	"code.rocket9labs.com/tslocum/etk/messeji"
     8  	"github.com/hajimehoshi/ebiten/v2"
     9  	"golang.org/x/image/font"
    10  	"golang.org/x/image/font/sfnt"
    11  )
    12  
    13  // Button is a clickable button.
    14  type Button struct {
    15  	*Box
    16  	field         *messeji.TextField
    17  	btnBackground color.RGBA
    18  	textFont      *sfnt.Font
    19  	textSize      int
    20  	textAutoSize  int
    21  	borderSize    int
    22  	borderTop     color.RGBA
    23  	borderRight   color.RGBA
    24  	borderBottom  color.RGBA
    25  	borderLeft    color.RGBA
    26  	onSelected    func() error
    27  	pressed       bool
    28  }
    29  
    30  // NewButton returns a new Button widget.
    31  func NewButton(label string, onSelected func() error) *Button {
    32  	textColor := Style.ButtonTextColor
    33  	if textColor.A == 0 {
    34  		textColor = Style.TextColorDark
    35  	}
    36  	f := newText()
    37  	f.SetText(label)
    38  	f.SetForegroundColor(textColor)
    39  	f.SetHorizontal(messeji.AlignCenter)
    40  	f.SetVertical(messeji.AlignCenter)
    41  	f.SetScrollBarVisible(false)
    42  
    43  	b := &Button{
    44  		Box:           NewBox(),
    45  		field:         f,
    46  		btnBackground: Style.ButtonBgColor,
    47  		textFont:      Style.TextFont,
    48  		textSize:      Scale(Style.TextSize),
    49  		onSelected:    onSelected,
    50  		borderSize:    Scale(Style.ButtonBorderSize),
    51  		borderTop:     Style.ButtonBorderTop,
    52  		borderRight:   Style.ButtonBorderRight,
    53  		borderBottom:  Style.ButtonBorderBottom,
    54  		borderLeft:    Style.ButtonBorderLeft,
    55  	}
    56  	b.SetBackground(Style.ButtonBgColor)
    57  	b.resizeFont()
    58  	return b
    59  }
    60  
    61  // SetRect sets the position and size of the Button.
    62  func (b *Button) SetRect(r image.Rectangle) {
    63  	b.Box.rect = r
    64  
    65  	b.field.SetRect(r)
    66  	b.resizeFont()
    67  
    68  	for _, w := range b.children {
    69  		w.SetRect(r)
    70  	}
    71  }
    72  
    73  // SetBorderSize sets the size of the border around the button.
    74  func (b *Button) SetBorderSize(size int) {
    75  	b.Lock()
    76  	defer b.Unlock()
    77  
    78  	b.borderSize = size
    79  }
    80  
    81  // SetBorderColors sets the color of the top, right, bottom and left border.
    82  func (b *Button) SetBorderColors(top color.RGBA, right color.RGBA, bottom color.RGBA, left color.RGBA) {
    83  	b.Lock()
    84  	defer b.Unlock()
    85  
    86  	b.borderTop = top
    87  	b.borderRight = right
    88  	b.borderBottom = bottom
    89  	b.borderLeft = left
    90  }
    91  
    92  // SetForeground sets the color of the button label.
    93  func (b *Button) SetForeground(c color.RGBA) {
    94  	b.Lock()
    95  	defer b.Unlock()
    96  
    97  	b.field.SetForegroundColor(c)
    98  }
    99  
   100  // SetBackground sets the background color of the button label.
   101  func (b *Button) SetBackground(background color.RGBA) {
   102  	b.Lock()
   103  	defer b.Unlock()
   104  
   105  	b.btnBackground = background
   106  	b.field.SetBackgroundColor(b.btnBackground)
   107  }
   108  
   109  // Text returns the content of the text buffer.
   110  func (b *Button) Text() string {
   111  	b.Lock()
   112  	defer b.Unlock()
   113  
   114  	return b.field.Text()
   115  }
   116  
   117  // SetText sets the text in the field.
   118  func (b *Button) SetText(text string) {
   119  	b.Lock()
   120  	defer b.Unlock()
   121  
   122  	b.field.SetText(text)
   123  	b.resizeFont()
   124  }
   125  
   126  // SetFont sets the font and text size of button label. Scaling is not applied.
   127  func (b *Button) SetFont(fnt *sfnt.Font, size int) {
   128  	b.Lock()
   129  	defer b.Unlock()
   130  
   131  	b.textFont, b.textSize = fnt, size
   132  	b.resizeFont()
   133  }
   134  
   135  func (b *Button) resizeFont() {
   136  	w, h := b.rect.Dx()-b.field.Padding()*2, b.rect.Dy()-b.field.Padding()*2
   137  	if w == 0 || h == 0 {
   138  		if b.textAutoSize == b.textSize {
   139  			return
   140  		}
   141  		b.textAutoSize = b.textSize
   142  		ff := FontFace(b.textFont, b.textSize)
   143  		b.field.SetFont(ff, fontMutex)
   144  		return
   145  	}
   146  
   147  	var autoSize int
   148  	var ff font.Face
   149  	for autoSize = b.textSize; autoSize > 0; autoSize-- {
   150  		ff = FontFace(b.textFont, autoSize)
   151  		bounds := BoundString(ff, b.field.Text())
   152  		if bounds.Dx() <= w && bounds.Dy() <= h {
   153  			break
   154  		}
   155  	}
   156  	if b.textAutoSize == autoSize {
   157  		return
   158  	}
   159  
   160  	b.field.SetFont(ff, fontMutex)
   161  	b.textAutoSize = autoSize
   162  }
   163  
   164  // SetHorizontal sets the horizontal alignment of the button label.
   165  func (b *Button) SetHorizontal(h Alignment) {
   166  	b.Lock()
   167  	defer b.Unlock()
   168  
   169  	b.field.SetHorizontal(messeji.Alignment(h))
   170  }
   171  
   172  // SetVertical sets the vertical alignment of the button label.
   173  func (b *Button) SetVertical(v Alignment) {
   174  	b.Lock()
   175  	defer b.Unlock()
   176  
   177  	b.field.SetVertical(messeji.Alignment(v))
   178  }
   179  
   180  // Cursor returns the cursor shape shown when a mouse cursor hovers over the
   181  // widget, or -1 to let widgets beneath determine the cursor shape.
   182  func (b *Button) Cursor() ebiten.CursorShapeType {
   183  	return ebiten.CursorShapePointer
   184  }
   185  
   186  // HandleKeyboard is called when a keyboard event occurs.
   187  func (b *Button) HandleKeyboard(ebiten.Key, rune) (handled bool, err error) {
   188  	return false, nil
   189  }
   190  
   191  // HandleMouse is called when a mouse event occurs.
   192  func (b *Button) HandleMouse(cursor image.Point, pressed bool, clicked bool) (handled bool, err error) {
   193  	if !clicked {
   194  		if b.pressed && !pressed {
   195  			b.Lock()
   196  			b.pressed = false
   197  			b.background = b.btnBackground
   198  			b.Unlock()
   199  		}
   200  		return true, nil
   201  	}
   202  
   203  	b.Lock()
   204  	b.pressed = true
   205  	b.background = color.RGBA{uint8(float64(b.btnBackground.R) * 0.95), uint8(float64(b.btnBackground.G) * 0.95), uint8(float64(b.btnBackground.B) * 0.95), 255}
   206  	onSelected := b.onSelected
   207  	if onSelected == nil {
   208  		b.Unlock()
   209  		return true, nil
   210  	}
   211  	b.Unlock()
   212  
   213  	return true, onSelected()
   214  }
   215  
   216  // Draw draws the button on the screen.
   217  func (b *Button) Draw(screen *ebiten.Image) error {
   218  	r := b.rect
   219  
   220  	// Draw label.
   221  	b.field.Draw(screen)
   222  
   223  	// Draw border.
   224  	if b.borderSize != 0 {
   225  		if !b.pressed {
   226  			screen.SubImage(image.Rect(r.Min.X, r.Min.Y, r.Min.X+b.borderSize, r.Max.Y)).(*ebiten.Image).Fill(b.borderLeft)
   227  			screen.SubImage(image.Rect(r.Min.X, r.Min.Y, r.Max.X, r.Min.Y+b.borderSize)).(*ebiten.Image).Fill(b.borderTop)
   228  			screen.SubImage(image.Rect(r.Max.X-b.borderSize, r.Min.Y, r.Max.X, r.Max.Y)).(*ebiten.Image).Fill(b.borderRight)
   229  			screen.SubImage(image.Rect(r.Min.X, r.Max.Y-b.borderSize, r.Max.X, r.Max.Y)).(*ebiten.Image).Fill(b.borderBottom)
   230  		} else {
   231  			screen.SubImage(image.Rect(r.Max.X-b.borderSize, r.Min.Y, r.Max.X, r.Max.Y)).(*ebiten.Image).Fill(b.borderLeft)
   232  			screen.SubImage(image.Rect(r.Min.X, r.Max.Y-b.borderSize, r.Max.X, r.Max.Y)).(*ebiten.Image).Fill(b.borderTop)
   233  			screen.SubImage(image.Rect(r.Min.X, r.Min.Y, r.Min.X+b.borderSize, r.Max.Y)).(*ebiten.Image).Fill(b.borderRight)
   234  			screen.SubImage(image.Rect(r.Min.X, r.Min.Y, r.Max.X, r.Min.Y+b.borderSize)).(*ebiten.Image).Fill(b.borderBottom)
   235  		}
   236  	}
   237  
   238  	return nil
   239  }
   240  

View as plain text