summaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-01-02 22:39:11 +0200
committerPaul Buetow <paul@buetow.org>2026-01-02 22:40:19 +0200
commit96f92ddd8ea1554766b358333e911f607c3e5d6c (patch)
treeafc26a32d4c42c752165e4975ab74b597ac4c09f /internal
parent7afe4c2c1afe9d8f4ba8887758523999fde12b7b (diff)
add large ASCII fonts and font cycling to live modev0.3.0main
- Add 4 new ASCII art fonts (mono12, rebel, ansi, ansiShadow) adapted from pomo project - Implement random font selection on startup when no font is specified - Add 'f' hotkey in live mode to cycle through fonts - Update LICENSE with MIT attribution for pomo fonts - Update README with comprehensive font documentation - Bump version to v0.3.0 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Diffstat (limited to 'internal')
-rw-r--r--internal/ascii/fonts.go439
-rw-r--r--internal/ascii/render.go47
-rw-r--r--internal/live/live.go52
3 files changed, 532 insertions, 6 deletions
diff --git a/internal/ascii/fonts.go b/internal/ascii/fonts.go
new file mode 100644
index 0000000..daea4bb
--- /dev/null
+++ b/internal/ascii/fonts.go
@@ -0,0 +1,439 @@
+// Package ascii provides ASCII art fonts for timer display.
+// This code is adapted from https://github.com/Bahaaio/pomo
+// Copyright (c) 2025 Bahaa El Deen Mohamed
+// Licensed under the MIT License
+package ascii
+
+type Font [11]string
+
+const (
+ Mono12 = "mono12"
+ Rebel = "rebel"
+ Ansi = "ansi"
+ AnsiShadow = "ansiShadow"
+ Doom = "doom" // figlet doom font
+ DefaultFont = Mono12
+)
+
+// AllFonts returns a list of all available font names.
+// This includes both ASCII art fonts and the figlet doom font.
+var AllFonts = []string{Doom, Mono12, Rebel, Ansi, AnsiShadow}
+
+var fonts = map[string]Font{
+ Mono12: {
+ `
+ ▄▄▄▄
+ ██▀▀██
+ ██ ██
+ ██ ██ ██
+ ██ ██
+ ██▄▄██
+ ▀▀▀▀
+`,
+ `
+ ▄▄▄
+ █▀██
+ ██
+ ██
+ ██
+ ▄▄▄██▄▄▄
+ ▀▀▀▀▀▀▀▀
+`,
+ `
+ ▄▄▄▄▄
+ █▀▀▀▀██▄
+ ██
+ ▄█▀
+ ▄█▀
+ ▄██▄▄▄▄▄
+ ▀▀▀▀▀▀▀▀
+`,
+ `
+ ▄▄▄▄▄
+ █▀▀▀▀██▄
+ ▄██
+ █████
+ ▀██
+ █▄▄▄▄██▀
+ ▀▀▀▀▀
+`,
+ `
+ ▄▄▄
+ ▄███
+ █▀ ██
+ ▄█▀ ██
+ ████████
+ ██
+ ▀▀
+`,
+ `
+ ▄▄▄▄▄▄▄
+ ██▀▀▀▀▀
+ ██▄▄▄▄
+ █▀▀▀▀██▄
+ ██
+ █▄▄▄▄██▀
+ ▀▀▀▀▀
+`,
+ `
+ ▄▄▄▄
+ ██▀▀▀█
+ ██ ▄▄▄
+ ███▀▀██▄
+ ██ ██
+ ▀██▄▄██▀
+ ▀▀▀▀
+`,
+ `
+ ▄▄▄▄▄▄▄▄
+ ▀▀▀▀▀███
+ ▄██
+ ██
+ ██
+ ██
+ ▀▀
+`,
+ `
+ ▄▄▄▄
+ ▄██▀▀██▄
+ ██▄ ▄██
+ ██████
+ ██▀ ▀██
+ ▀██▄▄██▀
+ ▀▀▀▀
+`,
+ `
+ ▄▄▄▄
+ ▄██▀▀██▄
+ ██ ██
+ ▀██▄▄███
+ ▀▀▀ ██
+ █▄▄▄██
+ ▀▀▀▀
+`,
+ `
+
+ ▄▄
+ ██
+
+ ██
+ ▀▀
+
+`,
+ },
+
+ Rebel: {
+ `
+ █████
+ ███▒▒▒███
+ ███ ▒▒███
+▒███ ▒███
+▒███ ▒███
+▒▒███ ███
+ ▒▒▒█████▒
+ ▒▒▒▒▒▒
+`,
+
+ `
+ ████
+▒▒███
+ ▒███
+ ▒███
+ ▒███
+ ▒███
+ █████
+▒▒▒▒▒
+`,
+
+ `
+ ████████
+ ███▒▒▒▒███
+▒▒▒ ▒███
+ ███████
+ ███▒▒▒▒
+ ███ █
+▒██████████
+▒▒▒▒▒▒▒▒▒▒
+`,
+
+ `
+ ████████
+ ███▒▒▒▒███
+▒▒▒ ▒███
+ ██████▒
+ ▒▒▒▒▒▒███
+ ███ ▒███
+▒▒████████
+ ▒▒▒▒▒▒▒▒
+`,
+
+ `
+ █████ █████
+▒▒███ ▒▒███
+ ▒███ ▒███ █
+ ▒███████████
+ ▒▒▒▒▒▒▒███▒█
+ ▒███▒
+ █████
+ ▒▒▒▒▒
+`,
+
+ `
+ ██████████
+▒███▒▒▒▒▒▒█
+▒███ ▒
+▒█████████
+▒▒▒▒▒▒▒▒███
+ ███ ▒███
+▒▒████████
+ ▒▒▒▒▒▒▒▒
+`,
+
+ `
+ ████████
+ ███▒▒▒▒███
+▒███ ▒▒▒
+▒█████████
+▒███▒▒▒▒███
+▒███ ▒███
+▒▒████████
+ ▒▒▒▒▒▒▒▒
+`,
+
+ `
+ ██████████
+▒███▒▒▒▒███
+▒▒▒ ███
+ ███
+ ███
+ ███
+ ███
+ ▒▒▒
+`,
+
+ `
+ ████████
+ ███▒▒▒▒███
+▒███ ▒███
+▒▒████████
+ ███▒▒▒▒███
+▒███ ▒███
+▒▒████████
+ ▒▒▒▒▒▒▒▒
+`,
+
+ `
+ ████████
+ ███▒▒▒▒███
+▒███ ▒███
+▒▒█████████
+ ▒▒▒▒▒▒▒███
+ ███ ▒███
+▒▒████████
+ ▒▒▒▒▒▒▒▒
+`,
+
+ `
+
+ ██
+ ▒▒
+
+
+ ██
+ ▒▒
+
+
+`,
+ },
+
+ Ansi: {
+ `
+ ██████
+██ ████
+██ ██ ██
+████ ██
+ ██████
+`,
+
+ `
+ ██
+███
+ ██
+ ██
+ ██
+`,
+
+ `
+██████
+ ██
+ █████
+██
+███████
+`,
+
+ `
+██████
+ ██
+ █████
+ ██
+██████
+`,
+
+ `
+██ ██
+██ ██
+███████
+ ██
+ ██
+`,
+
+ `
+███████
+██
+███████
+ ██
+███████
+`,
+
+ `
+ ██████
+██
+███████
+██ ██
+ ██████
+`,
+
+ `
+███████
+ ██
+ ██
+ ██
+ ██
+`,
+
+ `
+ █████
+██ ██
+ █████
+██ ██
+ █████
+`,
+
+ `
+ █████
+██ ██
+ ██████
+ ██
+ █████
+`,
+
+ `
+
+ ██
+
+ ██
+
+`,
+ },
+
+ AnsiShadow: {
+ `
+ ██████╗
+██╔═████╗
+██║██╔██║
+████╔╝██║
+╚██████╔╝
+ ╚═════╝
+`,
+
+ `
+ ██╗
+███║
+╚██║
+ ██║
+ ██║
+ ╚═╝
+`,
+
+ `
+██████╗
+╚════██╗
+ █████╔╝
+██╔═══╝
+███████╗
+╚══════╝
+`,
+
+ `
+██████╗
+╚════██╗
+ █████╔╝
+ ╚═══██╗
+██████╔╝
+╚═════╝
+`,
+
+ `
+██╗ ██╗
+██║ ██║
+███████║
+╚════██║
+ ██║
+ ╚═╝
+`,
+
+ `
+███████╗
+██╔════╝
+███████╗
+╚════██║
+███████║
+╚══════╝
+`,
+
+ `
+ ██████╗
+██╔════╝
+███████╗
+██╔═══██╗
+╚██████╔╝
+ ╚═════╝
+`,
+
+ `
+███████╗
+╚════██║
+ ██╔╝
+ ██╔╝
+ ██║
+ ╚═╝
+`,
+
+ `
+ █████╗
+██╔══██╗
+╚█████╔╝
+██╔══██╗
+╚█████╔╝
+ ╚════╝
+`,
+
+ `
+ █████╗
+██╔══██╗
+╚██████║
+ ╚═══██║
+ █████╔╝
+ ╚════╝
+`,
+
+ `
+
+ ██╗
+ ╚═╝
+ ██╗
+ ╚═╝
+
+`,
+ },
+}
diff --git a/internal/ascii/render.go b/internal/ascii/render.go
new file mode 100644
index 0000000..eb41dd1
--- /dev/null
+++ b/internal/ascii/render.go
@@ -0,0 +1,47 @@
+// Package ascii provides ASCII art rendering for timer display.
+// This code is adapted from https://github.com/Bahaaio/pomo
+// Copyright (c) 2025 Bahaa El Deen Mohamed
+// Licensed under the MIT License
+package ascii
+
+import (
+ "github.com/charmbracelet/lipgloss"
+)
+
+// RenderNumber renders a string of digits as ASCII art using the specified font.
+// The number parameter should contain only digits (0-9) and colons (:).
+// Returns the ASCII art representation as a single string with properly aligned characters.
+func RenderNumber(number string, font Font) string {
+ digits := make([]string, 0, len(number))
+
+ for _, digit := range number {
+ digits = append(digits, renderDigit(digit, font))
+ }
+
+ asciiDigits := lipgloss.JoinHorizontal(lipgloss.Top, digits...)
+ return asciiDigits
+}
+
+// GetFont returns the font with the given name.
+// If the font doesn't exist, it returns the default font.
+func GetFont(fontName string) Font {
+ if font, exists := fonts[fontName]; exists {
+ return font
+ }
+
+ return fonts[DefaultFont]
+}
+
+// renderDigit converts a single digit character to its ASCII art representation.
+// Supports digits 0-9 and colon (:). Returns empty string for unsupported characters.
+func renderDigit(digit rune, font Font) string {
+ if digit == ':' {
+ return font[len(font)-1]
+ }
+
+ if digit < '0' || digit > '9' {
+ return ""
+ }
+
+ return font[digit-'0']
+}
diff --git a/internal/live/live.go b/internal/live/live.go
index 60a4ce7..9590622 100644
--- a/internal/live/live.go
+++ b/internal/live/live.go
@@ -1,11 +1,13 @@
package live
import (
+ "fmt"
"time"
"github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/common-nighthawk/go-figure"
+ "codeberg.org/snonux/timr/internal/ascii"
timrTimer "codeberg.org/snonux/timr/internal/timer"
)
@@ -26,20 +28,35 @@ type Model struct {
statusStyle lipgloss.Style
width int
height int
+ font string // Font selection: "doom" for figlet, or ASCII font names
+ fontIndex int // Current index in the AllFonts slice for cycling
}
-// NewModel creates a new Model.
-func NewModel() Model {
+// NewModel creates a new Model with the specified font.
+// The font parameter can be "doom" for the original figlet font,
+// or one of the ASCII font names: "mono12", "rebel", "ansi", "ansiShadow".
+func NewModel(font string) Model {
state, err := timrTimer.LoadState()
if err != nil {
panic(err) // Or handle more gracefully
}
+ // Find the current font index in AllFonts for cycling support
+ fontIndex := 0
+ for i, f := range ascii.AllFonts {
+ if f == font {
+ fontIndex = i
+ break
+ }
+ }
+
return Model{
state: state,
helpStyle: lipgloss.NewStyle().Faint(true),
timerStyle: lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("#00BFFF")),
statusStyle: lipgloss.NewStyle().Italic(true),
+ font: font,
+ fontIndex: fontIndex,
}
}
@@ -93,6 +110,12 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// handle error
}
return m, nil // Stop ticking
+
+ case "f":
+ // Cycle to the next font
+ m.fontIndex = (m.fontIndex + 1) % len(ascii.AllFonts)
+ m.font = ascii.AllFonts[m.fontIndex]
+ return m, nil
}
}
@@ -100,6 +123,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
// View renders the model's state to the terminal.
+// Supports both figlet-style fonts (doom) and large ASCII art fonts (mono12, rebel, ansi, ansiShadow).
func (m Model) View() string {
if m.quitting {
return ""
@@ -117,12 +141,27 @@ func (m Model) View() string {
status = "Running"
}
- // Large timer text
- figure := figure.NewFigure(totalElapsed.String(), "doom", true)
- timerStr := m.timerStyle.Render(figure.String())
+ // Render timer based on selected font
+ var timerStr string
+ if m.font == "doom" {
+ // Use original figlet font
+ figure := figure.NewFigure(totalElapsed.String(), "doom", true)
+ timerStr = m.timerStyle.Render(figure.String())
+ } else {
+ // Use large ASCII art fonts
+ font := ascii.GetFont(m.font)
+ // Format time as HH:MM:SS for better ASCII display
+ hours := int(totalElapsed.Hours())
+ minutes := int(totalElapsed.Minutes()) % 60
+ seconds := int(totalElapsed.Seconds()) % 60
+ timeStr := fmt.Sprintf("%02d:%02d:%02d", hours, minutes, seconds)
+ asciiTime := ascii.RenderNumber(timeStr, font)
+ timerStr = m.timerStyle.Render(asciiTime)
+ }
statusStr := m.statusStyle.Render(status)
- helpStr := m.helpStyle.Render("q: quit, s: start/stop, r: reset")
+ fontInfo := m.helpStyle.Render(fmt.Sprintf("Font: %s", m.font))
+ helpStr := m.helpStyle.Render("q: quit, s: start/stop, r: reset, f: change font")
// Centering
return lipgloss.Place(
@@ -135,6 +174,7 @@ func (m Model) View() string {
timerStr,
statusStr,
"", // spacer
+ fontInfo,
helpStr,
),
)