|
|
|
@@ -17,27 +17,47 @@ var (
|
|
|
|
|
uiCyan = rgb{0x5c, 0xc8, 0xe0}
|
|
|
|
|
uiGreen = rgb{0x6c, 0xdc, 0x86}
|
|
|
|
|
uiRed = rgb{0xf0, 0x6b, 0x6b}
|
|
|
|
|
uiYellow = rgb{0xe0, 0xb0, 0x40}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Every gap is a multiple of one step, so the spacing carries meaning: things
|
|
|
|
|
// a step apart belong together, things eight steps apart do not. Picking each
|
|
|
|
|
// number for itself is what produced a panel where a label could have gone with
|
|
|
|
|
// either the figure above it or the one below.
|
|
|
|
|
//
|
|
|
|
|
// These are distances actually seen, since layout measures a line of text from
|
|
|
|
|
// the top of a digit to the baseline rather than across a cell with accent and
|
|
|
|
|
// descender slack in it. Values that looked right when that slack was padding
|
|
|
|
|
// them out are too small once it is gone.
|
|
|
|
|
const (
|
|
|
|
|
step = 4
|
|
|
|
|
|
|
|
|
|
spaceTight = step * 2 // neighbouring chips
|
|
|
|
|
spaceGroup = step * 4 // a figure and its label, chip padding, block to block
|
|
|
|
|
spaceRow = step * 8 // one labelled pair and the next
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
uiMargin = 16
|
|
|
|
|
uiPad = 14
|
|
|
|
|
uiBorder = 12
|
|
|
|
|
rowGap = 5
|
|
|
|
|
blockGap = 16
|
|
|
|
|
uiMargin = spaceGroup
|
|
|
|
|
uiPad = spaceGroup
|
|
|
|
|
uiBorder = step * 3
|
|
|
|
|
pairGap = spaceGroup
|
|
|
|
|
blockGap = spaceGroup
|
|
|
|
|
|
|
|
|
|
btnW = 240
|
|
|
|
|
btnH = 64
|
|
|
|
|
btnRadius = 10
|
|
|
|
|
btnBorder = 2
|
|
|
|
|
btnW = 300
|
|
|
|
|
btnH = 80
|
|
|
|
|
holdDuration = time.Second
|
|
|
|
|
|
|
|
|
|
chipCols = 2
|
|
|
|
|
chipPadY = 9
|
|
|
|
|
chipGap = 8
|
|
|
|
|
chipRadius = 8
|
|
|
|
|
// Shared by the chips and the button, which are the same object drawn at
|
|
|
|
|
// different sizes.
|
|
|
|
|
chipRadius = spaceTight
|
|
|
|
|
chipBorder = 2
|
|
|
|
|
|
|
|
|
|
// One grid for the panel: the figures and the chips beneath them stand in
|
|
|
|
|
// the same columns because they are placed by the same arithmetic.
|
|
|
|
|
gridCols = 2
|
|
|
|
|
chipPadY = spaceGroup
|
|
|
|
|
chipGap = spaceTight
|
|
|
|
|
statRowGap = spaceRow
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type rect struct {
|
|
|
|
@@ -50,14 +70,14 @@ func (r rect) contains(x, y int) bool {
|
|
|
|
|
|
|
|
|
|
type display struct {
|
|
|
|
|
fb *framebuffer
|
|
|
|
|
huge *textFace
|
|
|
|
|
big *textFace
|
|
|
|
|
grid *textFace
|
|
|
|
|
gridB *textFace
|
|
|
|
|
|
|
|
|
|
nowPanel rect
|
|
|
|
|
sincePanel rect
|
|
|
|
|
nowH int
|
|
|
|
|
sinceH int
|
|
|
|
|
nowYs []int
|
|
|
|
|
sinceYs []int
|
|
|
|
|
resetBtn rect
|
|
|
|
|
holdStart time.Time
|
|
|
|
|
holdFrac float64
|
|
|
|
@@ -75,9 +95,9 @@ func newDisplay() (*display, error) {
|
|
|
|
|
bold bool
|
|
|
|
|
size float64
|
|
|
|
|
}{
|
|
|
|
|
{&d.huge, true, 60},
|
|
|
|
|
{&d.grid, false, 22},
|
|
|
|
|
{&d.gridB, true, 22},
|
|
|
|
|
{&d.big, true, 40},
|
|
|
|
|
{&d.grid, false, 34},
|
|
|
|
|
{&d.gridB, true, 34},
|
|
|
|
|
} {
|
|
|
|
|
face, err := loadFace(spec.bold, spec.size)
|
|
|
|
|
if err != nil {
|
|
|
|
@@ -91,28 +111,63 @@ func newDisplay() (*display, error) {
|
|
|
|
|
return nil, fmt.Errorf("grid faces disagree on cell width: %d vs %d",
|
|
|
|
|
d.grid.cellW, d.gridB.cellW)
|
|
|
|
|
}
|
|
|
|
|
// Guessed heights collide on a screen this small, so the split follows what
|
|
|
|
|
// Guessed heights collide on a screen this small, so the layout follows what
|
|
|
|
|
// the loaded faces actually measure.
|
|
|
|
|
gridLine := d.grid.cellH + rowGap
|
|
|
|
|
errH := len(errRows) * gridLine
|
|
|
|
|
d.nowH = d.huge.cellH + rowGap + gridLine + blockGap + d.chipsH()
|
|
|
|
|
d.sinceH = 4*gridLine + blockGap + errH + blockGap + btnH
|
|
|
|
|
now := []int{d.statsH(d.big, 2), d.chipsH()}
|
|
|
|
|
since := []int{d.statsH(d.gridB, 4), d.countsH(), btnH}
|
|
|
|
|
|
|
|
|
|
// One gap for the whole screen rather than one per panel: whatever is left
|
|
|
|
|
// after the blocks is divided between every gap in both of them, so the
|
|
|
|
|
// space above the first figure, between each block, and below the last is
|
|
|
|
|
// the same distance everywhere. Each panel is then sized to exactly the
|
|
|
|
|
// blocks it holds plus its share, which is also what puts the button in the
|
|
|
|
|
// flow instead of pinned to the bottom with the remainder above it.
|
|
|
|
|
// Vertically the frame is the border and nothing else: the gap is the only
|
|
|
|
|
// whitespace there is. Insetting by uiPad as well would add it to the gaps
|
|
|
|
|
// at the top and bottom of a panel but not to the ones between blocks,
|
|
|
|
|
// which is not equal spacing however evenly the remainder is divided.
|
|
|
|
|
gaps := len(now) + len(since) + 2
|
|
|
|
|
spare := fb.h - 2*uiMargin - blockGap - 4*uiBorder - sum(now) - sum(since)
|
|
|
|
|
if spare < 0 {
|
|
|
|
|
fb.close()
|
|
|
|
|
return nil, fmt.Errorf("panel content is %dpx taller than the screen", -spare)
|
|
|
|
|
}
|
|
|
|
|
gap := spare / gaps
|
|
|
|
|
|
|
|
|
|
inner := fb.w - 2*uiMargin
|
|
|
|
|
chrome := 2 * (uiBorder + uiPad)
|
|
|
|
|
avail := fb.h - 2*uiMargin - blockGap
|
|
|
|
|
h1 := (avail-2*chrome)*d.nowH/(d.nowH+d.sinceH) + chrome
|
|
|
|
|
d.nowPanel = rect{uiMargin, uiMargin, inner, h1}
|
|
|
|
|
d.sincePanel = rect{uiMargin, uiMargin + h1 + blockGap, inner, avail - h1}
|
|
|
|
|
nowH := 2*uiBorder + sum(now) + (len(now)+1)*gap
|
|
|
|
|
sinceH := 2*uiBorder + sum(since) + (len(since)+1)*gap
|
|
|
|
|
d.nowPanel = rect{uiMargin, uiMargin, inner, nowH}
|
|
|
|
|
d.sincePanel = rect{uiMargin, uiMargin + nowH + blockGap, inner, sinceH}
|
|
|
|
|
|
|
|
|
|
d.nowYs = stack(d.nowPanel.y+uiBorder+gap, now, gap)
|
|
|
|
|
d.sinceYs = stack(d.sincePanel.y+uiBorder+gap, since, gap)
|
|
|
|
|
d.resetBtn = rect{
|
|
|
|
|
x: d.sincePanel.x + (inner-btnW)/2,
|
|
|
|
|
y: d.sincePanel.y + d.sincePanel.h - uiBorder - uiPad - btnH,
|
|
|
|
|
y: d.sinceYs[len(d.sinceYs)-1],
|
|
|
|
|
w: btnW,
|
|
|
|
|
h: btnH,
|
|
|
|
|
}
|
|
|
|
|
return d, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func sum(hs []int) int {
|
|
|
|
|
var t int
|
|
|
|
|
for _, h := range hs {
|
|
|
|
|
t += h
|
|
|
|
|
}
|
|
|
|
|
return t
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func stack(y int, hs []int, gap int) []int {
|
|
|
|
|
ys := make([]int, len(hs))
|
|
|
|
|
for i, h := range hs {
|
|
|
|
|
ys[i] = y
|
|
|
|
|
y += h + gap
|
|
|
|
|
}
|
|
|
|
|
return ys
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Tracks a press and hold on the reset button, returning true once it has been
|
|
|
|
|
// held long enough. Lifting or sliding off cancels, and the press has to be
|
|
|
|
|
// released before it can arm again.
|
|
|
|
@@ -141,23 +196,23 @@ func (d *display) holdReset(x, y int, down bool, now time.Time) bool {
|
|
|
|
|
// something to press, not something being reported.
|
|
|
|
|
func (d *display) drawResetButton() {
|
|
|
|
|
r := d.resetBtn
|
|
|
|
|
d.fb.roundRect(r.x, r.y, r.w, r.h, btnRadius, uiCyan)
|
|
|
|
|
d.fb.roundRect(r.x+btnBorder, r.y+btnBorder, r.w-2*btnBorder, r.h-2*btnBorder,
|
|
|
|
|
btnRadius-btnBorder, uiBg)
|
|
|
|
|
d.fb.roundRect(r.x, r.y, r.w, r.h, chipRadius, uiCyan)
|
|
|
|
|
d.fb.roundRect(r.x+chipBorder, r.y+chipBorder, r.w-2*chipBorder, r.h-2*chipBorder,
|
|
|
|
|
chipRadius-chipBorder, uiBg)
|
|
|
|
|
|
|
|
|
|
// The hold fills the well rather than the whole button, so the outline stays
|
|
|
|
|
// put and it reads as the button filling up.
|
|
|
|
|
split := r.x + btnBorder
|
|
|
|
|
split := r.x + chipBorder
|
|
|
|
|
if d.holdFrac > 0 {
|
|
|
|
|
w := int(float64(r.w-2*btnBorder) * math.Min(d.holdFrac, 1))
|
|
|
|
|
d.fb.roundRect(r.x+btnBorder, r.y+btnBorder, w, r.h-2*btnBorder,
|
|
|
|
|
btnRadius-btnBorder, uiCyan)
|
|
|
|
|
w := int(float64(r.w-2*chipBorder) * math.Min(d.holdFrac, 1))
|
|
|
|
|
d.fb.roundRect(r.x+chipBorder, r.y+chipBorder, w, r.h-2*chipBorder,
|
|
|
|
|
chipRadius-chipBorder, uiCyan)
|
|
|
|
|
split += w
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
label := "RESET"
|
|
|
|
|
lx := r.x + (r.w-len(label)*d.gridB.cellW)/2
|
|
|
|
|
ly := r.y + (r.h-d.gridB.cellH)/2
|
|
|
|
|
ly := r.y + (r.h-d.gridB.lineH)/2 - d.gridB.capTop
|
|
|
|
|
// The label straddles the fill, so each glyph takes the colour that reads
|
|
|
|
|
// against whatever is behind it.
|
|
|
|
|
for i, c := range label {
|
|
|
|
@@ -174,20 +229,54 @@ func (d *display) close() {
|
|
|
|
|
d.fb.close()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (d *display) right(f *textFace, xEnd, y int, s string, col rgb) {
|
|
|
|
|
f.draw(d.fb, xEnd-len([]rune(s))*f.cellW, y, s, col)
|
|
|
|
|
// y is the top of the line as read, so text and a bordered box placed the same
|
|
|
|
|
// distance apart are the same distance apart to look at.
|
|
|
|
|
func (d *display) centerIn(f *textFace, x, w, y int, s string, col rgb) int {
|
|
|
|
|
f.draw(d.fb, x+(w-len([]rune(s))*f.cellW)/2, y-f.capTop, s, col)
|
|
|
|
|
return y + f.lineH + pairGap
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (d *display) row(f *textFace, x, w, y int, label, value string, col rgb) int {
|
|
|
|
|
f.draw(d.fb, x, y, label, uiDim)
|
|
|
|
|
d.right(f, x+w, y, value, col)
|
|
|
|
|
return y + f.cellH + rowGap
|
|
|
|
|
type statCell struct {
|
|
|
|
|
value string
|
|
|
|
|
label string
|
|
|
|
|
col rgb
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (d *display) rateRow(x, w, y int, label string, gb, target float64) int {
|
|
|
|
|
d.gridB.draw(d.fb, x, y+(d.huge.cellH-d.gridB.cellH)/2, label, uiDim)
|
|
|
|
|
d.right(d.huge, x+w, y, fmt.Sprintf("%.2f Gb/s", gb), rateColor(gb, target))
|
|
|
|
|
return y + d.huge.cellH + rowGap
|
|
|
|
|
func (d *display) statPairH(vf *textFace) int {
|
|
|
|
|
return vf.lineH + pairGap + d.grid.lineH
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func gridRows(n int) int { return (n + gridCols - 1) / gridCols }
|
|
|
|
|
|
|
|
|
|
func (d *display) statsH(vf *textFace, n int) int {
|
|
|
|
|
return gridRows(n)*(d.statPairH(vf)+statRowGap) - statRowGap
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Where cell i of n falls in the panel's grid. A last row that does not fill
|
|
|
|
|
// the grid is centred, so the odd one out balances the rows above rather than
|
|
|
|
|
// hanging off the left of them.
|
|
|
|
|
func gridCell(i, n, x, w int) (cx, cw int) {
|
|
|
|
|
cw = (w - (gridCols-1)*chipGap) / gridCols
|
|
|
|
|
inRow := min(n-(i/gridCols)*gridCols, gridCols)
|
|
|
|
|
cx = x + (w-(inRow*cw+(inRow-1)*chipGap))/2 + (i%gridCols)*(cw+chipGap)
|
|
|
|
|
return cx, cw
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// A figure with its label directly underneath, two to a row. The gap between
|
|
|
|
|
// rows is wider than the one inside a pair, so which label belongs to which
|
|
|
|
|
// figure is a matter of spacing rather than of guessing. An empty value takes
|
|
|
|
|
// its space without drawing, so nothing below moves when it arrives.
|
|
|
|
|
func (d *display) stats(vf *textFace, x, w, y int, cells []statCell) int {
|
|
|
|
|
for i, c := range cells {
|
|
|
|
|
if c.value == "" {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
cx, cw := gridCell(i, len(cells), x, w)
|
|
|
|
|
cy := y + (i/gridCols)*(d.statPairH(vf)+statRowGap)
|
|
|
|
|
ly := d.centerIn(vf, cx, cw, cy, c.value, c.col)
|
|
|
|
|
d.centerIn(d.grid, cx, cw, ly, c.label, uiDim)
|
|
|
|
|
}
|
|
|
|
|
return y + d.statsH(vf, len(cells))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var errRows = []struct {
|
|
|
|
@@ -200,19 +289,31 @@ var errRows = []struct {
|
|
|
|
|
{"internal", func(e errs) uint64 { return e.internal }},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (d *display) errBlock(x, w, y int, e errs) int {
|
|
|
|
|
for _, r := range errRows {
|
|
|
|
|
n := r.get(e)
|
|
|
|
|
y = d.row(d.grid, x, w, y, r.label, commas(n), errColor(n))
|
|
|
|
|
}
|
|
|
|
|
return y
|
|
|
|
|
}
|
|
|
|
|
func (d *display) chipH() int { return d.grid.lineH + 2*chipPadY }
|
|
|
|
|
|
|
|
|
|
func (d *display) chipH() int { return d.grid.cellH + 2*chipPadY }
|
|
|
|
|
// Taller by a line, since these carry the count under the kind.
|
|
|
|
|
func (d *display) countChipH() int { return d.chipH() + d.gridB.lineH + pairGap }
|
|
|
|
|
|
|
|
|
|
func (d *display) chipsH() int {
|
|
|
|
|
rows := (len(errRows) + chipCols - 1) / chipCols
|
|
|
|
|
return rows*(d.chipH()+chipGap) - chipGap
|
|
|
|
|
return gridRows(len(errRows))*(d.chipH()+chipGap) - chipGap
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (d *display) countsH() int {
|
|
|
|
|
return gridRows(len(errRows))*(d.countChipH()+chipGap) - chipGap
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Shared by both panels so they are demonstrably the same object, one carrying
|
|
|
|
|
// a count and one not.
|
|
|
|
|
func (d *display) chipAt(i, x, w, y, h int, c rgb) (int, int, int) {
|
|
|
|
|
cx, cw := gridCell(i, len(errRows), x, w)
|
|
|
|
|
cy := y + (i/gridCols)*(h+chipGap)
|
|
|
|
|
|
|
|
|
|
// Outlined by drawing the border colour and then sinking a smaller well of
|
|
|
|
|
// background into it, so both curves get the same antialiasing.
|
|
|
|
|
d.fb.roundRect(cx, cy, cw, h, chipRadius, c)
|
|
|
|
|
d.fb.roundRect(cx+chipBorder, cy+chipBorder,
|
|
|
|
|
cw-2*chipBorder, h-2*chipBorder, chipRadius-chipBorder, uiBg)
|
|
|
|
|
return cx, cw, cy
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The same kinds as errBlock, but answering whether rather than how many, and
|
|
|
|
@@ -220,30 +321,28 @@ func (d *display) chipsH() int {
|
|
|
|
|
// a window this short a count is a number nobody can read before it changes;
|
|
|
|
|
// the only thing worth knowing at a glance is which kinds are happening now.
|
|
|
|
|
func (d *display) errChips(x, w, y int, e errs) int {
|
|
|
|
|
ch := d.chipH()
|
|
|
|
|
cw := (w - (chipCols-1)*chipGap) / chipCols
|
|
|
|
|
for i, r := range errRows {
|
|
|
|
|
col, row := i%chipCols, i/chipCols
|
|
|
|
|
// A last row with nothing to sit beside is centred, so the odd one out
|
|
|
|
|
// balances the rows above rather than hanging off the left of them.
|
|
|
|
|
n := min(len(errRows)-row*chipCols, chipCols)
|
|
|
|
|
cx := x + (w-(n*cw+(n-1)*chipGap))/2 + col*(cw+chipGap)
|
|
|
|
|
cy := y + row*(ch+chipGap)
|
|
|
|
|
|
|
|
|
|
// Outlined by drawing the border colour and then sinking a smaller well
|
|
|
|
|
// of background into it, so both curves get the same antialiasing.
|
|
|
|
|
c := errColor(r.get(e))
|
|
|
|
|
d.fb.roundRect(cx, cy, cw, ch, chipRadius, c)
|
|
|
|
|
d.fb.roundRect(cx+chipBorder, cy+chipBorder,
|
|
|
|
|
cw-2*chipBorder, ch-2*chipBorder, chipRadius-chipBorder, uiBg)
|
|
|
|
|
|
|
|
|
|
tx := cx + (cw-len([]rune(r.label))*d.grid.cellW)/2
|
|
|
|
|
d.grid.draw(d.fb, tx, cy+chipPadY, r.label, c)
|
|
|
|
|
cx, cw, cy := d.chipAt(i, x, w, y, d.chipH(), c)
|
|
|
|
|
d.centerIn(d.grid, cx, cw, cy+chipPadY, r.label, c)
|
|
|
|
|
}
|
|
|
|
|
return y + d.chipsH()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (d *display) panel(p rect, e errs, contentH int) (int, int, int) {
|
|
|
|
|
func (d *display) errCounts(x, w, y int, e errs) int {
|
|
|
|
|
for i, r := range errRows {
|
|
|
|
|
n := r.get(e)
|
|
|
|
|
c := errColor(n)
|
|
|
|
|
cx, cw, cy := d.chipAt(i, x, w, y, d.countChipH(), c)
|
|
|
|
|
ty := d.centerIn(d.gridB, cx, cw, cy+chipPadY, commas(n), c)
|
|
|
|
|
d.centerIn(d.grid, cx, cw, ty, r.label, c)
|
|
|
|
|
}
|
|
|
|
|
return y + d.countsH()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Draws the frame and hands back the writable width inside it. Where the blocks
|
|
|
|
|
// sit within it was settled once at startup, since it never changes.
|
|
|
|
|
func (d *display) panel(p rect, e errs) (int, int) {
|
|
|
|
|
fill, edge := uiOKFill, uiOKEdge
|
|
|
|
|
if e.total() > 0 {
|
|
|
|
|
fill, edge = uiErrFil, uiErrEdg
|
|
|
|
@@ -253,23 +352,7 @@ func (d *display) panel(p rect, e errs, contentH int) (int, int, int) {
|
|
|
|
|
p.w-2*uiBorder, p.h-2*uiBorder, fill)
|
|
|
|
|
|
|
|
|
|
inset := uiBorder + uiPad
|
|
|
|
|
x, w := p.x+inset, p.w-2*inset
|
|
|
|
|
y := p.y + inset
|
|
|
|
|
if slack := (p.y + p.h - inset) - y - contentH; slack > 0 {
|
|
|
|
|
y += slack / 2
|
|
|
|
|
}
|
|
|
|
|
return x, w, y
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Rendered rates are quantised so the text only changes when the value moves
|
|
|
|
|
// meaningfully. Without this the low digits churn every frame no matter how
|
|
|
|
|
// long the averaging window is.
|
|
|
|
|
func roundPPS(v float64) uint64 {
|
|
|
|
|
const unit = 1000
|
|
|
|
|
if v < 0 {
|
|
|
|
|
return 0
|
|
|
|
|
}
|
|
|
|
|
return uint64((v+unit/2)/unit) * unit
|
|
|
|
|
return p.x + inset, p.w - 2*inset
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func errColor(n uint64) rgb {
|
|
|
|
@@ -279,32 +362,25 @@ func errColor(n uint64) rgb {
|
|
|
|
|
return uiRed
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func rateColor(gb, target float64) rgb {
|
|
|
|
|
switch {
|
|
|
|
|
case gb >= target*rateGreenFrac:
|
|
|
|
|
return uiGreen
|
|
|
|
|
case gb >= target*rateYellowFrac:
|
|
|
|
|
return uiYellow
|
|
|
|
|
default:
|
|
|
|
|
return uiRed
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (d *display) render(v view, elapsed time.Duration, target float64, cable string) error {
|
|
|
|
|
func (d *display) render(v view, elapsed time.Duration, cable string) error {
|
|
|
|
|
fb := d.fb
|
|
|
|
|
fb.fill(uiBg)
|
|
|
|
|
|
|
|
|
|
x, w, y := d.panel(d.nowPanel, v.window, d.nowH)
|
|
|
|
|
y = d.rateRow(x, w, y, "RX", v.rxGbps, target)
|
|
|
|
|
y = d.row(d.grid, x, w, y, "packets/s", commas(roundPPS(v.rxPPS)), uiFg)
|
|
|
|
|
d.errChips(x, w, y+blockGap, v.window)
|
|
|
|
|
x, w := d.panel(d.nowPanel, v.window)
|
|
|
|
|
d.stats(d.big, x, w, d.nowYs[0], []statCell{
|
|
|
|
|
{scaleSI(v.rxGbps * 1e9), "bits/s", uiFg},
|
|
|
|
|
{scaleSI(v.rxPPS), "packets/s", uiFg},
|
|
|
|
|
})
|
|
|
|
|
d.errChips(x, w, d.nowYs[1], v.window)
|
|
|
|
|
|
|
|
|
|
x, w, y = d.panel(d.sincePanel, v.since, d.sinceH)
|
|
|
|
|
y = d.row(d.grid, x, w, y, "uptime", uptime(elapsed), uiFg)
|
|
|
|
|
y = d.row(d.grid, x, w, y, "frames", commas(v.rxFrames), uiFg)
|
|
|
|
|
y = d.row(d.grid, x, w, y, "data", humanBytes(v.rxGot), uiFg)
|
|
|
|
|
y = d.row(d.grid, x, w, y, "cable", cable, uiCyan)
|
|
|
|
|
d.errBlock(x, w, y+blockGap, v.since)
|
|
|
|
|
x, w = d.panel(d.sincePanel, v.since)
|
|
|
|
|
d.stats(d.gridB, x, w, d.sinceYs[0], []statCell{
|
|
|
|
|
{scaleTime(elapsed), "elapsed", uiFg},
|
|
|
|
|
{scaleSI(float64(v.rxFrames)), "packets", uiFg},
|
|
|
|
|
{scaleSI(float64(v.rxGot)), "bytes", uiFg},
|
|
|
|
|
{cable, "m", uiFg},
|
|
|
|
|
})
|
|
|
|
|
d.errCounts(x, w, d.sinceYs[1], v.since)
|
|
|
|
|
|
|
|
|
|
d.drawResetButton()
|
|
|
|
|
return fb.flush()
|
|
|
|
|