...

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

Documentation: code.rocket9labs.com/tslocum/etk

     1  package etk
     2  
     3  import (
     4  	"image"
     5  	"image/color"
     6  
     7  	"github.com/hajimehoshi/ebiten/v2"
     8  )
     9  
    10  // Select is a dropdown selection widget.
    11  type Select struct {
    12  	*Box
    13  	label    *Text
    14  	list     *List
    15  	onSelect func(index int) (accept bool)
    16  	items    []string
    17  	open     bool
    18  }
    19  
    20  // NewSelect returns a new Select widget.
    21  func NewSelect(itemHeight int, onSelect func(index int) (accept bool)) *Select {
    22  	s := &Select{
    23  		Box:      NewBox(),
    24  		label:    NewText(""),
    25  		onSelect: onSelect,
    26  	}
    27  	s.label.SetVertical(AlignCenter)
    28  	s.label.SetForeground(Style.ButtonTextColor)
    29  	s.SetBackground(Style.ButtonBgColor)
    30  	s.list = NewList(itemHeight, s.selectList)
    31  	s.list.SetBackground(Style.ButtonBgColor)
    32  	s.list.SetDrawBorder(true)
    33  	s.list.SetVisible(false)
    34  	s.list.SetSelectionMode(SelectRow)
    35  	s.AddChild(s.list)
    36  	s.updateLabel()
    37  	return s
    38  }
    39  
    40  // SetRect sets the position and size of the widget.
    41  func (s *Select) SetRect(r image.Rectangle) {
    42  	s.Lock()
    43  	defer s.Unlock()
    44  	s.rect = r
    45  	s.label.SetRect(r)
    46  	listRect := r.Add(image.Point{X: 0, Y: r.Dy()})
    47  	itemCount := len(s.items)
    48  	listRect.Max.Y = listRect.Min.Y + itemCount*s.list.itemHeight
    49  	_, height := ScreenSize()
    50  	if listRect.Max.Y > height {
    51  		listRect.Max.Y = height
    52  	}
    53  	s.list.SetRect(listRect)
    54  }
    55  
    56  // SetHighlightColor sets the color used to highlight the currently selected item.
    57  func (s *Select) SetHighlightColor(c color.RGBA) {
    58  	s.list.SetHighlightColor(c)
    59  }
    60  
    61  // SetSelectedItem sets the currently selected item.
    62  func (s *Select) SetSelectedItem(index int) {
    63  	s.Lock()
    64  	defer s.Unlock()
    65  	if index < 0 || index >= len(s.items) {
    66  		return
    67  	}
    68  	s.list.SetSelectedItem(0, index)
    69  	s.updateLabel()
    70  }
    71  
    72  // Children returns the children of the widget.
    73  func (s *Select) Children() []Widget {
    74  	s.Lock()
    75  	defer s.Unlock()
    76  
    77  	return s.children
    78  }
    79  
    80  // AddChild adds a child to the widget. Selection options are added via AddOption.
    81  func (s *Select) AddChild(w ...Widget) {
    82  	s.Lock()
    83  	defer s.Unlock()
    84  
    85  	s.children = append(s.children, w...)
    86  }
    87  
    88  // Clear removes all children from the widget.
    89  func (s *Select) Clear() {
    90  	s.Lock()
    91  	defer s.Unlock()
    92  
    93  	s.items = nil
    94  	s.list.Clear()
    95  	s.updateLabel()
    96  }
    97  
    98  // AddOption adds an option to the widget.
    99  func (s *Select) AddOption(label string) {
   100  	s.Lock()
   101  	defer s.Unlock()
   102  
   103  	s.items = append(s.items, label)
   104  	if len(s.items) == 1 {
   105  		s.list.selectedY = 0
   106  		s.updateLabel()
   107  	}
   108  
   109  	t := NewText(label)
   110  	t.SetVertical(AlignCenter)
   111  	t.SetForeground(Style.ButtonTextColor)
   112  	s.list.AddChildAt(t, 0, len(s.items)-1)
   113  }
   114  
   115  func (s *Select) updateLabel() {
   116  	var text string
   117  	if len(s.items) > 0 && s.list.selectedY >= 0 && s.list.selectedY < len(s.items) {
   118  		text = s.items[s.list.selectedY]
   119  	}
   120  	if s.open {
   121  		text = "▼ " + text
   122  	} else {
   123  		text = "▶ " + text
   124  	}
   125  	s.label.SetText(text)
   126  }
   127  
   128  func (s *Select) selectList(index int) (accept bool) {
   129  	s.Lock()
   130  	s.list.grid.visible = false
   131  	s.open = false
   132  	onSelect := s.onSelect
   133  	s.Unlock()
   134  
   135  	if onSelect != nil {
   136  		if !onSelect(index) {
   137  			return false
   138  		}
   139  	}
   140  
   141  	s.list.selectedY = index
   142  	s.updateLabel()
   143  	return true
   144  }
   145  
   146  // SetMenuVisible sets the visibility of the dropdown menu.
   147  func (s *Select) SetMenuVisible(visible bool) {
   148  	s.Lock()
   149  	defer s.Unlock()
   150  
   151  	s._setMenuVisible(visible)
   152  }
   153  
   154  func (s *Select) _setMenuVisible(visible bool) {
   155  	s.open = visible
   156  	s.list.SetVisible(visible)
   157  	s.updateLabel()
   158  
   159  	if !visible {
   160  		s.background = Style.ButtonBgColor
   161  	} else {
   162  		s.background = color.RGBA{uint8(float64(Style.ButtonBgColor.R) * 0.95), uint8(float64(Style.ButtonBgColor.G) * 0.95), uint8(float64(Style.ButtonBgColor.B) * 0.95), 255}
   163  	}
   164  }
   165  
   166  // HandleKeyboard is called when a keyboard event occurs.
   167  func (s *Select) HandleKeyboard(ebiten.Key, rune) (handled bool, err error) {
   168  	return false, nil
   169  }
   170  
   171  // HandleMouse is called when a mouse event occurs.
   172  func (s *Select) HandleMouse(cursor image.Point, pressed bool, clicked bool) (handled bool, err error) {
   173  	s.Lock()
   174  	defer s.Unlock()
   175  
   176  	if clicked {
   177  		s._setMenuVisible(!s.open)
   178  	}
   179  	return true, nil
   180  }
   181  
   182  // Draw draws the widget on the screen.
   183  func (s *Select) Draw(screen *ebiten.Image) error {
   184  	s.Lock()
   185  	defer s.Unlock()
   186  
   187  	// Draw label.
   188  	s.label.Draw(screen)
   189  
   190  	// Draw border.
   191  	r := s.rect
   192  	borderSize := Scale(Style.BorderSize)
   193  	borderLeft, borderTop := Style.BorderColorLeft, Style.BorderColorTop
   194  	borderRight, borderBottom := Style.BorderColorRight, Style.BorderColorBottom
   195  	if !s.open {
   196  		screen.SubImage(image.Rect(r.Min.X, r.Min.Y, r.Min.X+borderSize, r.Max.Y)).(*ebiten.Image).Fill(borderLeft)
   197  		screen.SubImage(image.Rect(r.Min.X, r.Min.Y, r.Max.X, r.Min.Y+borderSize)).(*ebiten.Image).Fill(borderTop)
   198  		screen.SubImage(image.Rect(r.Max.X-borderSize, r.Min.Y, r.Max.X, r.Max.Y)).(*ebiten.Image).Fill(borderRight)
   199  		screen.SubImage(image.Rect(r.Min.X, r.Max.Y-borderSize, r.Max.X, r.Max.Y)).(*ebiten.Image).Fill(borderBottom)
   200  	} else {
   201  		borderLeft, borderTop, borderRight, borderBottom = borderRight, borderBottom, borderLeft, borderTop
   202  		screen.SubImage(image.Rect(r.Max.X-borderSize, r.Min.Y, r.Max.X, r.Max.Y)).(*ebiten.Image).Fill(borderRight)
   203  		screen.SubImage(image.Rect(r.Min.X, r.Max.Y-borderSize, r.Max.X, r.Max.Y)).(*ebiten.Image).Fill(borderBottom)
   204  		screen.SubImage(image.Rect(r.Min.X, r.Min.Y, r.Min.X+borderSize, r.Max.Y)).(*ebiten.Image).Fill(borderLeft)
   205  		screen.SubImage(image.Rect(r.Min.X, r.Min.Y, r.Max.X, r.Min.Y+borderSize)).(*ebiten.Image).Fill(borderTop)
   206  	}
   207  	return nil
   208  }
   209  

View as plain text