feat: initial gosleep-timer implementation

Go TUI sleep timer with modular architecture.
- Config: YAML-based, profile inheritance, auto WM detection
- Engine: timer, executor, stage pipeline (pre→timer→post)
- Modules: workspace (niri/hyprland/kde), media, lock, kill,
  notify, sound, brightness, mute, custom, script
- TUI: bubbletea app with profiles, module toggles, progress bar
- CLI: run, list-profiles, export, import, history, stats
- History: JSONL log with statistics
- QR: config export as ASCII QR
This commit is contained in:
2026-07-03 01:43:22 +02:00
commit 8501fd074c
34 changed files with 3401 additions and 0 deletions
+104
View File
@@ -0,0 +1,104 @@
package tui
import (
"fmt"
"strings"
"time"
"github.com/charmbracelet/lipgloss"
)
// ProgressBar renders a progress bar with given width, percent (0.0-1.0)
func ProgressBar(width int, percent float64, label string) string {
if width < 10 {
width = 10
}
filled := int(float64(width) * percent)
if filled > width {
filled = width
}
empty := width - filled
bar := strings.Repeat("█", filled) + strings.Repeat("░", empty)
bar = lipgloss.NewStyle().Foreground(lipgloss.Color("#33ccff")).Render(bar)
pct := fmt.Sprintf("%3d%%", int(percent*100))
return fmt.Sprintf("%s %s %s", pct, bar, label)
}
// TimerBlock renders the main timer display (HH:MM:SS)
func TimerBlock(remaining time.Duration) string {
totalSec := int(remaining.Seconds())
h := totalSec / 3600
m := (totalSec % 3600) / 60
s := totalSec % 60
timeStr := fmt.Sprintf("%02d:%02d:%02d", h, m, s)
return TimerStyle.Render(timeStr)
}
// ModuleCheckbox renders a module toggle line
func ModuleCheckbox(name string, enabled bool, checked bool) string {
checkmark := " "
if checked {
checkmark = "●"
}
style := InactiveModuleStyle
if enabled {
style = ActiveModuleStyle
}
return style.Render(fmt.Sprintf("[ %s ] %s", checkmark, name))
}
// StatusLine renders a status message
func StatusLine(msg string, isError bool) string {
if isError {
return ErrorStyle.Render(msg)
}
return StatusStyle.Render(msg)
}
// HelpBar renders the bottom help bar with keybindings
func HelpBar(items ...string) string {
return HelpStyle.Render(strings.Join(items, " · "))
}
// InputField renders a labeled input field
func InputField(label, value, placeholder string, focused bool) string {
style := LabelStyle
borderColor := "#444"
if focused {
borderColor = "#33ccff"
}
val := value
if val == "" {
val = placeholder
style = DimmedStyle
}
inputBox := lipgloss.NewStyle().
Border(lipgloss.RoundedBorder()).
BorderForeground(lipgloss.Color(borderColor)).
Width(30).
Render(val)
return fmt.Sprintf("%s\n%s", style.Render(label), inputBox)
}
// SelectWidget renders a simple select list
func SelectWidget(label string, options []string, selected int) string {
var b strings.Builder
b.WriteString(LabelStyle.Render(label))
b.WriteString("\n")
for i, opt := range options {
if i == selected {
b.WriteString(lipgloss.NewStyle().
Foreground(lipgloss.Color("#33ccff")).
Bold(true).
Render("▸ " + opt))
} else {
b.WriteString(" " + opt)
}
b.WriteString("\n")
}
return b.String()
}