// Package renderer provides screen rendering implementations for hdmistat package renderer import ( "fmt" "image/color" "strings" "git.eeqj.de/sneak/hdmistat/internal/layout" "git.eeqj.de/sneak/hdmistat/internal/statcollector" "github.com/dustin/go-humanize" ) // OverviewScreen displays system overview type OverviewScreen struct{} // NewOverviewScreen creates a new overview screen renderer func NewOverviewScreen() *OverviewScreen { return &OverviewScreen{} } // Name returns the name of this screen func (s *OverviewScreen) Name() string { return "System Overview" } // Render draws the overview screen to the provided canvas func (s *OverviewScreen) Render(canvas *layout.Canvas, info *statcollector.SystemInfo) error { _, _ = canvas.Size() // Colors textColor := color.RGBA{255, 255, 255, 255} headerColor := color.RGBA{100, 200, 255, 255} // Styles titleStyle := layout.TextStyle{Size: 48, Color: headerColor} headerStyle := layout.TextStyle{Size: 18, Color: headerColor, Bold: true} normalStyle := layout.TextStyle{Size: 18, Color: textColor} y := 120 // Start below header // Get short hostname shortHostname := info.Hostname if idx := strings.Index(shortHostname, "."); idx > 0 { shortHostname = shortHostname[:idx] } // Title - left aligned at consistent position titleText := fmt.Sprintf("%s: status", shortHostname) _ = canvas.DrawText(titleText, layout.Point{X: 50, Y: y}, layout.TextStyle{ Size: 36, // Smaller than before Color: titleStyle.Color, Alignment: layout.AlignLeft, }) y += 60 // Standard bar dimensions barWidth := 400 barHeight := 20 sectionSpacing := 60 // CPU section _ = canvas.DrawText("CPU", layout.Point{X: 50, Y: y}, headerStyle) y += 30 // Calculate average CPU usage totalCPU := 0.0 for _, cpu := range info.CPUPercent { totalCPU += cpu } avgCPU := totalCPU / float64(len(info.CPUPercent)) // Draw composite CPU bar below header cpuBar := &layout.ProgressBar{ X: 50, Y: y, Width: barWidth, Height: barHeight, Value: avgCPU / 100.0, Label: fmt.Sprintf("%.1f%% average across %d cores", avgCPU, len(info.CPUPercent)), LeftLabel: "0%", RightLabel: "100%", BarColor: color.RGBA{255, 100, 100, 255}, } cpuBar.Draw(canvas) y += sectionSpacing // Memory section _ = canvas.DrawText("MEMORY", layout.Point{X: 50, Y: y}, headerStyle) y += 30 memUsedPercent := float64(info.MemoryUsed) / float64(info.MemoryTotal) memoryBar := &layout.ProgressBar{ X: 50, Y: y, Width: barWidth, Height: barHeight, Value: memUsedPercent, Label: fmt.Sprintf("%s of %s", layout.FormatBytes(info.MemoryUsed), layout.FormatBytes(info.MemoryTotal)), LeftLabel: "0B", RightLabel: layout.FormatBytes(info.MemoryTotal), BarColor: color.RGBA{100, 200, 100, 255}, } memoryBar.Draw(canvas) y += sectionSpacing // Temperature section if len(info.Temperature) > 0 { _ = canvas.DrawText("TEMPERATURE", layout.Point{X: 50, Y: y}, headerStyle) y += 30 // Find the highest temperature maxTemp := 0.0 maxSensor := "" for sensor, temp := range info.Temperature { if temp > maxTemp { maxTemp = temp maxSensor = sensor } } // Temperature scale from 30°C to 99°C tempValue := (maxTemp - 30.0) / (99.0 - 30.0) if tempValue < 0 { tempValue = 0 } if tempValue > 1 { tempValue = 1 } tempBar := &layout.ProgressBar{ X: 50, Y: y, Width: barWidth, Height: barHeight, Value: tempValue, Label: fmt.Sprintf("%s: %.1f°C", maxSensor, maxTemp), LeftLabel: "30°C", RightLabel: "99°C", BarColor: color.RGBA{255, 150, 50, 255}, } tempBar.Draw(canvas) y += sectionSpacing } // Disk usage section _ = canvas.DrawText("DISK USAGE", layout.Point{X: 50, Y: y}, headerStyle) y += 30 for _, disk := range info.DiskUsage { // Skip snap disks if strings.HasPrefix(disk.Path, "/snap") { continue } diskBar := &layout.ProgressBar{ X: 50, Y: y, Width: barWidth, Height: barHeight, Value: disk.UsedPercent / 100.0, Label: fmt.Sprintf("%s: %s of %s", disk.Path, layout.FormatBytes(disk.Used), layout.FormatBytes(disk.Total)), LeftLabel: "0B", RightLabel: layout.FormatBytes(disk.Total), BarColor: color.RGBA{200, 200, 100, 255}, } diskBar.Draw(canvas) y += 40 if y > 700 { break // Don't overflow the screen } } y += sectionSpacing // Network section if len(info.Network) > 0 { _ = canvas.DrawText("NETWORK", layout.Point{X: 50, Y: y}, headerStyle) y += 30 for _, net := range info.Network { // Network interface info interfaceText := net.Name if len(net.IPAddresses) > 0 { interfaceText = fmt.Sprintf("%s (%s)", net.Name, net.IPAddresses[0]) } _ = canvas.DrawText(interfaceText, layout.Point{X: 50, Y: y}, normalStyle) y += 25 // Get link speed for scaling (default to 1 Gbps if unknown) linkSpeed := net.LinkSpeed if linkSpeed == 0 { linkSpeed = 1000 * 1000 * 1000 // 1 Gbps in bits } // TX rate bar txValue := float64(net.BitsSentRate) / float64(linkSpeed) txBar := &layout.ProgressBar{ X: 50, Y: y, Width: barWidth/2 - 10, Height: barHeight, Value: txValue, Label: fmt.Sprintf("↑ %s", net.FormatSentRate()), LeftLabel: "0", RightLabel: humanize.SI(float64(linkSpeed), "bit/s"), BarColor: color.RGBA{100, 255, 100, 255}, } txBar.Draw(canvas) // RX rate bar rxValue := float64(net.BitsRecvRate) / float64(linkSpeed) rxBar := &layout.ProgressBar{ X: 50 + barWidth/2 + 10, Y: y, Width: barWidth/2 - 10, Height: barHeight, Value: rxValue, Label: fmt.Sprintf("↓ %s", net.FormatRecvRate()), LeftLabel: "0", RightLabel: humanize.SI(float64(linkSpeed), "bit/s"), BarColor: color.RGBA{100, 100, 255, 255}, } rxBar.Draw(canvas) y += 60 if y > 900 { break // Don't overflow the screen } } } return nil }