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