diff options
Diffstat (limited to 'internal/display/display.go')
| -rw-r--r-- | internal/display/display.go | 486 |
1 files changed, 382 insertions, 104 deletions
diff --git a/internal/display/display.go b/internal/display/display.go index 49f9f10..33120dc 100644 --- a/internal/display/display.go +++ b/internal/display/display.go @@ -5,6 +5,8 @@ import ( "fmt" "os" "sort" + "strconv" + "strings" "time" "codeberg.org/snonux/loadbars/internal/collector" @@ -56,10 +58,20 @@ func Run(ctx context.Context, cfg *config.Config, src stats.Source) error { // Previous CPU state for delta (key = host;cpuName) prevCPU := make(map[string]collector.CPULine) - // We need collector.CPULine - use stats.HostStats.CPU which is map[string]collector.CPULine. So we need to import collector for CPULine. - // Actually we have stats.HostStats which has CPU map[string]collector.CPULine. So we need to import collector in display for the type. Let me add the import and a type alias or use the type from collector. So display will import collector for CPULine. - _ = prevCPU + // Smoothed values for transitions (blend toward target each frame) + const smoothFactor = 0.12 // lower = smoother, less flicker from noisy samples + smoothedCPU := make(map[string]*[9]float64) + smoothedMem := make(map[string]*struct{ ramUsed, swapUsed float64 }) + smoothedNet := make(map[string]*struct{ rxPct, txPct float64 }) + prevNet := make(map[string]stats.NetStamp) + netIntIndex := make(map[string]int) // for cycling interface per host + var cycleNetNext bool + var printNetInfoOnce bool = showNet // print chosen interface when net view is on (once at start or after toggling on) + // Peak history for extended mode: per CPU bar key, ring of (system+user) % + peakHistory := make(map[string][]float64) + lastNumBars := -1 + lastWinW, lastWinH := int32(0), int32(0) ticker := time.NewTicker(time.Duration(constants.IntervalSDL * float64(time.Second))) defer ticker.Stop() @@ -92,10 +104,35 @@ func Run(ctx context.Context, cfg *config.Config, src stats.Source) error { case sdl.K_3: showNet = !showNet fmt.Println("==> Toggled show net:", showNet) + if showNet { + printNetInfoOnce = true + } case sdl.K_e: extended = !extended + fmt.Println("==> Toggled extended (peak line):", extended) + case sdl.K_a: + cfg.CPUAverage++ + fmt.Println("==> CPU average samples:", cfg.CPUAverage) + case sdl.K_y: + if cfg.CPUAverage > 1 { + cfg.CPUAverage-- + } + fmt.Println("==> CPU average samples:", cfg.CPUAverage) + case sdl.K_d: + cfg.NetAverage++ + fmt.Println("==> Net average samples:", cfg.NetAverage) + case sdl.K_c: + if cfg.NetAverage > 1 { + cfg.NetAverage-- + } + fmt.Println("==> Net average samples:", cfg.NetAverage) case sdl.K_h: printHotkeys() + case sdl.K_n: + cycleNetNext = true + if showNet { + fmt.Println("==> Cycling to next network interface (per host)") + } case sdl.K_w: cfg.ShowCores = showCores cfg.ShowMem = showMem @@ -136,19 +173,30 @@ func Run(ctx context.Context, cfg *config.Config, src stats.Source) error { } snap := src.Snapshot() - // Count total CPU bars we will draw (so width is shared across all bars) + if cycleNetNext { + for _, host := range sortedHosts(snap) { + netIntIndex[host]++ + } + cycleNetNext = false + } + // One-time: print which interface is used for net stats and how to configure + if printNetInfoOnce && showNet { + printNetInfoOnce = false + printNetInterfaceHelp(snap, cfg, netIntIndex) + } + // Count total bars we will draw (only non-nil hosts) so layout matches draw order numBars := 0 for _, host := range sortedHosts(snap) { if h := snap[host]; h != nil { numBars += len(sortedCPUNames(h.CPU, showCores)) + if showMem { + numBars++ + } + if showNet { + numBars++ + } } } - if showMem { - numBars += len(snap) - } - if showNet { - numBars += len(snap) - } if numBars == 0 { numBars = 1 } @@ -158,9 +206,13 @@ func Run(ctx context.Context, cfg *config.Config, src stats.Source) error { barWidth = 1 } - // Clear every frame so toggling cores off (or changing bar count) doesn't leave stale bars - renderer.SetDrawColor(0, 0, 0, 255) - renderer.Clear() + // Clear only when layout changes (bar count or window size) to avoid full-screen flicker + if numBars != lastNumBars || winW != lastWinW || winH != lastWinH { + renderer.SetDrawColor(0, 0, 0, 255) + renderer.Clear() + lastNumBars = numBars + lastWinW, lastWinH = winW, winH + } x := int32(0) hosts := sortedHosts(snap) @@ -169,15 +221,63 @@ func Run(ctx context.Context, cfg *config.Config, src stats.Source) error { if h == nil { continue } - // Draw CPU bars for this host (aggregate or per-core) + // Draw CPU bars for this host (aggregate or per-core), with smoothing cpuNames := sortedCPUNames(h.CPU, showCores) for _, name := range cpuNames { - drawCPUBar(renderer, h.CPU[name], prevCPU[host+";"+name], barWidth, &x, winH) - prevCPU[host+";"+name] = h.CPU[name] + key := host + ";" + name + cur := h.CPU[name] + prev := prevCPU[key] + prevCPU[key] = cur + target, ok := cpuBarTargetPcts(cur, prev) + s := smoothedCPU[key] + if s == nil { + s = &[9]float64{} + smoothedCPU[key] = s + if ok { + *s = target + } + } else if ok { + for i := 0; i < 9; i++ { + (*s)[i] += (target[i] - (*s)[i]) * smoothFactor + } + normalizePcts9(s) + } + // Peak line (extended): max of (system+user) over last CPUAverage samples + var peakPct float64 + if extended && s != nil { + userSys := (*s)[0] + (*s)[1] + hist := peakHistory[key] + hist = append(hist, userSys) + n := cfg.CPUAverage + if n < 1 { + n = 1 + } + for len(hist) > n { + hist = hist[1:] + } + peakHistory[key] = hist + for _, v := range hist { + if v > peakPct { + peakPct = v + } + } + } + // Always draw (smoothed or last state) so we never leave a blank bar and cause flicker + drawCPUBarFromPcts(renderer, s, barWidth, &x, winH, extended, peakPct) } - // Draw memory bar(s) for this host when showMem + // Draw memory bar(s) for this host when showMem, with smoothing if showMem { - drawMemBar(renderer, h, barWidth, &x, winH) + if smoothedMem[host] == nil { + smoothedMem[host] = &struct{ ramUsed, swapUsed float64 }{} + } + drawMemBarSmoothed(renderer, h, smoothedMem[host], smoothFactor, barWidth, &x, winH) + } + // Draw network bar(s) for this host when showNet + if showNet { + if smoothedNet[host] == nil { + smoothedNet[host] = &struct{ rxPct, txPct float64 }{} + } + prevNet[host] = drawNetBarSmoothed(renderer, h, cfg, smoothedNet[host], prevNet[host], netIntIndex, host, smoothFactor, barWidth, &x, winH) } } @@ -220,128 +320,306 @@ func sortedCPUNames(cpu map[string]collector.CPULine, showCores bool) []string { return names } -func drawCPUBar(renderer *sdl.Renderer, cur, prev collector.CPULine, barW int32, x *int32, winH int32) { - defer func() { *x += barW + 1 }() - // Compute delta and normalize to % +// cpuBarTargetPcts returns the 9 segment percentages (system, user, nice, idle, iowait, irq, softirq, guest, steal) from cur/prev delta. ok is false if no valid sample. +func cpuBarTargetPcts(cur, prev collector.CPULine) (out [9]float64, ok bool) { totalCur := cur.Total() totalPrev := prev.Total() if totalPrev == 0 || totalCur <= totalPrev { - return + return out, false } scale := float64(totalCur-totalPrev) / 100.0 if scale <= 0 { - return + return out, false } - userPct := int(float64(cur.User-prev.User) / scale) - nicePct := int(float64(cur.Nice-prev.Nice) / scale) - sysPct := int(float64(cur.System-prev.System) / scale) - idlePct := int(float64(cur.Idle-prev.Idle) / scale) - iowaitPct := int(float64(cur.Iowait-prev.Iowait) / scale) - irqPct := int(float64(cur.IRQ-prev.IRQ) / scale) - softirqPct := int(float64(cur.SoftIRQ-prev.SoftIRQ) / scale) - guestPct := int(float64(cur.Guest-prev.Guest) / scale) - stealPct := int(float64(cur.Steal-prev.Steal) / scale) - - norm := func(v int) int { - if v < 0 { - return 0 + out[0] = float64(cur.System-prev.System) / scale + out[1] = float64(cur.User-prev.User) / scale + out[2] = float64(cur.Nice-prev.Nice) / scale + out[3] = float64(cur.Idle-prev.Idle) / scale + out[4] = float64(cur.Iowait-prev.Iowait) / scale + out[5] = float64(cur.IRQ-prev.IRQ) / scale + out[6] = float64(cur.SoftIRQ-prev.SoftIRQ) / scale + out[7] = float64(cur.Guest-prev.Guest) / scale + out[8] = float64(cur.Steal-prev.Steal) / scale + for i := range out { + if out[i] < 0 { + out[i] = 0 } - if v > 100 { - return 100 + if out[i] > 100 { + out[i] = 100 } - return v - } - userPct = norm(userPct) - nicePct = norm(nicePct) - sysPct = norm(sysPct) - idlePct = norm(idlePct) - iowaitPct = norm(iowaitPct) - irqPct = norm(irqPct) - softirqPct = norm(softirqPct) - guestPct = norm(guestPct) - stealPct = norm(stealPct) + } + return out, true +} + +func normalizePcts9(s *[9]float64) { + var sum float64 + for i := 0; i < 9; i++ { + sum += (*s)[i] + } + if sum <= 0 { + return + } + for i := 0; i < 9; i++ { + (*s)[i] = (*s)[i] * 100 / sum + } +} +// drawCPUBarFromPcts draws one CPU bar from 9 smoothed segment percentages. If s is nil, advances x only. +// When extended is true and peakPct > 0, draws a 1px peak line (max system+user over history). +func drawCPUBarFromPcts(renderer *sdl.Renderer, s *[9]float64, barW int32, x *int32, winH int32, extended bool, peakPct float64) { + defer func() { *x += barW + 1 }() + // Clear this slot so we never leave previous (e.g. mem/net) content visible + renderer.SetDrawColor(constants.Black.R, constants.Black.G, constants.Black.B, 255) + renderer.FillRect(&sdl.Rect{X: *x, Y: 0, W: barW, H: winH}) + if s == nil { + return + } barH := float64(winH) / 100.0 y := float64(winH) - fill := func(r, g, b uint8, h int) { - hh := int32(float64(h) * barH) - if hh < 1 && h > 0 { + fill := func(r, g, b uint8, pct float64) { + hh := int32(pct * barH) + if hh < 1 && pct > 0 { hh = 1 } y -= float64(hh) renderer.SetDrawColor(r, g, b, 255) - rect := sdl.Rect{X: *x, Y: int32(y), W: barW, H: hh} - renderer.FillRect(&rect) - } - // Order bottom to top: system, user, nice, idle, iowait, irq, softirq, guest, steal (match Perl) - fill(constants.Blue.R, constants.Blue.G, constants.Blue.B, sysPct) - fill(constants.Yellow.R, constants.Yellow.G, constants.Yellow.B, userPct) - fill(constants.Green.R, constants.Green.G, constants.Green.B, nicePct) - fill(constants.Black.R, constants.Black.G, constants.Black.B, idlePct) - fill(constants.Purple.R, constants.Purple.G, constants.Purple.B, iowaitPct) - fill(constants.White.R, constants.White.G, constants.White.B, irqPct) - fill(constants.White.R, constants.White.G, constants.White.B, softirqPct) - fill(constants.Red.R, constants.Red.G, constants.Red.B, guestPct) - fill(constants.Red.R, constants.Red.G, constants.Red.B, stealPct) + renderer.FillRect(&sdl.Rect{X: *x, Y: int32(y), W: barW, H: hh}) + } + fill(constants.Blue.R, constants.Blue.G, constants.Blue.B, (*s)[0]) // system + fill(constants.Yellow.R, constants.Yellow.G, constants.Yellow.B, (*s)[1]) // user + fill(constants.Green.R, constants.Green.G, constants.Green.B, (*s)[2]) // nice + fill(constants.Black.R, constants.Black.G, constants.Black.B, (*s)[3]) // idle + fill(constants.Purple.R, constants.Purple.G, constants.Purple.B, (*s)[4]) // iowait + fill(constants.White.R, constants.White.G, constants.White.B, (*s)[5]) // irq + fill(constants.White.R, constants.White.G, constants.White.B, (*s)[6]) // softirq + fill(constants.Red.R, constants.Red.G, constants.Red.B, (*s)[7]) // guest + fill(constants.Red.R, constants.Red.G, constants.Red.B, (*s)[8]) // steal + // Extended: 1px peak line at max (system+user) over history + if extended && peakPct > 0 { + peakY := winH - int32(peakPct*barH) + if peakY < 0 { + peakY = 0 + } + if peakY >= winH { + peakY = winH - 1 + } + if peakPct > float64(constants.UserOrangeThreshold) { + renderer.SetDrawColor(constants.Orange.R, constants.Orange.G, constants.Orange.B, 255) + } else if peakPct > float64(constants.UserYellowThreshold) { + renderer.SetDrawColor(constants.Yellow0.R, constants.Yellow0.G, constants.Yellow0.B, 255) + } else { + renderer.SetDrawColor(constants.Yellow.R, constants.Yellow.G, constants.Yellow.B, 255) + } + renderer.FillRect(&sdl.Rect{X: *x, Y: peakY, W: barW, H: 1}) + } } -// drawMemBar draws one memory bar (RAM left half, Swap right half) for a host. -func drawMemBar(renderer *sdl.Renderer, h *stats.HostStats, barW int32, x *int32, winH int32) { +// drawMemBarSmoothed blends mem stats toward target and draws one memory bar (RAM left, Swap right). +func drawMemBarSmoothed(renderer *sdl.Renderer, h *stats.HostStats, smoothed *struct{ ramUsed, swapUsed float64 }, factor float64, barW int32, x *int32, winH int32) { defer func() { *x += barW + 1 }() + // Clear this slot so we never leave previous (e.g. CPU/net) content visible + renderer.SetDrawColor(constants.Black.R, constants.Black.G, constants.Black.B, 255) + renderer.FillRect(&sdl.Rect{X: *x, Y: 0, W: barW, H: winH}) if h.Mem == nil { return } - memTotal := h.Mem["MemTotal"] - memFree := h.Mem["MemFree"] - swapTotal := h.Mem["SwapTotal"] - swapFree := h.Mem["SwapFree"] + var targetRam, targetSwap float64 + if memTotal := h.Mem["MemTotal"]; memTotal > 0 { + targetRam = 100 - 100*float64(h.Mem["MemFree"])/float64(memTotal) + if targetRam < 0 { + targetRam = 0 + } + if targetRam > 100 { + targetRam = 100 + } + } + if swapTotal := h.Mem["SwapTotal"]; swapTotal > 0 { + targetSwap = 100 - 100*float64(h.Mem["SwapFree"])/float64(swapTotal) + if targetSwap < 0 { + targetSwap = 0 + } + if targetSwap > 100 { + targetSwap = 100 + } + } + smoothed.ramUsed += (targetRam - smoothed.ramUsed) * factor + smoothed.swapUsed += (targetSwap - smoothed.swapUsed) * factor halfW := barW / 2 barH := float64(winH) / 100.0 // RAM: used (dark grey) from bottom, free (black) on top - if memTotal > 0 { - ramUsedPct := 100 - int(100*memFree/memTotal) - if ramUsedPct < 0 { - ramUsedPct = 0 + ramUsedH := int32(smoothed.ramUsed * barH) + if ramUsedH > 0 { + renderer.SetDrawColor(constants.DarkGrey.R, constants.DarkGrey.G, constants.DarkGrey.B, 255) + renderer.FillRect(&sdl.Rect{X: *x, Y: winH - ramUsedH, W: halfW, H: ramUsedH}) + } + if ramFreeH := winH - ramUsedH; ramFreeH > 0 { + renderer.SetDrawColor(constants.Black.R, constants.Black.G, constants.Black.B, 255) + renderer.FillRect(&sdl.Rect{X: *x, Y: 0, W: halfW, H: ramFreeH}) + } + + // Swap: used (grey) from bottom, free (black) on top + swapUsedH := int32(smoothed.swapUsed * barH) + if swapUsedH > 0 { + renderer.SetDrawColor(constants.Grey.R, constants.Grey.G, constants.Grey.B, 255) + renderer.FillRect(&sdl.Rect{X: *x + halfW, Y: winH - swapUsedH, W: halfW, H: swapUsedH}) + } + if swapFreeH := winH - swapUsedH; swapFreeH > 0 { + renderer.SetDrawColor(constants.Black.R, constants.Black.G, constants.Black.B, 255) + renderer.FillRect(&sdl.Rect{X: *x + halfW, Y: 0, W: halfW, H: swapFreeH}) + } +} + +func printHotkeys() { + fmt.Println("=> Hotkeys: 1=cores 2=mem 3=net e=extended h=help n=next net q=quit w=write config a/y=cpu avg d/c=net avg f/v=link scale arrows=resize") +} + +// printNetInterfaceHelp prints which interface is used per host and how to set netint (when net view is toggled on). +func printNetInterfaceHelp(snap map[string]*stats.HostStats, cfg *config.Config, netIntIndex map[string]int) { + for _, host := range sortedHosts(snap) { + h := snap[host] + if h == nil || h.Net == nil || len(h.Net) == 0 { + fmt.Printf("Net: %s => (no interfaces yet, wait for data)\n", host) + continue } - if ramUsedPct > 100 { - ramUsedPct = 100 + iface := chooseNetIface(h, cfg, host, netIntIndex) + all := make([]string, 0, len(h.Net)) + for name := range h.Net { + all = append(all, name) } - ramUsedH := int32(float64(ramUsedPct) * barH) - if ramUsedH > 0 { - renderer.SetDrawColor(constants.DarkGrey.R, constants.DarkGrey.G, constants.DarkGrey.B, 255) - renderer.FillRect(&sdl.Rect{X: *x, Y: winH - ramUsedH, W: halfW, H: ramUsedH}) + sort.Strings(all) + if iface == "" { + fmt.Printf("Net: %s => (no non-lo interface; seen: %s)\n", host, strings.Join(all, ", ")) + continue } - ramFreeH := winH - ramUsedH - if ramFreeH > 0 { - renderer.SetDrawColor(constants.Black.R, constants.Black.G, constants.Black.B, 255) - renderer.FillRect(&sdl.Rect{X: *x, Y: 0, W: halfW, H: ramFreeH}) + hint := "set netint=IFACE in ~/.loadbarsrc or --netint IFACE" + if cfg.NetInt != "" { + hint = "using netint=" + cfg.NetInt + " from config" } + fmt.Printf("Net: %s => %s (all: %s). %s\n", host, iface, strings.Join(all, ", "), hint) } + fmt.Println("=> Link speed: netlink=" + cfg.NetLink + " (gbit/mbit/10mbit/100mbit/10gbit or number). Change in ~/.loadbarsrc or --netlink") +} - // Swap: used (grey) from bottom, free (black) on top - if swapTotal > 0 { - swapUsedPct := 100 - int(100*swapFree/swapTotal) - if swapUsedPct < 0 { - swapUsedPct = 0 - } - if swapUsedPct > 100 { - swapUsedPct = 100 - } - swapUsedH := int32(float64(swapUsedPct) * barH) - if swapUsedH > 0 { - renderer.SetDrawColor(constants.Grey.R, constants.Grey.G, constants.Grey.B, 255) - renderer.FillRect(&sdl.Rect{X: *x + halfW, Y: winH - swapUsedH, W: halfW, H: swapUsedH}) +// netLinkBytesPerSec returns link speed in bytes/sec from cfg.NetLink (e.g. "gbit", "10gbit", "100mbit", or numeric mbit). +func netLinkBytesPerSec(cfg *config.Config) int64 { + s := strings.ToLower(strings.TrimSpace(cfg.NetLink)) + switch s { + case "gbit", "1gbit": + return int64(constants.BytesGbit) + case "10gbit": + return int64(constants.Bytes10Gbit) + case "mbit", "1mbit": + return int64(constants.BytesMbit) + case "10mbit": + return int64(constants.Bytes10Mbit) + case "100mbit": + return int64(constants.Bytes100Mbit) + case "": + return int64(constants.BytesGbit) + } + if n, err := strconv.ParseInt(s, 10, 64); err == nil { + return n * int64(constants.BytesMbit) + } + return int64(constants.BytesGbit) +} + +// chooseNetIface returns the interface name to use for this host: cfg.NetInt if set and present, else first non-lo, cycling with n key. +func chooseNetIface(h *stats.HostStats, cfg *config.Config, host string, netIntIndex map[string]int) string { + if h.Net == nil || len(h.Net) == 0 { + return "" + } + if cfg.NetInt != "" { + if _, ok := h.Net[cfg.NetInt]; ok { + return cfg.NetInt } - swapFreeH := winH - swapUsedH - if swapFreeH > 0 { - renderer.SetDrawColor(constants.Black.R, constants.Black.G, constants.Black.B, 255) - renderer.FillRect(&sdl.Rect{X: *x + halfW, Y: 0, W: halfW, H: swapFreeH}) + } + names := make([]string, 0, len(h.Net)) + for iface := range h.Net { + if iface == "lo" { + continue } + names = append(names, iface) + } + sort.Strings(names) + if len(names) == 0 { + return "" } + idx := netIntIndex[host] % len(names) + if idx < 0 { + idx += len(names) + } + return names[idx] } -func printHotkeys() { - fmt.Println("=> Hotkeys: 1=cores 2=mem 3=net e=extended h=help n=next net q=quit w=write config a/y=cpu avg d/c=net avg f/v=link scale arrows=resize") +func drawNetBarSmoothed(renderer *sdl.Renderer, h *stats.HostStats, cfg *config.Config, smoothed *struct{ rxPct, txPct float64 }, prev stats.NetStamp, netIntIndex map[string]int, host string, factor float64, barW int32, x *int32, winH int32) stats.NetStamp { + defer func() { *x += barW + 1 }() + // Clear this slot so we never leave previous (e.g. CPU/mem) content visible + renderer.SetDrawColor(constants.Black.R, constants.Black.G, constants.Black.B, 255) + renderer.FillRect(&sdl.Rect{X: *x, Y: 0, W: barW, H: winH}) + iface := chooseNetIface(h, cfg, host, netIntIndex) + if iface == "" { + renderer.SetDrawColor(constants.Red.R, constants.Red.G, constants.Red.B, 255) + renderer.FillRect(&sdl.Rect{X: *x, Y: 0, W: barW, H: winH}) + return prev + } + cur, ok := h.Net[iface] + if !ok { + renderer.SetDrawColor(constants.Red.R, constants.Red.G, constants.Red.B, 255) + renderer.FillRect(&sdl.Rect{X: *x, Y: 0, W: barW, H: winH}) + return prev + } + linkBps := netLinkBytesPerSec(cfg) + if linkBps <= 0 { + linkBps = int64(constants.BytesGbit) + } + var targetRx, targetTx float64 + if prev.Stamp > 0 && cur.Stamp > prev.Stamp { + dt := float64(cur.Stamp-prev.Stamp) / 1e9 + if dt > 0 { + deltaB := cur.B - prev.B + deltaTb := cur.Tb - prev.Tb + if deltaB < 0 { + deltaB = 0 + } + if deltaTb < 0 { + deltaTb = 0 + } + targetRx = 100 * float64(deltaB) / (float64(linkBps) * dt) + targetTx = 100 * float64(deltaTb) / (float64(linkBps) * dt) + } + } + smoothed.rxPct += (targetRx - smoothed.rxPct) * factor + smoothed.txPct += (targetTx - smoothed.txPct) * factor + + halfW := barW / 2 + barH := float64(winH) / 100.0 + // Left half: RX from top (light green = used) + rxH := int32(smoothed.rxPct * barH) + if rxH > winH/2 { + rxH = winH / 2 + } + if rxH > 0 { + renderer.SetDrawColor(constants.LightGreen.R, constants.LightGreen.G, constants.LightGreen.B, 255) + renderer.FillRect(&sdl.Rect{X: *x, Y: 0, W: halfW, H: rxH}) + } + if halfW > 0 && winH/2-rxH > 0 { + renderer.SetDrawColor(constants.Black.R, constants.Black.G, constants.Black.B, 255) + renderer.FillRect(&sdl.Rect{X: *x, Y: rxH, W: halfW, H: winH/2 - rxH}) + } + // Right half: TX from bottom (light green = used) + txH := int32(smoothed.txPct * barH) + if txH > winH/2 { + txH = winH / 2 + } + if txH > 0 { + renderer.SetDrawColor(constants.LightGreen.R, constants.LightGreen.G, constants.LightGreen.B, 255) + renderer.FillRect(&sdl.Rect{X: *x + halfW, Y: winH - txH, W: halfW, H: txH}) + } + if halfW > 0 && (winH - txH) > 0 { + renderer.SetDrawColor(constants.Black.R, constants.Black.G, constants.Black.B, 255) + renderer.FillRect(&sdl.Rect{X: *x + halfW, Y: 0, W: halfW, H: winH - txH}) + } + return cur } |
