Detail panes: poster top right, title and facts left, plot below; sextant rendering

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-20 21:57:22 +02:00
co-authored by Claude Sonnet 5.5
parent 365ba9dee8
commit b4fe931fa9
15 changed files with 468 additions and 153 deletions
+49 -24
View File
@@ -8,39 +8,49 @@ import (
)
const (
coverRows = 20 // poster height in terminal rows
minCoverRows = 6 // a smaller poster is not worth showing
minCoverCols = 8 //
minTextRows = 10 // the poster never takes these rows from the text
)
// detailPane shows a poster above a text. The poster shrinks on short panes
// and is dropped when the text would get too little room.
// detailPane shows the details of one title: the head (title and facts) with
// the poster to its right on top, and the plot below both. On panes too small
// for the poster everything is one text.
type detailPane struct {
*tview.Flex
app *App
cover *coverView
text *tview.TextView
*tview.Flex // column: top row, plot
app *App
top *tview.Flex // row: head, cover
head *tview.TextView
cover *coverView
plot *tview.TextView
want string // key of the poster that should be shown
hasCover bool // a poster (or its loading note) is part of the content
coverSize int // rows currently given to the poster
text detailText
want string // key of the poster that should be shown
hasCover bool // a poster (or its loading note) is part of the content
combined bool // head and plot are shown together in the plot view
}
func newDetailPane(app *App) *detailPane {
d := &detailPane{
Flex: tview.NewFlex().SetDirection(tview.FlexRow),
app: app,
cover: newCoverView(),
text: tview.NewTextView().SetDynamicColors(true).SetWordWrap(true),
top: tview.NewFlex(),
head: tview.NewTextView().SetDynamicColors(true).SetWordWrap(true),
cover: newCoverView(app.coverStyle),
plot: tview.NewTextView().SetDynamicColors(true).SetWordWrap(true),
}
d.AddItem(d.cover, 0, 0, false).AddItem(d.text, 0, 1, false)
d.cover.SetBorderPadding(0, 0, 1, 0)
d.top.AddItem(d.head, 0, 1, false).AddItem(d.cover, 0, 0, false)
d.AddItem(d.top, 0, 0, false).AddItem(d.plot, 0, 1, false)
d.SetBorder(true).SetTitle(" Details ")
d.combined = true
return d
}
// Show displays the text and, if there is one, the poster.
func (d *detailPane) Show(text string, ref coverRef) {
d.text.SetText(text).ScrollToBeginning()
func (d *detailPane) Show(text detailText, ref coverRef) {
d.text = text
d.applyText()
if !d.app.covers || ref.fetch == nil {
d.want, d.hasCover = "", false
d.cover.setImage(nil)
@@ -63,20 +73,35 @@ func (d *detailPane) Show(text string, ref coverRef) {
}
// Clear empties the pane.
func (d *detailPane) Clear() { d.Show("", coverRef{}) }
func (d *detailPane) Clear() { d.Show(detailText{}, coverRef{}) }
// applyText puts the texts into their views for the current layout.
func (d *detailPane) applyText() {
if d.combined {
d.head.SetText("")
d.plot.SetText(d.text.String()).ScrollToBeginning()
return
}
d.head.SetText(d.text.head).ScrollToBeginning()
d.plot.SetText(d.text.plot).ScrollToBeginning()
}
func (d *detailPane) Draw(screen tcell.Screen) {
_, _, _, h := d.GetInnerRect()
rows := 0
_, _, w, h := d.GetInnerRect()
coverW, coverH := 0, 0
if d.hasCover {
rows = min(coverRows, h-minTextRows)
if rows < minCoverRows {
rows = 0
aw, ah := d.cover.aspect()
// At most 40% of the width, and the plot keeps its rows.
coverW, coverH = coverCells(aw, ah, w*2/5, h-minTextRows)
if coverH < minCoverRows || coverW < minCoverCols {
coverW, coverH = 0, 0
}
}
if rows != d.coverSize {
d.coverSize = rows
d.ResizeItem(d.cover, rows, 0)
if combined := coverW == 0; combined != d.combined {
d.combined = combined
d.applyText()
}
d.ResizeItem(d.top, coverH, 0)
d.top.ResizeItem(d.cover, coverW+1, 0) // +1: the padding left of the poster
d.Flex.Draw(screen)
}