From 97fc81f3a8a69332342288d5e0f1937f6e74f26e Mon Sep 17 00:00:00 2001 From: "jesse.ward@gmail.com" Date: Sun, 7 Dec 2025 18:35:22 +0000 Subject: [PATCH] Adding themes and styles. --- cmd/impulse/info.go | 95 ++++++++++++++++--- cmd/impulse/main.go | 6 ++ cmd/impulse/play.go | 5 + internal/player/protracker_ticker.go | 4 - internal/player/s3m_ticker.go | 6 -- internal/ui/footer.go | 9 +- internal/ui/header.go | 29 ++---- internal/ui/model.go | 8 +- internal/ui/sampler.go | 14 +-- internal/ui/styles.go | 52 +++++++++++ internal/ui/theme.go | 135 +++++++++++++++++++++++++++ internal/ui/tracker.go | 34 +++---- internal/ui/ui.go | 9 -- internal/ui/waveform.go | 6 +- 14 files changed, 326 insertions(+), 86 deletions(-) create mode 100644 internal/ui/styles.go create mode 100644 internal/ui/theme.go diff --git a/cmd/impulse/info.go b/cmd/impulse/info.go index cbb4673..d5410bb 100644 --- a/cmd/impulse/info.go +++ b/cmd/impulse/info.go @@ -3,7 +3,10 @@ package main import ( "errors" "fmt" + "strconv" + "github.com/charmbracelet/lipgloss" + "github.com/jesseward/impulse/internal/ui" "github.com/jesseward/impulse/pkg/module" "github.com/urfave/cli/v2" ) @@ -13,6 +16,14 @@ func infoAction(c *cli.Context) error { return cli.Exit(errors.New("no file specified"), 1) } filePath := c.Args().Get(0) + // Apply the theme if specified, otherwise default (OceanSky) will be used + themeName := c.String("theme") + if themeName != "" { + if err := ui.SetTheme(themeName); err != nil { + return cli.Exit(fmt.Sprintf("Invalid theme: %v", err), 1) + } + } + module, err := loadModule(filePath) if err != nil { return cli.Exit(err.Error(), 1) @@ -23,16 +34,78 @@ func infoAction(c *cli.Context) error { } func printModuleInfo(module module.Module) { - fmt.Printf("Successfully parsed file: %s\n", module.Name()) - fmt.Printf("Module Type: %s\n", module.Type()) - fmt.Printf("Song Length: %d\n", module.SongLength()) - fmt.Printf("Song BPM: %d\n", module.DefaultBPM()) - fmt.Printf("Song Speed: %d\n", module.DefaultSpeed()) - - fmt.Printf("Number of channels: %d\n", module.NumChannels()) - fmt.Printf("Number of patterns: %d\n", module.NumPatterns()) - fmt.Println("Samples:") - for i, sample := range module.Samples() { - fmt.Printf("Sample %d: %s\n", i+1, sample.Name()) + const keyWidth = 15 + + // Helper to render a key-value pair + renderKV := func(key, value string) string { + k := ui.LabelStyle.Copy().Width(keyWidth).Render(key) + sep := ui.SeparatorStyle.Render(" │ ") + v := ui.ValueStyle.Render(value) + return lipgloss.JoinHorizontal(lipgloss.Left, k, sep, v) } + + title := ui.TitleStyle.Render(" Module Information ") + + metadata := []string{ + renderKV("Filename", module.Name()), + renderKV("Type", module.Type()), + renderKV("Song Length", strconv.Itoa(module.SongLength())), + renderKV("BPM", strconv.Itoa(module.DefaultBPM())), + renderKV("Speed", strconv.Itoa(module.DefaultSpeed())), + renderKV("Channels", strconv.Itoa(module.NumChannels())), + renderKV("Patterns", strconv.Itoa(module.NumPatterns())), + } + metaBlock := lipgloss.JoinVertical(lipgloss.Left, metadata...) + metaBox := lipgloss.NewStyle(). + Border(ui.CurrentTheme.AppBorder, true). + Inherit(ui.BorderColorStyle). + Padding(0, 1). + Render(metaBlock) + + samples := module.Samples() + numSamples := len(samples) + midpoint := (numSamples + 1) / 2 + + var leftColRows, rightColRows []string + + for i, sample := range samples { + // Format: "01: SampleName" + // Distinct coloring: Index (LabelStyle) : (SeparatorStyle) Name (ValueStyle) + indexStr := ui.LabelStyle.Render(fmt.Sprintf("%02d", i+1)) + sepStr := ui.SeparatorStyle.Render(":") + nameStr := ui.ValueStyle.Render(" " + sample.Name()) + + renderedRow := lipgloss.JoinHorizontal(lipgloss.Left, indexStr, sepStr, nameStr) + + if i < midpoint { + leftColRows = append(leftColRows, renderedRow) + } else { + rightColRows = append(rightColRows, renderedRow) + } + } + + leftBlock := lipgloss.JoinVertical(lipgloss.Left, leftColRows...) + rightBlock := lipgloss.JoinVertical(lipgloss.Left, rightColRows...) + + // Join columns with a gap (e.g., 4 spaces) + sampleBlock := lipgloss.JoinHorizontal(lipgloss.Top, + leftBlock, + lipgloss.NewStyle().Width(4).Render(""), + rightBlock, + ) + + sampleHeader := ui.LabelStyle.Render("Samples:") + sampleBox := lipgloss.NewStyle(). + Border(ui.CurrentTheme.AppBorder, true). + Inherit(ui.BorderColorStyle). + Padding(0, 1). + Render(lipgloss.JoinVertical(lipgloss.Left, sampleHeader, "", sampleBlock)) + + output := lipgloss.JoinVertical(lipgloss.Left, + title, + metaBox, + sampleBox, + ) + + fmt.Println(output) } diff --git a/cmd/impulse/main.go b/cmd/impulse/main.go index a09e005..27c3296 100644 --- a/cmd/impulse/main.go +++ b/cmd/impulse/main.go @@ -50,6 +50,12 @@ func main() { Name: "v2", Usage: "start the new v2 terminal UI", }, + &cli.StringFlag{ + Name: "theme", + Aliases: []string{"t"}, + Value: "ocean", + Usage: "UI theme (ocean, sunset, nature, gray)", + }, }, }, { diff --git a/cmd/impulse/play.go b/cmd/impulse/play.go index f0e0ec6..6a8b0bd 100644 --- a/cmd/impulse/play.go +++ b/cmd/impulse/play.go @@ -17,6 +17,7 @@ import ( func playAction(c *cli.Context) error { filePath := c.String("file") startUI := c.Bool("ui") + themeName := c.String("theme") logFile, err := os.OpenFile("impulse.log", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666) if err != nil { @@ -25,6 +26,10 @@ func playAction(c *cli.Context) error { defer logFile.Close() log.SetOutput(logFile) + if err := ui.SetTheme(themeName); err != nil { + return cli.Exit(fmt.Sprintf("Invalid theme: %v", err), 1) + } + var m module.Module m, err = loadModule(filePath) if err != nil { diff --git a/internal/player/protracker_ticker.go b/internal/player/protracker_ticker.go index f287487..d8d5d3f 100644 --- a/internal/player/protracker_ticker.go +++ b/internal/player/protracker_ticker.go @@ -21,8 +21,6 @@ var periodTable = [16 * 36]uint16{ 862, 814, 768, 725, 684, 646, 610, 575, 543, 513, 484, 457, 431, 407, 384, 363, 342, 323, 305, 288, 272, 256, 242, 228, 216, 203, 192, 181, 171, 161, 152, 144, 136, 128, 121, 114, } - - type ProtrackerTicker struct{} func (t *ProtrackerTicker) ProcessTick(p *Player, playerState *playerState, channelState *channelState, cell *module.Cell, speed, bpm, nextRow, nextOrder, currentOrder *int, tick int) { @@ -214,8 +212,6 @@ func (t *ProtrackerTicker) handleExtendedEffect(state *channelState, command, va } } - - func (t *ProtrackerTicker) GetPeriod(period uint16, offset int) uint16 { finetune := 0 note := 0 diff --git a/internal/player/s3m_ticker.go b/internal/player/s3m_ticker.go index 5646662..8f6479b 100644 --- a/internal/player/s3m_ticker.go +++ b/internal/player/s3m_ticker.go @@ -143,10 +143,6 @@ func (t *S3MTicker) handleEffect(p *Player, state *channelState, cell *module.Ce } } - - - - func (t *S3MTicker) tonePortamento(state *channelState, param byte) { if param > 0 { state.portaSpeed = uint16(param) * 4 @@ -233,8 +229,6 @@ func (t *S3MTicker) specialEffect(state *channelState, param byte, nextRow *int, } } - - func (t *S3MTicker) GetPeriod(period uint16, offset int) uint16 { // this is not correct. // find the note from the period diff --git a/internal/ui/footer.go b/internal/ui/footer.go index b758146..d63eda6 100644 --- a/internal/ui/footer.go +++ b/internal/ui/footer.go @@ -20,13 +20,12 @@ func (m *footerModel) SetWidth(width int) { func (m footerModel) View() string { style := lipgloss.NewStyle(). - Foreground(lipgloss.Color("#FFFFC0")). - Border(lipgloss.NormalBorder(), true). - Inherit(borderColorStyle). - Faint(true). + Foreground(CurrentTheme.Primary). + Border(CurrentTheme.AppBorder, true). + Inherit(BorderColorStyle). Width(m.width). Align(lipgloss.Center) - text := "'tab' toggle pattern or sample view | 'spacebar' Start/Stop Song | 'q' Quit" + text := "'tab' toggle pattern or sample view │ 'spacebar' Start/Stop Song │ 'q' Quit" return style.Render(text) } diff --git a/internal/ui/header.go b/internal/ui/header.go index f9808c4..588fa46 100644 --- a/internal/ui/header.go +++ b/internal/ui/header.go @@ -13,16 +13,6 @@ const ( keyWidth = 12 ) -var ( - headerStyle = lipgloss.NewStyle(). - Border(lipgloss.NormalBorder(), true). - Inherit(borderColorStyle). - Padding(0, 1) - labelStyle = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("27")) - valueStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("15")) - separatorStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("238")) -) - type headerModel struct { module module.Module width int @@ -69,29 +59,28 @@ func (m headerModel) View() string { {"Position", strconv.Itoa(m.state.Order), "Time", fmt.Sprintf("%02d:%02d", m.duration/60, m.duration%60), "Speed", strconv.Itoa(m.speed)}, } - contentWidth := m.width - headerStyle.GetHorizontalFrameSize() - 3 + contentWidth := m.width - HeaderStyle.GetHorizontalFrameSize() - 3 columnWidth := contentWidth / 3 lastColumnWidth := contentWidth - (columnWidth * 2) var rows []string for _, r := range data { - // --- ✨ FIX: Build each part of the column separately --- - key1 := labelStyle.Copy().Width(keyWidth).Render(r.left) - sep := separatorStyle.Render(" | ") - val1 := valueStyle.Render(r.lvalue) + key1 := LabelStyle.Copy().Width(keyWidth).Render(r.left) + sep := SeparatorStyle.Render(" │ ") + val1 := ValueStyle.Render(r.lvalue) // Join the parts to create the aligned content for the column. col1Content := lipgloss.JoinHorizontal(lipgloss.Left, key1, sep, val1) // Render the full column with its calculated width. col1 := lipgloss.NewStyle().Width(columnWidth).Render(col1Content) - key2 := labelStyle.Copy().Width(keyWidth).Render(r.center) - val2 := valueStyle.Render(r.cvalue) + key2 := LabelStyle.Copy().Width(keyWidth).Render(r.center) + val2 := ValueStyle.Render(r.cvalue) col2Content := lipgloss.JoinHorizontal(lipgloss.Left, key2, sep, val2) col2 := lipgloss.NewStyle().Width(columnWidth).Render(col2Content) - key3 := labelStyle.Copy().Width(keyWidth).Render(r.right) - val3 := valueStyle.Render(r.rvalue) + key3 := LabelStyle.Copy().Width(keyWidth).Render(r.right) + val3 := ValueStyle.Render(r.rvalue) col3Content := lipgloss.JoinHorizontal(lipgloss.Left, key3, sep, val3) col3 := lipgloss.NewStyle().Width(lastColumnWidth).Render(col3Content) @@ -99,5 +88,5 @@ func (m headerModel) View() string { } content := lipgloss.JoinHorizontal(lipgloss.Top, rows...) - return headerStyle.Width(m.width - 2).Render(content) + return HeaderStyle.Width(m.width - 2).Render(content) } diff --git a/internal/ui/model.go b/internal/ui/model.go index 91aa03d..bcfe679 100644 --- a/internal/ui/model.go +++ b/internal/ui/model.go @@ -203,8 +203,8 @@ func (m model) View() string { var footerView string if m.flashMessage != "" { style := lipgloss.NewStyle(). - Foreground(lipgloss.Color("#FFFFFF")). - Background(lipgloss.Color("200")). + Foreground(CurrentTheme.ActiveRowFg). + Background(CurrentTheme.ActiveRowBg). Padding(0, 1) footerView = style.Render(m.flashMessage) } else { @@ -219,8 +219,8 @@ func (m model) View() string { if m.activeView == showQuitConfirmation { dialogBox := lipgloss.NewStyle(). - Border(lipgloss.RoundedBorder()). - Inherit(borderColorStyle). + Border(CurrentTheme.AppBorder). + Inherit(BorderColorStyle). Padding(1, 0). BorderTop(true). BorderLeft(true). diff --git a/internal/ui/sampler.go b/internal/ui/sampler.go index f78c414..2d91eca 100644 --- a/internal/ui/sampler.go +++ b/internal/ui/sampler.go @@ -54,13 +54,13 @@ func newSamplerModel(m module.Module) samplerModel { s := table.DefaultStyles() s.Header = s.Header. - BorderStyle(lipgloss.NormalBorder()). - BorderForeground(lipgloss.Color("240")). + BorderStyle(CurrentTheme.HeaderBorder). + BorderForeground(CurrentTheme.Border). BorderBottom(true). - Bold(false) + Bold(true) s.Selected = s.Selected. - Foreground(lipgloss.Color("15")). - Background(lipgloss.Color("27")). + Foreground(CurrentTheme.ActiveRowFg). + Background(CurrentTheme.ActiveRowBg). Bold(false) t.SetStyles(s) @@ -80,8 +80,8 @@ func (m samplerModel) Update(msg tea.Msg) (samplerModel, tea.Cmd) { func (m samplerModel) View() string { style := lipgloss.NewStyle(). - Border(lipgloss.NormalBorder(), true). - Inherit(borderColorStyle). + Border(CurrentTheme.AppBorder, true). + Inherit(BorderColorStyle). Width(m.width - 2). Height(m.height) return style.Render(m.table.View()) diff --git a/internal/ui/styles.go b/internal/ui/styles.go new file mode 100644 index 0000000..eb0ad77 --- /dev/null +++ b/internal/ui/styles.go @@ -0,0 +1,52 @@ +package ui + +import "github.com/charmbracelet/lipgloss" + +var ( + // Global styles + TitleStyle lipgloss.Style + BorderColorStyle lipgloss.Style + + // Header styles + HeaderStyle lipgloss.Style + LabelStyle lipgloss.Style + ValueStyle lipgloss.Style + SeparatorStyle lipgloss.Style + + // Tracker styles + NoteStyle lipgloss.Style + InstrumentStyle lipgloss.Style + EffectStyle lipgloss.Style +) + +func init() { + updateStyles() +} + +func updateStyles() { + // Global + TitleStyle = lipgloss.NewStyle(). + Background(CurrentTheme.ActiveRowBg). + Foreground(CurrentTheme.ActiveRowFg). + Bold(true). + Padding(0, 1) + + BorderColorStyle = lipgloss.NewStyle(). + BorderForeground(CurrentTheme.Border). + BorderStyle(CurrentTheme.AppBorder) + + // Header + HeaderStyle = lipgloss.NewStyle(). + Border(CurrentTheme.HeaderBorder, true). + Inherit(BorderColorStyle). + Padding(0, 1) + + LabelStyle = lipgloss.NewStyle().Bold(true).Foreground(CurrentTheme.Label) + ValueStyle = lipgloss.NewStyle().Foreground(CurrentTheme.Value) + SeparatorStyle = lipgloss.NewStyle().Foreground(CurrentTheme.Separator) + + // Tracker + NoteStyle = lipgloss.NewStyle().Foreground(CurrentTheme.Note) + InstrumentStyle = lipgloss.NewStyle().Foreground(CurrentTheme.Instrument) + EffectStyle = lipgloss.NewStyle().Foreground(CurrentTheme.Effect) +} diff --git a/internal/ui/theme.go b/internal/ui/theme.go new file mode 100644 index 0000000..8cf6b9f --- /dev/null +++ b/internal/ui/theme.go @@ -0,0 +1,135 @@ +package ui + +import ( + "fmt" + + "github.com/charmbracelet/lipgloss" +) + +// Theme defines the color palette and styles for the application. +type Theme struct { + Name string + Primary lipgloss.Color // Main accent (e.g., Cyan) + Secondary lipgloss.Color // Secondary accent (e.g., White/Grey) + Background lipgloss.Color // Main background (e.g., Black) + Border lipgloss.Color // Border color + HeaderBorder lipgloss.Border + AppBorder lipgloss.Border + Label lipgloss.Color // Field labels + Value lipgloss.Color // Field values + Separator lipgloss.Color + ActiveRowBg lipgloss.Color // Highlighted row background + ActiveRowFg lipgloss.Color // Highlighted row foreground + Note lipgloss.Color + Instrument lipgloss.Color + Effect lipgloss.Color + Fade lipgloss.Color // Faint/dimmed text +} + +// SunsetFireTheme (Red to Yellow) + +var SunsetFireTheme = Theme{ + Name: "Sunset Fire", + Primary: lipgloss.Color("#FF0000"), // Bright Red + Secondary: lipgloss.Color("#FFFF00"), // Bright Yellow + Background: lipgloss.Color("#000000"), // Black + Border: lipgloss.Color("#FF0000"), // Red + HeaderBorder: lipgloss.RoundedBorder(), + AppBorder: lipgloss.RoundedBorder(), + Label: lipgloss.Color("#FFFF00"), // Yellow + Value: lipgloss.Color("#FFFFFF"), // Bright White + Separator: lipgloss.Color("#800000"), // Dark Red + ActiveRowBg: lipgloss.Color("#FF0000"), // Red + ActiveRowFg: lipgloss.Color("#000000"), // Black + Note: lipgloss.Color("#FF0000"), // Bright Red + Instrument: lipgloss.Color("#FFFF00"), // Bright Yellow + Effect: lipgloss.Color("#FFFFFF"), // White + Fade: lipgloss.Color("#808080"), // Gray +} + +// OceanSkyTheme (Blue to Cyan) +var OceanSkyTheme = Theme{ + Name: "Ocean Sky", + Primary: lipgloss.Color("#00FFFF"), // Bright Cyan + Secondary: lipgloss.Color("#0000FF"), // Bright Blue + Background: lipgloss.Color("#000000"), // Black + Border: lipgloss.Color("#00FFFF"), // Cyan + HeaderBorder: lipgloss.RoundedBorder(), + AppBorder: lipgloss.RoundedBorder(), + Label: lipgloss.Color("#00FFFF"), // Cyan + Value: lipgloss.Color("#FFFFFF"), // Bright White + Separator: lipgloss.Color("#000080"), // Dark Blue + ActiveRowBg: lipgloss.Color("#00FFFF"), // Cyan + ActiveRowFg: lipgloss.Color("#000000"), // Black + Note: lipgloss.Color("#00FFFF"), // Bright Cyan + Instrument: lipgloss.Color("#5F5FFF"), // Light Blue + Effect: lipgloss.Color("#FFFFFF"), // White + Fade: lipgloss.Color("#808080"), // Gray +} + +// NatureFadeTheme (Green) +var NatureFadeTheme = Theme{ + Name: "Nature Fade", + Primary: lipgloss.Color("#00FF00"), // Bright Green + Secondary: lipgloss.Color("#00FFFF"), // Cyan + Background: lipgloss.Color("#000000"), // Black + Border: lipgloss.Color("#00FF00"), // Green + HeaderBorder: lipgloss.RoundedBorder(), + AppBorder: lipgloss.RoundedBorder(), + Label: lipgloss.Color("#00FF00"), // Bright Green + Value: lipgloss.Color("#FFFFFF"), // Bright White + Separator: lipgloss.Color("#008000"), // Dark Green + ActiveRowBg: lipgloss.Color("#008000"), // Green + ActiveRowFg: lipgloss.Color("#FFFFFF"), // Bright White + Note: lipgloss.Color("#00FF00"), // Bright Green + Instrument: lipgloss.Color("#00FFFF"), // Cyan + Effect: lipgloss.Color("#FFFF00"), // Yellow + Fade: lipgloss.Color("#808080"), // Gray +} + +// GrayscaleFadeTheme +var GrayscaleFadeTheme = Theme{ + Name: "Grayscale", + Primary: lipgloss.Color("#FFFFFF"), // Bright White + Secondary: lipgloss.Color("#808080"), // Gray + Background: lipgloss.Color("#000000"), // Black + Border: lipgloss.Color("#808080"), // Gray + HeaderBorder: lipgloss.RoundedBorder(), + AppBorder: lipgloss.RoundedBorder(), + Label: lipgloss.Color("#C0C0C0"), // Silver + Value: lipgloss.Color("#FFFFFF"), // Bright White + Separator: lipgloss.Color("#404040"), // Dark Gray + ActiveRowBg: lipgloss.Color("#C0C0C0"), // Silver + ActiveRowFg: lipgloss.Color("#000000"), // Black + Note: lipgloss.Color("#FFFFFF"), // Bright White + Instrument: lipgloss.Color("#808080"), // Gray + Effect: lipgloss.Color("#606060"), // Dim Gray + Fade: lipgloss.Color("#404040"), // Dark Gray +} + +// CurrentTheme holds the active theme configuration. +// This makes it easy to swap themes at runtime or compile time. +var CurrentTheme = OceanSkyTheme + +// SetTheme sets the CurrentTheme based on the provided name. +func SetTheme(name string) error { + switch name { + case "sunset": + CurrentTheme = SunsetFireTheme + case "ocean": + CurrentTheme = OceanSkyTheme + case "nature": + CurrentTheme = NatureFadeTheme + case "gray", "grayscale": + CurrentTheme = GrayscaleFadeTheme + default: + return fmt.Errorf("unknown theme: %s", name) + } + updateStyles() + return nil +} + +// ThemeNames returns a list of available theme names. +func ThemeNames() []string { + return []string{"ocean", "sunset", "nature", "gray"} +} diff --git a/internal/ui/tracker.go b/internal/ui/tracker.go index bc88e79..03c28da 100644 --- a/internal/ui/tracker.go +++ b/internal/ui/tracker.go @@ -9,12 +9,6 @@ import ( "github.com/jesseward/impulse/pkg/module" ) -var ( - noteStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("45")) - instrumentStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("27")) - effectStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("21")) -) - const ( trackerHeight = 24 playheadDisplayRow = trackerHeight/2 - 1 @@ -53,11 +47,11 @@ func (m trackerModel) View() string { maxVisibleChannels := max((availableWidth-rowNumWidth)/channelWidth, 0) numChannelsToDisplay := min(m.module.NumChannels(), maxVisibleChannels) - title := titleStyle.Render(m.module.Name()) + title := TitleStyle.Render(m.module.Name()) b.WriteString(title + "\n") // Header - headerStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#FFFFFF")) + headerStyle := lipgloss.NewStyle().Foreground(CurrentTheme.Secondary).Bold(true) header := " " for ch := range numChannelsToDisplay { header += fmt.Sprintf(" Chan %-4d", ch+1) @@ -72,21 +66,27 @@ func (m trackerModel) View() string { for displayRow := 1; displayRow <= availableHeight; displayRow++ { patternRow := m.row + (displayRow - playheadDisplayRow) - rowStyle := lipgloss.NewStyle() // .Background(lipgloss.Color("6")) + rowStyle := lipgloss.NewStyle() + // Default row number style (dimmed) + rowNumFg := CurrentTheme.Fade + if displayRow == playheadDisplayRow { - rowStyle = rowStyle.Background(lipgloss.Color("254")) + rowStyle = rowStyle.Background(CurrentTheme.ActiveRowBg).Foreground(CurrentTheme.ActiveRowFg) + // On active row, use the active foreground color (Black) for high contrast + rowNumFg = CurrentTheme.ActiveRowFg } if patternRow >= 0 && patternRow < m.module.NumRows(patternRow) { rowNumStr := fmt.Sprintf("%02d", patternRow+1) - b.WriteString(rowStyle.Foreground(lipgloss.Color("15")).Render(rowNumStr)) + // Apply the calculated foreground to the row number + b.WriteString(rowStyle.Copy().Foreground(rowNumFg).Render(rowNumStr)) for ch := range numChannelsToDisplay { cellData := m.module.PatternCell(m.pattern, patternRow, ch) - noteStr := noteStyle.Copy().Inherit(rowStyle).Render(cellData.HumanNote) - instrumentStr := instrumentStyle.Copy().Inherit(rowStyle).Render(fmt.Sprintf("%02X", cellData.Instrument)) - effectStr := effectStyle.Copy().Inherit(rowStyle).Render(fmt.Sprintf("%X%02X", cellData.Effect, cellData.EffectParam)) - cellStr := fmt.Sprintf(" %s %s %s |", noteStr, instrumentStr, effectStr) + noteStr := NoteStyle.Copy().Inherit(rowStyle).Render(cellData.HumanNote) + instrumentStr := InstrumentStyle.Copy().Inherit(rowStyle).Render(fmt.Sprintf("%02X", cellData.Instrument)) + effectStr := EffectStyle.Copy().Inherit(rowStyle).Render(fmt.Sprintf("%X%02X", cellData.Effect, cellData.EffectParam)) + cellStr := fmt.Sprintf(" %s %s %s │", noteStr, instrumentStr, effectStr) b.WriteString(rowStyle.Render(cellStr)) } b.WriteString("\n") @@ -96,8 +96,8 @@ func (m trackerModel) View() string { } style := lipgloss.NewStyle(). - Border(lipgloss.NormalBorder(), true). - Inherit(borderColorStyle). + Border(CurrentTheme.AppBorder, true). + Inherit(BorderColorStyle). Width(m.width - 2). Height(m.height - 2) return style.Render(b.String()) diff --git a/internal/ui/ui.go b/internal/ui/ui.go index ecf30a2..9f4ef1f 100644 --- a/internal/ui/ui.go +++ b/internal/ui/ui.go @@ -5,7 +5,6 @@ import ( "time" tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" "github.com/jesseward/impulse/internal/player" "github.com/jesseward/impulse/pkg/module" ) @@ -15,14 +14,6 @@ const ( minHeight = 24 ) -var ( - titleStyle = lipgloss.NewStyle(). - Background(lipgloss.Color("27")). // A nice purple - Foreground(lipgloss.Color("15")). - Padding(0, 1) - borderColorStyle = lipgloss.NewStyle().BorderForeground(lipgloss.Color("15")) -) - func New(m module.Module) { stateUpdateChan := make(chan player.PlayerStateUpdate) opts := player.DefaultPlayerOptions() diff --git a/internal/ui/waveform.go b/internal/ui/waveform.go index 12303eb..e40723f 100644 --- a/internal/ui/waveform.go +++ b/internal/ui/waveform.go @@ -20,8 +20,8 @@ func newWaveformModel(s module.Sample, w, h int) waveformModel { } func (m waveformModel) View() string { - title := titleStyle.Render("Sample '" + m.sample.Name() + "'") - waveform := noteStyle.Render(module.AsciiWaveform(m.sample, m.width/2, m.height/2)) + title := TitleStyle.Render("Sample '" + m.sample.Name() + "'") + waveform := NoteStyle.Render(module.AsciiWaveform(m.sample, m.width/2, m.height/2)) // Calculate the size of the dialog box (50% of screen) and add some padding dialogWidth := (m.width / 2) + 6 @@ -29,7 +29,7 @@ func (m waveformModel) View() string { // Create the styled dialog box dialogBox := lipgloss.NewStyle(). - Border(lipgloss.RoundedBorder(), true).Inherit(borderColorStyle). + Border(CurrentTheme.AppBorder, true).Inherit(BorderColorStyle). Padding(1, 1). Width(dialogWidth). // Set the width Height(dialogHeight). // Set the height