package ui import ( "github.com/gdamore/tcell/v2" "rickub.com/turbo-editors/turbo-core/theme" ) // StatusItem is one clickable hint on the status bar, such as "F2 Save". type StatusItem struct { Key string Label string Action func() } // width returns how many cells the item occupies, the separating space // included. func (s StatusItem) width() int { return len([]rune(s.Key)) + 1 + len([]rune(s.Label)) + 2 } // StatusBar is the row of key hints along the bottom of the screen. // // A message set with SetMessage temporarily replaces the hints, which is how // the editor reports "File saved" or a language-server error without opening a // dialog for it. type StatusBar struct { Box items []StatusItem message string extra string } // NewStatusBar returns a status bar showing the given hints. func NewStatusBar(items ...StatusItem) *StatusBar { return &StatusBar{items: items} } // SetItems replaces the hints, as changing context does. func (s *StatusBar) SetItems(items ...StatusItem) { s.items = items } // Message returns the message currently shown instead of the hints. func (s *StatusBar) Message() string { return s.message } // SetMessage shows a message in place of the hints. Pass the empty string to // bring the hints back. func (s *StatusBar) SetMessage(message string) { s.message = message } // SetExtra sets the text shown right-aligned, which the editor uses for the // cursor position and the language-server state. func (s *StatusBar) SetExtra(extra string) { s.extra = extra } // Draw paints the bar. func (s *StatusBar) Draw(p *Painter, th *theme.Theme) { bar := p.Sub(s.Bounds()) normal := th.Style(theme.KeyStatusBar) bar.Clear(normal) // The right-aligned text is drawn first and its start becomes the limit // for everything on the left, so the two can never overwrite each other. limit := s.drawExtra(bar, th) if s.message != "" { bar.TextLimited(1, 0, limit-1, s.message, normal) return } s.drawItems(bar, th, limit) } // drawItems paints the key hints from the left, stopping at limit. func (s *StatusBar) drawItems(p *Painter, th *theme.Theme, limit int) { normal, key := th.Style(theme.KeyStatusBar), th.Style(theme.KeyStatusBarKey) x := 1 for _, item := range s.items { if x+item.width() > limit { return // the rest would run into the right-aligned text } x = p.Text(x, 0, item.Key, key) x = p.Text(x, 0, " "+item.Label+" ", normal) } } // drawExtra paints the right-aligned text and returns the column it starts at, // which is where everything drawn from the left has to stop. func (s *StatusBar) drawExtra(p *Painter, th *theme.Theme) int { width := p.Size().W if s.extra == "" { return width } // The text is cut from the left when it is too long, so that the part // that matters most — the cursor position — is the part that survives. text := []rune(s.extra) if len(text) > width-2 { text = text[:max(width-2, 0)] } x := max(width-len(text)-1, 0) p.Text(x, 0, string(text), th.Style(theme.KeyStatusBarHint)) return x } // HandleMouse runs the action of the hint that was clicked. func (s *StatusBar) HandleMouse(ev *tcell.EventMouse) bool { if !s.hit(ev) || ev.Buttons() != tcell.Button1 || s.message != "" { return s.hit(ev) } x, _ := ev.Position() cursor := s.Bounds().X + 1 for _, item := range s.items { if x >= cursor && x < cursor+item.width() && item.Action != nil { item.Action() return true } cursor += item.width() } return true }