Files
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

165 lines
4.6 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package progress
import (
"strings"
"github.com/charmbracelet/lipgloss"
)
// The gortex mark is a 5×5 dot-matrix "G": an open ring with a tongue in the
// middle row whose tip — the accent dot — always renders in brand green. The
// live renderer lights the remaining dots progressively as work completes, so
// the logo itself is the coarse progress gauge; banners render it fully lit.
type gDot struct {
r, c int
accent bool
}
// gDots lists the mark's dots in row-major order — the order they light up.
var gDots = buildGDots()
func buildGDots() []gDot {
const cells = 5
mid := cells / 2
var dots []gDot
for r := 0; r < cells; r++ {
for c := 0; c < cells; c++ {
onTop := r == 0 && c >= 1 && c <= cells-2
onBot := r == cells-1 && c >= 1 && c <= cells-2
onLeft := c == 0 && r >= 1 && r <= cells-2
onTongue := r == mid && c >= mid && c <= cells-1
onTopRight := c == cells-1 && r == 1
onBotRight := c == cells-1 && r == cells-2
if onTop || onBot || onLeft || onTongue || onTopRight || onBotRight {
dots = append(dots, gDot{r: r, c: c, accent: r == mid && c == cells-1})
}
}
}
return dots
}
// litDotCount is the number of dots that participate in progressive lighting
// (every dot except the always-lit accent tip).
func litDotCount() int { return len(gDots) - 1 }
// logoLines renders the mark as 5 rows. frac in [0,1] lights that fraction of
// the dots bottom-up in row-major order; frac < 0 selects the indeterminate
// marquee — a short lit window orbiting the mark at the given tick. glint, when
// >= 0, additionally paints dot (glint % len) in accent green: the traveling
// highlight used by animated banners over a fully lit mark.
func logoLines(lit, dim, accent lipgloss.Style, g glyphSet, frac float64, tick, glint int) [5]string {
const cells = 5
byPos := make(map[[2]int]int, len(gDots))
for i, d := range gDots {
byPos[[2]int{d.r, d.c}] = i
}
litN := -1
if frac >= 0 {
if frac > 1 {
frac = 1
}
litN = int(frac*float64(litDotCount()) + 0.5)
}
// Non-accent ordinal per dot index, for both fill and marquee windows.
ordinal := make([]int, len(gDots))
ord := 0
for i, d := range gDots {
if d.accent {
ordinal[i] = -1
continue
}
ordinal[i] = ord
ord++
}
const marqueeWindow = 4
var lines [5]string
for r := 0; r < cells; r++ {
var row strings.Builder
for c := 0; c < cells; c++ {
if c > 0 {
row.WriteString(" ")
}
idx, isDot := byPos[[2]int{r, c}]
if !isDot {
row.WriteString(" ")
continue
}
d := gDots[idx]
switch {
case d.accent:
row.WriteString(accent.Render(g.Dot))
case glint >= 0 && idx == glint%len(gDots):
row.WriteString(accent.Render(g.Dot))
case litN >= 0 && ordinal[idx] < litN:
row.WriteString(lit.Render(g.Dot))
case litN < 0 && marqueeHit(ordinal[idx], tick, marqueeWindow):
row.WriteString(lit.Render(g.Dot))
default:
row.WriteString(dim.Render(g.DotDim))
}
}
lines[r] = row.String()
}
return lines
}
// marqueeHit reports whether the dot with the given non-accent ordinal falls
// inside the lit window at this tick.
func marqueeHit(ord, tick, window int) bool {
n := litDotCount()
if ord < 0 || n == 0 {
return false
}
start := tick % n
for i := 0; i < window; i++ {
if (start+i)%n == ord {
return true
}
}
return false
}
// MeshLogo renders one static frame of the gortex mark, fully lit, styled
// with the package palette. A positive tick adds a traveling green glint —
// animated banners (the wizard dashboard) pass a rising tick; static banners
// pass 0 for the pure mark.
func MeshLogo(tick int) string {
glint := -1
if tick > 0 {
glint = tick
}
lines := logoLines(stylePerim, styleInner, styleAccent, activeGlyphs(), 1, tick, glint)
return strings.Join(lines[:], "\n")
}
// MeshFrame returns the gortex mark with label (bold) and sub (dim) beside
// it. Used by watch loops or custom views that want the brand block without
// owning a live tracker.
func MeshFrame(tick int, label, sub string) string {
mesh := MeshLogo(tick)
if label == "" && sub == "" {
return mesh + "\n"
}
right := lipgloss.JoinVertical(
lipgloss.Left,
"",
styleLabel.Render(label),
"",
styleSub.Render(sub),
"",
)
return lipgloss.JoinHorizontal(lipgloss.Top, mesh, " ", right) + "\n"
}
// MeshLogoLines returns the number of vertical rows the mark occupies.
// Exported so wizard / dashboard layouts can reserve space without re-counting
// the constant.
func MeshLogoLines() int { return 5 }
// MeshLogoWidth returns the rendered visual width of the mark in cells.
// 5 cells × "● " spacing = 9 chars.
func MeshLogoWidth() int { return 9 }