Posters: re-place after a resize, measure cell size without padding, log failed posters

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-21 00:14:50 +02:00
co-authored by Claude Sonnet 5.5
parent b40ee8b61c
commit 9edd62634f
5 changed files with 111 additions and 4 deletions
+36 -2
View File
@@ -66,6 +66,7 @@ type App struct {
covers bool // poster thumbnails enabled
coverStyle coverStyle
gfx *kittyGraphics // real-image posters, nil when not used
lastWindow [3]int // columns, rows and cell shape at the last draw
cellAspect float64 // height of a terminal cell divided by its width
coverCache *coverCache
}
@@ -181,6 +182,7 @@ func New(cfg *config.Config) *App {
s.HideCursor()
a.screen = s
a.cellAspect = cellAspectOf(s)
a.noteWindow(s)
return false
})
a.app.SetRoot(&toastRoot{Flex: layout, app: a}, true).EnableMouse(false)
@@ -190,6 +192,22 @@ func New(cfg *config.Config) *App {
return a
}
// noteWindow notices when the terminal's size or cell size changed. The
// placements of real-image posters are then made again: the picture would
// otherwise rely on the terminal rescaling an existing placement correctly.
func (a *App) noteWindow(s tcell.Screen) {
w, h := s.Size()
win := [3]int{w, h, int(a.cellAspect * 1000)}
if win == a.lastWindow {
return
}
first := a.lastWindow == [3]int{}
a.lastWindow = win
if !first && a.gfx != nil {
a.gfx.invalidate()
}
}
// writeTerminal sends raw bytes (graphics commands) to the terminal through the
// screen's own connection. UI goroutine only, so it stays in step with drawing.
func (a *App) writeTerminal(b []byte) error {
@@ -208,13 +226,29 @@ func (a *App) writeTerminal(b []byte) error {
// pixels; 2 when the terminal does not report pixel sizes.
func cellAspectOf(s tcell.Screen) float64 {
if tty, ok := s.Tty(); ok {
if ws, err := tty.WindowSize(); err == nil && ws.Width > 0 && ws.Height > 0 && ws.PixelWidth > 0 && ws.PixelHeight > 0 {
return (float64(ws.PixelHeight) / float64(ws.Height)) / (float64(ws.PixelWidth) / float64(ws.Width))
if ws, err := tty.WindowSize(); err == nil {
return cellAspectFromWindow(ws.Width, ws.Height, ws.PixelWidth, ws.PixelHeight)
}
}
return 2
}
// cellAspectFromWindow derives the cell shape from the window size in cells
// and pixels. The pixel size usually includes some padding around the cell
// grid, which would skew a plain division (more with a small grid, and it
// changes while the window is resized); cell sizes are whole pixels, so the
// division is rounded down, which drops that padding.
func cellAspectFromWindow(cols, rows, pixelW, pixelH int) float64 {
if cols <= 0 || rows <= 0 || pixelW <= 0 || pixelH <= 0 {
return 2
}
cw, ch := pixelW/cols, pixelH/rows
if cw <= 0 || ch <= 0 {
return 2
}
return float64(ch) / float64(cw)
}
// coverMaxHeight is how tall the stored poster copies are: real images keep
// more detail than block characters need.
func (a *App) coverMaxHeight() int {