ui.go

  1package ui
  2
  3import (
  4	"image"
  5
  6	"github.com/charmbracelet/bubbles/v2/key"
  7	tea "github.com/charmbracelet/bubbletea/v2"
  8	"github.com/charmbracelet/crush/internal/app"
  9	"github.com/charmbracelet/crush/internal/ui/dialog"
 10	"github.com/charmbracelet/lipgloss/v2"
 11	uv "github.com/charmbracelet/ultraviolet"
 12)
 13
 14type uiState uint8
 15
 16const (
 17	uiStateMain uiState = iota
 18)
 19
 20type Model struct {
 21	app           *app.App
 22	width, height int
 23	state         uiState
 24
 25	keyMap KeyMap
 26
 27	dialog *dialog.Overlay
 28}
 29
 30func New(app *app.App) *Model {
 31	return &Model{
 32		app:    app,
 33		dialog: dialog.NewOverlay(),
 34		keyMap: DefaultKeyMap(),
 35	}
 36}
 37
 38func (m *Model) Init() tea.Cmd {
 39	return nil
 40}
 41
 42func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 43	var cmds []tea.Cmd
 44	switch msg := msg.(type) {
 45	case tea.WindowSizeMsg:
 46		m.width = msg.Width
 47		m.height = msg.Height
 48	case tea.KeyPressMsg:
 49		switch m.state {
 50		case uiStateMain:
 51			switch {
 52			case key.Matches(msg, m.keyMap.Quit):
 53				quitDialog := dialog.NewQuit()
 54				if !m.dialog.ContainsDialog(quitDialog.ID()) {
 55					m.dialog.AddDialog(quitDialog)
 56					return m, nil
 57				}
 58			}
 59		}
 60	}
 61
 62	updatedDialog, cmd := m.dialog.Update(msg)
 63	m.dialog = updatedDialog
 64	if cmd != nil {
 65		cmds = append(cmds, cmd)
 66	}
 67
 68	return m, tea.Batch(cmds...)
 69}
 70
 71func (m *Model) View() tea.View {
 72	var v tea.View
 73
 74	// The screen area we're working with
 75	area := image.Rect(0, 0, m.width, m.height)
 76	layers := []*lipgloss.Layer{}
 77
 78	if dialogView := m.dialog.View(); dialogView != "" {
 79		dialogWidth, dialogHeight := lipgloss.Width(dialogView), lipgloss.Height(dialogView)
 80		dialogArea := centerRect(area, dialogWidth, dialogHeight)
 81		layers = append(layers,
 82			lipgloss.NewLayer(dialogView).
 83				X(dialogArea.Min.X).
 84				Y(dialogArea.Min.Y),
 85		)
 86	}
 87
 88	v.Layer = lipgloss.NewCanvas(layers...)
 89
 90	return v
 91}
 92
 93// centerRect returns a new [Rectangle] centered within the given area with the
 94// specified width and height.
 95func centerRect(area uv.Rectangle, width, height int) uv.Rectangle {
 96	centerX := area.Min.X + area.Dx()/2
 97	centerY := area.Min.Y + area.Dy()/2
 98	minX := centerX - width/2
 99	minY := centerY - height/2
100	maxX := minX + width
101	maxY := minY + height
102	return image.Rect(minX, minY, maxX, maxY)
103}