diff --git a/cursor/cursor.go b/cursor/cursor.go index 5abda654..6c157436 100644 --- a/cursor/cursor.go +++ b/cursor/cursor.go @@ -72,11 +72,23 @@ type Model struct { blinkTag int // mode determines the behavior of the cursor mode Mode + + re *lipgloss.Renderer +} + +// Option is used to set options in New. +type Option func(*Model) + +// WithRenderer sets the Lip Gloss renderer for the cursor. +func WithRenderer(r *lipgloss.Renderer) Option { + return func(m *Model) { + m.re = r + } } // New creates a new model with default settings. -func New() Model { - return Model{ +func New(opts ...Option) Model { + m := Model{ BlinkSpeed: defaultBlinkSpeed, Blink: true, @@ -86,6 +98,18 @@ func New() Model { ctx: context.Background(), }, } + + for _, opt := range opts { + opt(&m) + } + + if m.re == nil { + m.re = lipgloss.DefaultRenderer() + } + + m.Style = m.Style.Renderer(m.re) + + return m } // Update updates the cursor. diff --git a/filepicker/filepicker.go b/filepicker/filepicker.go index 2df4032c..50cbc235 100644 --- a/filepicker/filepicker.go +++ b/filepicker/filepicker.go @@ -27,9 +27,19 @@ func nextID() int { return lastID } +// Option is used to set options in New. +type Option func(*Model) + +// WithRenderer sets the Lip Gloss renderer for the filepicker. +func WithRenderer(r *lipgloss.Renderer) Option { + return func(m *Model) { + m.re = r + } +} + // New returns a new filepicker model with default styling and key bindings. -func New() Model { - return Model{ +func New(opts ...Option) Model { + m := Model{ id: nextID(), CurrentDirectory: ".", Cursor: ">", @@ -46,8 +56,19 @@ func New() Model { minStack: newStack(), maxStack: newStack(), KeyMap: DefaultKeyMap(), - Styles: DefaultStyles(), } + + for _, opt := range opts { + opt(&m) + } + + if m.re == nil { + m.re = lipgloss.DefaultRenderer() + } + + m.Styles = DefaultStyles().Renderer(m.re) + + return m } type errorMsg struct { @@ -108,32 +129,51 @@ type Styles struct { EmptyDirectory lipgloss.Style } +// Renderer returns a new Styles copy with the given Lip Gloss renderer. +func (s Styles) Renderer(re *lipgloss.Renderer) Styles { + s.DisabledCursor = s.DisabledCursor.Copy().Renderer(re) + s.Cursor = s.Cursor.Copy().Renderer(re) + s.Symlink = s.Symlink.Copy().Renderer(re) + s.Directory = s.Directory.Copy().Renderer(re) + s.File = s.File.Copy().Renderer(re) + s.DisabledFile = s.DisabledFile.Copy().Renderer(re) + s.Permission = s.Permission.Copy().Renderer(re) + s.Selected = s.Selected.Copy().Renderer(re) + s.DisabledSelected = s.DisabledSelected.Copy().Renderer(re) + s.FileSize = s.FileSize.Copy().Renderer(re) + s.EmptyDirectory = s.EmptyDirectory.Copy().Renderer(re) + return s +} + // DefaultStyles defines the default styling for the file picker. func DefaultStyles() Styles { - return DefaultStylesWithRenderer(lipgloss.DefaultRenderer()) + return Styles{ + DisabledCursor: lipgloss.NewStyle().Foreground(lipgloss.Color("247")), + Cursor: lipgloss.NewStyle().Foreground(lipgloss.Color("212")), + Symlink: lipgloss.NewStyle().Foreground(lipgloss.Color("36")), + Directory: lipgloss.NewStyle().Foreground(lipgloss.Color("99")), + File: lipgloss.NewStyle(), + DisabledFile: lipgloss.NewStyle().Foreground(lipgloss.Color("243")), + DisabledSelected: lipgloss.NewStyle().Foreground(lipgloss.Color("247")), + Permission: lipgloss.NewStyle().Foreground(lipgloss.Color("244")), + Selected: lipgloss.NewStyle().Foreground(lipgloss.Color("212")).Bold(true), + FileSize: lipgloss.NewStyle().Foreground(lipgloss.Color("240")).Width(fileSizeWidth).Align(lipgloss.Right), + EmptyDirectory: lipgloss.NewStyle().Foreground(lipgloss.Color("240")).PaddingLeft(paddingLeft).SetString("Bummer. No Files Found."), + } } // DefaultStylesWithRenderer defines the default styling for the file picker, // with a given Lip Gloss renderer. +// +// Deprecated: use Styles.Renderer instead. func DefaultStylesWithRenderer(r *lipgloss.Renderer) Styles { - return Styles{ - DisabledCursor: r.NewStyle().Foreground(lipgloss.Color("247")), - Cursor: r.NewStyle().Foreground(lipgloss.Color("212")), - Symlink: r.NewStyle().Foreground(lipgloss.Color("36")), - Directory: r.NewStyle().Foreground(lipgloss.Color("99")), - File: r.NewStyle(), - DisabledFile: r.NewStyle().Foreground(lipgloss.Color("243")), - DisabledSelected: r.NewStyle().Foreground(lipgloss.Color("247")), - Permission: r.NewStyle().Foreground(lipgloss.Color("244")), - Selected: r.NewStyle().Foreground(lipgloss.Color("212")).Bold(true), - FileSize: r.NewStyle().Foreground(lipgloss.Color("240")).Width(fileSizeWidth).Align(lipgloss.Right), - EmptyDirectory: r.NewStyle().Foreground(lipgloss.Color("240")).PaddingLeft(paddingLeft).SetString("Bummer. No Files Found."), - } + return DefaultStyles().Renderer(r) } // Model represents a file picker. type Model struct { id int + re *lipgloss.Renderer // Path is the path which the user has selected with the file picker. Path string diff --git a/help/help.go b/help/help.go index 8e5f77f1..a10daf1b 100644 --- a/help/help.go +++ b/help/help.go @@ -42,6 +42,43 @@ type Styles struct { FullSeparator lipgloss.Style } +// DefaultStyles returns a set of default styles for the help bubble. +func DefaultStyles() Styles { + keyStyle := lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{ + Light: "#909090", + Dark: "#626262", + }) + descStyle := lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{ + Light: "#B2B2B2", + Dark: "#4A4A4A", + }) + sepStyle := lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{ + Light: "#DDDADA", + Dark: "#3C3C3C", + }) + return Styles{ + ShortKey: keyStyle, + ShortDesc: descStyle, + ShortSeparator: sepStyle, + Ellipsis: sepStyle.Copy(), + FullKey: keyStyle.Copy(), + FullDesc: descStyle.Copy(), + FullSeparator: sepStyle.Copy(), + } +} + +// Renderer returns a copy of Styles with the given Lip Gloss renderer set. +func (s Styles) Renderer(r *lipgloss.Renderer) Styles { + s.Ellipsis = s.Ellipsis.Copy().Renderer(r) + s.ShortKey = s.ShortKey.Copy().Renderer(r) + s.ShortDesc = s.ShortDesc.Copy().Renderer(r) + s.ShortSeparator = s.ShortSeparator.Copy().Renderer(r) + s.FullKey = s.FullKey.Copy().Renderer(r) + s.FullDesc = s.FullDesc.Copy().Renderer(r) + s.FullSeparator = s.FullSeparator.Copy().Renderer(r) + return s +} + // Model contains the state of the help view. type Model struct { Width int @@ -55,39 +92,39 @@ type Model struct { Ellipsis string Styles Styles -} -// New creates a new help view with some useful defaults. -func New() Model { - keyStyle := lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{ - Light: "#909090", - Dark: "#626262", - }) + re *lipgloss.Renderer +} - descStyle := lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{ - Light: "#B2B2B2", - Dark: "#4A4A4A", - }) +// Option is used to set options in New. +type Option func(*Model) - sepStyle := lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{ - Light: "#DDDADA", - Dark: "#3C3C3C", - }) +// WithRenderer sets the Lip Gloss renderer for the help model. +func WithRenderer(r *lipgloss.Renderer) Option { + return func(m *Model) { + m.re = r + } +} - return Model{ +// New creates a new help view with some useful defaults. +func New(opts ...Option) Model { + m := Model{ ShortSeparator: " • ", FullSeparator: " ", Ellipsis: "…", - Styles: Styles{ - ShortKey: keyStyle, - ShortDesc: descStyle, - ShortSeparator: sepStyle, - Ellipsis: sepStyle.Copy(), - FullKey: keyStyle.Copy(), - FullDesc: descStyle.Copy(), - FullSeparator: sepStyle.Copy(), - }, } + + for _, opt := range opts { + opt(&m) + } + + if m.re == nil { + m.re = lipgloss.DefaultRenderer() + } + + m.Styles = DefaultStyles().Renderer(m.re) + + return m } // NewModel creates a new help view with some useful defaults. diff --git a/list/list.go b/list/list.go index 7b47879f..e7a29ff1 100644 --- a/list/list.go +++ b/list/list.go @@ -134,8 +134,20 @@ func (f FilterState) String() string { }[f] } +// Option is used to set options in New. +type Option func(*Model) + +// WithRenderer sets the Lip Gloss renderer for the list model. +func WithRenderer(r *lipgloss.Renderer) Option { + return func(m *Model) { + m.re = r + } +} + // Model contains the state of this component. type Model struct { + re *lipgloss.Renderer + showTitle bool showFilter bool showStatusBar bool @@ -195,8 +207,37 @@ type Model struct { } // New returns a new model with sensible defaults. -func New(items []Item, delegate ItemDelegate, width, height int) Model { - styles := DefaultStyles() +func New(items []Item, delegate ItemDelegate, width, height int, opts ...Option) Model { + m := Model{ + showTitle: true, + showFilter: true, + showStatusBar: true, + showPagination: true, + showHelp: true, + itemNameSingular: "item", + itemNamePlural: "items", + filteringEnabled: true, + KeyMap: DefaultKeyMap(), + Filter: DefaultFilter, + Title: "List", + StatusMessageLifetime: time.Second, + + width: width, + height: height, + delegate: delegate, + items: items, + Help: help.New(), + } + + for _, opt := range opts { + opt(&m) + } + + if m.re == nil { + m.re = lipgloss.DefaultRenderer() + } + + styles := DefaultStyles().Renderer(m.re) sp := spinner.New() sp.Spinner = spinner.Line @@ -214,30 +255,10 @@ func New(items []Item, delegate ItemDelegate, width, height int) Model { p.ActiveDot = styles.ActivePaginationDot.String() p.InactiveDot = styles.InactivePaginationDot.String() - m := Model{ - showTitle: true, - showFilter: true, - showStatusBar: true, - showPagination: true, - showHelp: true, - itemNameSingular: "item", - itemNamePlural: "items", - filteringEnabled: true, - KeyMap: DefaultKeyMap(), - Filter: DefaultFilter, - Styles: styles, - Title: "List", - FilterInput: filterInput, - StatusMessageLifetime: time.Second, - - width: width, - height: height, - delegate: delegate, - items: items, - Paginator: p, - spinner: sp, - Help: help.New(), - } + m.Styles = styles + m.FilterInput = filterInput + m.Paginator = p + m.spinner = sp m.updatePagination() m.updateKeybindings() diff --git a/list/style.go b/list/style.go index e4451f87..500a35b2 100644 --- a/list/style.go +++ b/list/style.go @@ -39,6 +39,28 @@ type Styles struct { DividerDot lipgloss.Style } +// Renderer returns a copy of Styles with the given Lip Gloss renderer set. +func (s Styles) Renderer(r *lipgloss.Renderer) Styles { + s.TitleBar = s.TitleBar.Copy().Renderer(r) + s.Title = s.Title.Copy().Renderer(r) + s.Spinner = s.Spinner.Copy().Renderer(r) + s.FilterPrompt = s.FilterPrompt.Copy().Renderer(r) + s.FilterCursor = s.FilterCursor.Copy().Renderer(r) + s.DefaultFilterCharacterMatch = s.DefaultFilterCharacterMatch.Copy().Renderer(r) + s.StatusBar = s.StatusBar.Copy().Renderer(r) + s.StatusEmpty = s.StatusEmpty.Copy().Renderer(r) + s.StatusBarActiveFilter = s.StatusBarActiveFilter.Copy().Renderer(r) + s.StatusBarFilterCount = s.StatusBarFilterCount.Copy().Renderer(r) + s.NoItems = s.NoItems.Copy().Renderer(r) + s.PaginationStyle = s.PaginationStyle.Copy().Renderer(r) + s.HelpStyle = s.HelpStyle.Copy().Renderer(r) + s.ActivePaginationDot = s.ActivePaginationDot.Copy().Renderer(r) + s.InactivePaginationDot = s.InactivePaginationDot.Copy().Renderer(r) + s.ArabicPagination = s.ArabicPagination.Copy().Renderer(r) + s.DividerDot = s.DividerDot.Copy().Renderer(r) + return s +} + // DefaultStyles returns a set of default style definitions for this list // component. func DefaultStyles() (s Styles) { diff --git a/progress/progress.go b/progress/progress.go index 871d35dc..31bbed45 100644 --- a/progress/progress.go +++ b/progress/progress.go @@ -108,12 +108,22 @@ func WithSpringOptions(frequency, damping float64) Option { } // WithColorProfile sets the color profile to use for the progress bar. +// +// Deprecated: use [WithRenderer] to set a custom Lip Gloss renderer that uses +// a specific color profile. func WithColorProfile(p termenv.Profile) Option { return func(m *Model) { m.colorProfile = p } } +// WithRenderer sets the Lip Gloss renderer for the progress model. +func WithRenderer(r *lipgloss.Renderer) Option { + return func(m *Model) { + m.re = r + } +} + // FrameMsg indicates that an animation step should occur. type FrameMsg struct { id int @@ -164,6 +174,9 @@ type Model struct { // Color profile for the progress bar. colorProfile termenv.Profile + + // The Lipgloss renderer for the cursor. + re *lipgloss.Renderer } // New returns a model with default values. @@ -177,7 +190,7 @@ func New(opts ...Option) Model { EmptyColor: "#606060", ShowPercentage: true, PercentFormat: " %3.0f%%", - colorProfile: termenv.ColorProfile(), + colorProfile: -1, } if !m.springCustomized { m.SetSpringOptions(defaultFrequency, defaultDamping) @@ -186,6 +199,17 @@ func New(opts ...Option) Model { for _, opt := range opts { opt(&m) } + + if m.re == nil { + m.re = lipgloss.DefaultRenderer() + } + + // Set the renderer on the percentage style + m.PercentageStyle = m.PercentageStyle.Copy().Renderer(m.re) + if m.colorProfile < 0 { + m.colorProfile = m.re.ColorProfile() + } + return m } diff --git a/spinner/spinner.go b/spinner/spinner.go index bb53597f..0f786d7c 100644 --- a/spinner/spinner.go +++ b/spinner/spinner.go @@ -105,6 +105,9 @@ type Model struct { frame int id int tag int + + // The renderer for the spinner. If nil, the default renderer will be used. + re *lipgloss.Renderer } // ID returns the spinner's unique ID. @@ -123,6 +126,12 @@ func New(opts ...Option) Model { opt(&m) } + if m.re == nil { + m.re = lipgloss.DefaultRenderer() + } + + m.Style = m.Style.Copy().Renderer(m.re) + return m } @@ -228,3 +237,10 @@ func WithStyle(style lipgloss.Style) Option { m.Style = style } } + +// WithRenderer sets the Lip Gloss renderer for the spinner. +func WithRenderer(r *lipgloss.Renderer) Option { + return func(m *Model) { + m.re = r + } +} diff --git a/table/table.go b/table/table.go index 36549e3b..2acb2d45 100644 --- a/table/table.go +++ b/table/table.go @@ -23,6 +23,10 @@ type Model struct { viewport viewport.Model start int end int + + // The renderer is used to render the table styles. If nil, the default + // renderer will be used. + re *lipgloss.Renderer } // Row represents one line in the table. @@ -94,6 +98,14 @@ type Styles struct { Selected lipgloss.Style } +// Renderer returns a copy of Styles with the given Lip Gloss renderer set. +func (s Styles) Renderer(r *lipgloss.Renderer) Styles { + s.Header = s.Header.Copy().Renderer(r) + s.Cell = s.Cell.Copy().Renderer(r) + s.Selected = s.Selected.Copy().Renderer(r) + return s +} + // DefaultStyles returns a set of default style definitions for this table. func DefaultStyles() Styles { return Styles{ @@ -114,6 +126,13 @@ func (m *Model) SetStyles(s Styles) { // table := New(WithColumns([]Column{{Title: "ID", Width: 10}})) type Option func(*Model) +// WithRenderer sets the Lip Gloss renderer for the table model. +func WithRenderer(r *lipgloss.Renderer) Option { + return func(m *Model) { + m.re = r + } +} + // New creates a new model for the table widget. func New(opts ...Option) Model { m := Model{ @@ -121,13 +140,17 @@ func New(opts ...Option) Model { viewport: viewport.New(0, 20), KeyMap: DefaultKeyMap(), - styles: DefaultStyles(), } for _, opt := range opts { opt(&m) } + if m.re == nil { + m.re = lipgloss.DefaultRenderer() + } + + m.styles = DefaultStyles().Renderer(m.re) m.UpdateViewport() return m diff --git a/textarea/textarea.go b/textarea/textarea.go index b614a900..a50f98e0 100644 --- a/textarea/textarea.go +++ b/textarea/textarea.go @@ -111,14 +111,14 @@ type LineInfo struct { CharOffset int } -// Style that will be applied to the text area. +// Styles that will be applied to the text area. // -// Style can be applied to focused and unfocused states to change the styles +// Styles can be applied to focused and unfocused states to change the styles // depending on the focus state. // // For an introduction to styling with Lip Gloss see: // https://github.com/charmbracelet/lipgloss -type Style struct { +type Styles struct { Base lipgloss.Style CursorLine lipgloss.Style CursorLineNumber lipgloss.Style @@ -129,6 +129,34 @@ type Style struct { Text lipgloss.Style } +// Renderer returns a copy of Styles with the given Lip Gloss renderer set. +func (s Styles) Renderer(r *lipgloss.Renderer) Styles { + s.Base = s.Base.Copy().Renderer(r) + s.CursorLine = s.CursorLine.Copy().Renderer(r) + s.CursorLineNumber = s.CursorLineNumber.Copy().Renderer(r) + s.EndOfBuffer = s.EndOfBuffer.Copy().Renderer(r) + s.LineNumber = s.LineNumber.Copy().Renderer(r) + s.Placeholder = s.Placeholder.Copy().Renderer(r) + s.Prompt = s.Prompt.Copy().Renderer(r) + s.Text = s.Text.Copy().Renderer(r) + return s +} + +// Style is an alias for Styles. +// +// Deprecated: Use Styles instead. +type Style = Styles + +// Option is used to set options in New. +type Option func(*Model) + +// WithRenderer sets the Lip Gloss renderer for the textarea model. +func WithRenderer(r *lipgloss.Renderer) Option { + return func(m *Model) { + m.re = r + } +} + // Model is the Bubble Tea model for this text area element. type Model struct { Err error @@ -159,13 +187,13 @@ type Model struct { // Styling. FocusedStyle and BlurredStyle are used to style the textarea in // focused and blurred states. - FocusedStyle Style - BlurredStyle Style + FocusedStyle Styles + BlurredStyle Styles // style is the current styling to use. // It is used to abstract the differences in focus state when styling the // model, since we can simply assign the set of styles to this variable // when switching focus states. - style *Style + style *Styles // Cursor is the text area cursor. Cursor cursor.Model @@ -224,24 +252,22 @@ type Model struct { // rune sanitizer for input. rsan runeutil.Sanitizer + + // The Lip Gloss renderer to be used for styling. + re *lipgloss.Renderer } // New creates a new model with default settings. -func New() Model { +func New(opts ...Option) Model { vp := viewport.New(0, 0) vp.KeyMap = viewport.KeyMap{} cur := cursor.New() - focusedStyle, blurredStyle := DefaultStyles() - m := Model{ CharLimit: defaultCharLimit, MaxHeight: defaultMaxHeight, MaxWidth: defaultMaxWidth, Prompt: lipgloss.ThickBorder().Left + " ", - style: &blurredStyle, - FocusedStyle: focusedStyle, - BlurredStyle: blurredStyle, EndOfBufferCharacter: '~', ShowLineNumbers: true, Cursor: cur, @@ -256,6 +282,21 @@ func New() Model { viewport: &vp, } + for _, opt := range opts { + opt(&m) + } + + if m.re == nil { + m.re = lipgloss.DefaultRenderer() + } + + focusedStyle, blurredStyle := DefaultStyles() + focusedStyle = focusedStyle.Renderer(m.re) + blurredStyle = blurredStyle.Renderer(m.re) + m.style = &blurredStyle + m.FocusedStyle = focusedStyle + m.BlurredStyle = blurredStyle + m.SetHeight(defaultHeight) m.SetWidth(defaultWidth) @@ -264,8 +305,8 @@ func New() Model { // DefaultStyles returns the default styles for focused and blurred states for // the textarea. -func DefaultStyles() (Style, Style) { - focused := Style{ +func DefaultStyles() (Styles, Styles) { + focused := Styles{ Base: lipgloss.NewStyle(), CursorLine: lipgloss.NewStyle().Background(lipgloss.AdaptiveColor{Light: "255", Dark: "0"}), CursorLineNumber: lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "240"}), @@ -275,7 +316,7 @@ func DefaultStyles() (Style, Style) { Prompt: lipgloss.NewStyle().Foreground(lipgloss.Color("7")), Text: lipgloss.NewStyle(), } - blurred := Style{ + blurred := Styles{ Base: lipgloss.NewStyle(), CursorLine: lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "245", Dark: "7"}), CursorLineNumber: lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "249", Dark: "7"}), diff --git a/textinput/textinput.go b/textinput/textinput.go index f8990f66..c1410126 100644 --- a/textinput/textinput.go +++ b/textinput/textinput.go @@ -134,22 +134,49 @@ type Model struct { // rune sanitizer for input. rsan runeutil.Sanitizer + + // The lipgloss renderer used for the styles. + re *lipgloss.Renderer +} + +// Option is used to set options in New. +type Option func(*Model) + +// WithRenderer sets the Lip Gloss renderer for the textinput model. +func WithRenderer(r *lipgloss.Renderer) Option { + return func(m *Model) { + m.re = r + } } // New creates a new model with default settings. -func New() Model { - return Model{ - Prompt: "> ", - EchoCharacter: '*', - CharLimit: 0, - PlaceholderStyle: lipgloss.NewStyle().Foreground(lipgloss.Color("240")), - Cursor: cursor.New(), - KeyMap: DefaultKeyMap, +func New(opts ...Option) Model { + m := Model{ + Prompt: "> ", + EchoCharacter: '*', + CharLimit: 0, + Cursor: cursor.New(), + KeyMap: DefaultKeyMap, value: nil, focus: false, pos: 0, } + + for _, opt := range opts { + opt(&m) + } + + if m.re == nil { + m.re = lipgloss.DefaultRenderer() + } + + m.PromptStyle = m.re.NewStyle() + m.TextStyle = m.re.NewStyle() + m.PlaceholderStyle = m.re.NewStyle().Foreground(lipgloss.Color("240")) + m.CursorStyle = m.re.NewStyle() + + return m } // NewModel creates a new model with default settings. diff --git a/viewport/viewport.go b/viewport/viewport.go index b13e33c0..1b749ff4 100644 --- a/viewport/viewport.go +++ b/viewport/viewport.go @@ -11,13 +11,34 @@ import ( // New returns a new model with the given width and height as well as default // key mappings. -func New(width, height int) (m Model) { +func New(width, height int, opts ...Option) (m Model) { m.Width = width m.Height = height + + for _, opt := range opts { + opt(&m) + } + + if m.re == nil { + m.re = lipgloss.DefaultRenderer() + } + + m.Style = m.re.NewStyle() + m.setInitialValues() return m } +// Option is used to set options in New. +type Option func(*Model) + +// WithRenderer sets the Lip Gloss renderer for the viewport model. +func WithRenderer(r *lipgloss.Renderer) Option { + return func(m *Model) { + m.re = r + } +} + // Model is the Bubble Tea model for this viewport element. type Model struct { Width int @@ -54,6 +75,9 @@ type Model struct { initialized bool lines []string + + // The lipgloss renderer used for the styles. + re *lipgloss.Renderer } func (m *Model) setInitialValues() {