...

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  // Clip returns whether the widget and its children are restricted to drawing
    62  // within the widget's rect area of the screen. For best performance, Clip
    63  // should return false unless clipping is actually needed.
    64  func (s *Select) Clip() bool {
    65  	return false
    66  }
    67  
    68  // SetHighlightColor sets the color used to highlight the currently selected item.
    69  func (s *Select) SetHighlightColor(c color.RGBA) {
    70  	s.list.SetHighlightColor(c)
    71  }
    72  
    73  // SetSelectedItem sets the currently selected item.
    74  func (s *Select) SetSelectedItem(index int) {
    75  	s.Lock()
    76  	defer s.Unlock()
    77  	if index < 0 || index >= len(s.items) {
    78  		return
    79  	}
    80  	s.list.SetSelectedItem(0, index)
    81  	s.updateLabel()
    82  }
    83  
    84  // Children returns the children of the widget.
    85  func (s *Select) Children() []Widget {
    86  	s.Lock()
    87  	defer s.Unlock()
    88  
    89  	return s.children
    90  }
    91  
    92  // AddChild adds a child to the widget. Selection options are added via AddOption.
    93  func (s *Select) AddChild(w ...Widget) {
    94  	s.Lock()
    95  	defer s.Unlock()
    96  
    97  	s.children = append(s.children, w...)
    98  }
    99  
   100  // Clear removes all children from the widget.
   101  func (s *Select) Clear() {
   102  	s.Lock()
   103  	defer s.Unlock()
   104  
   105  	s.items = nil
   106  	s.list.Clear()
   107  	s.updateLabel()
   108  }
   109  
   110  // AddOption adds an option to the widget.
   111  func (s *Select) AddOption(label string) {
   112  	s.Lock()
   113  	defer s.Unlock()
   114  
   115  	s.items = append(s.items, label)
   116  	if len(s.items) == 1 {
   117  		s.list.selectedY = 0
   118  		s.updateLabel()
   119  	}
   120  
   121  	textColor := Style.ButtonTextColor
   122  	if textColor.A == 0 {
   123  		textColor = Style.TextColorDark
   124  	}
   125  
   126  	t := NewText(label)
   127  	t.SetVertical(AlignCenter)
   128  	t.SetForeground(textColor)
   129  	t.SetAutoResize(true)
   130  	s.list.AddChildAt(t, 0, len(s.items)-1)
   131  }
   132  
   133  func (s *Select) updateLabel() {
   134  	var text string
   135  	if len(s.items) > 0 && s.list.selectedY >= 0 && s.list.selectedY < len(s.items) {
   136  		text = s.items[s.list.selectedY]
   137  	}
   138  	if s.open {
   139  		text = "▼ " + text
   140  	} else {
   141  		text = "▶ " + text
   142  	}
   143  	s.label.SetText(text)
   144  }
   145  
   146  func (s *Select) selectList(index int) (accept bool) {
   147  	s.Lock()
   148  	s.list.grid.visible = false
   149  	s.open = false
   150  	onSelect := s.onSelect
   151  	s.Unlock()
   152  
   153  	if onSelect != nil {
   154  		if !onSelect(index) {
   155  			return false
   156  		}
   157  	}
   158  
   159  	s.list.selectedY = index
   160  	s.updateLabel()
   161  	return true
   162  }
   163  
   164  // SetMenuVisible sets the visibility of the dropdown menu.
   165  func (s *Select) SetMenuVisible(visible bool) {
   166  	s.Lock()
   167  	defer s.Unlock()
   168  
   169  	s._setMenuVisible(visible)
   170  }
   171  
   172  func (s *Select) _setMenuVisible(visible bool) {
   173  	s.open = visible
   174  	s.list.SetVisible(visible)
   175  	s.updateLabel()
   176  
   177  	if !visible {
   178  		s.background = Style.ButtonBgColor
   179  	} else {
   180  		const dim = 0.9
   181  		s.background = color.RGBA{uint8(float64(Style.ButtonBgColor.R) * dim), uint8(float64(Style.ButtonBgColor.G) * dim), uint8(float64(Style.ButtonBgColor.B) * dim), 255}
   182  	}
   183  }
   184  
   185  // Cursor returns the cursor shape shown when a mouse cursor hovers over the
   186  // widget, or -1 to let widgets beneath determine the cursor shape.
   187  func (s *Select) Cursor() ebiten.CursorShapeType {
   188  	return ebiten.CursorShapePointer
   189  }
   190  
   191  // HandleKeyboard is called when a keyboard event occurs.
   192  func (s *Select) HandleKeyboard(ebiten.Key, rune) (handled bool, err error) {
   193  	return false, nil
   194  }
   195  
   196  // HandleMouse is called when a mouse event occurs.
   197  func (s *Select) HandleMouse(cursor image.Point, pressed bool, clicked bool) (handled bool, err error) {
   198  	s.Lock()
   199  	defer s.Unlock()
   200  
   201  	if clicked {
   202  		s._setMenuVisible(!s.open)
   203  	}
   204  	return true, nil
   205  }
   206  
   207  // Draw draws the widget on the screen.
   208  func (s *Select) Draw(screen *ebiten.Image) error {
   209  	s.Lock()
   210  	defer s.Unlock()
   211  
   212  	screen.SubImage(s.label.rect).(*ebiten.Image).Fill(s.background)
   213  
   214  	// Draw label.
   215  	s.label.Draw(screen)
   216  
   217  	// Draw border.
   218  	r := s.rect
   219  	borderSize := Scale(Style.ButtonBorderSize)
   220  	borderLeft, borderTop := Style.ButtonBorderLeft, Style.ButtonBorderTop
   221  	borderRight, borderBottom := Style.ButtonBorderRight, Style.ButtonBorderBottom
   222  	if !s.open {
   223  		screen.SubImage(image.Rect(r.Min.X, r.Min.Y, r.Min.X+borderSize, r.Max.Y)).(*ebiten.Image).Fill(borderLeft)
   224  		screen.SubImage(image.Rect(r.Min.X, r.Min.Y, r.Max.X, r.Min.Y+borderSize)).(*ebiten.Image).Fill(borderTop)
   225  		screen.SubImage(image.Rect(r.Max.X-borderSize, r.Min.Y, r.Max.X, r.Max.Y)).(*ebiten.Image).Fill(borderRight)
   226  		screen.SubImage(image.Rect(r.Min.X, r.Max.Y-borderSize, r.Max.X, r.Max.Y)).(*ebiten.Image).Fill(borderBottom)
   227  	} else {
   228  		borderLeft, borderTop, borderRight, borderBottom = borderRight, borderBottom, borderLeft, borderTop
   229  		screen.SubImage(image.Rect(r.Max.X-borderSize, r.Min.Y, r.Max.X, r.Max.Y)).(*ebiten.Image).Fill(borderRight)
   230  		screen.SubImage(image.Rect(r.Min.X, r.Max.Y-borderSize, r.Max.X, r.Max.Y)).(*ebiten.Image).Fill(borderBottom)
   231  		screen.SubImage(image.Rect(r.Min.X, r.Min.Y, r.Min.X+borderSize, r.Max.Y)).(*ebiten.Image).Fill(borderLeft)
   232  		screen.SubImage(image.Rect(r.Min.X, r.Min.Y, r.Max.X, r.Min.Y+borderSize)).(*ebiten.Image).Fill(borderTop)
   233  	}
   234  	return nil
   235  }
   236  

View as plain text