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 (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math"
|
||||
"path/filepath"
|
||||
"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.
|
||||
func (fb *framebuffer) blend(x, y int, c rgb, cov uint8) {
|
||||
if x < 0 || y < 0 || x >= fb.w || y >= fb.h || cov == 0 {
|
||||
|
||||
@@ -12,7 +12,6 @@ var (
|
||||
uiOKEdge = rgb{0x3c, 0xe0, 0x70}
|
||||
uiErrFil = rgb{0x54, 0x18, 0x1c}
|
||||
uiErrEdg = rgb{0xff, 0x46, 0x46}
|
||||
uiButton = rgb{0x25, 0x2b, 0x33}
|
||||
uiFg = rgb{0xe6, 0xe8, 0xea}
|
||||
uiDim = rgb{0x9a, 0xa2, 0xac}
|
||||
uiCyan = rgb{0x5c, 0xc8, 0xe0}
|
||||
@@ -30,7 +29,15 @@ const (
|
||||
|
||||
btnW = 240
|
||||
btnH = 64
|
||||
btnRadius = 10
|
||||
btnBorder = 2
|
||||
holdDuration = time.Second
|
||||
|
||||
chipCols = 2
|
||||
chipPadY = 9
|
||||
chipGap = 8
|
||||
chipRadius = 8
|
||||
chipBorder = 2
|
||||
)
|
||||
|
||||
type rect struct {
|
||||
@@ -88,7 +95,7 @@ func newDisplay() (*display, error) {
|
||||
// the loaded faces actually measure.
|
||||
gridLine := d.grid.cellH + rowGap
|
||||
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
|
||||
|
||||
inner := fb.w - 2*uiMargin
|
||||
@@ -129,21 +136,23 @@ func (d *display) holdReset(x, y int, down bool, now time.Time) bool {
|
||||
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() {
|
||||
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 {
|
||||
w := int(float64(r.w) * math.Min(d.holdFrac, 1))
|
||||
d.fb.rect(r.x, r.y, w, r.h, uiCyan)
|
||||
}
|
||||
for _, e := range []rect{
|
||||
{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)
|
||||
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)
|
||||
split += w
|
||||
}
|
||||
|
||||
label := "RESET"
|
||||
@@ -151,10 +160,9 @@ func (d *display) drawResetButton() {
|
||||
ly := r.y + (r.h-d.gridB.cellH)/2
|
||||
// The label straddles the fill, so each glyph takes the colour that reads
|
||||
// against whatever is behind it.
|
||||
split := r.x + int(float64(r.w)*math.Min(d.holdFrac, 1))
|
||||
for i, c := range label {
|
||||
gx := lx + i*d.gridB.cellW
|
||||
col := uiFg
|
||||
col := uiCyan
|
||||
if gx+d.gridB.cellW/2 < split {
|
||||
col = uiBg
|
||||
}
|
||||
@@ -203,6 +211,41 @@ func (d *display) errBlock(x, w, y int, e errs) int {
|
||||
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) {
|
||||
fill, edge := uiOKFill, uiOKEdge
|
||||
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)
|
||||
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.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)
|
||||
y = d.row(d.grid, x, w, y, "uptime", uptime(elapsed), uiFg)
|
||||
|
||||
Reference in New Issue
Block a user