diff options
Diffstat (limited to 'internal/display')
| -rw-r--r-- | internal/display/display.go | 31 | ||||
| -rw-r--r-- | internal/display/display_test.go | 148 |
2 files changed, 173 insertions, 6 deletions
diff --git a/internal/display/display.go b/internal/display/display.go index 54c5c51..d3f27ca 100644 --- a/internal/display/display.go +++ b/internal/display/display.go @@ -25,8 +25,9 @@ type runState struct { showIOAvgLine bool showCores bool showMem bool - showNet bool - extended bool + showNet bool + showSeparators bool + extended bool winW int32 winH int32 prevCPU map[string]collector.CPULine @@ -44,8 +45,9 @@ func newRunState(cfg *config.Config, winW, winH int32) *runState { showIOAvgLine: cfg.ShowIOAvgLine, showCores: cfg.ShowCores, showMem: cfg.ShowMem, - showNet: cfg.ShowNet, - extended: cfg.Extended, + showNet: cfg.ShowNet, + showSeparators: cfg.ShowSeparators, + extended: cfg.Extended, winW: winW, winH: winH, prevCPU: make(map[string]collector.CPULine), @@ -160,6 +162,9 @@ func handleKey(sym sdl.Keycode, window *sdl.Window, cfg *config.Config, state *r case sdl.K_i: state.showIOAvgLine = !state.showIOAvgLine fmt.Println("==> Toggled global I/O avg line:", state.showIOAvgLine) + case sdl.K_s: + state.showSeparators = !state.showSeparators + fmt.Println("==> Toggled host separators:", state.showSeparators) case sdl.K_a: cfg.CPUAverage++ fmt.Println("==> CPU average samples:", cfg.CPUAverage) @@ -188,6 +193,7 @@ func handleKey(sym sdl.Keycode, window *sdl.Window, cfg *config.Config, state *r cfg.ShowCores = state.showCores cfg.ShowMem = state.showMem cfg.ShowNet = state.showNet + cfg.ShowSeparators = state.showSeparators cfg.Extended = state.extended if err := cfg.Write(); err != nil { fmt.Fprintf(os.Stderr, "!!! Write config: %v\n", err) @@ -271,12 +277,25 @@ func countBars(snap map[string]*stats.HostStats, showCores, showMem, showNet boo // drawBars draws CPU, memory, and network bars for all hosts in snap. func drawBars(renderer *sdl.Renderer, snap map[string]*stats.HostStats, cfg *config.Config, state *runState, numBars int) { barIndex := 0 - for _, host := range sortedHosts(snap) { + hosts := sortedHosts(snap) + // Track where each host's bars end so we can draw separators after all bars + var separatorXs []int32 + for i, host := range hosts { h := snap[host] if h == nil { continue } drawHostBars(renderer, h, host, cfg, state, numBars, &barIndex) + // Record separator position between hosts (not after the last one) + if state.showSeparators && i < len(hosts)-1 { + sepX, _ := barBounds(state.winW, numBars, barIndex) + separatorXs = append(separatorXs, sepX) + } + } + // Draw 1px yellow vertical separators on top of all bars + for _, sepX := range separatorXs { + renderer.SetDrawColor(constants.Yellow.R, constants.Yellow.G, constants.Yellow.B, 255) + renderer.FillRect(&sdl.Rect{X: sepX, Y: 0, W: 1, H: state.winH}) } } @@ -611,7 +630,7 @@ func drawMemBarSmoothed(renderer *sdl.Renderer, h *stats.HostStats, smoothed *st } func printHotkeys() { - fmt.Println("=> Hotkeys: 1=cores 2/m=mem 3/n=net e=extended g=avg line i=io avg h=help q=quit w=write config a/y=cpu avg d/c=net avg f/v=link scale arrows=resize") + fmt.Println("=> Hotkeys: 1=cores 2/m=mem 3/n=net e=extended g=avg line i=io avg s=separators h=help q=quit w=write config a/y=cpu avg d/c=net avg f/v=link scale arrows=resize") } diff --git a/internal/display/display_test.go b/internal/display/display_test.go index 734e144..f5f2e39 100644 --- a/internal/display/display_test.go +++ b/internal/display/display_test.go @@ -1266,3 +1266,151 @@ func TestHandleKey_WriteConfig_IOAvgLine(t *testing.T) { t.Error("expected ShowIOAvgLine=true in config after 'w'") } } + +func TestHandleKey_ToggleSeparators(t *testing.T) { + cfg := defaultTestConfig() + state := newRunState(cfg, 200, 100) + if state.showSeparators { + t.Fatal("expected showSeparators=false initially") + } + handleKey(sdl.K_s, nil, cfg, state) + if !state.showSeparators { + t.Fatal("expected showSeparators=true after pressing s") + } + handleKey(sdl.K_s, nil, cfg, state) + if state.showSeparators { + t.Fatal("expected showSeparators=false after pressing s again") + } +} + +func TestSeparator_TwoHosts_Enabled(t *testing.T) { + // Two hosts (100% system = blue) with separators enabled: yellow pixel at boundary + const w, h int32 = 200, 100 + + renderer, surface, err := createTestRenderer(w, h) + if err != nil { + t.Fatal(err) + } + defer renderer.Destroy() + defer surface.Free() + + prev1, cur1 := makeCPUPair(100, 0, 0) // all system → blue + prev2, cur2 := makeCPUPair(100, 0, 0) + + cfg := defaultTestConfig() + cfg.ShowCores = false + cfg.ShowMem = false + cfg.ShowNet = false + cfg.ShowSeparators = true + + src := &mockSource{ + data: map[string]*stats.HostStats{ + "alpha": {CPU: map[string]collector.CPULine{"cpu": cur1}}, + "beta": {CPU: map[string]collector.CPULine{"cpu": cur2}}, + }, + } + + state := newRunState(cfg, w, h) + state.prevCPU["alpha;cpu"] = prev1 + state.prevCPU["beta;cpu"] = prev2 + + drawFrame(renderer, src, cfg, state) + + // 2 bars at 200px → each 100px. Separator at x=100 (start of second host's bars) + assertPixelColor(t, surface, 100, 50, constants.Yellow, 3, "separator yellow at x=100") +} + +func TestSeparator_TwoHosts_Disabled(t *testing.T) { + // Two hosts (100% system = blue) with separators disabled: no yellow at boundary + const w, h int32 = 200, 100 + + renderer, surface, err := createTestRenderer(w, h) + if err != nil { + t.Fatal(err) + } + defer renderer.Destroy() + defer surface.Free() + + prev1, cur1 := makeCPUPair(100, 0, 0) // all system → blue + prev2, cur2 := makeCPUPair(100, 0, 0) + + cfg := defaultTestConfig() + cfg.ShowCores = false + cfg.ShowMem = false + cfg.ShowNet = false + cfg.ShowSeparators = false + + src := &mockSource{ + data: map[string]*stats.HostStats{ + "alpha": {CPU: map[string]collector.CPULine{"cpu": cur1}}, + "beta": {CPU: map[string]collector.CPULine{"cpu": cur2}}, + }, + } + + state := newRunState(cfg, w, h) + state.prevCPU["alpha;cpu"] = prev1 + state.prevCPU["beta;cpu"] = prev2 + + drawFrame(renderer, src, cfg, state) + + // At x=100, should be blue (second host's system bar), NOT yellow separator + assertPixelColor(t, surface, 100, 50, constants.Blue, 3, "no separator, should be blue") +} + +func TestSeparator_SingleHost(t *testing.T) { + // Single host: no separator should be drawn even when enabled + const w, h int32 = 100, 100 + + renderer, surface, err := createTestRenderer(w, h) + if err != nil { + t.Fatal(err) + } + defer renderer.Destroy() + defer surface.Free() + + prev, cur := makeCPUPair(50, 30, 20) + cfg := defaultTestConfig() + cfg.ShowCores = false + cfg.ShowMem = false + cfg.ShowNet = false + cfg.ShowSeparators = true + + src := &mockSource{ + data: map[string]*stats.HostStats{ + "host1": {CPU: map[string]collector.CPULine{"cpu": cur}}, + }, + } + + state := newRunState(cfg, w, h) + state.prevCPU["host1;cpu"] = prev + + drawFrame(renderer, src, cfg, state) + + // No separator at the edges — just verify no yellow at x=0 or x=99 + r, g, b := getPixelColor(surface, 0, 50) + if r == constants.Yellow.R && g == constants.Yellow.G && b == constants.Yellow.B { + t.Errorf("unexpected yellow separator at x=0 with single host") + } + r, g, b = getPixelColor(surface, 99, 50) + if r == constants.Yellow.R && g == constants.Yellow.G && b == constants.Yellow.B { + t.Errorf("unexpected yellow separator at x=99 with single host") + } +} + +func TestHandleKey_WriteConfig_Separators(t *testing.T) { + // Verify that 'w' hotkey persists showSeparators to config + tmpDir := t.TempDir() + origHome := os.Getenv("HOME") + os.Setenv("HOME", tmpDir) + defer os.Setenv("HOME", origHome) + + cfg := defaultTestConfig() + state := newRunState(cfg, 200, 100) + state.showSeparators = true + + handleKey(sdl.K_w, nil, cfg, state) + + if !cfg.ShowSeparators { + t.Error("expected ShowSeparators=true in config after 'w'") + } +} |
