1package dialog
2
3import (
4 "strings"
5
6 tea "charm.land/bubbletea/v2"
7 "github.com/charmbracelet/crush/internal/ui/common"
8 "github.com/charmbracelet/crush/internal/ui/styles"
9)
10
11// InputCursor adjusts the cursor position for an input field within a dialog.
12func InputCursor(t *styles.Styles, cur *tea.Cursor) *tea.Cursor {
13 if cur != nil {
14 titleStyle := t.Dialog.Title
15 dialogStyle := t.Dialog.View
16 inputStyle := t.Dialog.InputPrompt
17 // Adjust cursor position to account for dialog layout
18 cur.X += inputStyle.GetBorderLeftSize() +
19 inputStyle.GetMarginLeft() +
20 inputStyle.GetPaddingLeft() +
21 dialogStyle.GetBorderLeftSize() +
22 dialogStyle.GetPaddingLeft() +
23 dialogStyle.GetMarginLeft()
24 cur.Y += titleStyle.GetVerticalFrameSize() +
25 inputStyle.GetBorderTopSize() +
26 inputStyle.GetMarginTop() +
27 inputStyle.GetPaddingTop() +
28 inputStyle.GetBorderBottomSize() +
29 inputStyle.GetMarginBottom() +
30 inputStyle.GetPaddingBottom() +
31 dialogStyle.GetPaddingTop() +
32 dialogStyle.GetMarginTop() +
33 dialogStyle.GetBorderTopSize()
34 }
35 return cur
36}
37
38// RenderContext is a dialog rendering context that can be used to render
39// common dialog layouts.
40type RenderContext struct {
41 // Styles is the styles to use for rendering.
42 Styles *styles.Styles
43 // Width is the total width of the dialog including any margins, borders,
44 // and paddings.
45 Width int
46 // Title is the title of the dialog. This will be styled using the default
47 // dialog title style and prepended to the content parts slice.
48 Title string
49 // Parts are the rendered parts of the dialog.
50 Parts []string
51 // Help is the help view content. This will be appended to the content parts
52 // slice using the default dialog help style.
53 Help string
54}
55
56// NewRenderContext creates a new RenderContext with the provided styles and width.
57func NewRenderContext(t *styles.Styles, width int) *RenderContext {
58 return &RenderContext{
59 Styles: t,
60 Width: width,
61 Parts: []string{},
62 }
63}
64
65// AddPart adds a rendered part to the dialog.
66func (rc *RenderContext) AddPart(part string) {
67 if len(part) > 0 {
68 rc.Parts = append(rc.Parts, part)
69 }
70}
71
72// Render renders the dialog using the provided context.
73func (rc *RenderContext) Render() string {
74 titleStyle := rc.Styles.Dialog.Title
75 dialogStyle := rc.Styles.Dialog.View.Width(rc.Width)
76
77 parts := []string{}
78 if len(rc.Title) > 0 {
79 title := common.DialogTitle(rc.Styles, rc.Title,
80 max(0, rc.Width-dialogStyle.GetHorizontalFrameSize()-
81 titleStyle.GetHorizontalFrameSize()))
82 parts = append(parts, titleStyle.Render(title), "")
83 }
84
85 for i, p := range rc.Parts {
86 if len(p) > 0 {
87 parts = append(parts, p)
88 }
89 if i < len(rc.Parts)-1 {
90 parts = append(parts, "")
91 }
92 }
93
94 if len(rc.Help) > 0 {
95 parts = append(parts, "")
96 helpStyle := rc.Styles.Dialog.HelpView
97 helpStyle = helpStyle.Width(rc.Width - dialogStyle.GetHorizontalFrameSize())
98 parts = append(parts, helpStyle.Render(rc.Help))
99 }
100
101 content := strings.Join(parts, "\n")
102
103 return dialogStyle.Render(content)
104}
105
106// HeaderInputListHelpView generates a view for dialogs with a header, input,
107// list, and help sections.
108func HeaderInputListHelpView(t *styles.Styles, width, listHeight int, header, input, list, help string) string {
109 rc := NewRenderContext(t, width)
110
111 titleStyle := t.Dialog.Title
112 inputStyle := t.Dialog.InputPrompt
113 listStyle := t.Dialog.List.Height(listHeight)
114 listContent := listStyle.Render(list)
115
116 if len(header) > 0 {
117 rc.AddPart(titleStyle.Render(header))
118 }
119 if len(input) > 0 {
120 rc.AddPart(inputStyle.Render(input))
121 }
122 if len(list) > 0 {
123 rc.AddPart(listContent)
124 }
125 if len(help) > 0 {
126 rc.Help = help
127 }
128
129 return rc.Render()
130}