package ui import ( "log" "github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2/inpututil" "golang.org/x/exp/slices" ) // Container to organise multiple Widgets // A collection does implement the Widget interface and thus can be nested in // another Collection. type Collection struct { Width, Height int hoverDetector hoverDetector widgets []Widget focused Widget } func (c *Collection) Layout() (int, int) { return c.Width, c.Height } func (c *Collection) Widgets() []Widget { return c.widgets } func (c *Collection) Clear() { c.widgets = []Widget{} } func (c *Collection) ResetHover() { c.hoverDetector.reset(0, 0) } func (c *Collection) AddWidget(w Widget) { if w == nil { log.Panicf("Adding nil widget to collection") } if slices.Contains(c.widgets, w) { log.Panicf("Double insertion of %v", w) } c.widgets = append(c.widgets, w) } func (c *Collection) FindWidget(toFind Widget) int { for i, w := range c.widgets { if w != toFind { continue } return i } return -1 } func (c *Collection) RemoveWidget(widget Widget) { idx := c.FindWidget(widget) if idx != -1 { c.RemoveWidgetAt(idx) } } func (c *Collection) RemoveWidgetAt(idx int) { nwidgets := len(c.widgets) c.widgets[idx] = c.widgets[nwidgets-1] c.widgets = c.widgets[:nwidgets-1] } func (c *Collection) Draw(screen *ebiten.Image) { for _, w := range c.widgets { w.Draw(screen) } } func (c *Collection) Contains(x, y int) bool { for _, w := range c.widgets { if w.Contains(x, y) { return true } } return false } func (c *Collection) FindWidgetAt(x, y int) Widget { // Iterate the widget in reverse order to ensure that upper widgets are // considered first for i := len(c.widgets) - 1; i >= 0; i-- { w := c.widgets[i] if !w.Contains(x, y) { continue } return w } return nil } func (c *Collection) FindObjectAt(x, y int) interface{} { // Iterate the widget in reverse order to ensure that upper widgets are // considered first for i := len(c.widgets) - 1; i >= 0; i-- { w := c.widgets[i] if o := w.FindObjectAt(x, y); o != nil { return o } } return nil } func (c *Collection) switchFocus(fcsbl Widget) { log.Printf("switch focus from %v to %v\n", c.focused, fcsbl) if c.focused != nil { c.focused.Focus(false) } if fcsbl != nil { fcsbl.Focus(true) c.focused = fcsbl } else { c.focused = nil } } func (c *Collection) handleTouchInputs() error { for _, tap := range TouchManager.Taps { for i := len(c.widgets) - 1; i >= 0; i-- { w := c.widgets[i] if !w.Contains(tap.X, tap.Y) { continue } if w.IsClickable() { w.Click(tap.X, tap.Y) } if w.IsFocusable() { c.switchFocus(w) } else if c.focused != nil { c.switchFocus(nil) } break } } // TODO: handle pinching if TouchManager.Pan != nil { pan := TouchManager.Pan iX, iY := pan.Incremental() w := c.FindWidgetAt(int(pan.originX), (pan.originY)) if w != nil && w.IsScrollable() { w.Scroll(int(iX)/10, int(iY)/10) } } return nil } func passInput(textInput *TextInput) { var input []rune input = ebiten.AppendInputChars(input) if len(input) > 0 { textInput.AddInput(input) } textInput.HandleKey() } func (c *Collection) IsUpdatable() bool { return true } func (c *Collection) Update() error { // Update all updatables for _, w := range c.widgets { if w.IsUpdatable() { if err := w.Update(); err != nil { return err } } } if err := c.handleTouchInputs(); err != nil { return err } x, y := ebiten.CursorPosition() activeWidget := c.FindWidgetAt(x, y) c.hoverDetector.update(c, x, y, activeWidget) if activeWidget == nil { if txt, ok := c.focused.(*TextInput); ok { if ebiten.IsKeyPressed(ebiten.KeyA) { txt.AddInput([]rune{'a'}) } passInput(txt) } return nil } scrollX, scrollY := ebiten.Wheel() if scrollX != 0 || scrollY != 0 { if activeWidget.IsScrollable() { activeWidget.Scroll(int(scrollX), int(scrollY)) } } if textInput, ok := activeWidget.(*TextInput); ok { passInput(textInput) } if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) { if activeWidget.IsClickable() { activeWidget.Click(x, y) } } return nil } // The events for the contained widgets // are emitted during the collections // Update method. func (c *Collection) IsClickable() bool { return false } func (c *Collection) Click(int, int) {} func (c *Collection) IsScrollable() bool { return false } func (c *Collection) Scroll(int, int) {} func (c *Collection) IsFocusable() bool { return false } func (c *Collection) Focus(bool) {}