diff --git a/setup/final.go b/setup/final.go index 8c582de..ea9334d 100644 --- a/setup/final.go +++ b/setup/final.go @@ -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.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.height = msg.Height + m.width = msg.Width + m.initViewport(msg.Width, msg.Height) } 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 } diff --git a/setup/form.go b/setup/form.go index 6f4031a..ea345fc 100644 --- a/setup/form.go +++ b/setup/form.go @@ -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() +} diff --git a/setup/list.go b/setup/list.go index 09ffe4b..4695153 100644 --- a/setup/list.go +++ b/setup/list.go @@ -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), - //) - } diff --git a/setup/main.go b/setup/main.go index 95083b1..acd7a21 100644 --- a/setup/main.go +++ b/setup/main.go @@ -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...) } diff --git a/setup/modal.go b/setup/modal.go index 5065427..c3ddb6b 100644 --- a/setup/modal.go +++ b/setup/modal.go @@ -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(). - // Width(modalWidth). - // 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), - //) + 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()). + // Render(modalBody) + } + return lipgloss.Place(m.width, m.height, + lipgloss.Center, lipgloss.Center, + modalView, + lipgloss.WithWhitespaceChars(symbolToRepeat), + lipgloss.WithWhitespaceForeground(ui.ColorGrey)) } diff --git a/setup/ui/style.go b/setup/ui/style.go index b8efb98..6ecce68 100644 --- a/setup/ui/style.go +++ b/setup/ui/style.go @@ -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). - Border(lipgloss.RoundedBorder()). - BorderForeground(ColorAccent) +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) +//) diff --git a/types/names.go b/types/names.go index c091c5c..5b1a858 100644 --- a/types/names.go +++ b/types/names.go @@ -1,4 +1,4 @@ package types -const SectionAbout = "abouta" +const SectionAbout = "about" const SectionFinal = "final"