single.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  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, BorderOptions{
  56. Active: s.focused,
  57. EmbeddedText: s.borderText,
  58. })
  59. }
  60. return content
  61. }
  62. func (s *singlePaneLayout) Blur() tea.Cmd {
  63. if s.focusable {
  64. s.focused = false
  65. }
  66. if blurable, ok := s.content.(Focusable); ok {
  67. return blurable.Blur()
  68. }
  69. return nil
  70. }
  71. func (s *singlePaneLayout) Focus() tea.Cmd {
  72. if s.focusable {
  73. s.focused = true
  74. }
  75. if focusable, ok := s.content.(Focusable); ok {
  76. return focusable.Focus()
  77. }
  78. return nil
  79. }
  80. func (s *singlePaneLayout) SetSize(width, height int) {
  81. s.width = width
  82. s.height = height
  83. childWidth, childHeight := s.width, s.height
  84. if s.bordered {
  85. childWidth -= 2
  86. childHeight -= 2
  87. }
  88. if s.padding != nil {
  89. if len(s.padding) == 1 {
  90. childWidth -= s.padding[0] * 2
  91. childHeight -= s.padding[0] * 2
  92. } else if len(s.padding) == 2 {
  93. childWidth -= s.padding[0] * 2
  94. childHeight -= s.padding[1] * 2
  95. } else if len(s.padding) == 3 {
  96. childWidth -= s.padding[0] * 2
  97. childHeight -= s.padding[1] + s.padding[2]
  98. } else if len(s.padding) == 4 {
  99. childWidth -= s.padding[0] + s.padding[2]
  100. childHeight -= s.padding[1] + s.padding[3]
  101. }
  102. }
  103. if s.content != nil {
  104. if c, ok := s.content.(Sizeable); ok {
  105. c.SetSize(childWidth, childHeight)
  106. }
  107. }
  108. }
  109. func (s *singlePaneLayout) IsFocused() bool {
  110. return s.focused
  111. }
  112. func (s *singlePaneLayout) GetSize() (int, int) {
  113. return s.width, s.height
  114. }
  115. func (s *singlePaneLayout) BindingKeys() []key.Binding {
  116. if b, ok := s.content.(Bindings); ok {
  117. return b.BindingKeys()
  118. }
  119. return []key.Binding{}
  120. }
  121. func (s *singlePaneLayout) Pane() tea.Model {
  122. return s.content
  123. }
  124. func NewSinglePane(content tea.Model, opts ...SinglePaneOption) SinglePaneLayout {
  125. layout := &singlePaneLayout{
  126. content: content,
  127. }
  128. for _, opt := range opts {
  129. opt(layout)
  130. }
  131. return layout
  132. }
  133. func WithSinglePaneSize(width, height int) SinglePaneOption {
  134. return func(opts *singlePaneLayout) {
  135. opts.width = width
  136. opts.height = height
  137. }
  138. }
  139. func WithSinglePaneFocusable(focusable bool) SinglePaneOption {
  140. return func(opts *singlePaneLayout) {
  141. opts.focusable = focusable
  142. }
  143. }
  144. func WithSinglePaneBordered(bordered bool) SinglePaneOption {
  145. return func(opts *singlePaneLayout) {
  146. opts.bordered = bordered
  147. }
  148. }
  149. func WithSinglePaneBorderText(borderText map[BorderPosition]string) SinglePaneOption {
  150. return func(opts *singlePaneLayout) {
  151. opts.borderText = borderText
  152. }
  153. }
  154. func WithSinglePanePadding(padding ...int) SinglePaneOption {
  155. return func(opts *singlePaneLayout) {
  156. opts.padding = padding
  157. }
  158. }
  159. func WithSinglePaneActiveColor(color lipgloss.TerminalColor) SinglePaneOption {
  160. return func(opts *singlePaneLayout) {
  161. opts.activeColor = color
  162. }
  163. }