common.go

  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}