about_render.go (2516B)
1 package tui 2 3 import ( 4 "strings" 5 "time" 6 7 tea "github.com/charmbracelet/bubbletea" 8 ) 9 10 const aboutLogoWidth = 32 11 12 type aboutASCIIRenderedMsg struct { 13 width int 14 ascii string 15 } 16 17 func (m *portfolioModel) maybeRenderAboutASCIICmd() tea.Cmd { 18 if m.activeTab != tabAbout { 19 return nil 20 } 21 22 width := aboutLogoWidth 23 if ascii, cached := m.aboutASCIICache[width]; cached { 24 if strings.TrimSpace(ascii) != "" { 25 return nil 26 } 27 } 28 if m.aboutASCIILoads[width] { 29 return nil 30 } 31 32 m.aboutASCIILoads[width] = true 33 return renderAboutASCIICmd(width) 34 } 35 36 func renderAboutASCIICmd(width int) tea.Cmd { 37 return func() tea.Msg { 38 return aboutASCIIRenderedMsg{ 39 width: width, 40 ascii: loadAboutASCII(width), 41 } 42 } 43 } 44 45 func (m *portfolioModel) applyAboutASCIIRendered(msg aboutASCIIRenderedMsg) { 46 delete(m.aboutASCIILoads, msg.width) 47 if strings.TrimSpace(msg.ascii) == "" { 48 delete(m.aboutASCIICache, msg.width) 49 return 50 } 51 m.aboutASCIICache[msg.width] = msg.ascii 52 } 53 54 // aboutLogoRowTick paces the portrait reveal: 16 rows in about 320ms, the 55 // same budget the web gives its row-by-row draw. 56 const aboutLogoRowTick = 20 * time.Millisecond 57 58 type aboutLogoRowMsg struct{ gen int } 59 60 func aboutLogoRowCmd(gen int) tea.Cmd { 61 return tea.Tick(aboutLogoRowTick, func(time.Time) tea.Msg { return aboutLogoRowMsg{gen: gen} }) 62 } 63 64 // startAboutLogoReveal restarts the portrait from its first row. A newer 65 // reveal bumps the generation so ticks from an older one are dropped. 66 func (m *portfolioModel) startAboutLogoReveal() tea.Cmd { 67 if m.activeTab != tabAbout || m.bootActive { 68 return nil 69 } 70 if strings.TrimSpace(m.aboutASCIICache[aboutLogoWidth]) == "" { 71 return nil // starts when the render lands 72 } 73 m.aboutLogoGen++ 74 m.aboutLogoRows = 1 75 return aboutLogoRowCmd(m.aboutLogoGen) 76 } 77 78 func (m *portfolioModel) stepAboutLogoReveal(msg aboutLogoRowMsg) tea.Cmd { 79 if msg.gen != m.aboutLogoGen { 80 return nil 81 } 82 total := strings.Count(m.aboutASCIICache[aboutLogoWidth], "\n") + 1 83 if m.aboutLogoRows >= total { 84 return nil 85 } 86 m.aboutLogoRows++ 87 if m.aboutLogoRows >= total { 88 return nil 89 } 90 return aboutLogoRowCmd(m.aboutLogoGen) 91 } 92 93 // revealedLogo keeps the portrait's full size so the fetch lines never 94 // shift, with the rows not drawn yet left blank. 95 func revealedLogo(logo string, rows int) string { 96 lines := strings.Split(logo, "\n") 97 if rows >= len(lines) { 98 return logo 99 } 100 blank := strings.Repeat(" ", aboutLogoWidth) 101 for i := rows; i < len(lines); i++ { 102 lines[i] = blank 103 } 104 return strings.Join(lines, "\n") 105 }