161 lines
4.2 KiB
Go
161 lines
4.2 KiB
Go
package tui
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"charm.land/lipgloss/v2"
|
|
)
|
|
|
|
type ModalConfig struct {
|
|
Title string
|
|
Content string
|
|
Footer string
|
|
Width int
|
|
MaxWidth int
|
|
BorderStyle lipgloss.Border
|
|
PaddingTop int
|
|
PaddingBottom int
|
|
PaddingLeft int
|
|
PaddingRight int
|
|
}
|
|
|
|
func DefaultModalConfig() ModalConfig {
|
|
return ModalConfig{
|
|
MaxWidth: 60,
|
|
BorderStyle: lipgloss.RoundedBorder(),
|
|
PaddingTop: 1,
|
|
PaddingBottom: 1,
|
|
PaddingLeft: 2,
|
|
PaddingRight: 2,
|
|
}
|
|
}
|
|
|
|
func RenderModal(baseContent string, config ModalConfig, styles Styles, viewportWidth, viewportHeight int) string {
|
|
cfg := DefaultModalConfig()
|
|
if config.Title != "" {
|
|
cfg.Title = config.Title
|
|
}
|
|
if config.Content != "" {
|
|
cfg.Content = config.Content
|
|
}
|
|
if config.Footer != "" {
|
|
cfg.Footer = config.Footer
|
|
}
|
|
if config.Width > 0 {
|
|
cfg.Width = config.Width
|
|
}
|
|
if config.MaxWidth > 0 {
|
|
cfg.MaxWidth = config.MaxWidth
|
|
}
|
|
if config.BorderStyle != (lipgloss.Border{}) {
|
|
cfg.BorderStyle = config.BorderStyle
|
|
}
|
|
cfg.PaddingTop = config.PaddingTop
|
|
cfg.PaddingBottom = config.PaddingBottom
|
|
cfg.PaddingLeft = config.PaddingLeft
|
|
cfg.PaddingRight = config.PaddingRight
|
|
var b strings.Builder
|
|
if cfg.Title != "" {
|
|
b.WriteString(styles.OverlayTitle.Render(cfg.Title))
|
|
b.WriteString("\n")
|
|
}
|
|
if cfg.Content != "" {
|
|
b.WriteString(cfg.Content)
|
|
if cfg.Footer != "" {
|
|
b.WriteString("\n\n")
|
|
}
|
|
}
|
|
if cfg.Footer != "" {
|
|
b.WriteString(styles.OverlayDim.Render(cfg.Footer))
|
|
}
|
|
contentW := cfg.Width
|
|
if contentW == 0 {
|
|
lines := strings.Split(b.String(), "\n")
|
|
for _, line := range lines {
|
|
w := lipgloss.Width(line)
|
|
if w+cfg.PaddingLeft+cfg.PaddingRight+2 > contentW {
|
|
contentW = w + cfg.PaddingLeft + cfg.PaddingRight + 2
|
|
}
|
|
}
|
|
}
|
|
if contentW > cfg.MaxWidth {
|
|
contentW = cfg.MaxWidth
|
|
}
|
|
if contentW < 30 {
|
|
contentW = 30
|
|
}
|
|
if contentW >= viewportWidth-4 {
|
|
contentW = viewportWidth - 4
|
|
}
|
|
box := lipgloss.NewStyle().
|
|
Border(cfg.BorderStyle).
|
|
BorderForeground(lipgloss.Color(styles.OverlayBorder)).
|
|
Padding(cfg.PaddingTop, cfg.PaddingLeft, cfg.PaddingBottom, cfg.PaddingRight).
|
|
Width(contentW)
|
|
return box.Render(b.String())
|
|
}
|
|
|
|
func CenterOverlay(baseContent, overlay string, viewportWidth, viewportHeight int) string {
|
|
baseLines := strings.Split(baseContent, "\n")
|
|
overlayLines := strings.Split(overlay, "\n")
|
|
startY := (len(baseLines) - len(overlayLines)) / 2
|
|
if startY < 0 {
|
|
startY = 0
|
|
}
|
|
for i, ol := range overlayLines {
|
|
row := startY + i
|
|
if row >= len(baseLines) {
|
|
break
|
|
}
|
|
olW := lipgloss.Width(ol)
|
|
padLeft := (viewportWidth - olW) / 2
|
|
if padLeft < 0 {
|
|
padLeft = 0
|
|
}
|
|
baseLines[row] = strings.Repeat(" ", padLeft) + ol
|
|
}
|
|
return strings.Join(baseLines, "\n")
|
|
}
|
|
|
|
type ModalBuilder struct {
|
|
config ModalConfig
|
|
}
|
|
|
|
func NewModal() *ModalBuilder {
|
|
return &ModalBuilder{config: DefaultModalConfig()}
|
|
}
|
|
|
|
func (mb *ModalBuilder) Title(title string) *ModalBuilder {
|
|
mb.config.Title = title
|
|
return mb
|
|
}
|
|
|
|
func (mb *ModalBuilder) Content(content string) *ModalBuilder {
|
|
mb.config.Content = content
|
|
return mb
|
|
}
|
|
|
|
func (mb *ModalBuilder) Footer(footer string) *ModalBuilder {
|
|
mb.config.Footer = footer
|
|
return mb
|
|
}
|
|
|
|
func (mb *ModalBuilder) Width(width int) *ModalBuilder {
|
|
mb.config.Width = width
|
|
return mb
|
|
}
|
|
|
|
func (mb *ModalBuilder) MaxWidth(maxWidth int) *ModalBuilder {
|
|
mb.config.MaxWidth = maxWidth
|
|
return mb
|
|
}
|
|
|
|
func (mb *ModalBuilder) Build(styles Styles, viewportWidth, viewportHeight int) string {
|
|
return RenderModal("", mb.config, styles, viewportWidth, viewportHeight)
|
|
}
|
|
|
|
func (mb *ModalBuilder) BuildOnContent(baseContent string, styles Styles, viewportWidth, viewportHeight int) string {
|
|
modal := RenderModal("", mb.config, styles, viewportWidth, viewportHeight)
|
|
return CenterOverlay(baseContent, modal, viewportWidth, viewportHeight)
|
|
}
|