Работа над отображением объектов, работа со стилями, работа с позиционированиме, изменение таблицы переменных

This commit is contained in:
XeRo 2026-01-14 18:08:43 +05:00
parent ada7db46cf
commit 2e8eee135e
7 changed files with 477 additions and 431 deletions

View file

@ -36,67 +36,19 @@ func (m *FinalModel) Init() tea.Cmd {
return nil
}
func (m *FinalModel) initViewport(width, height int) {
m.viewport = viewport.New(width, height-8)
m.viewport.Width = width
m.viewport.Height = height - 10
m.ready = true
m.rebuildTable(m.viewport.Width)
m.viewport.SetContent(m.table.Render())
}
func (m *FinalModel) rebuildTable(width int) {
if len(m.variables) == 0 {
m.table = table.New().Headers("Нет переменных")
return
}
availableWidth := width - 12 // примерно: 2 (отступы) + 4 (бордер) + запас
if availableWidth < 70 {
availableWidth = 70
}
t := table.New().
Border(lipgloss.RoundedBorder()).
BorderStyle(ui.TableBorder).
StyleFunc(func(row, col int) lipgloss.Style {
var style lipgloss.Style
switch {
case row == table.HeaderRow:
return ui.TableHeader
case row%2 == 0:
style = ui.TableRowEven.Padding(0, 2)
default:
style = ui.TableRowOdd.Padding(0, 2)
}
switch col {
case 0, 1:
style = style.Width(25)
case 2:
style = style.Width(availableWidth - 50 - 10)
}
return style
}).
Headers("Variable name", "Default value", "Used value")
for _, v := range m.variables {
used := fmt.Sprintf("%v", v.Value)
if used == "" {
used = "<пусто>"
}
t.Row(v.Name, v.Default, used)
}
m.table = t
}
func (m *FinalModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmd tea.Cmd
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "ctrl+c", "q":
return m, tea.Quit
case "up":
m.viewport.ScrollUp(1)
case "down":
m.viewport.ScrollDown(1)
case "left":
m.viewport.ScrollLeft(1)
case "right":
m.viewport.ScrollRight(1)
case "pageup":
m.viewport.HalfPageUp()
case "pagedown":
@ -107,15 +59,9 @@ func (m *FinalModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.viewport.GotoBottom()
}
case tea.WindowSizeMsg:
if !m.ready {
m.height = msg.Height
m.width = msg.Width
m.initViewport(msg.Width, msg.Height)
} else {
m.viewport.Width = msg.Width - 4
m.viewport.Height = msg.Height - 6
}
m.rebuildTable(m.viewport.Width)
rendered := m.table.Render()
m.viewport.SetContent(rendered)
}
m.viewport, cmd = m.viewport.Update(msg)
return m, cmd
@ -125,76 +71,103 @@ func (m *FinalModel) View() string {
if !m.ready {
return "Загрузка таблицы"
}
containerWidth, containerHeight, contentWidth, _ := ui.ContentSizes(m.width, m.height)
contentWidth := ui.ContentWidth(m.width)
m.rebuildTable(contentWidth)
header := ui.HeaderStyle.Width(contentWidth).Render("Итоговые значения переменных")
footer := ui.FooterStyle.Width(contentWidth).Render(fmt.Sprintf("Строк: %d • q — выход • ↑↓ / jk — скролл", len(m.variables)))
m.viewport.Width = contentWidth - 8
m.viewport.Height = m.height - 12
m.viewport.SetContent(m.table.Render())
header := ui.FinalHeader.
Width(contentWidth).
Render("Итоговые значения переменных")
footer := ui.FinalFooter.
Width(contentWidth).
Render(fmt.Sprintf("Строк: %d • q — выход • ↑↓ / jk — скролл", len(m.variables)))
renderedTable := m.table.Render()
content := lipgloss.NewStyle().
Align(lipgloss.Center).
Render(renderedTable)
m.viewport.SetContent(content)
body := m.viewport.View()
fullContent := lipgloss.JoinVertical(lipgloss.Top, header, body, footer)
content := lipgloss.JoinVertical(lipgloss.Left, header, body, footer)
container := ui.AppContainer.Width(contentWidth).Render(content)
return lipgloss.Place(m.width, m.height,
container := ui.AppContainer.
Width(containerWidth).
Height(containerHeight).
Render(fullContent)
return lipgloss.Place(
m.width, m.height,
lipgloss.Center, lipgloss.Center,
container,
lipgloss.WithWhitespaceChars("░"),
lipgloss.WithWhitespaceForeground(ui.ColorGrey),
lipgloss.WithWhitespaceChars("+"),
lipgloss.WithWhitespaceForeground(ui.ColorError),
)
//contentWidth := ui.AppContentWidth(m.width)
//m.viewport.Width = contentWidth - 6
//m.viewport.Height = m.height - 12
//m.rebuildTable(contentWidth)
//
//header := ui.AppHeaderStyle.
// Width(contentWidth).
// Render("Итоговые значения переменных")
//
//footer := ui.AppFooterStyle.
// Width(contentWidth).
// Render(fmt.Sprintf("Строк: %d • q — выход • ↑↓ / jk — скролл", len(m.variables)))
//
//body := m.viewport.View()
//content := lipgloss.JoinVertical(lipgloss.Left, header, body, footer)
//
//container := ui.AppContainerStyle.
// Width(contentWidth).
// Render(content)
//
//return lipgloss.Place(m.width, m.height,
// lipgloss.Center, lipgloss.Center,
// container,
// lipgloss.WithWhitespaceChars("░"),
// lipgloss.WithWhitespaceForeground(ui.ColorGrey),
//)
//header := lipgloss.NewStyle().
// Width(m.viewport.Width).
// Align(lipgloss.Center).
// Bold(true).
// Background(ui.ColorPurple).
// Foreground(ui.ColorWhite).
// Padding(1).
// Render("Итоговые значения переменных")
//
//footer := lipgloss.NewStyle().
// Width(m.viewport.Width).
// Align(lipgloss.Center).
// Foreground(ui.ColorGrey).
// Padding(0, 1).
// Render(fmt.Sprintf("Строк: %d • q — выход • ↑↓ / jk — скролл", len(m.variables)))
//
//body := m.viewport.View()
//return lipgloss.JoinVertical(lipgloss.Center,
// header,
// body,
// footer,
//)
}
func (m *FinalModel) initViewport(width, height int) {
_, _, contentWidth, contentHeight := ui.ContentSizes(width, height)
tableWidth := contentWidth - 2*1
tableHeight := int(float64(contentHeight)*ui.TableWithRatio) - 2*1
m.viewport = viewport.New(contentWidth, tableHeight)
//размеры для viewport в нем будет находиться таблица и перемещаться в зависимости от команд
m.ready = true
m.rebuildTable(tableWidth)
m.viewport.SetContent(m.table.Render())
}
func (m *FinalModel) rebuildTable(width int) {
if len(m.variables) == 0 {
m.table = table.New().
Border(lipgloss.RoundedBorder()).
BorderStyle(ui.FinalTableBorder).
StyleFunc(func(row, col int) lipgloss.Style {
return ui.FinalTableHeader
}).Headers("Нет переменных")
return
}
variableNameColWidth := 15
defaultValueColWidth := 25
usedValueColWidth := width - variableNameColWidth - defaultValueColWidth - 4*1
//ширина таблицы - ширина первой колонки - ширина второй колонки - 4 границы (2 крайние, 2 между колонками)
if usedValueColWidth < 30 { //если значение сильно меньше
usedValueColWidth = 30
}
t := table.New().
Width(variableNameColWidth+defaultValueColWidth+usedValueColWidth+4).
Border(lipgloss.RoundedBorder()).
BorderStyle(ui.FinalTableBorder).
StyleFunc(func(row, col int) lipgloss.Style {
var style lipgloss.Style
if row == table.HeaderRow {
return ui.FinalTableHeader
}
if row%2 == 0 {
style = ui.FinalTableEvenRow
} else {
style = ui.FinalTableOddRow
}
switch col {
case 0:
return style.Width(variableNameColWidth).Align(lipgloss.Left)
case 1:
return style.Width(defaultValueColWidth).Align(lipgloss.Left)
case 2:
return style.Width(usedValueColWidth).Align(lipgloss.Left)
}
return style
}).
Headers("Variable name", "Default value", "Used value")
for _, v := range m.variables {
used := fmt.Sprintf("%v", v.Value)
if used == "" {
used = "<Пусто>"
}
t.Row(v.Name, v.Default, used)
}
m.table = t
}

View file

@ -6,11 +6,11 @@ import (
"bubble/types"
"fmt"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/glamour"
"github.com/charmbracelet/lipgloss"
"github.com/muesli/reflow/wordwrap"
"github.com/sirupsen/logrus"
"strings"
"unicode/utf8"
)
type FormModel struct {
@ -77,12 +77,7 @@ func NewFormModel(application *types.PackageApplication, sectionIdx, focus, widt
height: height,
}
if currentSection.Type == types.SectionAbout {
md := strings.Join(currentSection.Paragraphs, "\n\n")
renderer, _ := glamour.NewTermRenderer(
glamour.WithAutoStyle(),
glamour.WithWordWrap(width-10))
rendered, _ := renderer.Render(md)
m.renderedContent = rendered
m.renderedContent = simpleAboutRender(currentSection.Paragraphs, width)
m.focus = 0
}
return m
@ -93,24 +88,17 @@ func (m *FormModel) Init() tea.Cmd {
}
func (m *FormModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.WindowSizeMsg:
m.width = msg.Width
m.height = msg.Height
if m.CurrentSection.Type == types.SectionAbout {
md := strings.Join(m.CurrentSection.Paragraphs, "\n\n")
renderer, _ := glamour.NewTermRenderer(
glamour.WithAutoStyle(),
glamour.WithWordWrap(m.width-10),
)
rendered, _ := renderer.Render(md)
m.renderedContent = rendered
m.renderedContent = simpleAboutRender(m.CurrentSection.Paragraphs, m.width)
}
return m, nil
case tea.KeyMsg:
switch msg.String() {
case "ctrl+c", "q":
return m, tea.Quit
case "esc":
for _, field := range m.FieldModels {
if field.CaptureKeys() {
@ -134,7 +122,6 @@ func (m *FormModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
},
}
}
//вернуться к списку приложений, чтобы выбрать другое приложение.
case "enter":
logrus.Infof("sections %s, focus %d, fields len %d", m.CurrentSection.Name, m.focus, len(m.FieldModels))
if len(m.FieldModels) == m.focus {
@ -176,71 +163,45 @@ func (m *FormModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case types.SelectedOptionMsg:
logrus.Infof("chose %+v", msg)
}
cmd := m.updateFields(msg)
return m, cmd
return m, m.updateFields(msg)
}
func (m *FormModel) View() string {
contentWidth := ui.ContentWidth(m.width)
var res strings.Builder
containerWidth, containerHeight, contentWidth, _ := ui.ContentSizes(m.width, m.height)
formWidth := contentWidth - 2*1
var strokes []string
for i, field := range m.FieldModels {
fieldStr := wordwrap.String(field.View(), contentWidth-10)
fieldStroke := wordwrap.String(field.View(), formWidth)
if i == m.focus {
res.WriteString(ui.FormFieldFocused.Render(fieldStr))
strokes = append(strokes, ui.FormFieldFocused.Render(fieldStroke))
} else {
res.WriteString(ui.FormFieldUnfocused.Render(fieldStr))
strokes = append(strokes, ui.FormFieldUnfocused.Render(fieldStroke))
}
res.WriteString("\n\n")
}
if m.CurrentSection.Type == types.SectionAbout {
res.WriteString(m.renderedContent)
strokes = append([]string{}, m.renderedContent)
}
nextBtn := "[ next section ]"
if m.focus == len(m.FieldModels) {
res.WriteString(ui.FormNextButton.Render(nextBtn))
strokes = append(strokes, ui.FormNextButton.Render(nextBtn))
} else {
res.WriteString(ui.FormFieldUnfocused.Render(nextBtn))
strokes = append(strokes, ui.FormFieldUnfocused.Render(nextBtn))
}
container := ui.FormContainer.Width(contentWidth).Render(res.String())
return lipgloss.Place(m.width, m.height,
container := ui.AppContainer.
Width(containerWidth).
Height(containerHeight).
Render(strings.Join(strokes, "\n\n"))
return lipgloss.Place(
m.width, m.height,
lipgloss.Center, lipgloss.Center,
container,
lipgloss.WithWhitespaceChars(" "),
)
//for i, field := range m.FieldModels {
// fieldStr := wordwrap.String(field.View(), contentWidth-8)
// if i == m.focus {
// res.WriteString(ui.FormFocusedStyle.Render(fieldStr))
// } else {
// res.WriteString(ui.FormUnfocusedStyle.Render(fieldStr))
// }
// res.WriteString("\n\n")
//}
//
//if m.CurrentSection.Type == types.SectionAbout {
// res.WriteString("\n\n" + m.renderedContent)
//}
//
//button := "[ next section ]"
//if m.focus == len(m.FieldModels) {
// res.WriteString(ui.FormFocusedStyle.Render(button))
//} else {
// res.WriteString(ui.FormUnfocusedStyle.Render(button))
//}
//
//container := ui.FormStyle.Width(contentWidth).Render(res.String())
//
//return lipgloss.Place(m.width, m.height,
// lipgloss.Center, lipgloss.Top,
// container,
// lipgloss.WithWhitespaceChars(" "),
//)
}
func (m *FormModel) updateFields(msg tea.Msg) tea.Cmd {
@ -283,3 +244,42 @@ func (m *FormModel) updateFields(msg tea.Msg) tea.Cmd {
}
return tea.Batch(cmds...)
}
func simpleAboutRender(paragraphs []string, width int) string {
var sb strings.Builder
for _, p := range paragraphs {
p = strings.TrimSpace(p)
if p == "" {
sb.WriteString("\n")
continue
}
switch {
case strings.HasPrefix(p, "# "):
// Заголовок 1 уровня
h1Len := utf8.RuneCountInString(p[2:])
h1Offset := strings.Repeat(" ", (width-h1Len)/2)
sb.WriteString(ui.AboutTitle.Render(h1Offset+p[2:]) + "\n\n")
case strings.HasPrefix(p, "## "):
// Заголовок 2 уровня
sb.WriteString(ui.AboutH2.Render(p[3:]) + "\n\n")
case strings.HasPrefix(p, "### "):
// Заголовок 3 уровня
sb.WriteString(" " + ui.AboutH3.Render(p[4:]) + "\n\n")
case strings.HasPrefix(p, "- "), strings.HasPrefix(p, "* "):
// Список простой
text := strings.TrimLeft(p[2:], " ")
wrapped := wordwrap.String(text, width-10)
sb.WriteString(" " + ui.AboutBullet.Render("• ") + ui.AboutNormal.Render(wrapped) + "\n")
case strings.HasPrefix(p, "> "):
// Цитаты
text := strings.TrimPrefix(p, "> ")
wrapped := wordwrap.String(text, width-12)
sb.WriteString(" " + ui.AboutNormal.Foreground(ui.ColorGrey).Render("│ "+wrapped) + "\n")
default:
// Обычный текст
wrapped := wordwrap.String(p, width-8)
sb.WriteString(" " + ui.AboutNormal.Render(wrapped) + "\n\n")
}
}
return sb.String()
}

View file

@ -7,6 +7,7 @@ import (
"github.com/charmbracelet/bubbles/list"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/sirupsen/logrus"
)
type ListApplicationItem struct {
@ -42,9 +43,9 @@ func NewListApplicationsModel(applications []types.PackageApplication, width, he
delegate.Styles.SelectedDesc = ui.ListSelectedStyle
delegate.ShowDescription = false
l := list.New(items, delegate, width, height)
l.Title = "Выберите приложение для настройки"
l.Title = "Приложения"
l.Styles.Title = ui.ListTitleStyle
l.SetShowHelp(false)
return &ListApplicationsModel{
List: l,
width: width,
@ -65,15 +66,13 @@ func (m *ListApplicationsModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, nil
case tea.KeyMsg:
switch msg.String() {
case "ctrl+c", "q":
return m, tea.Quit
case "enter":
selected := m.List.SelectedItem().(ListApplicationItem)
return m, func() tea.Msg {
return types.ShowModalMsg{
Type: types.ModalConfirm,
Title: "Начать установку",
Message: fmt.Sprintf("Вы хотит настроить приложение:\n\n%s\n\nПродолжить?", selected.Title()),
Message: fmt.Sprintf("Вы хотите настроить приложение:\n\n%s\n\nПродолжить?", selected.Title()),
ModalButtons: []types.ModalButton{
{Text: "Да", Value: true},
{Text: "Нет", Value: false},
@ -95,46 +94,63 @@ func (m *ListApplicationsModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
func (m *ListApplicationsModel) View() string {
contentWidth := ui.ContentWidth(m.width)
listWidth := int(float64(contentWidth) * ui.ListWithRatio)
m.List.SetSize(listWidth-6, m.height-10)
gapStroke := " "
containerWidth, containerHeight, contentWidth, contentHeight := ui.ContentSizes(m.width, m.height)
listView := m.List.View()
listWidth := int(float64(contentWidth)*ui.ListWithRatio) - 2*1
//ширина листа = пропорция от ширины контента - границы для контейнера листа
listHeight := contentHeight - 2*1
//высота листа = высота контента - границы для контейнера листа
descWidth := int(float64(contentWidth)*(1-ui.ListWithRatio)) - 2*1 - len(gapStroke)
//ширина описания = обратная пропорция листа от ширины контента - границы для контейнера описания - отступ от контейнера листа
descHeight := contentHeight - 2*1
//высота листа = высота контента - границы для контейнера описания
m.List.SetSize(listWidth, listHeight)
//logs.go logrus.DebugLevel set
logrus.Debugf(
"window: width %d, height %d\ncontainer: width %d, height %d\ncontent: width %d, height %d\nlist: width %d, height %d\ndesc: width %d, height %d\n\n",
m.width, m.height,
containerWidth, containerHeight,
contentWidth, contentHeight,
listWidth, listHeight,
descWidth, descHeight,
)
listView := lipgloss.NewStyle().
Width(listWidth).
Height(listHeight).
Border(lipgloss.RoundedBorder()).
BorderForeground(ui.ColorPurple).
AlignVertical(lipgloss.Top).
Render(m.List.View())
var desc string
if sel := m.List.SelectedItem(); sel != nil {
desc = sel.(ListApplicationItem).Description()
}
descView := ui.ListDescriptionStyle.Width(contentWidth - listWidth - 10).Render(desc)
body := lipgloss.JoinHorizontal(lipgloss.Top, listView, " ", descView)
container := ui.ListContainer.Width(contentWidth).Render(body)
return lipgloss.Place(m.width, m.height,
descView := ui.ListDescriptionStyle.
Width(descWidth).
Height(descHeight).
Border(lipgloss.RoundedBorder()).
BorderForeground(ui.ColorError).
AlignVertical(lipgloss.Top).
Render(
desc,
)
body := lipgloss.JoinHorizontal(lipgloss.Center, listView, gapStroke, descView)
container := ui.AppContainer.
Width(containerWidth).
Height(containerHeight).
Render(body)
return lipgloss.Place(
m.width, m.height,
lipgloss.Center, lipgloss.Center,
container,
lipgloss.WithWhitespaceChars("░"),
lipgloss.WithWhitespaceForeground(ui.ColorGrey),
lipgloss.WithWhitespaceForeground(ui.ColorWarning),
)
//var desc string
//if selected := m.List.SelectedItem(); selected != nil {
// desc = selected.(ListApplicationItem).Description()
//}
//descView := ui.ListDescriptionStyle.
// Width(contentWidth/2 - 4).
// Render(desc)
//
//body := lipgloss.JoinHorizontal(lipgloss.Top, listView, " ", descView)
//
//container := ui.ListStyle.
// Width(contentWidth).
// Render(body)
//
//return lipgloss.Place(m.width, m.height,
// lipgloss.Center, lipgloss.Center,
// container,
// lipgloss.WithWhitespaceChars("░"),
// lipgloss.WithWhitespaceForeground(ui.ColorGrey),
//)
}

View file

@ -67,8 +67,24 @@ func (m *Main) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
case tea.KeyMsg:
switch msg.String() {
case "ctrl+c", "q":
return m, tea.Quit
case "ctrl+c":
return m, func() tea.Msg {
return types.ShowModalMsg{
Type: types.ModalWarning,
Title: "Закрытие приложения",
Message: "Вы нажали ctrl+c, вы желаете закрыть приложение?",
ModalButtons: []types.ModalButton{
{Text: "Закрыть приложение", Value: true},
{Text: "Не закрывать", Value: false},
},
OnResult: func(result any) tea.Cmd {
if result == true {
return tea.Quit
}
return nil
},
}
}
}
case types.NextSectionMsg:
m.CurrentSectionIdx++
@ -103,14 +119,12 @@ func (m *Main) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.current = NewFinalModel(m.CurrentApplication.Variables, m.Width, m.Height)
} else {
m.current = NewFormModel(m.CurrentApplication, m.CurrentSectionIdx, 0, m.Width, m.Height)
}
case types.SugarMsg:
m.Sugar(msg)
case types.SelectedAppMsg:
//получаем сообщение о выборе приложения
m.CurrentApplication = &msg.App
//ищем доступную секцию
for sectionIdx := 0; sectionIdx < len(m.CurrentApplication.Sections); sectionIdx++ {
section := m.CurrentApplication.Sections[sectionIdx]
@ -136,7 +150,6 @@ func (m *Main) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.overlay = NewModalModel(msg, m.Width, m.Height)
return m, nil
case types.CloseModalMsg:
logrus.Infof("modal close msg get")
m.overlay = nil
return m, tea.Batch(cmds...)
}

View file

@ -73,113 +73,89 @@ func (m *ModalModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
func (m *ModalModel) View() string {
contentWidth := ui.ContentWidth(m.width)
modalWidth := min(contentWidth-10, ui.ModalMaxHeight)
if modalWidth < 60 {
modalWidth = 60
}
content := lipgloss.NewStyle().
Width(modalWidth-8).
Padding(1, 3).
Render(m.message)
containerWidth, containerHeight, _, _ := ui.ContentSizes(m.width, m.height)
modalWidth := min(containerWidth, ui.ModalMaxWidth)
modalHeight := min(containerHeight, ui.ModalMaxHeight)
message := lipgloss.NewStyle().Render(m.message)
var buttons []string
for i, btn := range m.buttons {
for i, button := range m.buttons {
style := ui.ModalButtonNormal
if i == m.focus {
style = ui.ModalButtonFocused
}
buttons = append(buttons, style.Render(btn.Text))
buttons = append(buttons, style.Render(button.Text))
}
buttonRow := lipgloss.JoinHorizontal(lipgloss.Center, buttons...)
modalBody := lipgloss.JoinVertical(lipgloss.Center, message, "", buttonRow)
body := lipgloss.JoinVertical(lipgloss.Center, content, "\n\n", buttonRow)
var borderColor lipgloss.Color
var modalTitleStyle lipgloss.Style
// Бордер и цвет в зависимости от типа
var modalStyle lipgloss.Style
var titleStyle lipgloss.Style
switch m.modalType {
case types.ModalError:
modalStyle = ui.ModalBorderError
titleStyle = ui.ModalTitleError
borderColor = ui.ColorError
modalTitleStyle = ui.ModalTitleError
case types.ModalWarning, types.ModalConfirm:
modalStyle = ui.ModalBorderWarning
titleStyle = ui.ModalTitleWarning
borderColor = ui.ColorWarning
modalTitleStyle = ui.ModalTitleWarning
default:
modalStyle = ui.ModalBorderInfo
titleStyle = ui.ModalTitleDefault
borderColor = ui.ColorAccent
modalTitleStyle = ui.ModalTitleDefault
}
modal := lipgloss.NewStyle().
Width(modalWidth).
Padding(2, 4).
Border(lipgloss.DoubleBorder()).
BorderForeground(modalStyle.GetBorderBottomForeground()).
Render(body)
if m.title != "" {
title := titleStyle.Width(modalWidth).Align(lipgloss.Center).Render(m.title)
modal = lipgloss.JoinVertical(lipgloss.Center, title, modal)
}
return lipgloss.Place(m.width, m.height,
lipgloss.Center, lipgloss.Center,
modal,
lipgloss.WithWhitespaceChars("."),
lipgloss.WithWhitespaceForeground(ui.ColorGrey))
//modalWidth := min(m.width-20, ui.ModalMaxHeight)
//if modalWidth < 60 {
// modalWidth = 60
//}
//var borderColor lipgloss.Color
//var titleStyle lipgloss.Style
//
//switch m.modalType {
//case types.ModalError:
// borderColor = ui.ColorRed
// titleStyle = lipgloss.NewStyle().Foreground(ui.ColorRed).Bold(true)
// modalStyle = ui.ModalBorderError
// modalTitleStyle = ui.ModalTitleError
//case types.ModalWarning, types.ModalConfirm:
// borderColor = ui.ColorPurple
// titleStyle = lipgloss.NewStyle().Foreground(ui.ColorPurple).Bold(true)
// modalStyle = ui.ModalBorderWarning
// modalTitleStyle = ui.ModalTitleWarning
//default:
// borderColor = ui.ColorBlue
// titleStyle = lipgloss.NewStyle().Foreground(ui.ColorBlue).Bold(true)
// modalStyle = ui.ModalBorderInfo
// modalTitleStyle = ui.ModalTitleDefault
//}
//content := lipgloss.NewStyle().Width(m.width-10).Padding(1, 2).Render(m.message)
//var buttons []string
//for i, btn := range m.buttons {
// style := lipgloss.NewStyle().
// Padding(0, 2).
// Border(lipgloss.RoundedBorder()).
// BorderForeground(ui.ColorGrey)
// if i == m.focus {
// style = style.Foreground(ui.ColorRed).BorderForeground(ui.ColorRed).Bold(true)
// } else {
// style = style.Foreground(ui.ColorGreenCyan)
// }
// buttons = append(buttons, style.Render(btn.Text))
//}
//buttonShow := lipgloss.JoinHorizontal(lipgloss.Center, buttons...)
//body := lipgloss.JoinVertical(lipgloss.Center, content, "\n", buttonShow)
//
//modal := lipgloss.NewStyle().
var modalView string
symbolToRepeat := "*"
if m.title != "" {
modalView = ui.ModalContainer.
Width(modalWidth).
Height(modalHeight).
Align(lipgloss.Center, lipgloss.Center).
Border(lipgloss.DoubleBorder()).
BorderForeground(borderColor).
Render(modalBody)
//modalView = ui.ModalContainer.
// Width(modalWidth).
// Height(modalHeight).
// BorderForeground(borderColor).
// Render(modalBody)
//repeatCount := modalWidth - utf8.RuneCountInString(m.title)
//if repeatCount < 0 {
// repeatCount = 0
//}
//symbolFill := strings.Repeat(symbolToRepeat, repeatCount/2)
//modalView = lipgloss.JoinVertical(lipgloss.Center, modalTitleStyle.Render(symbolFill+m.title+symbolFill), modalView)
modalView = lipgloss.JoinVertical(lipgloss.Center, modalTitleStyle.Render(m.title), modalView)
} else {
modalView = ui.ModalContainer.
Width(modalWidth).
Height(modalHeight).
BorderStyle(lipgloss.ASCIIBorder()).
BorderForeground(borderColor).
Render(modalBody)
//modalView = modalStyle.
// Width(modalWidth).
// Height(modalHeight).
// Padding(1, 3).
// Border(lipgloss.DoubleBorder()).
// BorderForeground(borderColor).
// Render(body)
//
//if m.title != "" {
// title := titleStyle.Width(modalWidth - 6).Render(m.title)
// modal = lipgloss.JoinVertical(lipgloss.Center, title, modal)
//}
//
//return lipgloss.Place(m.width, m.height,
// lipgloss.Center, lipgloss.Center,
// modal,
// lipgloss.WithWhitespaceChars("."),
// lipgloss.WithWhitespaceForeground(ui.ColorPurple),
//)
// Render(modalBody)
}
return lipgloss.Place(m.width, m.height,
lipgloss.Center, lipgloss.Center,
modalView,
lipgloss.WithWhitespaceChars(symbolToRepeat),
lipgloss.WithWhitespaceForeground(ui.ColorGrey))
}

View file

@ -1,27 +1,63 @@
package ui
import "github.com/charmbracelet/lipgloss"
const (
AppMaxWidth = 110
AppMinWidth = 80
AppSidePadding = 4
ModalMaxWidth = 80
ModalMaxHeight = 30
ListWithRatio = 0.4
import (
"github.com/charmbracelet/lipgloss"
)
func AppContentWidth(screenWidth int) int {
w := screenWidth - 2*AppSidePadding
if w > AppMaxWidth {
return AppMaxWidth
const (
ApplicationMinWidth = 60
ApplicationMaxHeight = 40
ApplicationMinHeight = 20
ApplicationHorizontalOffset = 3
ApplicationVerticalOffset = 1
ApplicationContainerPadding = 4
ModalMaxWidth = 50
ModalMaxHeight = 15
ListWithRatio = 0.4
TableWithRatio = 0.8
)
// ContentSizes вычисляет размеры для контейнера внутри окна и контента внутри контейнера.
//
// Принимает 2 параметра типа int, screenWidth и screenHeight ширина и высота окна.
//
// Возвращает 4 значения типа int.
//
// containerWidth это ширина контейнера внутри окна с отступами от левого и правого краёв, и с учетом границ
//
// containerHeight это высота контейнера внутри окна с отступами от верхнего и нижнего краёв, и с учетом границ
//
// contentWidth это ширина контента внутри контейнера с отступами от левого и правого краёв контейнера
//
// contentHeight это высота контента внутри контейнера с отступами от верхнего и нижнего краёв контейнера
func ContentSizes(screenWidth, screenHeight int) (containerWidth, containerHeight, contentWidth, contentHeight int) {
containerWidth = screenWidth - 2*ApplicationHorizontalOffset
if containerWidth < ApplicationMinWidth {
//если ширина слишком маленькая ставим минимальную поддерживаемую
containerWidth = ApplicationMinWidth
}
if w < AppMinWidth {
return AppMinWidth
containerWidth = containerWidth - 2*1
//Вычитаем из ширины контейнера 2 условных единицы для границ
//Пример, окно 70, контейнер будет 64, места для контента тогда 62
containerHeight = screenHeight - 2*ApplicationVerticalOffset
if containerHeight < ApplicationMinHeight {
//если высота слишком маленькая ставим минимально поддерживаемую
containerHeight = ApplicationMinHeight
}
return w
containerHeight = containerHeight - 2*1
//Вычитаем из высоты контейнера 2 условных единицы для границ
//Пример, окно 30, контейнер будет 28, места для контента тогда 26
contentWidth = containerWidth - 2*ApplicationHorizontalOffset
//Вычитаем из ширины контейнера отступы для контента внутри
contentHeight = containerHeight - 2*ApplicationVerticalOffset
//Вычитаем из высоты контейнера отступы для контента внутри
return
}
// Используемые цвета
var (
ColorFg = lipgloss.Color("#eeeeee")
ColorBg = lipgloss.Color("#0d1117")
@ -32,34 +68,93 @@ var (
ColorPurple = lipgloss.Color("#cba6f7")
ColorGrey = lipgloss.Color("#585b70")
ColorDarkGrey = lipgloss.Color("#313244")
)
AppContainer = lipgloss.NewStyle().
Padding(2, 4).
Border(lipgloss.RoundedBorder()).
BorderForeground(ColorPurple)
HeaderStyle = lipgloss.NewStyle().
Bold(true).
Foreground(ColorPurple).
Background(ColorDarkGrey).
Padding(1, 2).
MarginBottom(1).
Align(lipgloss.Center)
FooterStyle = lipgloss.NewStyle().
Foreground(ColorGrey).
Padding(0, 2).
MarginTop(1).
Align(lipgloss.Center)
ListContainer = lipgloss.NewStyle().
Padding(1, 3).
var AppContainer = lipgloss.NewStyle().
Padding(ApplicationVerticalOffset, ApplicationHorizontalOffset).
Border(lipgloss.RoundedBorder()).
BorderForeground(ColorAccent)
// Стили для списка приложений
var (
ListTitleStyle = lipgloss.NewStyle().Foreground(ColorPurple).Italic(true).Bold(true)
ListSelectedStyle = lipgloss.NewStyle().Foreground(ColorError).Bold(true)
ListDescriptionStyle = lipgloss.NewStyle().Foreground(ColorAccent2).MarginTop(1)
ListDescriptionStyle = lipgloss.NewStyle().Foreground(ColorAccent2)
)
// Стили для about
var (
AboutTitle = lipgloss.NewStyle().
Foreground(ColorAccent).
Bold(true).
AlignHorizontal(lipgloss.Center)
AboutH2 = lipgloss.NewStyle().
Foreground(ColorAccent).
Bold(true)
AboutH3 = lipgloss.NewStyle().
Foreground(ColorAccent).
Bold(true)
AboutBullet = lipgloss.NewStyle().
Foreground(ColorGrey)
AboutNormal = lipgloss.NewStyle().
Foreground(ColorFg)
)
// Стили для форм и полей ввода
var (
FormFieldFocused = lipgloss.NewStyle().Foreground(ColorError).Bold(true)
FormFieldUnfocused = lipgloss.NewStyle().Foreground(ColorAccent2)
FormNextButton = lipgloss.NewStyle().Foreground(ColorPurple).Bold(true)
SelectFileDirDeniedStyle = lipgloss.NewStyle().Foreground(ColorGrey)
SelectFileDirParentStyle = lipgloss.NewStyle().Foreground(ColorPurple)
SelectFileStyle = lipgloss.NewStyle().Foreground(ColorAccent)
SelectDirStyle = lipgloss.NewStyle().Foreground(ColorAccent2)
)
// Стили для таблицы переменных Final
var (
FinalHeader = lipgloss.NewStyle().
Background(ColorDarkGrey).
Foreground(ColorPurple).
Bold(true).
Align(lipgloss.Center).
MarginBottom(1).
Padding(1, 2)
FinalFooter = lipgloss.NewStyle().
Foreground(ColorGrey).
MarginTop(1).
Padding(1, 0).
Align(lipgloss.Center)
FinalTableBorder = lipgloss.NewStyle().
Foreground(ColorPurple)
FinalTableHeader = lipgloss.NewStyle().
Foreground(ColorPurple).
Bold(true).
Align(lipgloss.Center).
Padding(0, 1)
FinalTableEvenRow = lipgloss.NewStyle().
Background(ColorDarkGrey).
Padding(0, 2)
FinalTableOddRow = lipgloss.NewStyle().
Padding(0, 2)
)
// Стили для модальных окон
var (
ModalContainer = lipgloss.NewStyle().
Border(lipgloss.DoubleBorder()).
Align(lipgloss.Center, lipgloss.Center)
//Padding(ApplicationVerticalOffset, ApplicationHorizontalOffset)
ModalBorderError = lipgloss.NewStyle().BorderForeground(ColorError)
ModalBorderWarning = lipgloss.NewStyle().BorderForeground(ColorWarning)
@ -82,84 +177,57 @@ var (
BorderForeground(ColorError).
Foreground(ColorError).
Bold(true)
FormContainer = lipgloss.NewStyle().
Padding(2, 4).
Border(lipgloss.RoundedBorder()).
BorderForeground(ColorAccent)
FormFieldFocused = lipgloss.NewStyle().Foreground(ColorError).Bold(true)
FormFieldUnfocused = lipgloss.NewStyle().Foreground(ColorAccent2)
FormNextButton = lipgloss.NewStyle().Foreground(ColorPurple).Bold(true)
TableBorder = lipgloss.NewStyle().Foreground(ColorPurple)
TableHeader = lipgloss.NewStyle().Foreground(ColorPurple).Bold(true).Align(lipgloss.Center)
TableRowEven = lipgloss.NewStyle().Foreground(ColorFg).Background(ColorDarkGrey)
TableRowOdd = lipgloss.NewStyle().Foreground(ColorFg)
TableCell = lipgloss.NewStyle().Padding(0, 2)
//ColorWhite = lipgloss.Color("#ffffff")
//ColorGrey = lipgloss.Color("#626262")
//ColorBlack = lipgloss.Color("#000000")
//ColorRed = lipgloss.Color("#FF0000")
//ColorGreen = lipgloss.Color("#00FF00")
//ColorBlue = lipgloss.Color("#0000FF")
//ColorPurple = lipgloss.Color("#6f03fc")
//ColorGreenCyan = lipgloss.Color("#11CAAA")
//
//AppContainerStyle = lipgloss.NewStyle().
// Padding(1, 3).
// Border(lipgloss.RoundedBorder()).
// BorderForeground(ColorPurple)
//
//AppHeaderStyle = lipgloss.NewStyle().
// Bold(true).
// Foreground(ColorPurple).
// Align(lipgloss.Center).
// Padding(1, 0).
// Background(ColorWhite).
// Foreground(ColorBlack)
//
//AppFooterStyle = lipgloss.NewStyle().
// Foreground(ColorGrey).
// Align(lipgloss.Center).
// Padding(0, 1)
//
//TableHeaderStyle = lipgloss.NewStyle().Foreground(ColorPurple).Bold(true).Align(lipgloss.Center)
//TableCellStyle = lipgloss.NewStyle().Padding(0, 1).Width(14)
//TableOddRowStyle = TableCellStyle.Foreground(ColorWhite).Background(ColorGrey)
//TableEvenRowStyle = TableCellStyle.Foreground(ColorWhite)
//TableBorderStyle = lipgloss.NewStyle().Foreground(ColorPurple)
//
SelectFileDirDeniedStyle = lipgloss.NewStyle().Foreground(ColorGrey)
SelectFileDirParentStyle = lipgloss.NewStyle().Foreground(ColorPurple)
SelectFileStyle = lipgloss.NewStyle().Foreground(ColorAccent)
SelectDirStyle = lipgloss.NewStyle().Foreground(ColorAccent2)
//
//SelectStyle = lipgloss.NewStyle().Foreground(ColorGreenCyan)
//InputStyle = lipgloss.NewStyle().Foreground(ColorGreenCyan)
//ComputeStyle = lipgloss.NewStyle().Foreground(ColorGrey)
//CheckboxStyle = lipgloss.NewStyle().Foreground(ColorGreenCyan)
//
//FormFocusedStyle = lipgloss.NewStyle().Foreground(ColorRed).Bold(true).
// Border(lipgloss.NormalBorder(), false, false, false, true).BorderForeground(ColorRed)
//FormUnfocusedStyle = lipgloss.NewStyle().Foreground(ColorGreenCyan)
//FormStyle = lipgloss.NewStyle().Padding(1, 2).Foreground(ColorBlack).
// Border(lipgloss.RoundedBorder()).BorderForeground(ColorRed)
//
//ListStyle = lipgloss.NewStyle().Padding(1, 3).Border(lipgloss.RoundedBorder()).BorderForeground(ColorRed)
//ListSelectedItemStyle = lipgloss.NewStyle().Foreground(ColorRed).Bold(true)
//ListTitleStyle = lipgloss.NewStyle().Foreground(ColorPurple).Italic(true)
//ListDescriptionStyle = lipgloss.NewStyle().Foreground(ColorGreenCyan).MarginTop(1)
)
func ContentWidth(screenWidth int) int {
w := screenWidth - 2*AppSidePadding
if w > AppMaxWidth {
return AppMaxWidth
}
if w < AppMinWidth {
return AppMinWidth
}
return w
}
//Старые стили
//var (
//ColorWhite = lipgloss.Color("#ffffff")
//ColorGrey = lipgloss.Color("#626262")
//ColorBlack = lipgloss.Color("#000000")
//ColorRed = lipgloss.Color("#FF0000")
//ColorGreen = lipgloss.Color("#00FF00")
//ColorBlue = lipgloss.Color("#0000FF")
//ColorPurple = lipgloss.Color("#6f03fc")
//ColorGreenCyan = lipgloss.Color("#11CAAA")
//
//AppContainerStyle = lipgloss.NewStyle().
// Padding(1, 3).
// Border(lipgloss.RoundedBorder()).
// BorderForeground(ColorPurple)
//
//AppHeaderStyle = lipgloss.NewStyle().
// Bold(true).
// Foreground(ColorPurple).
// Align(lipgloss.Center).
// Padding(1, 0).
// Background(ColorWhite).
// Foreground(ColorBlack)
//
//AppFooterStyle = lipgloss.NewStyle().
// Foreground(ColorGrey).
// Align(lipgloss.Center).
// Padding(0, 1)
//
//TableHeaderStyle = lipgloss.NewStyle().Foreground(ColorPurple).Bold(true).Align(lipgloss.Center)
//TableCellStyle = lipgloss.NewStyle().Padding(0, 1).Width(14)
//TableOddRowStyle = TableCellStyle.Foreground(ColorWhite).Background(ColorGrey)
//TableEvenRowStyle = TableCellStyle.Foreground(ColorWhite)
//TableBorderStyle = lipgloss.NewStyle().Foreground(ColorPurple)
//
//
//SelectStyle = lipgloss.NewStyle().Foreground(ColorGreenCyan)
//InputStyle = lipgloss.NewStyle().Foreground(ColorGreenCyan)
//ComputeStyle = lipgloss.NewStyle().Foreground(ColorGrey)
//CheckboxStyle = lipgloss.NewStyle().Foreground(ColorGreenCyan)
//
//FormFocusedStyle = lipgloss.NewStyle().Foreground(ColorRed).Bold(true).
// Border(lipgloss.NormalBorder(), false, false, false, true).BorderForeground(ColorRed)
//FormUnfocusedStyle = lipgloss.NewStyle().Foreground(ColorGreenCyan)
//FormStyle = lipgloss.NewStyle().Padding(1, 2).Foreground(ColorBlack).
// Border(lipgloss.RoundedBorder()).BorderForeground(ColorRed)
//
//ListStyle = lipgloss.NewStyle().Padding(1, 3).Border(lipgloss.RoundedBorder()).BorderForeground(ColorRed)
//ListSelectedItemStyle = lipgloss.NewStyle().Foreground(ColorRed).Bold(true)
//ListTitleStyle = lipgloss.NewStyle().Foreground(ColorPurple).Italic(true)
//ListDescriptionStyle = lipgloss.NewStyle().Foreground(ColorGreenCyan).MarginTop(1)
//)

View file

@ -1,4 +1,4 @@
package types
const SectionAbout = "abouta"
const SectionAbout = "about"
const SectionFinal = "final"