single.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. package layout
  2. import (
  3. "github.com/charmbracelet/bubbles/key"
  4. tea "github.com/charmbracelet/bubbletea"
  5. "github.com/charmbracelet/lipgloss"
  6. )
  7. type SinglePaneLayout interface {
  8. tea.Model
  9. Focusable
  10. Sizeable
  11. Bindings
  12. Pane() tea.Model
  13. }
  14. type singlePaneLayout struct {
  15. width int
  16. height int
  17. focusable bool
  18. focused bool
  19. bordered bool
  20. borderText map[BorderPosition]string
  21. content tea.Model
  22. padding []int
  23. activeColor lipgloss.TerminalColor
  24. }
  25. type SinglePaneOption func(*singlePaneLayout)
  26. func (s *singlePaneLayout) Init() tea.Cmd {
  27. return s.content.Init()
  28. }
  29. func (s *singlePaneLayout) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
  30. switch msg := msg.(type) {
  31. case tea.WindowSizeMsg:
  32. s.SetSize(msg.Width, msg.Height)
  33. return s, nil
  34. }
  35. u, cmd := s.content.Update(msg)
  36. s.content = u
  37. return s, cmd
  38. }
  39. func (s *singlePaneLayout) View() string {
  40. style := lipgloss.NewStyle().Width(s.width).Height(s.height)
  41. if s.bordered {
  42. style = style.Width(s.width - 2).Height(s.height - 2)
  43. }
  44. if s.padding != nil {
  45. style = style.Padding(s.padding...)
  46. }
  47. content := style.Render(s.content.View())
  48. if s.bordered {
  49. if s.borderText == nil {
  50. s.borderText = map[BorderPosition]string{}
  51. }
  52. if bordered, ok := s.content.(Bordered); ok {
  53. s.borderText = bordered.BorderText()
  54. }
  55. return Borderize(content, s.focused, s.borderText, s.activeColor)
  56. }
  57. return content
  58. }
  59. func (s *singlePaneLayout) Blur() tea.Cmd {
  60. if s.focusable {
  61. s.focused = false
  62. }
  63. if blurable, ok := s.content.(Focusable); ok {
  64. return blurable.Blur()
  65. }
  66. return nil
  67. }
  68. func (s *singlePaneLayout) Focus() tea.Cmd {
  69. if s.focusable {
  70. s.focused = true
  71. }
  72. if focusable, ok := s.content.(Focusable); ok {
  73. return focusable.Focus()
  74. }
  75. return nil
  76. }
  77. func (s *singlePaneLayout) SetSize(width, height int) {
  78. s.width = width
  79. s.height = height
  80. childWidth, childHeight := s.width, s.height
  81. if s.bordered {
  82. childWidth -= 2
  83. childHeight -= 2
  84. }
  85. if s.padding != nil {
  86. if len(s.padding) == 1 {
  87. childWidth -= s.padding[0] * 2
  88. childHeight -= s.padding[0] * 2
  89. } else if len(s.padding) == 2 {
  90. childWidth -= s.padding[0] * 2
  91. childHeight -= s.padding[1] * 2
  92. } else if len(s.padding) == 3 {
  93. childWidth -= s.padding[0] * 2
  94. childHeight -= s.padding[1] + s.padding[2]
  95. } else if len(s.padding) == 4 {
  96. childWidth -= s.padding[0] + s.padding[2]
  97. childHeight -= s.padding[1] + s.padding[3]
  98. }
  99. }
  100. if s.content != nil {
  101. if c, ok := s.content.(Sizeable); ok {
  102. c.SetSize(childWidth, childHeight)
  103. }
  104. }
  105. }
  106. func (s *singlePaneLayout) IsFocused() bool {
  107. return s.focused
  108. }
  109. func (s *singlePaneLayout) GetSize() (int, int) {
  110. return s.width, s.height
  111. }
  112. func (s *singlePaneLayout) BindingKeys() []key.Binding {
  113. if b, ok := s.content.(Bindings); ok {
  114. return b.BindingKeys()
  115. }
  116. return []key.Binding{}
  117. }
  118. func (s *singlePaneLayout) Pane() tea.Model {
  119. return s.content
  120. }
  121. func NewSinglePane(content tea.Model, opts ...SinglePaneOption) SinglePaneLayout {
  122. layout := &singlePaneLayout{
  123. content: content,
  124. }
  125. for _, opt := range opts {
  126. opt(layout)
  127. }
  128. return layout
  129. }
  130. func WithSignlePaneSize(width, height int) SinglePaneOption {
  131. return func(opts *singlePaneLayout) {
  132. opts.width = width
  133. opts.height = height
  134. }
  135. }
  136. func WithSinglePaneFocusable(focusable bool) SinglePaneOption {
  137. return func(opts *singlePaneLayout) {
  138. opts.focusable = focusable
  139. }
  140. }
  141. func WithSinglePaneBordered(bordered bool) SinglePaneOption {
  142. return func(opts *singlePaneLayout) {
  143. opts.bordered = bordered
  144. }
  145. }
  146. func WithSignlePaneBorderText(borderText map[BorderPosition]string) SinglePaneOption {
  147. return func(opts *singlePaneLayout) {
  148. opts.borderText = borderText
  149. }
  150. }
  151. func WithSinglePanePadding(padding ...int) SinglePaneOption {
  152. return func(opts *singlePaneLayout) {
  153. opts.padding = padding
  154. }
  155. }
  156. func WithSinglePaneActiveColor(color lipgloss.TerminalColor) SinglePaneOption {
  157. return func(opts *singlePaneLayout) {
  158. opts.activeColor = color
  159. }
  160. }