prime8dev

prime8dev
git clone https://git.prime8.dev/prime8dev.git
Log | Files | Refs | README | LICENSE

layout.go (2604B)


      1 package tui
      2 
      3 import "github.com/charmbracelet/lipgloss"
      4 
      5 // detailViewKey identifies what the detail pane is showing. Scroll offsets
      6 // survive re-renders of the same view (async readme/markdown results, window
      7 // resizes, ticks) and only reset when the view itself changes.
      8 type detailViewKey struct {
      9 	tab, project, experience int
     10 }
     11 
     12 func (m portfolioModel) currentDetailViewKey() detailViewKey {
     13 	return detailViewKey{tab: m.activeTab, project: m.projectIndex, experience: m.experienceIndex}
     14 }
     15 
     16 // syncViewport re-renders the detail pane, keeping the scroll position when
     17 // the same tab/item is still selected.
     18 func (m *portfolioModel) syncViewport() {
     19 	key := m.currentDetailViewKey()
     20 	same := m.detailViewSet && key == m.detailView
     21 	m.detailView, m.detailViewSet = key, true
     22 
     23 	m.ensureSelectedProjectMarkdown()
     24 	content := m.detailContent()
     25 	m.viewport.SetContent(content)
     26 	m.detailLineWidths = lineWidths(content)
     27 	w, h := m.detailSize()
     28 	m.viewport.Width = max(w, 1)
     29 	m.viewport.Height = max(h, 1)
     30 
     31 	if !same {
     32 		m.detailXOffset = 0
     33 	}
     34 	m.detailXOffset = min(max(m.detailXOffset, 0), m.detailMaxXOffset())
     35 	m.viewport.SetXOffset(m.detailXOffset)
     36 
     37 	if m.activeTab == tabAbout && (m.shell.cleared || len(m.shell.history) > 0 || m.shell.input != "") {
     38 		m.viewport.GotoBottom()
     39 		return
     40 	}
     41 	if same {
     42 		m.viewport.SetYOffset(m.viewport.YOffset) // clamp to the new content
     43 		return
     44 	}
     45 	m.viewport.GotoTop()
     46 }
     47 
     48 // resetViewport re-renders the detail pane scrolled back to the top-left.
     49 func (m *portfolioModel) resetViewport() {
     50 	m.detailViewSet = false
     51 	m.syncViewport()
     52 }
     53 
     54 func (m portfolioModel) detailSize() (int, int) {
     55 	bodyW, bodyH := m.bodySize()
     56 
     57 	if m.activeTab == tabProjects {
     58 		if bodyW < 72 {
     59 			stackTop := max(bodyH/2, 3)
     60 			stackBottom := max(bodyH-stackTop, 1)
     61 			return max(bodyW-4, 10), max(stackBottom-2, 3)
     62 		}
     63 
     64 		sideW := m.sidebarWidth(bodyW)
     65 		detailPanelW := bodyW - sideW
     66 		return max(detailPanelW-4, 10), max(bodyH-2, 3)
     67 	}
     68 
     69 	return max(bodyW-4, 10), max(bodyH-2, 3)
     70 }
     71 
     72 func (m portfolioModel) bodySize() (int, int) {
     73 	w := max(m.width, 40)
     74 	h := max(m.height, 12)
     75 	headerH := lipgloss.Height(m.renderHeader(w))
     76 	footerH := lipgloss.Height(m.renderFooter(w))
     77 	bodyH := max(h-headerH-footerH, 3)
     78 	return max(w, 20), bodyH
     79 }
     80 
     81 func (m portfolioModel) sidebarWidth(bodyWidth int) int {
     82 	if bodyWidth < 80 {
     83 		return bodyWidth
     84 	}
     85 	return min(max(bodyWidth/3, 24), 40)
     86 }
     87 
     88 func (m portfolioModel) detailContent() string {
     89 	switch m.activeTab {
     90 	case tabExperience:
     91 		return m.experiencePage()
     92 	case tabProjects:
     93 		return m.projectDetail()
     94 	default:
     95 		return m.aboutDetail()
     96 	}
     97 }