...

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

View as plain text