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