Show recent errors as labelled chips and rebuild the reset button to match
This commit is contained in:
@@ -3,6 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
@@ -449,6 +450,31 @@ func (fb *framebuffer) rect(x0, y0, w, h int, c rgb) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Distance to the rectangle the corner radius sweeps around, which is zero
|
||||||
|
// across the whole flat middle and grows only near a corner. Taking coverage
|
||||||
|
// from that rather than from a plain inside test keeps the curves smooth
|
||||||
|
// instead of stepped.
|
||||||
|
func (fb *framebuffer) roundRect(x0, y0, w, h, r int, c rgb) {
|
||||||
|
// A radius past half the shorter side has no meaning and would put the
|
||||||
|
// swept rectangle inside out, which matters while something is growing from
|
||||||
|
// nothing.
|
||||||
|
r = min(r, min(w, h)/2)
|
||||||
|
ix0, iy0 := float64(x0+r), float64(y0+r)
|
||||||
|
ix1, iy1 := float64(x0+w-1-r), float64(y0+h-1-r)
|
||||||
|
for y := y0; y < y0+h; y++ {
|
||||||
|
for x := x0; x < x0+w; x++ {
|
||||||
|
fx, fy := float64(x), float64(y)
|
||||||
|
dx := math.Max(math.Max(ix0-fx, fx-ix1), 0)
|
||||||
|
dy := math.Max(math.Max(iy0-fy, fy-iy1), 0)
|
||||||
|
cov := float64(r) - math.Sqrt(dx*dx+dy*dy) + 0.5
|
||||||
|
if cov <= 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fb.blend(x, y, c, uint8(math.Min(cov, 1)*255))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Blends src over the existing pixel, with cov as 0-255 coverage.
|
// Blends src over the existing pixel, with cov as 0-255 coverage.
|
||||||
func (fb *framebuffer) blend(x, y int, c rgb, cov uint8) {
|
func (fb *framebuffer) blend(x, y int, c rgb, cov uint8) {
|
||||||
if x < 0 || y < 0 || x >= fb.w || y >= fb.h || cov == 0 {
|
if x < 0 || y < 0 || x >= fb.w || y >= fb.h || cov == 0 {
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ var (
|
|||||||
uiOKEdge = rgb{0x3c, 0xe0, 0x70}
|
uiOKEdge = rgb{0x3c, 0xe0, 0x70}
|
||||||
uiErrFil = rgb{0x54, 0x18, 0x1c}
|
uiErrFil = rgb{0x54, 0x18, 0x1c}
|
||||||
uiErrEdg = rgb{0xff, 0x46, 0x46}
|
uiErrEdg = rgb{0xff, 0x46, 0x46}
|
||||||
uiButton = rgb{0x25, 0x2b, 0x33}
|
|
||||||
uiFg = rgb{0xe6, 0xe8, 0xea}
|
uiFg = rgb{0xe6, 0xe8, 0xea}
|
||||||
uiDim = rgb{0x9a, 0xa2, 0xac}
|
uiDim = rgb{0x9a, 0xa2, 0xac}
|
||||||
uiCyan = rgb{0x5c, 0xc8, 0xe0}
|
uiCyan = rgb{0x5c, 0xc8, 0xe0}
|
||||||
@@ -30,7 +29,15 @@ const (
|
|||||||
|
|
||||||
btnW = 240
|
btnW = 240
|
||||||
btnH = 64
|
btnH = 64
|
||||||
|
btnRadius = 10
|
||||||
|
btnBorder = 2
|
||||||
holdDuration = time.Second
|
holdDuration = time.Second
|
||||||
|
|
||||||
|
chipCols = 2
|
||||||
|
chipPadY = 9
|
||||||
|
chipGap = 8
|
||||||
|
chipRadius = 8
|
||||||
|
chipBorder = 2
|
||||||
)
|
)
|
||||||
|
|
||||||
type rect struct {
|
type rect struct {
|
||||||
@@ -88,7 +95,7 @@ func newDisplay() (*display, error) {
|
|||||||
// the loaded faces actually measure.
|
// the loaded faces actually measure.
|
||||||
gridLine := d.grid.cellH + rowGap
|
gridLine := d.grid.cellH + rowGap
|
||||||
errH := len(errRows) * gridLine
|
errH := len(errRows) * gridLine
|
||||||
d.nowH = d.huge.cellH + rowGap + gridLine + blockGap + errH
|
d.nowH = d.huge.cellH + rowGap + gridLine + blockGap + d.chipsH()
|
||||||
d.sinceH = 4*gridLine + blockGap + errH + blockGap + btnH
|
d.sinceH = 4*gridLine + blockGap + errH + blockGap + btnH
|
||||||
|
|
||||||
inner := fb.w - 2*uiMargin
|
inner := fb.w - 2*uiMargin
|
||||||
@@ -129,21 +136,23 @@ func (d *display) holdReset(x, y int, down bool, now time.Time) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Built like the error chips, since it sits among them: a coloured outline
|
||||||
|
// around a dark well. Cyan rather than the status colours because it is
|
||||||
|
// something to press, not something being reported.
|
||||||
func (d *display) drawResetButton() {
|
func (d *display) drawResetButton() {
|
||||||
r := d.resetBtn
|
r := d.resetBtn
|
||||||
d.fb.rect(r.x, r.y, r.w, r.h, uiButton)
|
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)
|
||||||
|
|
||||||
|
// 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
|
||||||
if d.holdFrac > 0 {
|
if d.holdFrac > 0 {
|
||||||
w := int(float64(r.w) * math.Min(d.holdFrac, 1))
|
w := int(float64(r.w-2*btnBorder) * math.Min(d.holdFrac, 1))
|
||||||
d.fb.rect(r.x, r.y, w, r.h, uiCyan)
|
d.fb.roundRect(r.x+btnBorder, r.y+btnBorder, w, r.h-2*btnBorder,
|
||||||
}
|
btnRadius-btnBorder, uiCyan)
|
||||||
for _, e := range []rect{
|
split += w
|
||||||
{r.x, r.y, r.w, 2},
|
|
||||||
{r.x, r.y + r.h - 2, r.w, 2},
|
|
||||||
{r.x, r.y, 2, r.h},
|
|
||||||
{r.x + r.w - 2, r.y, 2, r.h},
|
|
||||||
} {
|
|
||||||
d.fb.rect(e.x, e.y, e.w, e.h, uiCyan)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
label := "RESET"
|
label := "RESET"
|
||||||
@@ -151,10 +160,9 @@ func (d *display) drawResetButton() {
|
|||||||
ly := r.y + (r.h-d.gridB.cellH)/2
|
ly := r.y + (r.h-d.gridB.cellH)/2
|
||||||
// The label straddles the fill, so each glyph takes the colour that reads
|
// The label straddles the fill, so each glyph takes the colour that reads
|
||||||
// against whatever is behind it.
|
// against whatever is behind it.
|
||||||
split := r.x + int(float64(r.w)*math.Min(d.holdFrac, 1))
|
|
||||||
for i, c := range label {
|
for i, c := range label {
|
||||||
gx := lx + i*d.gridB.cellW
|
gx := lx + i*d.gridB.cellW
|
||||||
col := uiFg
|
col := uiCyan
|
||||||
if gx+d.gridB.cellW/2 < split {
|
if gx+d.gridB.cellW/2 < split {
|
||||||
col = uiBg
|
col = uiBg
|
||||||
}
|
}
|
||||||
@@ -203,6 +211,41 @@ func (d *display) errBlock(x, w, y int, e errs) int {
|
|||||||
return y
|
return y
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *display) chipH() int { return d.grid.cellH + 2*chipPadY }
|
||||||
|
|
||||||
|
func (d *display) chipsH() int {
|
||||||
|
rows := (len(errRows) + chipCols - 1) / chipCols
|
||||||
|
return rows*(d.chipH()+chipGap) - chipGap
|
||||||
|
}
|
||||||
|
|
||||||
|
// The same kinds as errBlock, but answering whether rather than how many, and
|
||||||
|
// carrying their own labels so nothing has to be matched up across a row. Over
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
return y + d.chipsH()
|
||||||
|
}
|
||||||
|
|
||||||
func (d *display) panel(p rect, e errs, contentH int) (int, int, int) {
|
func (d *display) panel(p rect, e errs, contentH int) (int, int, int) {
|
||||||
fill, edge := uiOKFill, uiOKEdge
|
fill, edge := uiOKFill, uiOKEdge
|
||||||
if e.total() > 0 {
|
if e.total() > 0 {
|
||||||
@@ -257,7 +300,7 @@ func (d *display) render(v view, elapsed time.Duration, target float64, cable st
|
|||||||
x, w, y := d.panel(d.nowPanel, v.window, d.nowH)
|
x, w, y := d.panel(d.nowPanel, v.window, d.nowH)
|
||||||
y = d.rateRow(x, w, y, "RX", v.rxGbps, target)
|
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)
|
y = d.row(d.grid, x, w, y, "packets/s", commas(roundPPS(v.rxPPS)), uiFg)
|
||||||
d.errBlock(x, w, y+blockGap, v.window)
|
d.errChips(x, w, y+blockGap, v.window)
|
||||||
|
|
||||||
x, w, y = d.panel(d.sincePanel, v.since, d.sinceH)
|
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, "uptime", uptime(elapsed), uiFg)
|
||||||
|
|||||||
Reference in New Issue
Block a user