clone

fifteen puzzle games for the terminal, with daily puzzles, weekly ladders and xp, built on bubble tea

main
puzzletea / app / screen_layout.go
screen_layout.go 5.1K
1package app23import (4 "log"56 "github.com/FelineStateMachine/puzzletea/stats"7 "github.com/FelineStateMachine/puzzletea/theme"8 "github.com/FelineStateMachine/puzzletea/ui"910 "charm.land/bubbles/v2/list"11 "charm.land/bubbles/v2/viewport"12 "github.com/charmbracelet/glamour"13)1415const (16 helpPanelInsetX = 217 helpPanelInsetY = 118 helpPanelHorizontalTrim = 619 helpPanelVerticalTrim = 820 categoryPanelChrome = 821 categoryBodyMaxWidth = 8622 categoryBodyMaxHeight = 1623 categoryMinListWidth = 2424 categoryMaxListWidth = 3025 categoryGapWidth = 226 categoryDetailTrimX = 627 categoryDetailTrimY = 428 categoryStackGapHeight = 129 categoryMinSideBySideW = 7230)3132func helpViewportSize(width, height int) (int, int) {33 panelWidth := max(width-(helpPanelInsetX*2), 1)34 panelHeight := max(height-(helpPanelInsetY*2), 1)35 contentWidth := max(panelWidth-helpPanelHorizontalTrim, 1)36 contentHeight := max(panelHeight-helpPanelVerticalTrim, 1)37 return contentWidth, contentHeight38}3940func helpSelectListSize(width, height int, l list.Model) (int, int) {41 contentWidth, contentHeight := helpViewportSize(width, height)42 listWidth := min(contentWidth, 64)43 listHeight := min(contentHeight, ui.ListHeight(l))44 return listWidth, listHeight45}4647func statsViewportSize(width, height int, cards []stats.Card) (int, int) {48 contentWidth, _ := helpViewportSize(width, height)49 panelHeight := max(height-(helpPanelInsetY*2), 1)50 contentHeight := max(panelHeight-stats.StaticHeight(cards), 1)51 return contentWidth, contentHeight52}5354type categoryPickerMetrics struct {55 bodyWidth int56 bodyHeight int57 listWidth int58 listHeight int59 detailWidth int60 detailHeight int61 stacked bool62}6364func categoryPickerSize(width, height int) categoryPickerMetrics {65 bodyWidth := min(width, categoryBodyMaxWidth)66 bodyHeight := min(max(height-categoryPanelChrome, 1), categoryBodyMaxHeight)6768 if bodyWidth < categoryMinSideBySideW {69 listHeight := min(bodyHeight, categoryPickerListHeight())70 detailHeight := max(bodyHeight-listHeight-categoryStackGapHeight, 1)71 if detailHeight == 1 && bodyHeight > 1 {72 listHeight = max(bodyHeight-categoryStackGapHeight-detailHeight, 1)73 }74 return categoryPickerMetrics{75 bodyWidth: bodyWidth,76 bodyHeight: bodyHeight,77 listWidth: bodyWidth,78 listHeight: listHeight,79 detailWidth: bodyWidth,80 detailHeight: detailHeight,81 stacked: true,82 }83 }8485 listWidth := min(categoryMaxListWidth, max(categoryMinListWidth, bodyWidth/3))86 detailWidth := max(bodyWidth-listWidth-categoryGapWidth, 1)87 return categoryPickerMetrics{88 bodyWidth: bodyWidth,89 bodyHeight: bodyHeight,90 listWidth: listWidth,91 listHeight: bodyHeight,92 detailWidth: detailWidth,93 detailHeight: bodyHeight,94 }95}9697func selectedCategoryName(item list.Item) string {98 entry, ok := selectedCategoryEntry(item)99 if !ok {100 return ""101 }102 return entry.Definition.Name103}104105func (m model) updateCategoryDetailViewport() model {106 metrics := categoryPickerSize(m.width, m.height)107 contentWidth := max(metrics.detailWidth-categoryDetailTrimX, 1)108 contentHeight := max(metrics.detailHeight-categoryDetailTrimY, 1)109110 if m.nav.categoryDetail.Width() == 0 || m.nav.categoryDetail.Height() == 0 {111 m.nav.categoryDetail = viewport.New(112 viewport.WithWidth(contentWidth),113 viewport.WithHeight(contentHeight),114 )115 }116 m.nav.categoryDetail.SetWidth(contentWidth)117 m.nav.categoryDetail.SetHeight(contentHeight)118 m.nav.categoryDetail.FillHeight = true119120 entry, ok := selectedCategoryEntry(m.nav.gameSelectList.SelectedItem())121 if !ok {122 m.nav.categoryDetail.SetContent("")123 return m124 }125126 m.nav.categoryDetail.SetContent(renderCategoryDetailContent(entry, contentWidth))127 m.nav.categoryDetail.GotoTop()128 return m129}130131func (m model) updateHelpDetailViewport() model {132 helpWidth, helpHeight := helpViewportSize(m.width, m.height)133 palette := theme.Current()134 themeKey := helpMarkdownThemeKey(palette)135 if m.help.renderer == nil || m.help.rendererWidth != helpWidth || m.help.rendererTheme != themeKey {136 renderer, err := glamour.NewTermRenderer(137 glamour.WithStyles(helpMarkdownStyle(palette)),138 glamour.WithWordWrap(helpWidth),139 glamour.WithChromaFormatter("terminal16m"),140 )141 if err != nil {142 log.Printf("failed to create help renderer: %v", err)143 m.help.renderer = nil144 m.help.rendererWidth = 0145 m.help.rendererTheme = ""146 } else {147 m.help.renderer = renderer148 m.help.rendererWidth = helpWidth149 m.help.rendererTheme = themeKey150 }151 }152153 rendered := m.help.category.Help154 if m.help.renderer != nil {155 out, err := m.help.renderer.Render(m.help.category.Help)156 if err != nil {157 log.Printf("failed to render help: %v", err)158 } else {159 rendered = out160 }161 }162163 m.help.viewport = viewport.New(164 viewport.WithWidth(helpWidth),165 viewport.WithHeight(helpHeight),166 )167 m.help.viewport.SetContent(rendered)168 return m169}170171func (m model) updateStatsViewport() model {172 statsWidth, statsHeight := statsViewportSize(m.width, m.height, m.stats.cards)173 m.stats.viewport.SetWidth(statsWidth)174 m.stats.viewport.SetHeight(statsHeight)175 m.stats.viewport.SetContent(ui.RenderStatsCardGrid(m.stats.cards, statsWidth))176 return m177}