diff --git a/internal/ui/chat/messageslist/embed.go b/internal/ui/chat/messageslist/embed.go index 6e1a7aa..84211f2 100644 --- a/internal/ui/chat/messageslist/embed.go +++ b/internal/ui/chat/messageslist/embed.go @@ -13,6 +13,12 @@ import ( "github.com/yuin/goldmark/v2/ast" ) +// embedPrefix is drawn before each line of an embed, in the embed's color. +const embedPrefix = " ▎ " + +// embedPrefixWidth is how many cells embedPrefix takes. +var embedPrefixWidth = uniseg.StringWidth(embedPrefix) + func (m Model) drawEmbeds(builder *richtext.Builder, message discord.Message, baseStyle tview.Style, width int, contentRoot ast.Node, contentSource []byte) { if len(message.Embeds) == 0 { return @@ -32,11 +38,9 @@ func (m Model) drawEmbeds(builder *richtext.Builder, message discord.Message, ba } lineStyles := embedLineStyles(baseStyle, m.cfg.Theme.MessagesList.Embeds) - defaultBarStyle := baseStyle.Dim(true) - prefixText := " ▎ " - prefixWidth := uniseg.StringWidth(prefixText) + defaultPrefixStyle := baseStyle.Dim(true) // Wrap against the list's width, so the message is rendered again when it changes. - wrapWidth := max(width-prefixWidth, 1) + wrapWidth := max(width-embedPrefixWidth, 1) for _, embed := range message.Embeds { lines := embedLines(embed, contentURLs) @@ -45,11 +49,11 @@ func (m Model) drawEmbeds(builder *richtext.Builder, message discord.Message, ba } embedText := make(richtext.Text, 0, len(lines)*2) - barStyle := defaultBarStyle + prefixStyle := defaultPrefixStyle if embed.Color != discord.NullColor && embed.Color != 0 { - barStyle = barStyle.Foreground(tcell.NewHexColor(int32(embed.Color))) + prefixStyle = prefixStyle.Foreground(tcell.NewHexColor(int32(embed.Color))) } - prefix := richtext.NewSegment(prefixText, barStyle) + prefix := richtext.NewSegment(embedPrefix, prefixStyle) builder.NewLine() for _, line := range lines { if strings.TrimSpace(line.Text) == "" { @@ -64,7 +68,7 @@ func (m Model) drawEmbeds(builder *richtext.Builder, message discord.Message, ba if line.URL != "" { renderedLine = lineWithURL(renderedLine, line.URL) } - // Prefix must be applied after wrapping so every visual line keeps the embed bar marker ("▎"), not only the first logical line. + // The prefix is added after wrapping, so every visual line has it, not only the first. for _, wrapped := range richtext.Wrap(renderedLine, wrapWidth) { embedText = append(embedText, append(richtext.Line{prefix}, wrapped...)) } @@ -95,7 +99,6 @@ type embedLine struct { type embedLineKind uint8 const ( - // Keep this ordering stable: drawEmbeds indexes precomputed style slots by this enum. embedLineProvider embedLineKind = iota embedLineAuthor embedLineTitle @@ -104,19 +107,22 @@ const ( embedLineFieldValue embedLineFooter embedLineURL + + // embedLineKinds is how many kinds there are. + embedLineKinds ) -func embedLineStyles(baseStyle tview.Style, theme config.MessagesListEmbedsThemeConfig) [8]tview.Style { - styles := [8]tview.Style{} - styles[embedLineProvider] = tview.MergeStyle(baseStyle, theme.ProviderStyle.Style) - styles[embedLineAuthor] = tview.MergeStyle(baseStyle, theme.AuthorStyle.Style) - styles[embedLineTitle] = tview.MergeStyle(baseStyle, theme.TitleStyle.Style) - styles[embedLineDescription] = tview.MergeStyle(baseStyle, theme.DescriptionStyle.Style) - styles[embedLineFieldName] = tview.MergeStyle(baseStyle, theme.FieldNameStyle.Style) - styles[embedLineFieldValue] = tview.MergeStyle(baseStyle, theme.FieldValueStyle.Style) - styles[embedLineFooter] = tview.MergeStyle(baseStyle, theme.FooterStyle.Style) - styles[embedLineURL] = tview.MergeStyle(baseStyle, theme.URLStyle.Style) - return styles +func embedLineStyles(baseStyle tview.Style, theme config.MessagesListEmbedsThemeConfig) [embedLineKinds]tview.Style { + return [embedLineKinds]tview.Style{ + embedLineProvider: tview.MergeStyle(baseStyle, theme.ProviderStyle.Style), + embedLineAuthor: tview.MergeStyle(baseStyle, theme.AuthorStyle.Style), + embedLineTitle: tview.MergeStyle(baseStyle, theme.TitleStyle.Style), + embedLineDescription: tview.MergeStyle(baseStyle, theme.DescriptionStyle.Style), + embedLineFieldName: tview.MergeStyle(baseStyle, theme.FieldNameStyle.Style), + embedLineFieldValue: tview.MergeStyle(baseStyle, theme.FieldValueStyle.Style), + embedLineFooter: tview.MergeStyle(baseStyle, theme.FooterStyle.Style), + embedLineURL: tview.MergeStyle(baseStyle, theme.URLStyle.Style), + } } type embedLineDedupKey struct { diff --git a/internal/voice/player.go b/internal/voice/player.go index 009569d..2867979 100644 --- a/internal/voice/player.go +++ b/internal/voice/player.go @@ -135,8 +135,8 @@ func (p *Player) seek(position time.Duration) error { return err } - // The samples between first and position are decoded and dropped, at 2 bytes each per channel. - skip := (sample - first) * int64(decoder.Info().Channels) * 2 + // The samples between first and position are decoded and dropped. + skip := (sample - first) * int64(decoder.Info().Channels) * sampleBytes // Nothing is left to play if position is past the end. if _, err := io.CopyN(io.Discard, decoder, skip); err != nil && !errors.Is(err, io.EOF) { return err diff --git a/internal/voice/player_test.go b/internal/voice/player_test.go index e9f6be1..92499d6 100644 --- a/internal/voice/player_test.go +++ b/internal/voice/player_test.go @@ -16,7 +16,7 @@ func TestPlayerSeek(t *testing.T) { const seconds = 10 var audio bytes.Buffer - if err := oggopus.EncodeInterleaved(&audio, oggopus.Config{SampleRate: sampleRate, Channels: 1}, make([]byte, seconds*sampleRate*2)); err != nil { + if err := oggopus.EncodeInterleaved(&audio, oggopus.Config{SampleRate: sampleRate, Channels: 1}, make([]byte, seconds*sampleRate*sampleBytes)); err != nil { t.Fatal(err) } diff --git a/internal/voice/speaker.go b/internal/voice/speaker.go index abb8b07..a839e06 100644 --- a/internal/voice/speaker.go +++ b/internal/voice/speaker.go @@ -13,9 +13,11 @@ import ( const ( sampleRate = 48000 channels = 2 + // sampleBytes is the size of one 16-bit PCM sample of one channel. + sampleBytes = 2 // frameSamples and frameBytes are the sizes of 20ms of PCM. frameSamples = sampleRate / 50 * channels - frameBytes = frameSamples * 2 + frameBytes = frameSamples * sampleBytes // cushion is how much is buffered before it is played, so that packets arriving unevenly do not make gaps. cushion = 3 * frameBytes